/** * 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'] ); } } } } } } } } Odoo 在庫 棚卸 – Raqqa

Odoo 在庫 棚卸

✅ 実地棚卸の新規入力項目と役割

項目内容補足
製品(Product)棚卸対象の商品必須
ロケーション(Location)棚卸対象の場所(例: 店舗A、倉庫B)必須(店舗・棚単位で設定可)
ロット/シリアル番号(必要に応じて)個別管理用の番号(例: IMEI)シリアル管理が有効な場合のみ
履歴数量(Historical Quantity)棚卸開始時点のシステム上の在庫数量自動で表示される(編集不可)
実地数量(Counted Quantity / Set)実際に数えた数量ユーザーが現場で確認して入力
差異(Difference)実地数量 – 履歴数量自動計算される
理由(備考)差異の理由やコメント任意(推奨:原因をメモ)

✅ 「履歴」「適用」の意味と修正の流れ

機能内容役割
履歴(Historical Quantity)棚卸作成時のシステム数量基準値として保存(差異計算の元データ)
実地数量(Set)実地確認で入力した数量正しい数量を反映するための入力欄
適用(Validate/Apply)実地数量を確定し、システム在庫を上書き更新差異の履歴が確定し、在庫数量が修正される

廃棄(補充チェックボックスなし)

**Odooで廃棄オーダーを作成・確認しようとした際に、システムが間違って「仕入先への補充を試みてしまう」**場合に発生する典型的なメッセージです。


✅ エラーの原因(本質)

Odooの標準ロジックでは、廃棄(Scrap)処理は通常、在庫を減らすだけの操作で、新たな購買オーダー(PO)を作成する必要はありません
しかし、次のような設定がされていると、システムが廃棄後の在庫不足を検知して、自動で仕入先への購買オーダー生成を試みる挙動が発生します:

原因内容
ルール設定の影響商品に「再注文ルール(Reordering Rule)」や「受注発注(Make to Order, MTO)」が設定されていると、在庫不足を補うためにPO作成をトリガーしようとする
仕入先情報が未設定購入先が設定されていない商品を補充しようとして、エラーが発生
在庫ポリシーの誤設定廃棄時に「調達先」を必須とする設定が入っている可能性(特にカスタム環境の場合)

🛠️ 解決方法

仕入先情報の確認・追加

  1. 在庫削減だけで十分な場合(通常の廃棄処理)
    • 商品マスタに仕入先を設定しなくても問題ない。
    • ただしエラーが出る場合は、仕入先を仮に設定する(推奨:仕入先が設定されていない商品はPO作成しない設定を確認する)。
  2. 再注文ルールが不要なら削除
    • 商品マスタ > 「補充」タブ(または「在庫」タブ)で、再注文ルール(Reordering Rule)が設定されていないか確認。
    • 「受注発注(MTO)」が有効なら、これも無効化。

標準の廃棄処理フローの確認

✅ 通常の廃棄処理は以下の手順で動作します(補充処理は発生しないはず):

  1. 在庫モジュール > 「廃棄(Scrap)」で新規作成。
  2. 商品、数量、ロケーションを指定。
  3. 「確認」ボタンを押す。
  4. 在庫数量が減少し、廃棄履歴が記録される(stock.scrapモデル)。

→ これで十分。POは発生しない。


最終対処:設定の見直し

もし上記で解決しない場合:

  • 商品マスタで「補充ルール(Reordering Rule)」を削除
  • 受注発注(MTO)」フラグを無効化
  • カスタムロジック(在庫不足時に自動発注するルール)が入っていないか 確認

🎯 結論

✅ 廃棄処理でPOを作成しようとする挙動は通常不要。
✅ 「仕入先が定義されていない」エラーが出る場合は、不要な補充ロジックを無効化する or 仕入先を仮登録する
✅ 必要に応じて「廃棄処理は在庫数のみを減らす操作である」ことを再確認。

補充

✅ Odoo「補充(Replenishment)」機能とは?

  • 目的:在庫切れや不足を防ぐため、必要な商品を適切に補充(再発注)する
  • 使い方:手動または自動で、**購買オーダー(仕入れ)製造オーダー(製造業の場合)**を生成
  • 管理単位:各商品の各ロケーションごと

📝 補充画面の入力項目

項目内容備考
製品(Product)補充対象の商品必須
ロケーション(Location)補充元/先(通常は在庫倉庫)必須
最小数量(Min Quantity)在庫がこの数量を下回ったら補充が必要Reordering Ruleで設定
最大数量(Max Quantity)補充後の在庫をこの数量に調整する任意(在庫の上限を設定したい場合)
現在の在庫(On Hand)現在の物理在庫システムが自動計算
予約済み(Reserved)他のオーダーで引き当てられている在庫数システムが自動計算
予測(Forecasted Quantity)予測在庫数→ 後述の計算ロジック参照
必要数量(To Order)発注すべき数量(推奨値)予測数量とルールに基づき自動計算(手動編集可)
次の補充(Next Action Date)次の補充予定日Reordering Ruleで設定(自動化の場合)
アクション(Action)購買 or 製造商品の設定による自動判定(Buy / Manufacture)
オーダー作成(Order)手動でPO/MO作成クリックで補充オーダーを作成
自動化(Automated Ordering)自動補充を有効化有効にするとスケジューラで定期実行

🔍 予測数量(Forecasted Quantity)の計算ロジック

| 予測数量 = 現在庫(On Hand) – 予約済み(Reserved) + 未入荷の数量(Incoming) – 出荷予定の数量(Outgoing) |

つまり:

  • 現在庫:今ある実在庫
  • 予約済み:販売オーダーや出荷で引き当て済みの分
  • 未入荷:仕入れ中の数量(POが確定済みだがまだ入荷していない分)
  • 出荷予定:販売オーダーや出荷指示に基づき、出庫予定の数量

🧭 計算例

商品現在庫予約済み未入荷出荷予定予測数量
iPhone 14 Pro50台20台10台15台50 – 20 + 10 – 15 = 25台

🔧 オーダー生成と自動化の仕組み

項目内容備考
Orderボタン選択した行の補充オーダーを手動で生成PO(購買)またはMO(製造)を作成
自動化(Automated Ordering)補充ルールに基づき、スケジューラ(定時バッチ)が自動で補充オーダーを生成「再注文ルール(Reordering Rule)」が必要
再注文ルール(Reordering Rule)商品単位に「最小」「最大」数量を設定必要ならロケーションごとに設定可能
スケジューラ(Scheduler)stock.scheduler.compute通常は夜間実行(設定変更可)で補充オーダーを自動生成

🎯 まとめ:Odoo補充機能の本質

予測数量で在庫不足を予測し、必要数を算出
手動 or 自動で購買/製造オーダーを生成できる
再注文ルールとスケジューラを組み合わせれば、全自動で補充運用が可能
スマホ販売業でも、店舗ごとの補充や本部→店舗間補充に活用できる

📝 補足:各ノードのデータ出所

ノードデータモデルモジュール
商品マスタproduct.productProduct
在庫数量stock.quantInventory
予約済み数量stock.moveInventory
入荷予定数量stock.moveInventory
出荷予定数量stock.moveInventory
予測数量計算上記の差引Inventory
再注文ルールstock.warehouse.orderpointInventory
補充数量決定予測数量 – 最小数量Inventory
手動発注Orderボタン押下UIアクション
自動発注stock.scheduler.computeInventory
購買オーダーpurchase.orderPurchase
製造オーダーmrp.productionManufacturing

Comments

コメントを残す

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