コンテンツにスキップ

予約受注レーンと送料・配送制御

背景

予約商品を通常商品と同じ注文のまま扱うと、送料計算、配送日指定、受注分割、顧客向け表示が相互に干渉する。 そのため、予約受注は通常受注とは別のレーンとして扱い、送料と配送制御の境界を明確にすることを正とする。

要件

  • 予約注文の送料ルールは通常注文とは別レーンで扱い、欠品予約は原則送料無料、新商品予約は商品設定に応じて送料有無を切り替えられること。
  • 通常注文、欠品予約、新商品予約は同一 checkout / 同一決済に混在させず、顧客がそれぞれ別注文として注文確定し、注文ごとに独立して決済すること。
  • 予約商品を含む注文では、配送日の指定を無効化し、配送時間帯のみ指定可能とすること。
  • 注文確認・注文完了・注文詳細では、通常商品と予約商品を分けて表示し、それぞれの小計と総合計の関係を明示できること。
  • 出荷時期別の受注分割・統合は REQ-073 の制御と一体で扱い、送料・配送日指定・表示分離が同じ予約受注レーン前提で整合すること。
  • 既存カートに異なる注文レーンの商品が混在する場合は、そのまま単一決済へ進ませず、checkout 開始時に別注文へ分割した上で、送料・配送希望日・決済条件を子注文単位で再計算すること。
  • SMILE 受注エクスポートでは Order.customFields.orderLaneType を基準に通常受注と予約受注を絞り込めること。絞り込み未指定時も、SMILE 側で誤って同一取込ファイルとして扱わないよう通常受注と予約受注を ZIP 内で別ファイルに分けること。

受け入れ観点

  1. 欠品予約・新商品予約・通常商品の各組み合わせで、送料境界が期待どおりに反映される。
  2. 通常商品を含む注文に欠品予約または新商品予約を追加した場合、あるいは予約注文に通常商品を追加した場合、checkout 開始時に注文グループ + 子注文へ分割し、別注文・別決済として扱う。
  3. 欠品予約と新商品予約が混在する場合も checkout 開始時に予約グループ単位で分割し、種別ごとの別注文・別決済として扱う。
  4. 予約商品を含む注文では、配送日入力が無効化され、配送時間帯のみ選択できる。
  5. 注文確認・注文完了・注文詳細で通常商品と予約商品が分離表示される。
  6. SMILE 受注エクスポートで「通常受注のみ」「予約受注のみ」を選択でき、未指定時は通常受注・予約受注が別ファイルとして出力される。

実装整理(2026-05-11)

  • cart は 1 つの activeOrder として保持し、通常注文・欠品予約・新商品予約をまたぐ混在は checkout 開始 API で注文グループ + 子注文へ分割する。
  • 顧客には分割後の子注文ごとに独立した決済を完了してもらう。
  • seller order 分離は checkout 開始時 split の実装基盤として使い、親グループと子注文の採番・追跡に利用する。
  • 予約受注は releaseGroupKey と出荷予定日を基準に束ね、出荷時期が異なる予約商品は別受注へ分割する。
  • 欠品予約は原則送料無料とし、新商品予約は商品設定に応じて既存の配送計算を独立に再実行する。
  • 予約商品を含む checkout では配送日指定を無効化し、配送時間帯のみ入力可能とする。
  • 注文確認、注文完了、注文詳細、注文履歴では通常商品と予約商品を分離表示し、親注文配下の関連受注を辿れる状態を正とする。
  • storefront の /checkout 直アクセスは、split checkout で child order を active 化した注文だけが入れるようにし、混在 cart のままは通さない。
  • SMILE 受注エクスポートは orderLaneType で絞り込み可能にし、未指定時は通常受注と予約受注を別ファイルで出力する。

実装ステータス(2026-05-22 更新)

完了済み

  • startSplitCheckout mutation(reservation-split-checkout.service.ts + .shop-resolver.ts
  • 通常のみカートは orderGroupCode: null を返し、そのまま /checkout
  • 混在カートは aggregate 親注文 + 子注文群を生成し、orderGroupCode を返す
  • 冪等性ガード・二重 split 防止ガード実装済み
  • activateSplitCheckoutOrder mutation(子注文を active 化して /checkout へ)
  • Seller strategy (reservation-order-seller.strategy.ts) による releaseGroupKey / orderType ベースの分割ロジック
  • Order custom fields 拡張: orderGroupId, childOrderIndex, splitStatus, reservationSplitCheckoutRequested
  • DB migration: 20260514020000_add_split_checkout_order_custom_fields.ts
  • Storefront cart-page-content.tsx: 通常のみカート分岐・混在カート分岐の両方実装済み
  • Storefront order-detail-page-content.tsx: Seller + AddingItems 状態での「この注文の決済へ進む」ボタン表示
  • GraphQL スキーマ: orderGroupCode: String(nullable)
  • 自動テスト: reservation-split-checkout.service.spec.ts で guard clause を網羅 (空カート / 状態不一致 / 進行中フラグ / Aggregate parent / 終了済み / session 不在 / 他人の注文 / 存在しない注文コード / splitStatus="split" の冪等パス)

継続確認が必要な点

  • 子注文単位の送料再計算の完全な受け入れ証跡は別途残す(ACC-090)。
  • aggregate 親注文コードは現在一時コードのまま保持される場合がある。
  • 予約注文含有時メッセージの顧客ステータス別・商品別複合条件分岐は別要件で管理。
  • Dashboard と外部連携の初期リリース対応状況:
  • Smile CSV export: applyOrderExportFilterstype != 'Aggregate' ガード追加済み(既存の state フィルタに加え明示的に除外)
  • 帳票(report-order.listener.ts): Aggregate 注文は OrderPlacedEvent を発火しないため自然に除外される(変更不要)
  • 出荷連携(shipment-fulfillment-orchestrator): 子注文コードで検索するため正常動作する(変更不要)
  • Vendure Dashboard 注文一覧への Aggregate 注文表示: 現状フィルタなしで表示される(運用上は confusing だが data corruption なし)—別タスクでフィルタ/ラベル実装を検討
  • freee 販売・請求書連携: 現時点で子注文専用実装なし(該当連携自体が初期リリース scope 外)