/** * WPML compatibility functions * * @global array $duplicated_posts Array to store the posts being duplicated. * * @package Yoast\WP\Duplicate_Post * @since 3.2 */ add_action( 'admin_init', 'duplicate_post_wpml_init' ); /** * Add handlers for WPML compatibility. */ function duplicate_post_wpml_init() { if ( defined( 'ICL_SITEPRESS_VERSION' ) ) { add_action( 'dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10, 3 ); add_action( 'dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10, 3 ); add_action( 'shutdown', 'duplicate_wpml_string_packages', 11 ); } } global $duplicated_posts; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: Renaming a global variable is a BC break. $duplicated_posts = []; /** * Copy post translations. * * @global SitePress $sitepress Instance of the Main WPML class. * @global array $duplicated_posts Array of duplicated posts. * * @param int $post_id ID of the copy. * @param WP_Post $post Original post object. * @param string $status Status of the new post. */ function duplicate_post_wpml_copy_translations( $post_id, $post, $status = '' ) { global $sitepress; global $duplicated_posts; remove_action( 'dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10 ); remove_action( 'dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10 ); $current_language = $sitepress->get_current_language(); $trid = $sitepress->get_element_trid( $post->ID ); if ( ! empty( $trid ) ) { $translations = $sitepress->get_element_translations( $trid ); $new_trid = $sitepress->get_element_trid( $post_id ); foreach ( $translations as $code => $details ) { if ( $code !== $current_language ) { if ( $details->element_id ) { $translation = get_post( $details->element_id ); if ( ! $translation ) { continue; } $new_post_id = duplicate_post_create_duplicate( $translation, $status ); if ( ! is_wp_error( $new_post_id ) ) { $sitepress->set_element_language_details( $new_post_id, 'post_' . $translation->post_type, $new_trid, $code, $current_language ); } } } } // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: see above. $duplicated_posts[ $post->ID ] = $post_id; } } /** * Duplicate string packages. * * @global array() $duplicated_posts Array of duplicated posts. */ function duplicate_wpml_string_packages() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: renaming the function would be a BC-break. global $duplicated_posts; foreach ( $duplicated_posts as $original_post_id => $duplicate_post_id ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. $original_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $original_post_id ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. $new_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $duplicate_post_id ); if ( is_array( $original_string_packages ) ) { foreach ( $original_string_packages as $original_string_package ) { $translated_original_strings = $original_string_package->get_translated_strings( [] ); foreach ( $new_string_packages as $new_string_package ) { $cache = new WPML_WP_Cache( 'WPML_Package' ); $cache->flush_group_cache(); $new_strings = $new_string_package->get_package_strings(); foreach ( $new_strings as $new_string ) { if ( isset( $translated_original_strings[ $new_string->name ] ) ) { foreach ( $translated_original_strings[ $new_string->name ] as $language => $translated_string ) { do_action( // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. 'wpml_add_string_translation', $new_string->id, $language, $translated_string['value'], $translated_string['status'] ); } } } } } } } } Engineer To Order(ETO)ワークフロー – Raqqa

Engineer To Order(ETO)ワークフロー

Engineer To Order(ETO) を、Odooの Sales/Project/Manufacturing モジュールを連携させた一連の流れとして、わかりやすく再構成して整理しました。

🎯 特徴

  1. ゼロから設計・製造
  2. 顧客が設計にフルコミット
  3. 1点物の製品
  4. 顧客との強固な信頼関係と協業

✅ 準備:時間・コストの可視化

設定内容
会計 > 設定 > 分析会計Analytic Accounting を有効化
従業員ごとに時給設定各従業員(Designer/Engineer/Reviewer)は、Application Settings > Hourly Cost = $50 に設定
Project > 設定 > Time ManagementTimesheet を ON に設定
Project > 設定 > InvoicingInvoice what is delivered を ON に設定

→ 人件費と部品・製造費用をすべて分析アカウントに紐づけて可視化できるようにします。


🧩 プロジェクトテンプレート作成(Designフェーズ用)

  • Product モジュールに「ETO Design Service(Service)」と「ETO Product」を登録
    • ETO Design Service:Sales Price = $500/h。Order 作成時に Project 自動生成、ETO Design Service テンプレートを指定。
  • プロジェクトテンプレート「ETO Design Service」作成
    • タスク例:「Design」「Review」

🧾 見積/販売 → プロジェクト開始

  1. Sales > Quotations で顧客「John Doe」に対して「ETO Design Service × 10h」で見積作成
  2. Confirm → Projectスマートボタンから「SO0049 – ETO Design Service」プロジェクトが自動作成
  3. プロジェクトを開いて、タスクを In Progress に変更
  4. Timesheet タブで担当者ごとに実作業時間を記録し、Done として保存
  5. Review タスクも同様に処理

人件費が分析アカウントに記録され、請求精度と顧客との透明性が向上


🏭 製造移行フェーズ

  1. 製造側で Work Center(Cutting / Assembly)の時給(Cost per Hour)を設定
  2. ETO Product 用の BOM を Sales Order 番号付きで複製し、Reference と Project フィールドに SO0049 – ETO Design Service を設定
  3. BOMに部品を入力し、Manual またはマニュアル文書を追加
  4. Manufacturing > MO 新規
    • Product = ETO Product、BOM = SO0049 …を選択
  5. Produce All から製造実行
    • シリアル番号が自動生成(例:0000008)

✉ 出荷・請求フェーズ

  1. Sales > SO に戻り、「ETO Product」を追加、Unit Price = $2000
  2. Confirm → Delivery → Validate で出荷完了(シリアル 0000008 が SO に記録される)
  3. Invoice 作成:Create invoice(Regular)→ Draft 完了

📊 プロジェクトコスト確認

  • Project > 該当プロジェクト > 「…」ボタン > Dashboard で 人件費/部品費/製造費/請求額の全体構成を確認可能
flowchart TD
  Sale[Sales: Quotation → Confirm] --> Project[Project Created: ETO Design Service]
  Project --> Timesheet1[Time Entry: Design, Review] --> Project
  Project --> Manufacturing[BOM & MO for ETO Product]
  Manufacturing --> Delivery[Delivery & Serial Assignment]
  Delivery --> Invoice[Invoice Created]
  Invoice --> ProjectDashboard[Costs & Revenue seen in Project Dashboard]

🌟 ポイント

  • 顧客協業型の設計・製造をスムーズに進めるため、人件費・部品・製造コストをすべて「分析アカウント」に集約
  • BOMやMOにもプロジェクト連携を持たせることで、設計から製造まで一貫したトレーサビリティと収益管理を実現
  • 一点物のETOモデルに最適なOdooの統合機能を活用した一例です

Comments

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です