決済フロー
位置づけ: Phase 2(保留)。卸本体と同じく範囲外。実装は削除済み
商社(卸)モジュールでは、卸案件(WholesaleCase)ごとに決済区分を1つ選択し、選択した区分に応じて工程列が確定する。工程は直列に進行し、直前工程が完了するまで発注・請求は行えない。発注可否・請求可否の判定、入金の分割消込、案件一覧・ダッシュボードのステータス集計は、いずれもこの工程列を単一のソースとして導出する。
決済区分は 前金 / 掛売 / 三社間 / カード / その他 の5値。元請案件(deals)とは別軸の案件(WholesaleCase)として管理し、案件作成時に決済区分を選択する。
決済区分は案件作成後も変更できる。案件詳細画面(WholesaleDetail)の「決済区分」カードから任意のタイミングで選び直せ、選択と同時に useWholesale.updateCase(id, { settlement }) が即座に反映される(確認ダイアログ等のガードはない)。この操作は wholesale.ts(ドメイン層)に専用関数を持たず、画面側が汎用の updateCase で settlement フィールドを直接書き換える実装である。変更時に進捗フラグ(settlementConfirmed / ordered / delivered / invoiced / paymentReceived)や登録済みの実体(orders / invoices / payments 等)はリセットされないため、旧区分で進んだ工程が新区分の工程列上でどう解釈されるかは呼び出し時の状態次第になる(詳細は後述「エッジケース/異常系」)。
本ページはドメインロジック(crm_app/app/src/lib/wholesale.ts)と、それを利用する画面層(features/wholesale/*.tsx)・状態層(store/useWholesale.ts)・集計層(lib/wholesaleMetrics.ts)・支払予定算出(lib/supplierPayment.ts)を正として記述する。卸金額マスタ・販売店発注フォーム・パイプライン全体の仕様は features/wholesale を参照。
決済条件の詳細(手数料・頭金・支払条件・カード・信販関連の項目)と三社間マネー(信販入金→仕入先支払→販売店精算の金銭フロー)は、ValueOSの case_settlements / lib/threeParty に準拠する形で試作crm_app(lib/wholesale.ts)に実装済みである。
スコープ/対象
Section titled “スコープ/対象”| 項目 | 内容 |
|---|---|
| 対象 | 卸案件(WholesaleCase)の決済区分・工程進行・発注ゲート・請求ゲート・入金消込・買掛/未収管理・発注書/請求書印刷 |
| 対象外 | 卸金額マスタ、販売店発注フォーム、タスク(features/wholesale を参照) |
| 情報源 | 定例で確定した新要件(卸金額・発注管理・決済フロー・商社別軸)。正本は redesign/meeting-log |
| 関連ページ | features/wholesale、glossary、redesign/valueos-gap |
データ(エンティティ)
Section titled “データ(エンティティ)”卸案件は1件の中に発注・請求・入金を複数保持する集約構造とする。
| エンティティ | 主キー | 主なフィールド | 役割 |
|---|---|---|---|
| WholesaleCase | id(W-0001形式) | dealer, settlement, approvalNo, settlementDetail, items[], 進捗フラグ, cancelled, memo | 卸案件の本体。工程進行の起点 |
| WSettlementDetail | (WholesaleCaseに1件embed) | feeRate, feeAmount, depositRate, depositAmount, paymentTerms, cardBrand, cardStatus, financeCompany, loanStatus, approvalNo | 決済条件の詳細(ValueOS case_settlements 準拠)。区分別の出し分けは後述「決済条件の詳細」を参照 |
| WItem | (WholesaleCase.itemsの要素) | name, kind, qty, unitPrice, wholesalePriceSnapshot, purchasePriceSnapshot, isManualPrice, priceFetchedAt | 明細行。卸単価は受注時点のスナップショット |
| WOrder | id | orderNo(PO-0001形式), orderDate, supplier, amount, status, deliveredDate | 発注の実体。1案件に複数保持可 |
| WInvoice | id | invoiceNo(INV-0001形式), invoiceDate, dueDate, amount, status | 請求の実体。1案件に複数保持可 |
| WPayment | id | paymentDate, amount, method | 入金の実体。1案件に複数保持可(分割入金) |
| WFinanceReceipt | id | financeCompany, scheduledDate, scheduledAmount, actualDate, actualAmount, status | 三社間マネー:信販会社→VEの入金。後述「三社間マネー」を参照 |
| WSupplierPayment | id | supplier, orderNo, dueDate, scheduledAmount, paidDate, paidAmount, status | 三社間マネー:VE→仕入先の支払 |
| WDealerSettlement | id | statementNo, issueDate, creditReceived, veShare, adjustment, payout, status | 三社間マネー:VE→販売店の精算 |
WholesaleCase は工程進行を判定するための進捗フラグを持つ。フラグとエンティティ登録(発注書・請求書の追加)は連動して更新する。
| フラグ | 意味 | 対応する工程 |
|---|---|---|
| settlementConfirmed | 決済条件の確定 | settlement |
| paymentReceived | 入金完了 | prepay / payment(同一フラグを共有) |
| ordered | 発注済 | order |
| delivered | 納品済 | deliver |
| invoiced | 請求済 | invoice |
全フィールド表:WholesaleCase
Section titled “全フィールド表:WholesaleCase”| フィールド | 型 | 必須 | 既定 | 制約・備考 |
|---|---|---|---|---|
| id | string | 必須 | — | W-0001 形式。newWholesaleId() が既存IDの最大連番+1を採番 |
| createdAt | string(YYYY-MM-DD) | 必須 | 作成日当日 | 起票日。マイルストーン投影の起点 |
| orderReceivedDate | string(YYYY-MM-DD) | 任意 | なし(createdAtにフォールバック) | orderReceivedDateOf() が未設定時にcreatedAtを返す。KPIの期間集計はこの日付基準 |
| dealer | string | 必須 | — | 発注元の販売店(代理店)名。新規作成フォームで選択必須 |
| settlement | WSettlement(enum) | 必須 | 前金(新規作成フォームの初期値) | 前金/掛売/三社間/カード/その他。案件作成後も画面から自由に変更可(後述エッジケース) |
| approvalNo | string | 任意 | undefined | 三社間/カードの承認番号。settlementDetail.approvalNo とは別フィールドで、UIの承認番号入力欄はこちらのみを更新する(同期しない実装。後述エッジケース) |
| settlementDetail | WSettlementDetail | 任意 | undefined | 決済条件の詳細。新規作成フォームには入力欄がなく、詳細画面の「決済条件(詳細)」から後追いで編集する想定(シードデータのみ初期値を持つ) |
| items | WItem[] | 必須(1件以上をUIが強制) | [] | 明細。空配列だと卸金額0円の案件になり得る |
| settlementConfirmed | boolean | 必須 | false | trueでsettlement工程完了 |
| paymentReceived | boolean | 必須 | false | trueでprepay/payment工程完了。addPayment後にreconcile()が自動算出(後述) |
| ordered | boolean | 必須 | false | addOrder実行時にtrueへ |
| delivered | boolean | 必須 | false | 全ordersが納品済になった時点でmarkDeliveredがtrueへ |
| invoiced | boolean | 必須 | false | addInvoice実行時にtrueへ |
| orders | WOrder[] | 任意 | undefined(実質[]扱い) | wholesaleCost()の原資 |
| invoices | WInvoice[] | 任意 | undefined | wholesaleBilled()の原資 |
| payments | WPayment[] | 任意 | undefined | wholesalePaid()の原資 |
| financeReceipts | WFinanceReceipt[] | 任意 | undefined | 三社間のみ想定。区分による強制はない(型上は任意) |
| supplierPayments | WSupplierPayment[] | 任意 | undefined | 同上 |
| dealerSettlement | WDealerSettlement | 任意 | undefined | 同上 |
| cancelled | boolean | 任意 | false相当(未設定) | trueだと経営KPI集計(wholesaleMetrics)から除外。UIに設定導線がなく、シード/直接操作でしか立てられない。工程ゲートや一覧表示はこのフラグを見ない(後述エッジケース) |
| memo | string | 任意 | undefined | 自由記述。与信枠や複数請求の入金対応関係など、構造化されていない情報の運用逃し先になっている |
全フィールド表:WItem
Section titled “全フィールド表:WItem”| フィールド | 型 | 必須 | 既定 | 制約・備考 |
|---|---|---|---|---|
| name | string | 必須 | — | 品名 |
| kind | "商品" | "パッケージ" | 必須 | — | |
| qty | number | 必須 | — | UI下限1(Math.max(1, ...)) |
| unitPrice | number | 必須 | — | 販売店向け卸単価(税抜)。snapshotWItem()経由ではwholesalePriceがそのまま入る |
| purchasePriceSnapshot | number | 任意 | undefined | 受注時点の仕入価スナップ(case_products.purchase_price相当) |
| wholesalePriceSnapshot | number | 任意 | undefined | 受注時点の卸価スナップ。マスタ改定の影響を受けない |
| isManualPrice | boolean | 任意 | undefined | 価格表を使わず手入力したか |
| priceFetchedAt | string(YYYY-MM-DD) | 任意 | undefined | 価格取得時点 |
全フィールド表:発注・請求・入金・三社間マネー
Section titled “全フィールド表:発注・請求・入金・三社間マネー”| フィールド | 型 | 必須 | 既定 | 制約・備考 |
|---|---|---|---|---|
| WOrder.id | string | 必須 | — | |
| WOrder.orderNo | string | 必須 | — | PO-0001形式。newOrderNo()が全案件横断で連番採番 |
| WOrder.orderDate | string(YYYY-MM-DD) | 必須 | 登録日当日 | |
| WOrder.supplier | string | 任意 | undefined | 仕入先(商社/メーカー)。空欄のまま登録可能(印刷では「(仕入先未定)」表示) |
| WOrder.amount | number | 必須 | 案件の卸金額合計(wholesaleTotal) | 発注金額(=原価扱い)。UIは登録時に案件合計を仮当てするのみで、実発注額との差異チェックはない |
| WOrder.status | "発注済" | "納品済" | 必須 | "発注済" | markDeliveredで"納品済"へ |
| WOrder.deliveredDate | string | 任意 | undefined | |
| WInvoice.id | string | 必須 | — | |
| WInvoice.invoiceNo | string | 必須 | — | INV-0001形式。全案件横断で連番採番 |
| WInvoice.invoiceDate | string | 必須 | 登録日当日 | |
| WInvoice.dueDate | string | 必須 | 登録日+30日 | |
| WInvoice.amount | number | 必須 | 案件の卸金額合計 | 税抜。消費税は印刷画面でのみ都度計算(後述) |
| WInvoice.status | "請求済" | "一部入金" | "入金済" | 必須 | "請求済" | addPayment後にreconcile()が案件単位の合計から一律で再計算(後述) |
| WPayment.id | string | 必須 | — | |
| WPayment.paymentDate | string | 必須 | 登録日当日 | |
| WPayment.amount | number | 必須 | — | 0以下は画面側でエラートースト表示・登録拒否 |
| WPayment.method | string | 任意 | undefined | UIプルダウン候補:振込/カード/ローン会社/その他(自由記述も許容) |
| WFinanceReceipt.id | string | 必須 | — | |
| WFinanceReceipt.financeCompany | string | 必須 | — | |
| WFinanceReceipt.scheduledDate / scheduledAmount | string / number | 必須 | — | |
| WFinanceReceipt.actualDate / actualAmount | string / number | 任意 | undefined | |
| WFinanceReceipt.status | "予定" | "入金済" | 必須 | "予定" | |
| WSupplierPayment.id | string | 必須 | — | |
| WSupplierPayment.supplier | string | 必須 | — | |
| WSupplierPayment.orderNo | string | 任意 | undefined | 紐づくPO-xxxx。買掛管理画面の発注日引き当てに使用 |
| WSupplierPayment.dueDate | string | 必須 | — | 未入力時はsupplierPaymentDueDate()で画面表示のみ自動算出(永続化はされない) |
| WSupplierPayment.scheduledAmount | number | 必須 | — | |
| WSupplierPayment.paidDate / paidAmount | string / number | 任意 | undefined | |
| WSupplierPayment.status | "予定" | "支払済" | 必須 | "予定" | |
| WDealerSettlement.id | string | 必須 | — | 1案件に1件(配列でなく単一オブジェクト) |
| WDealerSettlement.statementNo | string | 必須 | — | DS-xxxx |
| WDealerSettlement.issueDate | string | 必須 | — | |
| WDealerSettlement.creditReceived | number | 必須 | — | 信販から受領した総額 |
| WDealerSettlement.veShare | number | 必須 | — | VEの取り分(粗利) |
| WDealerSettlement.adjustment | number | 必須 | — | 調整額(±)。仕入原価控除等 |
| WDealerSettlement.payout | number | 必須 | — | 算出値(creditReceived − veShare + adjustment)だが型上は独立フィールド。自動計算関数はなく、登録時に手計算した値を入力する運用(不整合が起きても検知されない) |
| WDealerSettlement.scheduledPayoutDate / actualPayoutDate | string | 任意 | undefined | |
| WDealerSettlement.status | "予定" | "精算済" | 必須 | "予定" | |
| WDealerSettlement.memo | string | 任意 | undefined |
決済区分と工程
Section titled “決済区分と工程”決済区分ごとに工程数が異なる。前金は4工程、掛売・その他は5工程、三社間・カードは4工程とする。三社間とカードは工程列そのものが完全に一致する。
| 決済区分 | 工程数 | 特徴 |
|---|---|---|
| 前金 | 4 | 入金確認後に発注する。VE(当方)のリスクを最小にする区分 |
| 掛売 | 5 | 先に発注し、全納品後に請求する(売りかけ) |
| 三社間 | 4 | 審査完了(承認番号)で先に発注し、工事後にローン会社から入金される |
| カード | 4 | カード会社の承認で先に発注し、カード会社から入金される |
| その他 | 5 | 掛売と同一の工程列を使用する |
工程は7種類のキーで定義し、settlement(決済条件確定)と approval(承認番号確認)は排他、prepay(前金入金確認)は前金のみ、invoice(販売店へ請求)は掛売・その他のみに登場する。担当は経理(settlement/approval/prepay/invoice/payment)と発注担当(order/deliver)の2ロールに限定する。この担当ラベルは画面表示用の文字列(STEP_META)にすぎず、後述「権限」のとおりロールベースの操作制御とは連動していない。
| 工程キー | 表示名 | 担当(表示ラベル) | 完了条件 | 登場する決済区分 |
|---|---|---|---|---|
| settlement | 決済条件の確定 | 経理 | settlementConfirmed = true | 掛売・前金・その他 |
| approval | 審査OK(承認番号)の確認 | 経理 | settlementApproved(c) = true(承認番号が空文字でない、またはローン状態=承認済/実行済、またはカード状態=承認済/決済済) | 三社間・カード |
| prepay | 前金の請求・入金確認 | 経理 | paymentReceived = true | 前金 |
| order | 商社/メーカーへ発注 | 発注担当 | ordered = true | 全区分 |
| deliver | 納品確認 | 発注担当 | delivered = true | 全区分 |
| invoice | 販売店へ請求 | 経理 | invoiced = true | 掛売・その他 |
| payment | 入金の消込 | 経理 | paymentReceived = true | 掛売・三社間・カード・その他 |
区分別の工程順序は次のとおり。
| 決済区分 | STEP1 | STEP2 | STEP3 | STEP4 | STEP5 |
|---|---|---|---|---|---|
| 前金 | settlement(経理) | prepay(経理) | order(発注担当) | deliver(発注担当) | — |
| 掛売 | settlement(経理) | order(発注担当) | deliver(発注担当) | invoice(経理) | payment(経理) |
| 三社間 | approval(経理) | order(発注担当) | deliver(発注担当) | payment(経理) | — |
| カード | approval(経理) | order(発注担当) | deliver(発注担当) | payment(経理) | — |
| その他 | settlement(経理) | order(発注担当) | deliver(発注担当) | invoice(経理) | payment(経理) |
工程は配列順に直列進行し、未完了の先頭工程を常に「現在の工程」として扱う。スキップ・並行実行は行わない。
前金は末尾に独立した payment 工程を持たず、prepay の入金確認をもって金銭処理を完結させる。三社間とカードは工程配列が完全に同一であり、両者を区別するのは決済区分の値と approvalNo / settlementDetail の内容のみである。prepay と payment はいずれも paymentReceived という同一のブール値で完了判定する。前金だけが prepay を、他区分だけが payment を持つため、判定上の衝突は生じない。
flowchart LR
subgraph zenkin["前金(4工程)"]
A1["決済条件確定\nsettlement"] --> A2["前金入金確認\nprepay"] --> A3["発注\norder"] --> A4["納品確認\ndeliver"]
end
subgraph kakeuri["掛売・その他(5工程)"]
B1["決済条件確定\nsettlement"] --> B2["発注\norder"] --> B3["納品確認\ndeliver"] --> B4["請求\ninvoice"] --> B5["入金消込\npayment"]
end
subgraph sansha["三社間・カード(4工程)"]
C1["承認番号確認\napproval"] --> C2["発注\norder"] --> C3["納品確認\ndeliver"] --> C4["入金消込\npayment"]
end
決済条件の詳細(区分別項目)
Section titled “決済条件の詳細(区分別項目)”WholesaleCase.settlementDetail(WSettlementDetail 型)に、決済条件の詳細を保持する。ValueOSの case_settlements(fee_rate / fee_amount / deposit_rate / deposit_amount / payment_terms / card_brand / card_status / finance_company / loan_status / approval_number)に準拠する。表示・入力項目は決済区分ごとに SETTLEMENT_FIELDS で出し分ける。
| フィールド | 意味 | 単位/取り得る値 | 必須 | 既定 |
|---|---|---|---|---|
| feeRate | 手数料率 | % | 任意 | undefined |
| feeAmount | 手数料額 | 円 | 任意 | undefined |
| depositRate | 頭金率 | % | 任意 | undefined |
| depositAmount | 頭金額 | 円 | 任意 | undefined |
| paymentTerms | 支払条件(掛売の締め/サイト) | 自由記述 | 任意 | undefined |
| cardBrand | カードブランド | 自由記述 | 任意 | undefined |
| cardStatus | カード状態 | 未処理 / 承認済 / 決済済 | 任意 | undefined |
| financeCompany | 信販会社 | 自由記述 | 任意 | undefined |
| loanStatus | ローン状態 | 審査中 / 承認済 / 実行済 | 任意 | undefined |
| approvalNo | 承認番号 | 自由記述 | 任意 | undefined |
区分ごとに表示・入力する項目は次のとおり(SETTLEMENT_FIELDS)。手数料率・手数料額はいずれの区分でも共通して出す。全項目が任意フィールドで、スキーマレベルの必須バリデーションは存在しない(値の有無はsettlementDetailEntries()が表示可否を判定するのみ)。
| 決済区分 | 出し分けする項目 |
|---|---|
| 前金 | 頭金率, 頭金額, 手数料率, 手数料額 |
| 掛売 | 支払条件, 手数料率, 手数料額 |
| 三社間 | 信販会社, 承認番号, ローン状態, 手数料率, 手数料額 |
| カード | カードブランド, 承認番号, カード状態, 手数料率, 手数料額 |
| その他 | 支払条件, 手数料率, 手数料額 |
WholesaleCase.approvalNo(単一の任意文字列フィールド)は後方互換のため残置し、settlementDetail.approvalNo と同義として扱う設計だが、案件詳細画面の入力欄は前者のみを更新するため、実運用では両者が乖離し得る(後述「エッジケース/異常系」)。値のある項目のみを画面に出す読み取り専用ヘルパーとして settlementDetailEntries() を用意する。
発注ゲート・請求ゲート
Section titled “発注ゲート・請求ゲート”発注可否(canOrder)と請求可否(canInvoice)は、工程配列上の位置関係から機械的に判定する。画面はこの判定結果を表示するレイヤーであり、判定ロジック自体は持たない。
- 発注ゲート:
orderより前の工程がすべて完了し、かつ未発注のときに true とする。 - 発注ブロック時のメッセージは、未完了の直前工程名を含む動的な文言(例:「決済条件の確定が未完了」)とする。
- 請求ゲート:
invoice工程を持つ区分(掛売・その他)でのみ判定し、invoiceより前の工程がすべて完了し、かつ未請求のときに true とする。三社間・カード・前金は請求工程を持たないため、この判定自体が発生しない。 - 請求ブロック時のメッセージは「納品などの前工程が未完了」という固定文言とする。
- 画面では発注・請求ボタンにブロック理由をツールチップで表示し、ボタン文言を可否に応じて出し分ける。
- いずれのゲートも
WholesaleCase.cancelledを参照しない。キャンセル済みの案件でもcanOrder/canInvoiceは工程進捗のみで判定され、発注・請求・入金の登録操作自体は止まらない(後述エッジケース)。
| 決済区分 | 発注(order)解放条件 | 請求(invoice)解放条件 | 備考 |
|---|---|---|---|
| 前金 | 前金入金確認(prepay)完了後 | 該当なし | 請求工程自体を持たない |
| 掛売 | 決済条件確定(settlement)後 | 納品確認(deliver)完了後 | 標準フロー |
| 三社間 | 承認番号確認(approval)後 | 該当なし | 入金は payment 工程のみで判定 |
| カード | 承認番号確認(approval)後 | 該当なし | 三社間と同一構造 |
| その他 | 決済条件確定(settlement)後 | 納品確認(deliver)完了後 | 掛売と同一実装 |
flowchart TD
O0["order が工程列に含まれるか"] -->|"含まれない(現行は全区分に order があるため常にYes)"| ONO["canOrder = false"]
O0 -->|Yes| O1{"order より前の全工程が\ndone か?"}
O1 -->|No| O2["canOrder = false\norderBlockedReason = 未完了の直前工程名"]
O1 -->|Yes| O3{"すでに ordered = true か?"}
O3 -->|Yes| O4["canOrder = false(重複発注防止)"]
O3 -->|No| O5["canOrder = true"]
I0{"invoice が工程列に\n含まれるか"} -->|"No(前金・三社間・カード)"| INO["canInvoice = false(invoiceBlockedReason なし)"]
I0 -->|"Yes(掛売・その他)"| I1{"invoice より前の\n全工程が done か?"}
I1 -->|No| I2["canInvoice = false\ninvoiceBlockedReason = 納品などの前工程が未完了"]
I1 -->|Yes| I3{"すでに invoiced = true か?"}
I3 -->|Yes| I4["canInvoice = false(重複請求防止)"]
I3 -->|No| I5["canInvoice = true"]
工程・バケットの状態遷移
Section titled “工程・バケットの状態遷移”案件は現在の工程から自動導出する6バケット(後述「ステータスバケット」)を単位として、次の状態を遷移する。前金だけが入金待ちを発注待ちより前に通過し、他区分は末尾で入金待ちに至る非対称な遷移になる。
stateDiagram-v2
[*] --> 決済待ち
決済待ち --> 入金待ち: 前金/settlement・approval完了後にprepayへ
決済待ち --> 発注待ち: 掛売・その他/settlement完了\n三社間・カード/approval(承認番号)確認完了
入金待ち --> 発注待ち: 前金/paymentReceived=true(prepay完了)
発注待ち --> 納品待ち: ordered=true(発注登録)
納品待ち --> 請求待ち: 掛売・その他/delivered=true
納品待ち --> 入金待ち: 三社間・カード/delivered=true(請求工程を経由しない)
請求待ち --> 入金待ち: invoiced=true(請求登録)
入金待ち --> 完了: paymentReceived=true(入金消込完了)
請求書ステータス(WInvoice.status)は、請求登録後の入金額に応じて店側フラグとは独立にストア層(useWholesale.reconcile())が自動遷移させる。判定は請求書単位ではなく案件単位の合計(wholesaleBilled / wholesalePaid)で行い、addInvoice / addPayment の直後に案件内の全請求書へ一律で反映する。
stateDiagram-v2
[*] --> 請求済: addInvoice実行
請求済 --> 一部入金: paid > 0 かつ paid < billed
一部入金 --> 入金済: paid >= billed
請求済 --> 入金済: 一括入金で paid >= billed
入金済 --> 一部入金: 追加請求でbilledが増え paid < billed に戻った場合(理論上。UIに請求取消操作はない)
入金管理(分割入金と残高消込)
Section titled “入金管理(分割入金と残高消込)”1案件の payments[] に複数の入金を登録でき、残高消込方式で管理する。残高は保存された値ではなく、請求・入金の実額から都度計算する派生値とする。
| 関数 | 計算内容 |
|---|---|
| wholesaleBilled | 請求書(invoices)があればその合計、なければ品目合計(卸金額)を採用 |
| wholesalePaid | 入金(payments)の合計 |
| wholesaleBalance | max(0, wholesaleBilled − wholesalePaid) |
請求書のステータスは 請求済 / 一部入金 / 入金済 の3値とし、payment 工程の完了フラグ(paymentReceived)とあわせて、入金登録のたびに useWholesale.reconcile() が自動算出する。reconcile() は fullyPaid = billed > 0 && paid >= billed を判定し、paymentReceived を更新すると同時に、案件配下の全ての請求書の status を同一の値(請求済/一部入金/入金済)へ一括で書き換える。請求書ごとに個別の消込状態を持つ設計ではない。入金(WPayment)は案件単位の配列として保持し、特定の請求書(WInvoice)に紐付ける識別子は持たない。複数請求がある案件での入金対応関係は、案件のメモ欄で管理する運用とする。
sequenceDiagram
actor U as 担当者(画面)
participant D as WholesaleDetail
participant S as useWholesale(store)
participant R as reconcile()
U->>D: 発注を登録
D->>S: addOrder(caseId, order)
S->>S: orders[] に追加、ordered = true
U->>D: 納品確認
D->>S: markDelivered(caseId, orderId, date)
S->>S: 対象orderのstatus="納品済"。全orderが納品済ならdelivered=true
U->>D: 請求を登録(掛売・その他のみ canInvoice=true)
D->>S: addInvoice(caseId, invoice)
S->>R: reconcile(case)
R-->>S: invoices[].status を billed/paid から再計算、invoiced=true
U->>D: 入金を登録(案件単位・分割可)
D->>S: addPayment(caseId, payment)
S->>R: reconcile(case)
R-->>S: paid>=billed なら paymentReceived=true、invoices[]全件のstatusを"入金済"へ
S-->>D: 最新caseを反映(wholesaleFlowがbucket/canOrder/canInvoiceを再導出)
与信枠・承認番号
Section titled “与信枠・承認番号”掛売は与信枠の範囲内で先に発注する区分と位置づけるが、与信枠を表すデータ項目は現行仕様に存在しない。案件のメモ欄に自由記述で残す運用とする。与信枠のデータ構造化は次節「確認事項」を参照。
三社間・カードは工程列の先頭に approval(承認番号確認)を置く。WholesaleCase.approvalNo(単一の任意文字列フィールド)は後方互換のため引き続き両区分で共用するが、決済条件の詳細(settlementDetail)では三社間に financeCompany(信販会社)、カードに cardBrand(カードブランド)という専用フィールドを持ち、発行元を区別できる。
承認(審査OK)の判定は settlementApproved() が担う。従来の approvalNo(案件直下または settlementDetail.approvalNo)に加え、ローン状態(loanStatus が 承認済 または 実行済)、カード状態(cardStatus が 承認済 または 決済済)のいずれかが成立していれば承認済みとみなす。工程表の approval 完了条件はこの判定結果を用いる(承認番号の有無のみで判定していた旧仕様から拡張済み)。
承認番号の形式(桁数・書式)に対するバリデーションは行わない。
案件詳細画面の「承認番号(審査OKの証跡)」入力欄は WholesaleCase.approvalNo(案件直下フィールド)のみを更新し、settlementDetail.approvalNo は書き換えない。settlementApproved() はどちらか一方が入っていれば承認扱いにするため発注ゲートの解放自体には影響しないが、詳細画面の「決済条件(詳細)」カードは settlementDetail.approvalNo(多くはシード時点の値のまま)を表示し続けるため、画面上で承認番号を入力・更新しても詳細カードの表示が追随しないという表示上の不整合が生じ得る。
三社間マネー(信販入金→仕入先支払→販売店精算)
Section titled “三社間マネー(信販入金→仕入先支払→販売店精算)”決済区分が三社間のとき、WholesaleCase は3種類の金銭移動を実体として保持する。ValueOSの lib/threeParty(finance_receipts / supplier_payments / dealer_settlements)に準拠する。流れは「信販会社が工事後にVEへ入金 → VEが仕入先へ支払 → VEが販売店へ精算」の順で、案件の入金消込(payments[] / paymentReceived)とは独立した実体として管理する。
| エンティティ | 主キー | 主なフィールド | 役割 |
|---|---|---|---|
| WFinanceReceipt | id | financeCompany, scheduledDate, scheduledAmount, actualDate, actualAmount, status(予定/入金済) | 信販会社 → VE の入金 |
| WSupplierPayment | id | supplier, orderNo, dueDate, scheduledAmount, paidDate, paidAmount, status(予定/支払済) | VE → 仕入先 の支払 |
| WDealerSettlement | id | statementNo, issueDate, creditReceived, veShare, adjustment, payout, status(予定/精算済) | VE → 販売店 の精算 |
WDealerSettlement.payout(販売店への支払額)は creditReceived − veShare + adjustment で算出する値だが、型上は独立フィールドであり自動計算関数はない。登録画面(詳細画面からのフォーム)で手計算した値をそのまま入力する運用であるため、3項目の入力に矛盾があっても検知されない。creditReceived は信販から受領した総額、veShare はVEの取り分(粗利)、adjustment は調整額(±、例: 仕入原価の控除)である。
sequenceDiagram
participant Loan as 信販会社
participant VE as VE(当方)
participant Sup as 仕入先(商社/メーカー)
participant Dealer as 販売店
VE->>Sup: 発注(PO-xxxx)
Sup-->>VE: 納品
Loan->>VE: 信販入金(WFinanceReceipt:scheduled→actual、status=入金済)
VE->>Sup: 仕入先支払(WSupplierPayment:scheduled→paid、status=支払済)
VE->>Dealer: 精算書発行・販売店精算(WDealerSettlement:payout=creditReceived-veShare+adjustment、status=精算済)
三社間マネーの集計は threePartySummary() が担う。
| 集計項目 | 内容 |
|---|---|
| creditScheduled / creditReceived | 信販入金の予定合計 / 実績合計(financeReceipts) |
| supplierScheduled / supplierPaid | 仕入先支払の予定合計 / 実績合計(supplierPayments) |
| veShare / dealerPayout | 精算書のVE取り分 / 販売店への支払額(dealerSettlement) |
| dealerSettled | 販売店精算が完了しているか(dealerSettlement.status = 精算済) |
| hasData | financeReceipts・supplierPayments・dealerSettlement のいずれかにデータがあるか |
三社間マネーの各実体(financeReceipts / supplierPayments / dealerSettlement)は工程ゲート(canOrder 等)の判定には用いない。工程進行の判定は引き続き settlementApproved() と進捗フラグのみで行う。請求書印刷(WholesaleInvoicePrint)は hasData が true の案件についてのみ「三社間精算」ブロックを表示する。
ステータスバケット(進捗ビュー)
Section titled “ステータスバケット(進捗ビュー)”案件一覧・ダッシュボードは、現在の工程から自動導出する6バケットで集計・絞り込みを行う。バケットは手動変更の対象にしない。
| バケット | 対応する工程 |
|---|---|
| 決済待ち | settlement, approval |
| 発注待ち | order |
| 納品待ち | deliver |
| 請求待ち | invoice |
| 入金待ち | prepay, payment |
| 完了 | 全工程完了 |
区分ごとに通過するバケットの並びが異なる点に注意する。前金は「決済待ち → 入金待ち(prepay) → 発注待ち → 納品待ち → 完了」の順で、入金待ちを発注待ちより前に通過する。掛売・その他・三社間・カードは入金に相当する工程が末尾に来るため、この4区分と前金とではバケットの通過順が逆になる。三社間・カードは請求待ちを経由しない。
マイルストーン(発注・請求/入金の予定日)
Section titled “マイルストーン(発注・請求/入金の予定日)”案件作成日(createdAt)を起点に、決済区分ごとのリードタイムで発注予定日・請求/入金予定日を投影し、タイムライン表示に用いる。実績(ordered・invoiced・paymentReceived が完了)が入るとマイルストーンは完了表示に切り替わる。
| 区分 | 発注までのリードタイム | 金銭マイルストーンの種別 |
|---|---|---|
| 前金 | 5日(ただし入金確認が発注より先に到来する) | 入金 → 発注の順 |
| 掛売 | 1日 | 発注 → 請求の順 |
| 三社間 | 2日 | 発注 → 入金の順 |
| カード | 2日 | 発注 → 入金の順 |
| その他 | 3日 | 発注 → 請求の順 |
前金以外は「発注」の10日後に納品、その3日後に請求または入金が到来する想定でマイルストーンを算出する。前金のみ、入金確認マイルストーンを発注マイルストーンより前に置く。マイルストーンは表示専用の投影値であり、WOrder.orderDate 等の実績日とは別に保持される(実績確定後もマイルストーン投影値は再計算されない)。
業務ルール(横断)
Section titled “業務ルール(横断)”粗利=卸価−原価
Section titled “粗利=卸価−原価”wholesaleGrossProfit(c) = wholesaleTotal(c) − wholesaleCost(c)。wholesaleCost() は orders[] の amount 合計であり、発注が1件も登録されていない案件(決済待ち・発注待ちバケットの案件)は wholesaleCost = 0 となるため、wholesaleGrossProfit は卸金額全額(=粗利率100%)として算出される。経営KPI(wholesaleMetrics)の実粗利は、発注登録前の案件を含めるとこの分だけ過大表示になり得る。
キャンセル除外
Section titled “キャンセル除外”WholesaleCase.cancelled が true の案件は、経営KPI(wholesaleMetrics の revenue / grossProfit / unpaid / 各スライス)からのみ除外される(ValueOSの activeRecords 除外相当)。案件一覧・詳細画面・買掛管理(WholesalePayables)・未収管理(WholesaleReceivables)・発注ゲート(canOrder/canInvoice)はいずれも cancelled を参照しないため、キャンセル済み案件でも通常どおり表示・操作できる。加えて、cancelled を true にするUI導線は現行実装に存在しない(シードデータまたは直接のデータ操作でのみ設定可能)。
締日・支払サイト → 支払予定日(買掛管理)
Section titled “締日・支払サイト → 支払予定日(買掛管理)”supplierPaymentDueDate(supplierName, orderDateISO)(lib/supplierPayment.ts)が、仕入先マスタ(SUPPLIERS)の closingDay(締日)・paymentSite(支払サイト)表記から支払予定日を算出する。買掛管理画面(WholesalePayables)でのみ、WSupplierPayment.dueDate が未入力の行に対する表示専用のフォールバックとして使う(算出結果を dueDate に書き戻すことはない)。
- 締日の解釈:
"月末"は当月末、"◯日"は当月のその日。発注日が締日以前ならその月で締め、超過していれば翌月に繰り越す。 - 支払サイトの解釈:
"翌"で始まれば+1ヶ月、"翌々"で始まれば+2ヶ月、それ以外は当月。加算した月の締日と同じ書式(月末/◯日)で支払日を決定する。 - 仕入先が見つからない、
closingDay/paymentSiteが未設定、または上記いずれの表記にも一致しない場合はundefinedを返す。画面はこの場合「未定」を表示し、overdue(期限超過)判定も行わない(dueDateが空のままなので恒常的にfalse)。
消費税・端数
Section titled “消費税・端数”WInvoice.amount および wholesaleBilled / wholesalePaid / wholesaleBalance はいずれも税抜金額で保持・計算する。消費税(10%、Math.round(subtotal * 0.1))は請求書印刷画面(WholesaleInvoicePrint)でのみ都度計算し、税込合計 = subtotal + tax として表示する。この税込合計と並んで表示される「残高(未収)」行は wholesaleBalance(税抜ベース)をそのまま出しており、両者の単位(税込/税抜)が画面上で揃っていない。消費税額はどのエンティティにも永続化されない。
反映日ベースの案件集計
Section titled “反映日ベースの案件集計”経営KPI(wholesaleMetrics)の売上・実粗利は orderReceivedDateOf(c)(受注日。未設定なら起票日)が集計期間内の案件のみを対象にする。未入金額(unpaid)は期間フィルタと無関係に、キャンセル以外の全案件の残高を合算する。
| 画面 | パス/コンポーネント | 主な挙動・操作可否 |
|---|---|---|
| 卸案件一覧 | /wholesale(WholesalePages) | 案件の新規起票(販売店・決済区分・明細を入力し W-xxxx を採番)。新規作成フォームの決済区分初期値は「前金」 |
| 案件詳細 | /wholesale/$caseId(WholesaleDetail) | 明細表示、発注登録/納品確認(発注ごと)、請求登録、入金登録(残高を既定値として入力)、承認番号入力(三社間/カードのみ表示)、決済区分の変更、発注書/請求書印刷への遷移。「次にやること」を1クリックで進める advance() ボタンは、現在工程が approval のときは表示せず、承認番号入力を促す文言のみ表示する |
| 発注書印刷 | /wholesale-order-print/:caseId(WholesaleOrderPrint) | 最新の発注(配列末尾)を発注書として整形。未発注の場合は明細合計を「仮」金額として表示し、注記を出す |
| 請求書印刷 | /wholesale-invoice-print/:caseId(WholesaleInvoicePrint) | 先頭の請求書(配列先頭)を請求書として整形。税込合計・決済条件詳細(settlementDetailEntries)・三社間精算(hasDataのときのみ)・振込先を表示 |
| 買掛管理(支払予定) | /wholesale-payables(WholesalePayables) | 全案件横断で supplierPayments[] をフラット化して一覧化。フィルタ(すべて/未払/支払済)・検索(案件・仕入先・発注番号)・サマリー(予定総額/支払済/未払残/期限超過)。支払予定日はdueDate未設定時のみ自動算出値を「(自動)」表示 |
| 請求・入金管理(未収管理) | /wholesale-billing(WholesaleReceivables) | 実際に請求書(invoices)が1件以上ある案件のみを一覧化。フィルタ(すべて/未収/期限超過/入金済)・検索(案件・販売店)・サマリー。行から PaymentSheet で入金登録(既定入金額=残高、方法は振込/カード/ローン会社/その他) |
| ダッシュボード(サマリー) | WholesaleDashboard(/wholesale の「サマリー」トグル) | 経営KPI(今月/先月/全期間の期間切替、受注日ベース・キャンセル除外)、バケット別・決済区分別の件数/金額内訳、タスクの状況(未対応/対応中/完了・期限超過件数) |
lib/permissions.ts はナビゲーション(画面到達可否)のみを制御し、卸モジュール内の操作(発注登録・請求登録・入金登録・決済区分変更・承認番号編集・入金消込)に対する個別のロールベース制御は実装されていない。
| ロール | /wholesale へのナビ可視性 | 備考 |
|---|---|---|
| 管理者 | 可(全画面 all) | |
| 事務(BO) | 可 | NAV_ALLOWに明記された唯一の非管理者ロール |
| 営業 | 不可 | |
| 発注・申請担当 | 不可 | 工程表の担当ラベル「発注担当」を字面上想起させるロールだが、NAV_ALLOWの許可リストに/wholesaleを含まない。実装上は管理者・事務(BO)のみが発注・納品確認の操作画面に到達できる |
| 代理店/代理店(メンバー) | 不可 | |
| 施工店 | 不可 |
到達できたロール(管理者・事務(BO))の間では、画面内の操作可否はすべて flow.canOrder / flow.canInvoice などワークフロー由来の判定のみで決まり、ロール差はない。仕切原価・粗利の可視性制御(canSeeCost)や案件編集可否(canEditDeal)に相当する卸モジュール専用の関数は存在しない。
エッジケース/異常系
Section titled “エッジケース/異常系”| 状況 | 現状の挙動 |
|---|---|
| 決済区分を案件作成後に変更する | 画面から無条件に変更可能。進捗フラグ・実体はリセットされないため、旧区分で完了扱いだった工程が新区分の工程列上で再評価される(例:掛売で settlementConfirmed=true の案件を三社間に変更すると、先頭工程がapprovalに切り替わり、approvalNo未入力なら再び未着手状態に見える) |
| 前金の「前金を請求し入金を確認する」を実行 | settlementDetail.depositAmount(頭金額)は使わず、案件の残高全額(未請求時は卸金額合計)を入金として登録する。頭金率・頭金額は表示専用の参考値で、実際の入金処理額とは連動しない |
| 発注未登録のまま経営KPIを見る | wholesaleCost()=0 となり wholesaleGrossProfit()=卸金額全額。決済待ち・発注待ちの案件が多いと実粗利が一時的に過大表示される |
| 請求書印刷の税込合計と残高表示 | 税込合計は都度計算(10%)だが、隣接する「残高(未収)」はwholesaleBalance(税抜)のまま。両者の単位が揃わない |
| 承認番号の入力 | 案件詳細の入力欄はWholesaleCase.approvalNoのみを更新。「決済条件(詳細)」カードはsettlementDetail.approvalNoを表示するため、入力しても詳細カードの表示は追随しない(発注ゲート判定自体はどちらでも成立するため機能上は問題ない) |
| 複数請求書がある案件への入金 | WPaymentは請求書と紐付かない案件単位のデータ。reconcile()は案件合計だけを見て全請求書のstatusを一律更新するため、請求書ごとの個別消込は表現できない(運用上はメモ欄で対応関係を管理) |
| 未収管理画面に案件が出ない | WholesaleReceivablesはinvoices.length>0の案件のみを対象にする。前金・三社間・カードのように請求書を発行しない区分の入金消込は、案件詳細画面でのみ操作できる |
| 買掛の支払予定日が算出できない | 仕入先マスタに締日/支払サイトが未設定、または解釈できない表記(例:日付以外の自由記述)の場合supplierPaymentDueDateはundefined。画面は「未定」表示とし、期限超過アラートも出さない |
| 明細0円・0件の案件 | items=[]ならwholesaleTotal=0。0円のまま発注書・請求書が印刷でき、請求登録するとbilled=0となりreconcile()のfullyPaid判定(billed>0 && paid>=billed)が常にfalseになるため、入金を登録してもpaymentReceivedが真にならずpayment工程が永久に完了しない |
| キャンセル済み案件の操作 | cancelled=trueでも一覧表示・工程ゲート・発注/請求/入金登録は通常どおり動作する。除外されるのは経営KPI集計のみ |
| 案件一覧・買掛・未収の0件表示 | いずれもEmptyStateコンポーネントで「◯件」の空状態を表示し、レイアウト崩れは生じない |
ValueOSとの対応
Section titled “ValueOSとの対応”ValueOSは決済区分を case_settlements.settlement_type で保持し、区分ごとに fee_rate / fee_amount(手数料)、deposit_rate / deposit_amount(頭金)、payment_terms(掛売の支払条件)、card_brand / card_status(カード)、finance_company / approval_number / loan_status(三社間)を専用カラムとして分離する。当方は WholesaleCase.settlementDetail(WSettlementDetail 型)にこれらと同名概念の項目を取り込み、SETTLEMENT_FIELDS で決済区分ごとに出し分けて実装済みである。従来の WholesaleCase.approvalNo(単一フィールド)は後方互換のため残置し、settlementDetail.approvalNo と同義として扱う(ただし前述のとおりUIは前者のみを更新する)。
三社間の金銭フロー(信販→VE入金、VE→仕入先支払、VE→販売店精算)についても、ValueOSの lib/threeParty(finance_receipts / supplier_payments / dealer_settlements)に対応する WFinanceReceipt / WSupplierPayment / WDealerSettlement を実装し、WholesaleCase.financeReceipts / supplierPayments / dealerSettlement として保持する。
発注可否・請求可否の判定は、ValueOSでは WorkflowEngine(canOrder / canInvoice)が担い、画面は表示レイヤーに徹する設計である。当方の wholesaleFlow(canOrder / canInvoice の判定と工程進行)はこの設計思想に沿っており、判定ロジックを工程配列から機械的に導出する点で対応する。承認(審査OK)の判定も同様に、当方は settlementApproved() としてロジックを一元化している。請求ステータスの案件単位での自動遷移(reconcile())や、仕入先の締日・支払サイトからの支払予定日算出(supplierPaymentDueDate())も、ValueOSの同等ロジックに範を取り試作段階で実装済みである。
| 観点 | 当方(WholesaleCase) | ValueOS |
|---|---|---|
| 決済区分 | settlement(5値) | case_settlements.settlement_type |
| 決済条件の詳細 | settlementDetail(WSettlementDetail) | case_settlements(fee_rate / fee_amount / deposit_rate / deposit_amount / payment_terms / card_brand / card_status / finance_company / loan_status / approval_number) |
| 承認(審査OK)判定 | settlementApproved()(承認番号、またはローン状態=承認済/実行済、またはカード状態=承認済/決済済) | 同等のロジック |
| 発注可否判定 | wholesaleFlow.canOrder | WorkflowEngine.canOrder |
| 請求可否判定 | wholesaleFlow.canInvoice | WorkflowEngine.canInvoice |
| 請求ステータス自動遷移 | useWholesale.reconcile()(案件単位の合計で一律判定) | 同等のロジック(請求書単位の粒度は未確認) |
| 支払予定日算出 | supplierPaymentDueDate()(表示専用フォールバック) | suppliers.closing_day / payment_site |
| 発注実体 | orders[](WOrder) | orders テーブル |
| 請求実体 | invoices[](WInvoice) | invoices テーブル |
| 入金実体 | payments[](WPayment) | payments テーブル |
| 三社間マネー:信販入金 | financeReceipts[](WFinanceReceipt) | finance_receipts テーブル |
| 三社間マネー:仕入先支払 | supplierPayments[](WSupplierPayment) | supplier_payments テーブル |
| 三社間マネー:販売店精算 | dealerSettlement(WDealerSettlement) | dealer_settlements テーブル |
差分の全体像は redesign/valueos-gap を参照(同ページの発注・請求・入金に関する記述は、本ページで示すWOrder/WInvoice/WPaymentのエンティティ化が実装された後の内容に更新する必要がある)。
- 与信枠(掛売)のデータ構造と、超過時の挙動(発注ブロック/警告のみ/前金への切替提案)
- 決済区分の途中変更を無条件に許可し続けるか。許可する場合、変更時に進捗フラグ・実体をリセット/警告する仕組みを設けるべきか(現状はガードなしで即時反映)
- 承認番号(approvalNo)は三社間・カードで単一フィールドを共用し、かつ
settlementDetail.approvalNoとはUI上同期しない状態にある。どちらを正とし、どう一本化するか - 前金の「前金入金確認」で登録する金額を、残高全額ではなく
settlementDetail.depositAmount(頭金額)に連動させるべきか - 工程完了の取り消し(差し戻し)を許容する運用の要否(現行に取り消し操作はない)
- 複数請求書がある案件で、請求書単位の個別消込表示が必要か(現状は案件単位の合計判定で全請求書のstatusを一律更新)
- 消費税額を永続データとして保持する必要があるか(現状は請求書印刷時に都度計算するのみで、内部の残高計算は税抜のまま)
cancelledフラグを業務上いつ・誰が・どの画面で立てる想定か(現行UIに設定導線がなく、工程ゲート・一覧表示も参照しない)- 同一バケット滞在日数の閾値超過を検知する滞留アラートの自動化要否
- 担当ロール単位ではなく担当者個人単位で「自分の待ち」に絞るビューの要否
- 「発注・申請担当」ロールが卸モジュールの発注/納品確認操作を担う想定であれば、
NAV_ALLOWへの/wholesale追加が必要(現状は管理者・事務(BO)のみ到達可能)
- features/wholesale
- redesign/valueos-gap(発注/請求/入金のエンティティ化に伴い要更新)
- redesign/meeting-log
- glossary