/** * 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'] ); } } } } } } } } FO×Odoo×Freee 原価計算と一般会計の融合 – Raqqa

FO×Odoo×Freee 原価計算と一般会計の融合

🎯 目標

  • 「D365 FOに売上時に原価を算出する」:FOの販売仕訳で原価反映が必要なため、原価情報の即時反映が目的。
  • 「FOの会計モジュールをFreeeに移行」:仕訳変換と柔軟なAPI連携により可能。

📌 現状の課題

  • ベンダーロックインによるデータ仕様不明:現状の仕訳CSVの仕様や、取り込んだCSVの変換データ構造確認をどう進めるかが鍵。
  • AI活用の可能性:データの場所と推測として正しいデータが出るか検証する必要があり、現状では大きなプロジェクトとなる可能性がある。

💡 標準原価法 + 時価補正の採用

  • MTX様のハイブリッド方式に寄せた形:非常に合理的。
  • 標準原価をベースに値洗いで時価に補正:Odooでは実装しやすく、Freeeに時価差異の仕訳も可能。

🔁 Freeeへの移行とOdooの役割

  • 仕訳変換の有無にかかわらずOdooで中継:Odooはマスタと仕訳両方に柔軟対応できる。
  • OdooでAPI・ETL的な役割を担う:将来のデータ変換と蓄積のハブとして機能できる。

🧠 原価計算ロジック(Odoo標準)

  • 製品マスタとBOMをベースに原価計算:Odoo標準の機能で可能。
  • 作業区(Work Center)単価や労務費率の設定mrp.workcenter, mrp.routing.workcenter で定義可能。
  • 製品ごとの労務費取得にも対応可:Yes、Odooなら柔軟な原価構成が組める。

🔄 D365側での利用方法

  • Odooで算出した原価をAPIでD365 FOに返す:製品単位での標準原価取込が可能。
  • 売上取引時に製品ごとに原価反映させる設計:D365の仕訳データをOdooに取り込み、原価を入れることも可能。

✅ システム連携構成のイメージ

 [ MAPS) ]  → 仕訳CSV/データ    → [ Odoo ]
↓ 原価計算 / 製品マスタ
[ 作業区単価 ] ↓ 労務費/時価補正
↓ API連携
↘ Freee ← Odoo(仕訳生成・投入)

[ D365 FO ] ← API → Odoo(製品別原価情報連携)

原価計算の方法:

🎯 前提:ロットサイズと原価の関係

原材料価格だけでは不十分な項目は係数で補正する。

  • 小ロット:準備作業や洗浄、段取り替えの工数が相対的に高くなり、単位原価が高くなる
  • 大ロット:スケールメリットが働き、単位原価は低くなる

✅ 設計方針:Odoo(または他のERP)でどう対応するか

【1】ロットサイズによる コスト関数型の原価

これはあなたの言う通り、関数ベースで単価を可変とする方法です。

実装例:

pythonコピーする編集する原価単価 = 基本原価 × f(ロットサイズ)
f(ロットサイズ) = a / ロットサイズ + b
  • a: 初期段取りなどの固定費要素
  • b: 材料費や変動要素

Odooでの方法:

  • **製品カテゴリの原価方法を「手動(標準)」または「実績」**にし、
  • 生産指示(MO)に類するデータがある場合、ロットサイズを元にスクリプトで原価を計算
  • 必要なら mrp.production のカスタムフィールドで補正係数を入れる

【2】BOMとWork Centerの設定をロット依存にする(準標準対応)

  • mrp.routing.workcenter にて作業ごとの 固定時間+数量比例時間 を設定
  • これにより、MOの製造数量が変わると、工数(労務費)が自動変動

📌 ただし、工数だけでなく材料単価も変動する場合は、手動調整 or カスタム計算が必要です。


【3】価格表(pricelist)方式による段階別原価

  • ロットサイズに応じて「5kgまで = 200円/個」「10kgまで = 180円/個」と価格を階層化
  • pricelist を原価計算に応用(通常は販売価格用だがカスタム可能)

🔁 その他アプローチ

方法メリットデメリット
ロットサイズ別にBOMを分ける精度高運用・メンテナンスが煩雑
◎モデルベースで関数的に単価計算自動化しやすいカスタマイズが必要
外部で計算しOdooに原価を渡す他システムと連携しやすいリアルタイム性に欠ける

**3分法(売上・仕入・棚卸に分けて処理する会計方式)**と連携し、締め処理のたびに実際原価との差異を計算する仕組みも十分可能です。


✅ 3分法と実際原価の差異計算の連携フロー

① 期間中の仕訳(通常運用)

  • 仕入:
    • 借方:仕入高
    • 貸方:買掛金
  • 売上:
    • 借方:売掛金
    • 貸方:売上高
  • 棚卸:
    • 期末に在庫を数えて帳簿に反映
    • 借方:棚卸資産
    • 貸方:仕入高(または期末棚卸減少仕訳)

② 月末締め時に行う処理(差異分析)

🔍 ステップ 1:Odoo側で「実際原価」を算出

  • BOM+作業時間+原材料+副資材で「MO単位の原価レイヤー」を記録
  • ロットベースでも製造ごとに原価差が生じる

🔍 ステップ 2:仕訳ベースの「計上原価」との比較

  • 売上に対応する「売上原価」は期末に棚卸から算出 or 見積(標準原価)で仮計上
  • 差異を以下のように算出:
textコピーする編集する実際原価 - 計上原価 = 差異(±)

③ 差異の会計処理(調整仕訳)

状況会計仕訳(例)
実際原価 > 計上原価借方:売上原価/貸方:原価差異(損)
実際原価 < 計上原価借方:原価差異(益)/貸方:売上原価

💡 実現方法(Odoo + 外部会計連携)

  • Odooで MOごとに原価履歴(stock.valuation layer)を保持
  • 締め処理時に 出荷済製品とMO実績を照合
  • 差異金額を計算し、journal entryでFreeeやD365へ自動出力(API or CSV)

📌 注意点

  • **在庫評価方法(FIFO/標準/平均)**に応じて差異の意味が変わる
  • 会計監査的に差異仕訳の根拠として「MO実績レポート」が必要になることもある

✅ 結論

Odooで実際原価を蓄積しておけば、3分法と連携しながら月次・期末の原価差異を自動計算・仕訳反映する仕組みは十分に構築可能です。
これは、FreeeやD365と連携したハイブリッド原価管理の実装モデルとして実用的です。

🧩 各システムの役割と位置づけ

システム役割
Odoo製品・BOM・製造・販売データの入力・中継/D365・Freeeへのデータ連携中心システム
D365 FO実原価の確定・3分法による会計仕訳生成・製品別原価付与
Freee最終的な帳簿管理(財務会計)/Odoo経由で原価付き仕訳データを受信・集計

🔄 データの流れ(詳細)

  1. Odoo → D365 FO
    • 製品マスタ(BOM含む)
    • 製造・販売トランザクション(指図・出荷・売上など)
  2. D365 FOで処理
    • 実際原価の算出(MAPS等との連携を前提)
    • 売上・仕入・在庫・棚卸仕訳を3分法で作成
    • 各取引に原価が付与された状態の仕訳データを生成
  3. D365 FO → Odoo(データ返却)
    • 原価が付与された売上・仕入・在庫・棚卸データ
    • ※インターフェース形式はCSV/APIのどちらでも可
  4. Odoo → Freee
    • Freee用に変換した会計仕訳データ(原価付き)
    • 勘定科目・税区分・取引先などFreee用にマッピング済

✅ 要点

  • D365 FOは原価計算と会計仕訳生成のエンジンで、Freeeは帳簿をつける最終目的地」
  • Odooはそのハブであり、D365とFreeeを接続する中継基盤」
  • 「Freeeの簡易会計機能を活かすために、原価付き仕訳はOdooで変換・登録
  • 「D365 FOとFreeeは直接つながらず、Odooで会計と業務のインターフェースを構築

機能一覧

✅ 実現可能なソリューション(Odoo中心)

項目実現内容
原価計算の自動化製造モジュール+BOMによって構成品・労務費を基に原価を自動計算可能
数量スケール補正ロットごとや数量に応じたコスト調整ロジックもPythonで柔軟に実装可能
原価差異のレポート化推定原価 vs 実原価の差異も自動で記録し、レポート化・分析可能
マスタ連携の容易さ製品・BOM・作業区マスタをCSV/APIでインポートし、変換不要な場合も多い
Freeeへのリアルタイム連携OAuth2.0によるFreee認証+API投稿で自動連携/CSVアップロード不要
会計事務所との連携強化Freee対応実績ある会計事務所により会計勘定・税区分のマッピングは即時実現可能
D365からの段階的脱却債権債務管理のモジュールもOdooで補完可能。高額なD365のライセンスを削減
AIとの親和性Odooのデータ構造は柔軟で、弊社独自のAIプラットフォームと連携しやすい
Copilotより優れたUX自然言語によるクエリ・レポート・計画立案がシームレス。非エンジニアも活用可能

🔧 Odooを活用したDX推進のご提案

✅ Odooを“DXハブ”として使う理由

OdooはPythonベースで構成されており、システム連携・カスタマイズの自由度が非常に高いため、複雑な業務シナリオにも柔軟に対応できます。以下のようなポイントで、DXの中核(ハブ)となり得ます。

  • 製造・販売・購買・原価など業務機能が一体化されているため、業務の一貫性を保ったデータ活用が可能。
  • カスタマイズに特化した設計:ビュー変更やロジック追加、API連携が容易。
  • Freee・D365・BI基盤・AI基盤との中継役として機能可能。
  • ERP本体の脱却戦略の第一歩として、「まず一部領域からOdooを導入」することが可能。

⚠️ D365 FO + Salesの現状課題

  • 高額なライセンス体系:SalesやFOはユーザー単位での課金で、かつ機能ごとに分断されている。
  • カスタマイズに工数がかかる:単純なビジネスイベントの作成にもX++での開発が必要で、改修単位は常に「大」。
  • セキュリティ階層の設計が過剰:結果として、ユーザー単価を増やす構成になりやすく、必要以上にコストが発生。
  • **Power Platformの連携も“エンタープライズ向け制限”**が多く、汎用性に乏しい。

💡 実例:OdooとPower Automateでできる簡単なカスタマイズ

◼ Odooでのビジネスイベント例

たとえば「請求書の支払い済みステータスが更新されたら通知を送る」といった処理:

  • Odoo Studio またはカスタムPythonでイベント定義(例:account.moveモデルの状態変更トリガー)
  • WebhookでPower Automateに通知
  • Power Automateで以下のような処理を定義:
    1. When a HTTP request is received
    2. Parse JSON(Odooから送信されたペイロード)
    3. List items present in tableで取引先情報を取得
    4. Send emailで経理担当へ通知

→ Odoo上での開発時間:数時間~1日程度

◼ D365 FOで同じことを実現する場合

  • X++クラス作成・契約クラス作成・イベント登録・ビルド・デプロイ
  • エンドポイント登録(Power Automate連携まで)
    → FOでの開発工数:3日~1週間

💰 工数・コスト比較(概算)

項目OdooD365 FO + Sales
ビジネスイベント作成・通知処理5~10万円(0.5~1人日)40~80万円(4~8人日)
権限設計シンプルで済む複雑なセキュリティロール
月額ライセンス(10名)5~10万円40~60万円以上
カスタマイズ対応速度数日数週間~月単位

🎯 今後の戦略に向けて

Odooをまずは中継・連携の役割として導入し、慣れていく中で順次業務を移管する構成をおすすめします。
D365やSalesと連携を保ちながら、将来的にはOdoo中心の業務プロセスへ移行することで、コスト圧縮・保守性向上・AI導入基盤構築を同時に達成できます。

これからのカスタマイズはOdooで行い、徐々にD365からの脱却を目指しましょう。


Comments

コメントを残す

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