コンテンツにスキップ

データモデル

位置づけ: 新CRM全体設計

先行リリース第1弾は催事・アイテム(商品マスタ)・商社(卸)の3ドメインで構成する。本ページはこの3ドメインの新エンティティと、後続フェーズ(商談CRM本体・見積・KPI予実)との接続点をデータ構造の観点で定義する。各エンティティは試作(React+TS)にコードとして実装済みであり、以下の記述は試作実装とクライアント定例会での合意事項に基づく。

マスタ系エンティティはすべて共通の定型画面(テーブル+編集ドロワー+変更履歴)に載る。画面仕様は各機能ページ(催事・アイテム・商社(卸))に委譲し、本ページはデータ構造とエンティティ間関係に集中する。実データ(実在の店舗名・取引先名・金額・件数)は記載せず、必要箇所は「(実データで確定)」と表記する。**先行リリース第1弾の全マスタ(店舗・代理店・系列・カテゴリを含む)はCSV一括登録に対応する方針とする(2026-08-19合意)。**個別の入出力仕様(列定義・エラー処理等)は各機能ページ側で確定する。

対象は先行リリース第1弾の3ドメイン、催事 / アイテム(商品マスタ) / 商社(卸)。顧客・商談CRM本体、見積、KPI予実、AIエージェントは後続フェーズであり本ページの対象外とする(参照関係が生じる箇所のみ言及する)。リリース段階の合意経緯は 定例会・意思決定ログ、フェーズ区分の詳細は スコープ・フェーズ分割 を参照する。

ドメインエンティティ役割主キー/識別子
催事EventStore店舗マスタid
催事EventCompany代理店マスタ(催事会社を統合。加盟店契約「会社情報」シート準拠に拡張)id
催事StoreChain系列マスタ(店舗。例:カインズ/島忠/ビバホーム等。店舗分類はこの系列1軸に統合し、朝礼チェック項目の出し分けにも用いる)id
催事EventShiftシフト(1店舗×1代理店)id(S-MMDD-NN)
催事StartReport開始報告(朝礼)シフトに内包
催事EndReport終了報告(現場ファネル)シフトに内包
催事HopeSubmission希望店舗ヒアリング回答(代理店・対象月ごとの日別稼働可否・希望店舗・希望エリア)id(H-{companyId}-{targetMonth})
催事AvailableSlotVEが確保した枠(日×店舗。1枠=1代理店)id(SL-YYYYMMDD-{storeId})
催事MatchDraft自動マッチング結果(1枠=1行)。非永続・都度算出— (永続化しない)
催事UnmetHopeあぶれた希望(自動マッチングで割り当たらなかった希望)。非永続・都度算出— (永続化しない)
催事DepartmentHistory部署履歴(社員の所属部署を時点管理)id
アイテムProduct商品マスタ(型番)model
アイテムSeriesシリーズマスタid
アイテムSupplier仕入先マスタid
アイテムVendor施工店マスタid
アイテムLoanCompanyローン会社(信販)マスタid
アイテムManufacturerメーカーマスタ(価格・見積ドメインlib/pricing.ts側)id
アイテムPackageパッケージ商品マスタ(蓄電システム等のセット商品)id(codeが識別コード)
商社(卸)WholesalePrice卸金額(品目×仕入先)id
商社(卸)WholesaleCase卸案件id(W-XXXX)
商社(卸)WSettlementDetail決済条件の詳細(手数料・頭金・支払条件・カード・信販)WholesaleCaseに内包(settlementDetail)
商社(卸)WOrder発注id(orderNo=PO-XXXX)
商社(卸)WInvoice請求id(invoiceNo=INV-XXXX)
商社(卸)WPayment入金id
商社(卸)WFinanceReceipt三社間マネー:信販会社→VE入金(予定/実績)id
商社(卸)WSupplierPayment三社間マネー:VE→仕入先支払(予定/実績)id
商社(卸)WDealerSettlement三社間マネー:VE→販売店精算(信販受領・VE取り分・調整・支払)id(statementNo=DS-XXXX)
商社(卸)WholesaleTask卸まわりの汎用タスクid(WT-XXXX)
商社(卸)DealerIntakeForm販売店発注フォーム— (独立エンティティではなくWholesaleCase生成の入口画面)

旧・独立の代理店マスタAgency(lib/masters.ts)は2026-07-22の合意によりEventCompanyに統合し、マスタ画面もEventCompany一覧を表示する実装に置き換えている。廃止済みのコードはコメント付きで残置しているが、参照はしない。

erDiagram
    EventStore ||--o{ EventShift : "storeId(ID参照)"
    EventCompany ||--o{ EventShift : "companyId(ID参照)"
    StoreChain ||--o{ EventStore : "seriesId(ID参照)"
    EventShift ||--o| StartReport : "内包"
    EventShift ||--o| EndReport : "内包"
    EventCompany ||--o{ HopeSubmission : "companyId(ID参照)"
    EventStore ||--o{ AvailableSlot : "storeId(ID参照)"
    Manufacturer ..o{ Series : "name一致(破線)"
    Series ..o{ Product : "series name一致(破線)"
    Manufacturer ..o{ Product : "maker name一致(破線)"
    Manufacturer ..o{ Package : "maker name一致(破線)"
    Series ..o{ Package : "series name一致(破線)"
    Supplier ..o{ WholesalePrice : "supplier name一致(破線)"
    WholesalePrice ..o{ Product : "itemName未連携(破線)"
    WholesaleCase ||--o| WSettlementDetail : "内包"
    WholesaleCase ||--o{ WOrder : "内包"
    WholesaleCase ||--o{ WInvoice : "内包"
    WholesaleCase ||--o{ WPayment : "内包"
    WholesaleCase ||--o{ WFinanceReceipt : "内包(三社間)"
    WholesaleCase ||--o{ WSupplierPayment : "内包(三社間)"
    WholesaleCase ||--o| WDealerSettlement : "内包(三社間)"
    WholesaleCase ||--o{ WholesaleTask : "caseId(ID参照/null許容)"
    EventCompany ..o{ WholesaleCase : "dealer name一致(破線)"
    EventShift ..o{ Deal_外部 : "channel/venue/日付 突合(破線・後続フェーズ)"
    Vendor ..o{ Deal_外部 : "construction.vendor name一致(破線)"

実線はID参照、破線は名称一致による関連を示す。名称一致箇所の棚卸しと対応方針は後述する。Deal_外部は後続フェーズで確定する商談エンティティを指す。DepartmentHistoryは社員(userId)に対して時点管理の部署を紐づけるエンティティだが、社員・アカウントのマスタ自体は先行リリース第1弾のスコープ外(後続フェーズで確定)のため、本図には含めない(部署履歴を参照)。MatchDraft・UnmetHopeはHopeSubmission・AvailableSlotを入力にautoMatch()が都度算出する非永続データであり、テーブルとして保存しないため本図には含めない(希望店舗ヒアリング〜自動マッチングを参照)。

催事ドメイン: Store / Company / Shift / Report

Section titled “催事ドメイン: Store / Company / Shift / Report”

EventStore { id, name, area?, seriesId?, category?, phone?, manager?, note? }が店舗マスタである(実装済み)。エリア(area)は自由文字列ではなく47都道府県の固定選択(lib/prefectures.tsのPREFECTURESが唯一の情報源。表記ゆれ防止・希望エリア照合・マッチングのエリア一致のため。件数固定・CRUD不要のためマスタ化はしない)。系列(seriesId)は系列マスタ(StoreChain)への参照(選択式)で、2026-08-19合意(Q11/Q12)どおり実装済みである。電話番号・責任者・メモ(備考)も実装済みで、CRM画面から住所・電話・責任者をすぐ確認したいという運用要望(Q11)に対応する。2026-08-20是正により、店舗のカテゴリを別軸として持たせる目的で新設していたカテゴリマスタ(StoreCategory・categoryId)は撤去した。クライアントの言う「カテゴリ」は本来この系列と同一概念(呼称違い)だったと判明したため、店舗分類は系列1軸に統合している。旧category(自由文字列)は既存データ・突合との後方互換のためlegacy表示用フィールドとして残置するが、新規登録・編集はseriesIdを正とする(STORE_CHAINSの名称→id変換により既存データへ自動付与済み)。

EventCompany(代理店){ id, name, contact?, business?: "催事" | "訪販" | "催事・訪販", paymentType?, defaultProfitAmount? }が現行実装である。代理店と催事会社のマスタは分けず、代理店マスタに一本化する(2026-07-22合意)。代理店は催事・訪販の両方を担い得るためbusiness1値で事業区分を表すが、未設定の代理店も許容する任意フィールドである。2026-08-19合意(Q14)により、代理店マスタは加盟店契約スプレッドシート「会社情報」シートの項目群に大幅拡張する目標定義とする。拡張項目の一覧は代理店マスタ(EventCompany)の拡張フィールドを参照する。

paymentType(既定支払区分。例:「前金」「掛売」「三社間」「カード」「その他」)とdefaultProfitAmount(既定利益額・円)は、代理店(=販売店/卸先)ごとの卸取引の既定値を持たせるための任意フィールドである。ValueOSのdealersに対応し、実装済みである(試作crm_app)。

EventShift { id, date, storeId, companyId, startTime, endTime, plannedLeader?, staffCount, note?, startReport?, endReport? }。1シフト=1店舗×1代理店の粒度で持つ。個人スタッフ単位の管理は行わず、「責任者名(plannedLeader、氏名の自由文字列)+人数(staffCount)」で運用する(2026-07-22合意)。

希望店舗ヒアリング〜自動マッチング(HopeSubmission・AvailableSlot・MatchDraft・UnmetHope)

Section titled “希望店舗ヒアリング〜自動マッチング(HopeSubmission・AvailableSlot・MatchDraft・UnmetHope)”

2026-08-19定例会で確定した希望店舗ヒアリング〜自動マッチング機能(催事:希望店舗ヒアリング〜自動マッチングを参照。画面は/events/matching)は、試作実装(lib/events.ts・lib/eventMatching.ts)に実装済みで、HopeSubmission・AvailableSlot・MatchDraft・UnmetHopeの4エンティティで構成する。

HopeSubmission { id, companyId, targetMonth, entries: HopeDateEntry[], status, submittedAt? }が代理店1社・対象月1件の希望提出である。自然キーid = "H-{companyId}-{targetMonth}"により1代理店・1対象月=1件に定まり、再提出は同じIDのレコードを更新する(newHopeSubmissionId())。entries(HopeDateEntry { date, available, hopeStoreIds, hopeAreas? })が日別の稼働可否・希望を持ち、hopeStoreIdsは配列順=優先順(0番目=第1希望、最大3件)で、hopePriorities()が{ rank, storeId }[]へ変換する唯一の変換元である(表示・マッチングの両方がこの関数を経由する)。statusは「下書き」「提出済」の2値で、自動マッチングの対象は「提出済」のみである。

AvailableSlot { id, date, storeId, note? }がVEの確保枠を表す。1件=日×店舗=1代理店(2026-08-19定例会Q9確定)で、旧lanes(レーン数)フィールドは過剰モデルのため撤廃済みである。idは"SL-YYYYMMDD-{storeId}"を基本形とし、同一日×店舗の重複登録時はnewSlotId()が-2,-3…を付与して一意化する。

MatchDraft { date, storeId, companyId?, source }とUnmetHope { companyId, date, hopeStoreIds, hopeAreas?, reason }はいずれも永続化しない。autoMatch(submissions, slots, stores)(lib/eventMatching.ts。純関数・Date.now()/乱数不使用の決定的貪欲法)を呼ぶたびにHopeSubmission・AvailableSlotから都度算出する導出データであり、MatchDraftは登録済み枠の1枠=1行、UnmetHopeは稼働可・希望提出済みだがどこにも割り当たらなかった代理店の1件である。アルゴリズムの詳細(優先順位付け・エリア救済・重複防止等)は催事:希望店舗ヒアリング〜自動マッチング側に記述する。

エンティティフィールド型・enum必須既定制約・備考
EventStoreidstring○—店舗ID(シードはST01形式)。主キー
EventStorenamestring○—店舗名
EventStorearea47都道府県の固定選択(PREFECTURE_OPTIONS)—なしlib/prefectures.tsのPREFECTURESが唯一の情報源。マスタ化はしない(実装済み。2026-08-19合意)
EventStoreseriesIdstring—なし系列(チェーン)マスタ参照。StoreChain.id。選択式(実装済み。2026-08-19合意)。旧categoryから移行
EventStorecategory(legacy)string—なし旧項目。seriesId解決名との後方互換表示用の自由文字列。新規登録・編集はseriesIdを正とする。2026-08-20是正で別軸だったcategoryId(店舗カテゴリマスタ参照)は撤去し、系列1軸に統合した
EventStorephonestring—なし電話番号(実装済み。2026-08-19合意)
EventStoremanagerstring—なし責任者名の自由文字列。専用マスタなし(実装済み。2026-08-19合意)
EventStorenotestring—なしメモ・備考(実装済み。2026-08-19合意)
EventCompanyidstring○—主キー
EventCompanynamestring○—代理店名
EventCompanycontactstring—なし連絡先
EventCompanybusinessEventBusiness(“催事”|“訪販”|“催事・訪販”)—なし未設定の代理店も許容
EventCompanypaymentTypestring—なし既定支払区分(例:“前金”/“掛売”/“三社間”/“カード”/“その他”)。WSettlementと語彙は揃えているが型としては自由文字列で強制はしていない
EventCompanydefaultProfitAmountnumber—なし既定利益額(円)
EventCompany(以下、目標定義)———上記6項目は現行実装。加盟店契約「会社情報」シート準拠の拡張項目(約40〜50項目)は代理店マスタ(EventCompany)の拡張フィールドに区分ごとの表で定義する(表が大きいため本表からは分離)
EventShiftidstring(S-MMDD-NN)○—newShiftId()が同日連番で採番
EventShiftdatestring(YYYY-MM-DD)○——
EventShiftstoreIdstring○—EventStore.id参照(ID参照)
EventShiftcompanyIdstring○—EventCompany.id参照(ID参照)
EventShiftstartTime / endTimestring(HH:MM)○—ローカル時刻として解釈(shiftStatus()内で日付と合成)
EventShiftplannedLeaderstring—なし責任者氏名の自由文字列。専用マスタなし
EventShiftstaffCountnumber○—人数(個人単位の内訳は持たない)
EventShiftnotestring—なし—
EventShiftstartReport / endReportStartReport? / EndReport?—undefinedシフトに内包(別エンティティとして独立保存しない)
StartReportleaderstring○——
StartReportemergencyContactstring○——
StartReportchecksRecord<チェック項目id, boolean>○—既定7項目(DEFAULT_START_CHECKS)のid→済みフラグ
StartReportcommentstring—なし—
StartReportphotosReportPhoto[]({ name, url })—なしモックはdataURL保持。実装ではStorage URLに差し替え
StartReportatstring(ISO datetime)○—報告時刻
EndReportdepartmentstring—なし現行実装は自由文字列。2026-08-19合意(Q26)により目標定義はマスタから選択、かつログインユーザー(自社の社員)の所属部署から自動導出(入力不要)する。申請時点の所属は部署履歴(DepartmentHistory)で事後追跡する
EndReportlottery / seated / appointmentsFunnelCount({ total, both, single })○—抽選/着座/アポの各段階を両面提案(both)/片面提案(single)の内訳つきで記録。数値の整合性チェック(total = both + single等)はコード上未実装
EndReportphotosReportPhoto[]—なし—
EndReportatstring(ISO datetime)○——
HopeSubmissionidstring(H-{companyId}-{targetMonth})○—主キー(自然キー)。1代理店・1対象月=1件。再提出は同じidを更新(newHopeSubmissionId())
HopeSubmissioncompanyIdstring○—EventCompany.id参照(ID参照)
HopeSubmissiontargetMonthstring(YYYY-MM)○—対象月。「今月に2ヶ月先の希望を回収する」運用のため実質的に今月+2ヶ月(HOPE_TARGET_MONTH)
HopeSubmissionentriesHopeDateEntry[]○[]可日別の稼働可否・希望
HopeSubmissionstatus”下書き”|“提出済”○“下書き”自動マッチング(autoMatch())は”提出済”のみを対象とする
HopeSubmissionsubmittedAtstring(ISO datetime)—なし”提出済”時のみ設定
HopeDateEntrydatestring(YYYY-MM-DD)○—HopeSubmission.entriesの要素
HopeDateEntryavailableboolean○—その日の稼働可否
HopeDateEntryhopeStoreIdsstring[]—[]可希望店舗。配列順=優先順(0番目=第1希望)。最大3件。hopePriorities()が{ rank, storeId }[]に変換する唯一の変換元
HopeDateEntryhopeAreasstring[]—なし希望店舗で漏れた場合のエリア指定。EventStore.areaと照合
AvailableSlotidstring(SL-YYYYMMDD-{storeId})○—主キー。同一日×店舗の重複登録時はnewSlotId()が-2,-3…を付与
AvailableSlotdatestring(YYYY-MM-DD)○——
AvailableSlotstoreIdstring○—EventStore.id参照(ID参照)
AvailableSlotnotestring—なし—
MatchDraftdatestring(YYYY-MM-DD)○—永続化しない。autoMatch()の返り値(1枠=1行)。ドラフト表(UI)で行ごとに手動差替可能
MatchDraftstoreIdstring○—EventStore.id参照
MatchDraftcompanyIdstring—なし未割当時は無し(sourceが”未割当”)
MatchDraftsource”第1希望”|“第2希望”|“第3希望”|“エリア”|“未割当”○—割当根拠。手動で差し替えた行はUI上「(手動)」表示を付加するが、フィールド自体の型は変わらない
UnmetHopecompanyIdstring○—永続化しない。autoMatch()の返り値。稼働可・希望を出したがその日どこにも割り当たらなかった代理店
UnmetHopedatestring(YYYY-MM-DD)○——
UnmetHopehopeStoreIdsstring[]○[]可—
UnmetHopehopeAreasstring[]—なし—
UnmetHopereason”店舗満枠”|“枠なし”○—“店舗満枠”=希望店舗/エリアに枠自体はあったが埋まった。“枠なし”=VEが希望店舗/エリアに枠を確保できていない

ShiftStatusは保存値ではなく基準時刻から都度導出する。判定順は次のとおり。

  1. 終了報告あり → 終了済み(時刻を問わない)
  2. 開始報告あり・終了報告なし・現在時刻が終了時刻超過 → 終了未報告
  3. 開始報告あり・終了報告なし・終了時刻前 → 開始済み
  4. 開始報告なし・現在時刻が開始時刻超過 → 未開始
  5. 開始報告なし・開始時刻前 → 予定

終了報告があれば時刻を問わず最優先で終了済みと判定する点、開始報告なしのまま終了報告のみ提出された場合も終了済みに遷移する点(現場運用上、開始報告忘れのまま終了することがあるため)が実装上のポイントである。

stateDiagram-v2
    [*] --> 予定
    予定 --> 未開始 : 開始時刻超過(開始報告なし)
    予定 --> 開始済み : 開始報告あり
    未開始 --> 開始済み : 開始報告あり
    開始済み --> 終了未報告 : 終了時刻超過(終了報告なし)
    開始済み --> 終了済み : 終了報告あり
    終了未報告 --> 終了済み : 終了報告あり
    未開始 --> 終了済み : 終了報告のみ提出(開始報告未提出)
    予定 --> 終了済み : 終了報告のみ提出(開始報告未提出)
    終了済み --> [*]

StartReport(開始報告=朝礼){ leader, emergencyContact, checks: Record<チェック項目id, boolean>, comment?, photos?, at }。既定チェック項目は7件(挨拶・声かけ/身だしなみ・気持ちの準備/ブース設営の確認/バックヤード整理/私語・スマホのルール確認/店舗ルールの確認/判断に迷ったら責任者へ)で運用する。

EndReport(終了報告=現場ファネル){ department?, lottery, seated, appointments, photos?, at }。lottery/seated/appointmentsはいずれもFunnelCount { total, both(両面), single(片面) }で持ち、抽選→着座→アポの各段階を両面提案/片面提案の内訳つきで記録する。

催事↔商談の突合はchannel === "催事" && venue === 店舗名 && 作成日 === シフト日による疎結合突合とし、専用の中間テーブルは持たない(2026-07-22合意)。商談CRM本体が後続フェーズであるための設計判断である。

エンティティ主要フィールドVE社内代理店
EventStorename, area, seriesId, phone, manager, note作成・編集参照不可(シフト経由で表示名のみ)
EventCompanyname, contact, business(他、拡張フィールド参照)作成・編集自社情報の参照のみ
EventShiftdate, storeId, companyId, plannedLeader, staffCount作成・編集自社シフトの閲覧のみ
StartReportleader, emergencyContact, checks閲覧自社シフトへの登録・編集
EndReportdepartment, lottery/seated/appointments閲覧自社シフトへの登録・編集
HopeSubmissioncompanyId, targetMonth, entries, status閲覧のみ(作成はしない)自社分の作成・編集(代理店ロールのみ。代理店(メンバー)はアクセス不可)
AvailableSlotdate, storeId作成・編集参照不可(マッチング結果として生成されるEventShift経由でのみ間接的に反映)

代理店マスタ(EventCompany)の拡張フィールド(会社情報シート準拠)

Section titled “代理店マスタ(EventCompany)の拡張フィールド(会社情報シート準拠)”

2026-08-19合意(Q14)により、代理店マスタは加盟店契約スプレッドシート「会社情報」シート1行目の項目群を区分ごとに整理して反映する。現行実装の最小項目(id, name, contact?, business?, paymentType?, defaultProfitAmount?)に加え、以下の拡張フィールドは試作crm_app(lib/agency.ts)で実装済みである。「いらない項目も一旦入れておく」方針(2026-08-19合意)のため、業務上の要否が未確定な項目も削除せず保持する。ラベルは加盟店契約シートの原文表記を尊重する。雇用形態・稼働状況・契約状況・販売チャネル・口座種別・初訪(相談)マエカク有無の6項目は実装で確定したenumとして下表に明記し、それ以外の型は加盟店契約シート原文からの推定である(確定は確認事項を参照)。本マスタはTSV一括取込(タブ区切り貼付)に対応する。詳細は催事:代理店マスタを参照する。

基本情報

フィールド型・enum(想定)備考
加盟店名string既存nameに相当
加盟店番号string(ERPxx形式)加盟店契約管理番号。会社(契約主体)の変更に伴い同一番号の履歴行が存在し得るため、一意キーとしては扱わない
代表者名(漢字)string—
代表者名(カナ)string—
設立年月日string(YYYY-MM-DD)—
資本金number円
本店所在地string—
他所在地string本店以外の所在地(支店等)
従業員数number—
代表番号string代表電話番号
代表者携帯番号string—
メールアドレス①string—
メールアドレス②string—
会社HPstringURL

事業・実績

フィールド型・enum(想定)備考
雇用形態enum(正社員|業務委託契約|契約社員|混同|なし)実装で確定した選択肢。未設定も許容する任意フィールド
販売実績歴boolean販売実績歴の有無
住宅設備商材販売経験歴boolean住宅設備商材の販売経験歴の有無
販売チャネルenum配列(複数選択。訪問販売|テレマーケティング|催事|反響営業|紹介営業)実装で確定した選択肢
営業歴string自由記述想定
稼働状況enum(撤退|稼働中|稼働中(アポ専))実装で確定した選択肢。未設定も許容する任意フィールド
契約状況enum(締結不要|契約済み|承認待ち|対応中)実装で確定した選択肢。未設定も許容する任意フィールド
直契約有無string自由記述想定(例:“無し”)。真偽値には正規化していない
営業マン展開条件string自由記述想定

税務・法人

フィールド型・enum(想定)備考
インボイス番号string—
法人番号string(13桁)—

金融・口座

フィールド型・enum(想定)備考
金融機関string—
種別enum(普通|当座)預金種別。実装で確定した選択肢
支店string—
口座番号string—
口座名義string—

連帯保証人(乙/丙-1/丙-2の3系統。各系統に氏名・住所・メールアドレスの3項目)

フィールド型・enum(想定)備考
連帯保証人氏名(乙)string—
連帯保証人住所(乙)string—
連帯保証人メールアドレス(乙)string—
連帯保証人氏名(丙-1)string—
連帯保証人住所(丙-1)string—
連帯保証人メールアドレス(丙-1)string—
連帯保証人氏名(丙-2)string—
連帯保証人住所(丙-2)string—
連帯保証人メールアドレス(丙-2)string—

書類添付(実ファイルは本アプリでは保持せず、いずれもGoogle DriveのURL文字列として保持する)

フィールド型・enum(想定)備考
履歴事項全部証明書string(Google Drive URL)—
公的書類写真(表)string(Google Drive URL)—
公的書類写真(裏)string(Google Drive URL)—

リレーション/運用

フィールド型・enum(想定)備考
施工店リレーションstringVendorとの関連付けを想定するが名称一致か専用IDかは未確定
共有ドライブstring(URL想定)—
発注担当者string社員名の自由文字列を想定
バリュー名→エネレイズ名変更日string(YYYY-MM-DD想定)社名変更(バリューエコロジー→エネレイズ)に伴う変更日
預かり保証金の有無string自由記述想定(有無の別を含む)
初訪(相談)マエカク有無enum(不要|初訪のみ|初訪・商談両方)実装で確定した選択肢

2026-08-19合意(Q12)により、EventStoreの系列は軽量な選択用マスタとして新設した(実装済み)。名前のみを持つ最小構成である。

StoreChain(系列マスタ){ id, name }。店舗の系列(チェーン)を選択式で持たせるためのマスタで、カインズ/島忠/ビバホーム等、今後増える可能性が高い前提で運用する。EventStore.seriesIdが本マスタのidを参照する(旧EventStore.categoryの自由文字列から移行済み)。

2026-08-20是正により、開始報告のチェック項目を「店舗のカテゴリごとに出し分け」る(Q23)用途を想定して別軸として新設していた店舗カテゴリマスタ(StoreCategory)は撤去した。クライアントの言う「カテゴリ」は本来この系列と同一概念(呼称違い)だったと判明したため、店舗分類・朝礼チェック項目の出し分けはいずれも系列(StoreChain/seriesId)1軸に統合している(出し分けの詳細は催事:朝礼チェック項目を参照)。

エンティティフィールド型・enum必須既定制約・備考
StoreChainidstring○—主キー
StoreChainnamestring○—系列名(例:カインズ/島忠/ビバホーム)

CSV一括登録に対応する(概要の方針を参照)。「系列で選んで店舗名を一括登録できると嬉しい」(Q12)という運用要望に対応し、StoreChain単位での店舗一括登録UIを想定する(画面仕様は催事側で確定)。

2026-08-19合意(Q26)により、催事終了報告等の「部門」はマスタから選択する運用としつつ、ログインユーザー(自社の社員)の所属部署から自動導出し入力不要とする方針とする。ただしイレギュラー(特別に今日だけ別部署扱い等)もあり得るため、申請時点でどの部署に所属していたかを後から追跡できるよう、社員の部署所属を時点管理するDepartmentHistoryエンティティを新設する。

DepartmentHistory { id, userId, department, effectiveFrom, effectiveTo?, isOverride? }。

エンティティフィールド型・enum必須既定制約・備考
DepartmentHistoryidstring○—主キー
DepartmentHistoryuserIdstring○—社員(営業マン)を指すID。社員・アカウントのマスタ自体は先行リリース第1弾のスコープ外(後続フェーズで確定)であり、本フィールドは先行して外部キーの形だけ持つ
DepartmentHistorydepartmentstring○—部署名。部署マスタ自体は後続フェーズで確定するため、当面はマスタ参照ID/自由文字列いずれで持つか未確定(確認事項参照)
DepartmentHistoryeffectiveFromstring(YYYY-MM-DD)○—この部署に所属していた開始日
DepartmentHistoryeffectiveTostring(YYYY-MM-DD)—なし(現在も所属中)未設定=現在も所属中
DepartmentHistoryisOverrideboolean—false相当イレギュラー上書き(例:今日だけ別部署扱い)かどうかを区別するフラグ

用途はEndReport.department(催事終了報告の「部門」)を筆頭に、部門を伴う申請系フィールドの自動導出・事後追跡である。KPIダッシュボード(後続フェーズ)は本エンティティに紐づく想定(Q26)。等級・インセンティブ制度とは別軸の情報であり、混同しない。代理店側にも自社メンバーと代理店メンバーが混在するケースがあり代理店側の部門分けも必要になりうるが、代理店が登録するデータは自社側とは別途変更可能な設計とする(詳細は後続フェーズで確定、確認事項参照)。基本方針は「ログインしている自社の人の部署から取る」(Q26)である。

アイテムドメイン: Product を中心とした周辺マスタ

Section titled “アイテムドメイン: Product を中心とした周辺マスタ”

Product { model(型番=主キー), kind, maker(必須), series?, name?, spec?, leadTimeDays?, stock?, note? }。型番(model)を主キーとする。

kindはItemKindとして"PV" | "BT" | "EQ" | "IH" | "AC" | "付帯"の6分類を持つ。stockは"潤沢" | "少" | "欠品"の3値のみを持ち、在庫数量は管理しない(在庫数量管理は本モジュールの非対象)。

Productは価格を持たない(2026-07-17方針確定)。価格は商社ドメインのWholesalePrice、および価格・見積ドメイン(lib/pricing.tsのPriceBook/Part/PackageSet)側が保持する。アイテムマスタは識別・在庫情報を担い、価格は別ドメインが保持する責務分離が設計原則である。

Package { id, code(パッケージコード=識別コード), name, maker?, series?, capacity?, systemType?, warrantyYears?, spec?, note? }。蓄電システム等のセット商品を型番横断で束ねるパッケージ商品マスタで、Productとは独立したエンティティである。systemTypeは"全負荷" | "特定負荷"の2値。maker・seriesはいずれもProductと同様に文字列一致でManufacturer.name・Series.nameと結びつく(リレーションの現状を参照)。ValueOSのpackages(package_code/manufacturer_id/series_id/system_type/warranty_years)に対応し、実装済みである(試作crm_app)。専用画面(/master-packages、PackagesMasterPage)を持ち、他マスタと同じ定型(テーブル+ドロワー+変更履歴)に載る。

エンティティフィールド型・enum必須既定制約・備考
Productmodelstring○—型番=主キー
ProductkindItemKind(“PV”|“BT”|“EQ”|“IH”|“AC”|“付帯”)○—lib/crmからimport typeのみで参照(値依存を避ける設計)
Productmakerstring○—Manufacturer.nameと文字列一致
Productseriesstring—なしSeries.nameと文字列一致
Productnamestring—なし商品名
Productspecstring—なし容量・枚数等の既定表記
ProductleadTimeDaysnumber—なし納期目安。無ければメーカー単位フォールバック(フォールバック先の実装はマスタ側になし、呼び出し側の運用)
Productstock”潤沢”|“少”|“欠品”—なし在庫数量は管理しない(3値のみ)
Productnotestring—なし—
Packageidstring○—主キー
Packagecodestring○—パッケージコード=識別コード
Packagenamestring○——
Packagemakerstring—なしManufacturer.nameと文字列一致
Packageseriesstring—なしSeries.nameと文字列一致
Packagecapacitystring—なし容量表記(例:9.8kWh)
PackagesystemType”全負荷”|“特定負荷”—なしValueOSのsystem_type相当
PackagewarrantyYearsnumber—なし—
Packagespecstring—なし—
Packagenotestring—なし—
マスタフィールド他エンティティとの関連
Seriesid, name, maker?, note?maker(文字列)がManufacturer.nameと名称一致
Supplierid, name, type(商社/メーカー直送), note?, closingDay?, paymentSite?, creditLimit?WholesalePrice.supplierと名称一致
Vendorid, name, type(自社/他社), tool(CRMチャット/ANDPAD/LINE), area?Deal.construction.vendor(Construction.vendor)と名称一致
LoanCompanyid, name, daysDirect(2社間), daysViaTrader(3社間)入金予定算出に使用する単独マスタ
Manufacturerid, namePriceBook.manufacturerIdからのみID参照。Productからは未参照

SupplierのclosingDay(締日。例:「月末」)・paymentSite(支払サイト。例:「翌月末払い」)・creditLimit(与信枠・円)はいずれも任意フィールドで、仕入先ごとの支払条件を持たせる。ValueOSのsuppliersに対応し、実装済みである(試作crm_app)。

lib/supplierPayment.tsのsupplierPaymentDueDate(supplierName, orderDateISO)は、Supplier.closingDay・Supplier.paymentSiteの表記(「月末」「20日」「翌月末払い」「翌々月10日払い」等)を解釈し、発注日から仕入先支払予定日を算出する純関数である。判定は「発注日が当月の締日以前ならその月で締め、締日超過なら翌月に繰り越して締める」→「締め月からpaymentSiteのoffsetMonths分だけ加算した月のday(末日または指定日)を支払予定日とする」の2段階で行う。仕入先が見つからない、締日・支払サイトが未設定、または表記を解釈できない場合はundefinedを返し、呼び出し側(WSupplierPayment.dueDateの入力)で手入力にフォールバックする。ValueOSのsuppliers(closing_day/payment_site)を用いた支払予定算出に対応し、実装済みである(試作crm_app)。

階層関係(Manufacturer > Series > Product)は名称一致(文字列一致)で成立する。対応方針はリレーションの現状を参照する。廃番概念は現状持たない(確認事項を参照)。

商社(卸)ドメイン(1): 卸金額と決済フロー

Section titled “商社(卸)ドメイン(1): 卸金額と決済フロー”

WholesalePrice { id, itemKind: "商品"|"パッケージ", itemName(フリーテキスト), productModel?, supplier(商社名), purchasePrice(仕入), wholesalePrice(卸), startDate(反映日), endDate?, memo?, dealer? }。現在価格はstartDate <= 基準日かつ(endDate未設定またはendDate >= 基準日)を満たす候補のうちstartDate最新(同日なら最安)を採用する。itemNameはフリーテキストでProductと未連携であり、正規化の対象とする(正規化方針を参照)。

dealer(販売店名、任意・空=標準価格)は同一品目でも販売店ごとに卸金額が異なる運用に対応するための軸で、ValueOSのsales_pricesにおけるdealer_id軸に相当する。値はEventCompany.name(催事ドメインの代理店マスタ)を想定した名称一致とする。currentWholesalePriceForDealer(prices, { productModel?, itemName?, kind?, dealer? }, baseDate)は、dealer指定時にまず当該販売店向けの価格を検索し、該当がなければdealer未設定(標準価格)にフォールバックする2段引き当てを行う。「卸金額は販売店ごとに異なるか」という確認事項に対する設計上の解であり、実装済みである(試作crm_app)。

エンティティフィールド型・enum必須既定制約・備考
WholesalePriceidstring○——
WholesalePriceitemKind”商品”|“パッケージ”○——
WholesalePriceitemNamestring○—フリーテキスト。Productと未連携(正規化方針を参照)
WholesalePriceproductModelstring—なしitemKind="商品"のときProduct.modelに紐付けるID連携フィールド(名称一致からの脱却の第一歩)
WholesalePricesupplierstring○—Supplier.nameと文字列一致
WholesalePricepurchasePricenumber○—仕入価格(税抜)
WholesalePricewholesalePricenumber○—販売店向け卸価格(税抜)
WholesalePricestartDatestring(YYYY-MM-DD)○—反映日。現在価格導出の起点
WholesalePriceendDatestring—なし未設定=無期限に有効
WholesalePricememostring—なし—
WholesalePricedealerstring—なし(=標準価格)EventCompany.nameを想定した名称一致。ValueOSのdealer_id軸に相当

現在価格導出のロジックはcurrentWholesalePrice()/currentWholesalePriceByModel()/currentWholesalePriceForDealer()の3関数がいずれも共通のソート規則(startDate降順、同日はwholesalePrice昇順=最安優先)を用いる。

決済区分WSettlementは"前金" | "掛売" | "三社間" | "カード" | "その他"の5種で持つ。区分ごとに通る工程が異なる点が本ドメインの核である。

工程(ステップ)は7種類、settlement(決済条件確定/経理)・approval(承認番号確認/経理)・prepay(前金請求入金/経理)・order(商社/メーカーへ発注/発注担当)・deliver(納品確認/発注担当)・invoice(販売店へ請求/経理)・payment(入金消込/経理)からなる。各ステップに担当部門(経理/発注担当)が紐づく。

決済区分工程順
前金settlement → prepay → order → deliver
掛売settlement → order → deliver → invoice → payment
三社間approval → order → deliver → payment
カードapproval → order → deliver → payment
その他settlement → order → deliver → invoice → payment

前金はpaymentを独立ステップとして持たず、prepay完了(入金確認)自体が金銭ゲートを兼ねる。三社間・カードはinvoice(販売店への請求)を通らない工程列であり、この妥当性の経理確認は確認事項に記載する。

flowchart TD
    subgraph SETTLE_前金["前金"]
        A1["settlement<br/>決済条件確定(経理)"] --> A2["prepay<br/>前金入金確認(経理)"] --> A3["order<br/>発注(発注担当)"] --> A4["deliver<br/>納品確認(発注担当)"] --> A5(("完了"))
    end
    subgraph SETTLE_掛売["掛売"]
        B1["settlement"] --> B2["order"] --> B3["deliver"] --> B4["invoice<br/>販売店へ請求(経理)"] --> B5["payment<br/>入金消込(経理)"] --> B6(("完了"))
    end
    subgraph SETTLE_三社間["三社間"]
        C1["approval<br/>審査OK確認(経理)"] --> C2["order"] --> C3["deliver"] --> C4["payment<br/>信販入金の消込(経理)"] --> C5(("完了"))
    end
    subgraph SETTLE_カード["カード"]
        D1["approval<br/>カード承認確認(経理)"] --> D2["order"] --> D3["deliver"] --> D4["payment"] --> D5(("完了"))
    end
    subgraph SETTLE_その他["その他"]
        E1["settlement"] --> E2["order"] --> E3["deliver"] --> E4["invoice"] --> E5["payment"] --> E6(("完了"))
    end

発注可否(canOrder)・請求可否(canInvoice)のゲート判定は次のとおりである(wholesaleFlow())。

flowchart TD
    O0{"order が工程列に<br/>含まれるか"} -->|なし| ONG["canOrder = false"]
    O0 -->|あり| O1{"order より前の<br/>全ステップが完了済みか"}
    O1 -->|未完了あり| ONG2["canOrder = false<br/>orderBlockedReason = 未完了ステップのlabel"]
    O1 -->|全完了| O2{"ordered が<br/>すでにtrueか"}
    O2 -->|true 既発注| ONG3["canOrder = false"]
    O2 -->|false 未発注| OOK["canOrder = true"]

    I0{"invoice が工程列に<br/>含まれるか(前金/三社間/カードは含まない)"} -->|なし| ING["canInvoice = false"]
    I0 -->|あり 掛売/その他| I1{"invoice より前の<br/>全ステップが完了済みか"}
    I1 -->|未完了あり| ING2["canInvoice = false<br/>invoiceBlockedReason = 納品などの前工程が未完了"]
    I1 -->|全完了| I2{"invoiced が<br/>すでにtrueか"}
    I2 -->|true 既請求| ING3["canInvoice = false"]
    I2 -->|false 未請求| IOK["canInvoice = true"]

決済条件の詳細はWSettlementDetail { feeRate?, feeAmount?, depositRate?, depositAmount?, paymentTerms?, cardBrand?, cardStatus?, financeCompany?, loanStatus?, approvalNo? }としてWholesaleCase.settlementDetailに保持する。ValueOSのcase_settlements(fee_rate/fee_amount/deposit_rate/deposit_amount/payment_terms/card_brand/card_status/finance_company/loan_status/approval_number)に対応する構造であり、実装済みである(試作crm_app)。cardStatusは"未処理" | "承認済" | "決済済"、loanStatusは"審査中" | "承認済" | "実行済"の3値で持つ。

フィールド型・enum必須既定備考
feeRatenumber(%)—なし手数料率
feeAmountnumber—なし手数料額
depositRatenumber(%)—なし頭金率(前金のみ表示)
depositAmountnumber—なし頭金額(前金のみ表示)
paymentTermsstring—なし支払条件(掛売・その他の締め/サイト表記)
cardBrandstring—なしカードブランド(カードのみ表示)
cardStatus”未処理”|“承認済”|“決済済”—なしカード状態(カードのみ表示)
financeCompanystring—なし信販会社(三社間のみ表示)
loanStatus”審査中”|“承認済”|“実行済”—なしローン審査状態(三社間のみ表示)
approvalNostring—なし承認番号。案件直下のWholesaleCase.approvalNoと二重に持ちうる(エッジケース・異常系を参照)

全項目が任意(optional)で、どの項目を入力・表示するかは決済区分に応じてSETTLEMENT_FIELDSが出し分ける(下表)。値の妥当性チェック(率の0〜100%範囲、金額の符号等)はコード上未実装である。

決済区分ごとに入力・表示する項目はSETTLEMENT_FIELDSで出し分ける。

決済区分表示する詳細項目
前金頭金率・頭金額・手数料率・手数料額
掛売支払条件・手数料率・手数料額
三社間信販会社・承認番号・ローン状態・手数料率・手数料額
カードカードブランド・承認番号・カード状態・手数料率・手数料額
その他支払条件・手数料率・手数料額

三社間・カードの「審査OK(発注可能)」判定はsettlementApproved()が担う。従来のapprovalNo(案件直下、後方互換のため残置)に加え、settlementDetail.approvalNo、またはloanStatusが承認済/実行済、またはcardStatusが承認済/決済済のいずれかを満たせば承認済みとみなし、発注ゲートを開く。

明細作成時は、反映日での現在価格導出とdealer軸→標準フォールバックを経て価格をスナップショットとして固定する。次のシーケンスはcurrentWholesalePriceForDealer()からsnapshotWItem()までの流れを示す。

sequenceDiagram
    actor U as 担当者(発注担当/経理)
    participant UI as 明細入力UI
    participant WP as WholesalePrice[]
    participant Fn as currentWholesalePriceForDealer()
    participant Item as WItem

    U->>UI: 品目・数量・(任意)販売店(dealer)を入力
    UI->>Fn: resolve(productModel/itemName, kind, dealer, baseDate)
    alt dealer指定あり
        Fn->>WP: dealer一致 かつ startDate<=baseDate<=endDate で検索
        alt 該当あり
            WP-->>Fn: 販売店別卸価格を返す
        else 該当なし
            Fn->>WP: dealer未設定(標準価格)で再検索
            WP-->>Fn: 標準卸価格 or undefined
        end
    else dealer指定なし
        Fn->>WP: dealer未設定(標準価格)で検索
        WP-->>Fn: 標準卸価格 or undefined
    end
    Fn-->>UI: 現在有効な WholesalePrice(未ヒット時 undefined)
    alt 価格ヒット
        UI->>Item: snapshotWItem(base, {wholesalePrice, purchasePrice, fetchedAt})
    else 未ヒット
        UI->>Item: 手入力(isManualPrice=true、unitPriceを直接入力)
    end
    Item-->>UI: WItem(unitPrice = wholesalePriceSnapshot で固定)
    Note over Item: 事後の WholesalePrice 改定は既存 WItem に影響しない(スナップショット固定)

商社(卸)ドメイン(2): 案件・発注・請求・入金・タスク

Section titled “商社(卸)ドメイン(2): 案件・発注・請求・入金・タスク”

WholesaleCase { id, createdAt, orderReceivedDate?, dealer, settlement, approvalNo?, settlementDetail?, items[], settlementConfirmed, paymentReceived, ordered, delivered, invoiced, orders?, invoices?, payments?, financeReceipts?, supplierPayments?, dealerSettlement?, cancelled?, memo? }。1案件が複数の発注・請求・入金を配列で束ねる集約構造である。dealerは発注元の代理店名(自由文字列、EventCompany.nameと名称一致)。元請案件(販売店/deals)とは別軸で持ち、商社目線の業務を独立して扱う設計とする。

orderReceivedDate(受注日、任意)は顧客がVEへ発注を確定した日を表し、ValueOSのorder_received_dateに対応する。未設定時は起票日(createdAt)を代用する(orderReceivedDateOf()が導出)。cancelled(キャンセル、任意・既定false相当)は案件がキャンセル済みで経営集計の対象外であることを表し、ValueOSのactiveRecords除外相当である。両フィールドはValueOS経営基準への整合(2026-08反映)で追加し、実装済みである(試作crm_app)。

エンティティフィールド型・enum必須既定制約・備考
WholesaleCaseidstring(W-XXXX)○—newWholesaleId()が全案件横断で採番
WholesaleCasecreatedAtstring(YYYY-MM-DD)○—起票日
WholesaleCaseorderReceivedDatestring—なし(createdAt代用)orderReceivedDateOf()が導出。経営集計の期間基準
WholesaleCasedealerstring○—発注元の代理店名。EventCompany.nameと名称一致
WholesaleCasesettlementWSettlement○——
WholesaleCaseapprovalNostring—なし三社間/カードの承認番号。settlementDetail.approvalNoと同義(後方互換のため残置)
WholesaleCasesettlementDetailWSettlementDetail—なし—
WholesaleCaseitemsWItem[]○[]可0件でも例外にならない
WholesaleCasesettlementConfirmed / paymentReceived / ordered / delivered / invoicedboolean○—ワークフロー判定のゲート。ステップ完了フラグ
WholesaleCaseorders / invoices / payments配列—なし実体(番号・日付・金額)。フラグはこれらの登録に連動して立てる運用(store側実装依存)
WholesaleCasefinanceReceipts / supplierPayments配列—なしsettlement="三社間"のときに使用
WholesaleCasedealerSettlementWDealerSettlement—なし配列ではなく単一(1案件1精算書)
WholesaleCasecancelledboolean—false相当経営集計から除外
WholesaleCasememostring—なし—
WItemnamestring○——
WItemkind”商品”|“パッケージ”○——
WItemqtynumber○——
WItemunitPricenumber○—販売店向け卸単価(税抜)。実際の請求単価
WItempurchasePriceSnapshotnumber—なし受注時点の仕入価スナップ(case_products.purchase_price相当)
WItemwholesalePriceSnapshotnumber—なし受注時点の卸価スナップ(unitPriceの取得元)
WItemisManualPriceboolean—なし(false相当)価格表を使わず手入力したか
WItempriceFetchedAtstring(YYYY-MM-DD)—なし価格取得時点
WOrderid / orderNostring○—orderNoはPO-XXXX。newOrderNo()が全案件横断で採番
WOrderorderDatestring○——
WOrdersupplierstring—なし仕入先(商社/メーカー)
WOrderamountnumber○—発注金額(=原価。無ければ卸金額を仮置きする運用)
WOrderstatus”発注済”|“納品済”○——
WOrderdeliveredDatestring—なし—
WInvoiceid / invoiceNostring○—invoiceNoはINV-XXXX。newInvoiceNo()が採番
WInvoiceinvoiceDate / dueDatestring○——
WInvoiceamountnumber○—請求金額(税抜卸金額)
WInvoicestatus”請求済”|“一部入金”|“入金済”○——
WPaymentidstring○——
WPaymentpaymentDatestring○——
WPaymentamountnumber○—積み上げ方式。専用の状態フィールドは持たない
WPaymentmethodstring—なし振込/カード/ローン会社 等
WFinanceReceiptid / financeCompanystring○—信販会社
WFinanceReceiptscheduledDate / scheduledAmountstring / number○—入金予定日・予定額
WFinanceReceiptactualDate / actualAmountstring / number—なし実入金日・実入金額
WFinanceReceiptstatus”予定”|“入金済”○——
WSupplierPaymentid / supplierstring○—仕入先(商社/メーカー)
WSupplierPaymentorderNostring—なしWOrder.orderNo参照(ID的だが型はstring自由入力)
WSupplierPaymentdueDatestring○—手入力が無い場合supplierPaymentDueDate()が自動算出
WSupplierPaymentscheduledAmountnumber○—支払予定額(=原価)
WSupplierPaymentpaidDate / paidAmountstring / number—なし実支払日・実支払額
WSupplierPaymentstatus”予定”|“支払済”○——
WDealerSettlementid / statementNostring○—statementNoはDS-XXXX
WDealerSettlementissueDatestring○—発行日
WDealerSettlementcreditReceivednumber○—信販から受領した総額
WDealerSettlementveSharenumber○—VEの取り分(粗利)
WDealerSettlementadjustmentnumber○—調整額(±)。符号の妥当性チェックはコード上未実装
WDealerSettlementpayoutnumber○—販売店への支払額。creditReceived − veShare + adjustmentで算出するが、保存値でありUI側の計算補助に依存(DBレベルの整合制約はない)
WDealerSettlementscheduledPayoutDate / actualPayoutDatestring—なし—
WDealerSettlementstatus”予定”|“精算済”○——
WDealerSettlementmemostring—なし—
WholesaleTaskidstring(WT-XXXX)○——
WholesaleTaskcaseIdstring | null—null許容案件に紐付くが未紐付けも許容
WholesaleTasktitlestring○——
WholesaleTaskstatus”未対応”|“対応中”|“完了”○——
WholesaleTaskassigneestring○——
WholesaleTaskdueDatestring○——
WholesaleTaskpriority”高”|“中”|“低”○——
WholesaleTaskmemostring—なし—
WholesaleTaskcreatedAtstring○——

WItem { name, kind, qty, unitPrice, purchasePriceSnapshot?, wholesalePriceSnapshot?, isManualPrice?, priceFetchedAt? }がWholesaleCase.itemsの明細1行である。unitPriceは従来通り実際の請求単価(税抜)を持つ一方、purchasePriceSnapshot(受注時点の仕入価)・wholesalePriceSnapshot(受注時点の卸価。unitPriceの取得元)・isManualPrice(価格表を使わず手入力したか)・priceFetchedAt(価格取得時点)の4項目は受注時点の価格スナップショットであり、事後のWholesalePrice(価格表)改定の影響を受けないよう明細に固定する。snapshotWItem(base, { wholesalePrice, purchasePrice?, manual?, fetchedAt? })が受注時にこの4項目を埋めてWItemを組み立てるヘルパである。ValueOSのcase_products(purchase_price/sales_price/gross_profit/is_manual_price/price_fetched_at)に対応し、実装済みである(試作crm_app)。

WOrder(状態: "発注済" | "納品済")/WInvoice(状態: "請求済" | "一部入金" | "入金済")/WPayment(amountの積み上げで分割入金・残高消込に対応、専用の状態フィールドは持たない)を独立エンティティとして持つ。

三社間(settlement === "三社間")の案件は、信販会社が工事後にVEへ入金→VEが仕入先へ支払→VEが販売店へ精算、という3段階の金銭移動をそれぞれ実体化して持つ。ValueOSのlib/threePartyに準拠し、実装済みである(試作crm_app)。

  • WFinanceReceipt { id, financeCompany, scheduledDate, scheduledAmount, actualDate?, actualAmount?, status }(状態: "予定" | "入金済")。信販会社→VEの入金予定/実績。WholesaleCase.financeReceiptsに配列で持つ。
  • WSupplierPayment { id, supplier, orderNo?, dueDate, scheduledAmount, paidDate?, paidAmount?, status }(状態: "予定" | "支払済")。VE→仕入先の支払予定/実績。orderNoでWOrder.orderNoと紐づく。WholesaleCase.supplierPaymentsに配列で持つ。dueDateは手入力が無い場合、lib/supplierPayment.tsのsupplierPaymentDueDate(supplier, orderDate)がSupplierの締日・支払サイトから自動算出する。
  • WDealerSettlement { id, statementNo, issueDate, creditReceived, veShare, adjustment, payout, scheduledPayoutDate?, actualPayoutDate?, status, memo? }(状態: "予定" | "精算済")。VE→販売店の精算書1件。creditReceivedは信販から受領した総額、veShareはVEの取り分(粗利)、adjustmentは調整額(±)、payoutは販売店への支払額でcreditReceived − veShare + adjustmentにより算出する。WholesaleCase.dealerSettlementに1件(配列ではない)で持つ。

三社間マネーの集計はthreePartySummary()が担う。financeReceiptsから信販入金の予定合計・実績合計、supplierPaymentsから仕入先支払の予定合計・実績合計、dealerSettlementからVE取り分・販売店への支払額・精算完了可否を導出し、案件単位のサマリとして返す。hasDataフラグにより「三社間データがまだ存在しない案件」と「金額が0の案件」を区別できる(エッジケース・異常系を参照)。

信販会社→VE入金→仕入先支払→販売店精算の金流は次のとおりである。

sequenceDiagram
    participant Dealer as 販売店(代理店)
    participant VE as VE(経理/発注担当)
    participant Sup as 仕入先(商社/メーカー)
    participant Fin as 信販会社

    Dealer->>VE: 発注受付(DealerIntakeForm)→ WholesaleCase生成(settlement=三社間)
    VE->>VE: approval: 承認番号確認(settlementApproved()=true)
    VE->>Sup: order: 発注登録(WOrder)
    Sup-->>VE: deliver: 納品・工事完了確認
    Fin->>VE: financeReceipts: 信販入金(予定→実績、status=入金済)
    VE->>Sup: supplierPayments: 仕入先へ支払(予定→実績、status=支払済)<br/>dueDateはSupplier締日/支払サイトから自動算出
    VE->>Dealer: dealerSettlement: 精算書発行(payout=creditReceived−veShare+adjustment、status=精算済)
    Note over VE: threePartySummary() が予定/実績・VE取り分・精算完了可否を集計

経営ダッシュボード集計(ValueOS経営基準)

Section titled “経営ダッシュボード集計(ValueOS経営基準)”

商社(卸)ダッシュボードの経営KPIはlib/wholesaleMetrics.tsのwholesaleMetrics()が集計し、ValueOSの経営集計基準に整合させている(2026-08実装済み・試作crm_app)。cancelled(キャンセル)が立つ案件は以下すべての集計から除外する。

指標定義期間フィルタValueOS対応
売上受注日(orderReceivedDateOf())が期間内の有効案件の卸金額(wholesaleTotal())合計受注日基準(opts.from/opts.to)sales_price合計相当
実粗利同条件の実粗利(wholesaleGrossProfit() = 卸金額 − 原価)合計受注日基準gross_profit合計相当
粗利率実粗利 ÷ 売上(売上0のとき0)受注日基準(分子・分母とも)—
未入金全有効案件の現在残高(wholesaleBalance())合計非連動(常に全期間)unpaidAmount相当

売上・実粗利はorderReceivedDate(無ければcreatedAt)が指定期間内の案件のみを積み上げる。未入金は期間指定の影響を受けず、常に現時点の未収残高全件を合計する。WholesaleDashboard.tsxは期間トグル(今月/先月/全期間)でこの4指標を表示する。

ルール内容実装根拠
現在価格導出startDate <= 基準日かつ(endDate未設定またはendDate >= 基準日)を満たす候補のうちstartDate最新(同日なら最安)を採用するcurrentWholesalePrice() / currentWholesalePriceByModel()
dealer軸→標準フォールバックdealer指定時は販売店別価格を優先検索し、該当がなければdealer未設定(標準価格)にフォールバックする2段引き当てcurrentWholesalePriceForDealer()
価格スナップショット固定受注時点の卸価/仕入価をWItemに固定し、事後のWholesalePrice改定の影響を受けないsnapshotWItem()
税・端数unitPrice/amount等の金額フィールドはいずれも「税抜」という前提コメントのみで、消費税計算・端数処理(四捨五入/切り捨て等)のロジックはコード上に存在しない(未実装)各金額フィールドのコメント
支払サイト→支払予定日算出発注日が当月の締日以前ならその月で締め、締日超過なら翌月に繰り越して締める→締め月からpaymentSiteのoffsetMonths分だけ加算した月の日(末日または指定日)を支払予定日とする。仕入先未検出・締日/支払サイト未設定・表記解釈不能のいずれかでundefinedを返し手入力にフォールバックするsupplierPaymentDueDate()(lib/supplierPayment.ts)
粗利実粗利=卸金額(wholesaleTotal())− 原価(wholesaleCost()=発注金額WOrder.amountの合計)wholesaleGrossProfit()
キャンセル除外cancelledが立つ案件は売上・実粗利・粗利率・未入金などダッシュボード集計すべてから除外する。一覧・詳細画面からは除外されない(集計のみの除外)wholesaleMetrics()(lib/wholesaleMetrics.ts)
残高の下限残高=請求ベース−入金済みだが、入金超過時もマイナスにはならない(0で下限打ち切り)wholesaleBalance()(Math.max(0, ...))
請求ベースのフォールバック請求書(WInvoice)が1件もない場合は卸金額合計(wholesaleTotal())を請求ベースとみなすwholesaleBilled()

WBucket(案件の現在地)は"決済待ち" | "発注待ち" | "納品待ち" | "請求待ち" | "入金待ち" | "完了"の6分類で、決済区分別の工程列と各フラグ・子エンティティの状態から都度導出する。ShiftStatusと同じ「保存せず導出する」パターンを採る。

flowchart TD
    S["wholesaleFlow(case)"] --> D{"全ステップ完了か<br/>firstUndone < 0"}
    D -->|はい| DONE["bucket = 完了"]
    D -->|いいえ| C["currentStep = 未完了の先頭ステップ"]
    C --> M{"currentStep.key"}
    M -->|"settlement / approval"| B1["決済待ち"]
    M -->|"prepay / payment"| B2["入金待ち"]
    M -->|"order"| B3["発注待ち"]
    M -->|"deliver"| B4["納品待ち"]
    M -->|"invoice"| B5["請求待ち"]

DealerIntakeFormは独立した保存エンティティではなく、代理店からの発注入力(発注元・決済区分・明細)を受けてWholesaleCaseを直接生成する入口画面である。発注元の選択肢はEventCompany(催事ドメインの代理店マスタ)から、明細候補はWholesalePriceから取得し、両ドメインをまたぐ実装上の接続点となる。

WholesaleTask { id, caseId(null許容), title, status: "未対応"|"対応中"|"完了", assignee, dueDate, priority: "高"|"中"|"低", memo?, createdAt }。工程に載らない対応を拾う汎用タスクで、案件(WholesaleCase)に紐付くがcaseIdは未紐付け(null)も許容する。

現状の試作は「ID参照」と「名称一致(文字列一致)」の2種類の結合が混在する。名称一致箇所の棚卸しと対応方針は次のとおり。

参照元(フィールド)参照先結合キー破綻シナリオ対応方針
Product.makerManufacturer.name(lib/pricing.ts)文字列一致メーカー名の改称・表記変更でProduct群との紐付きが一斉に切れる第2段階でID化
Product.seriesSeries.name文字列一致シリーズ名の表記ゆれ(全角/半角、カナ表記差)で別シリーズ扱いになる第2段階でID化
Series.makerManufacturer.name文字列一致メーカー改称の影響がSeries経由でも波及する第2段階でID化
Package.makerManufacturer.name文字列一致メーカー名の改称・表記変更でPackage群との紐付きが一斉に切れる第2段階でID化
Package.seriesSeries.name文字列一致シリーズ名の表記ゆれ(全角/半角、カナ表記差)で別シリーズ扱いになる第2段階でID化
WholesalePrice.supplierSupplier.name文字列一致商社名の表記ゆれで卸金額が別商社扱いになる第1段階でID化
WholesalePrice.itemNameProduct(未連携)参照なし(フリーテキスト)品名の言い換え(型番違い・表記違い)で同一商品の卸価格が分裂する第1段階でID参照を任意追加
WholesaleCase.dealerEventCompany.name文字列一致代理店名の表記ゆれで卸案件が正しい代理店に紐付かない第1段階でID化
WholesalePrice.dealerEventCompany.name文字列一致(任意・未設定=標準価格)代理店名の表記ゆれで販売店別卸価格が拾えず標準価格にフォールバックする第1段階でID化
Deal.construction.vendor(Construction.vendor)Vendor.name文字列一致施工店名の表記ゆれで案件と施工店マスタの紐付きが外れる後続フェーズでID化
EventShift.plannedLeader(マスタなし)自由文字列個人名の表記ゆれで担当者集計ができない確認事項を参照

催事↔商談の疎結合突合(channel === "催事" かつ venue === 店名 かつ日付一致)も名称一致の一種であり、店名表記ゆれが起きると突合が外れるリスクを持ったまま運用する。密結合にしない理由は商談CRM本体が後続フェーズであるためである。

名称一致箇所は段階的にID参照へ置き換える。一括移行はせず、先行リリースへの影響が大きい順に3段階で進める。

  1. 第1段階:商社系のsupplier / itemNameを優先する。WholesalePrice.itemNameはフリーテキスト併用のままProduct参照(型番)を任意フィールドとして追加し、既存入力を壊さない移行とする。WholesaleCase.dealer・WholesalePrice.dealerのID化も同段階で行う。
  2. 第2段階:アイテム階層(maker/series)のID化。Manufacturer/SeriesにIDを導入し、ProductからはID参照+表示用名称のキャッシュという構成にする。名称変更時の追従が自動になる。
  3. 第3段階:催事↔商談の突合キー強化(店舗マスタIDを商談側venueに持たせる)。商談CRM設計と同時に決定する。

移行時のデータ整備(表記ゆれの名寄せ)は人手確認を要する作業として別途タスク化する。件数規模は(実データで確定)。

代替案として「名称一致を維持し、保存時バリデーション(マスタに存在する名称のみ許可)で凌ぐ」案も検討したが、段階的ID化を採用する。

横断仕様: 変更履歴・導出ステータス・権限

Section titled “横断仕様: 変更履歴・導出ステータス・権限”

変更履歴はフィールド単位で before/after を記録する共通機構である。現状Seriesと催事系エンティティ(EventStore/EventCompany/EventShift)は履歴対象外であり、対象化の要否は確認事項に記載する。

導出ステータスの共通パターンとして、ShiftStatus(催事)とWBucket(商社)はどちらも「保存せず都度導出」で統一する。状態の二重管理をしない設計原則を横断で貫く。

権限は催事ドメインのみ社内/代理店の2ロールを実装済みである(詳細は役割・権限(先行リリース第1弾)を参照)。アイテム・商社ドメインの権限設計(経理/発注担当/販売店それぞれの見え方)は工程の担当部門定義(STEP_METAのassignee)から示唆されるが、ロールとしては未実装であり後続フェーズで確定する。

対象実装状況詳細
催事ドメイン実装済み(社内/代理店の2ロール)役割・権限(先行リリース第1弾)、および本ページ催事ドメイン節の権限テーブルを参照
商社(卸)ドメインロールとしては未実装STEP_METAのassignee(経理/発注担当)は工程の担当部門を示す表示用ラベルに留まる。実際の操作可否はcanOrder/canInvoiceという業務状態フラグのみで判定しており、ログインユーザーの部門・役割に応じたUI/API側の制限はコード上に存在しない(誰でも状態が揃えば発注・請求操作を実行できる)
アイテムドメイン(マスタ)ロールとしては未実装マスタ共通定型(テーブル+ドロワー+変更履歴)は閲覧・編集を分けるロール判定を持たない
DealerIntakeForm認証方式・公開範囲とも未定代理店が直接アクセスする入口画面。認証・アクセス制御の実装は未確認(確認事項を参照)

マスタ画面の共通定型(テーブル+ドロワー+変更履歴)に本ページの全マスタが載ることで、エンティティ追加時のUI実装コストを低く抑えている。

先行リリース第1弾の主要画面と、データ構造から導かれる操作可否を整理する。個別のUI仕様は各機能ページ(催事・アイテム・商社(卸))に委譲し、ここではデータモデル・ワークフローと直結する挙動のみ扱う。

  • 商社(卸)ダッシュボード(WholesaleDashboard.tsx):期間トグル(今月/先月/全期間)で売上・実粗利・粗利率・未入金の4指標を表示する。未入金のみ期間トグルに非連動(常に全期間の現在残高)。
  • 案件ボード:WBUCKETS(決済待ち/発注待ち/納品待ち/請求待ち/入金待ち/完了)の6列で案件を分類表示する。WBUCKET_METAは列ごとに配色を持ち、状態専用の色として使う(ブランド色・操作色とは別軸)。
  • 案件詳細画面:wholesaleFlow(case)が返すsteps(各ステップの完了/現在地)・currentStep(次にやること)・nextAction/assignee(担当部門)でタイムラインを表示する。canOrder/canInvoiceがfalseのときは発注・請求の登録操作をボタンレベルでブロックし、orderBlockedReason/invoiceBlockedReasonを理由として表示する。
  • マイルストーン表示:wholesaleMilestones(case)が発注/請求/入金の予定日をcreatedAt起点のリードタイムから投影する。実績(ordered/invoiced/paymentReceived)が揃うとdone: trueに更新される。前金のみ「入金→発注」の順で2件、それ以外は「発注→請求 or 入金」の順で2件を表示する。
  • DealerIntakeForm(販売店発注フォーム):発注元(EventCompanyから選択)・決済区分・明細(WholesalePriceから選択)を入力しWholesaleCaseを直接生成する。独立した保存エンティティは持たない入口画面。
  • マスタ画面(Product/Series/Supplier/Vendor/LoanCompany/Package/EventStore/EventCompany 等):共通定型(テーブル+編集ドロワー+変更履歴)に統一し、サイドバー「マスタ」に集約表示する。
  • 催事シフト一覧:ShiftStatusの5分類(予定/開始済み/終了済み/未開始/終了未報告)で表示し、SHIFT_STATUS_METAにより未開始・終了未報告を注意色で強調する。

クライアント自作ERP「ValueOS」(正本 github.com/stanaka-blip/valueos)は受発注〜請求〜入金(後段)に強く、当方CRMは案件獲得〜見積〜催事(前段)に強い。両者は相補的な関係にあり、以下のとおり用語・テーブルが対応する。

当方(試作)ValueOS対応関係
WholesaleCasecases卸案件↔案件。dealer↔dealer_id(当方は名称直持ち、ValueOSはID参照)。orderReceivedDate↔order_received_date(受注日、経営集計の期間基準)、cancelled↔activeRecords除外相当(集計対象外)。実装済み(試作crm_app)
WholesalePricedealer_sales_prices / supplier_purchase_prices卸金額。dealer↔dealer_id軸(販売店ごとの卸価格、未設定=標準価格)。実装済み(試作crm_app)
WItem(purchasePriceSnapshot/wholesalePriceSnapshot/isManualPrice/priceFetchedAt)case_products(purchase_price/sales_price/gross_profit/is_manual_price/price_fetched_at)受注時の価格スナップショット。snapshotWItem()で受注時点の仕入価/卸価を明細に固定し、マスタ改定の影響を受けない。実装済み(試作crm_app)
WSettlement(前金/掛売/三社間/カード/その他)case_settlements.settlement_type決済区分(2026-08-01正規化)。当方語彙↔ValueOS正式値:前金=前金/掛売=売掛/三社間=3社間決済/カード=カード(「その他」は正式区分外)
WSettlementDetailcase_settlements決済条件の詳細。feeRate/feeAmount↔fee_rate/fee_amount、depositRate/depositAmount↔deposit_rate/deposit_amount、paymentTerms↔payment_terms、cardBrand/cardStatus↔card_brand/card_status、financeCompany/loanStatus↔finance_company/loan_status、approvalNo↔approval_number。実装済み(試作crm_app)
WOrderorders発注
WInvoiceinvoices請求
WPaymentpayments入金
WFinanceReceiptfinance_receipts三社間マネー:信販→VE入金。実装済み(試作crm_app)
WSupplierPaymentsupplier_payments三社間マネー:VE→仕入先支払。実装済み(試作crm_app)
WDealerSettlementdealer_settlements三社間マネー:VE→販売店精算。creditReceived↔信販受領額、veShare↔VE取り分(粗利)、adjustment↔調整額、payout↔支払額。実装済み(試作crm_app)
WholesaleTasktasksタスク
DealerIntakeFormapp/dealer/orders/new(→case_registration_requests)販売店発注の受付画面
EventCompanydealers代理店(販売店/卸先)マスタ。paymentType↔既定支払区分、defaultProfitAmount↔既定利益額。実装済み(試作crm_app)
Suppliersuppliers仕入先マスタ。closingDay↔締日、paymentSite↔支払サイト、creditLimit↔与信枠。実装済み(試作crm_app)
supplierPaymentDueDate()(lib/supplierPayment.ts)suppliers(closing_day/payment_site)を用いた支払予定算出Supplier.closingDay・Supplier.paymentSiteの表記からWSupplierPayment.dueDate(仕入先支払予定日)を自動算出。実装済み(試作crm_app)
Packagepackagesパッケージ商品マスタ。code↔package_code、maker/series↔manufacturer_id/series_id(名称一致、当方は未ID化)、systemType↔system_type、warrantyYears↔warranty_years。実装済み(試作crm_app)

詳細な機能差分・項目突合は ValueOS 差分・項目突合 を参照する。

  • 品目名の不一致(WholesalePrice.itemName):フリーテキストのため、明細プルダウンはwholesaleItemOptions()が品目名+区分の重複を除いた選択肢を提示するのみで、表記ゆれ自体を防ぐ仕組みはない。手入力欄がある場合は同一商品が別品目として分裂しうる。
  • 販売店(dealer)名の不一致:currentWholesalePriceForDealer()は完全一致でのみ販売店別価格を検索するため、表記ゆれがあると販売店別価格がヒットせず標準価格(dealer未設定)へ静かにフォールバックする。エラーや警告表示はない。
  • 未マップの支払サイト表記:supplierPaymentDueDate()は既知パターン(「月末」「20日」「翌月末払い」「翌々月10日払い」等)以外は解釈できずundefinedを返す。呼び出し側はWSupplierPayment.dueDateを手入力にフォールバックする。
  • 未請求:wholesaleBilled()はWInvoiceが1件もない案件ではwholesaleTotal()(卸金額合計)を請求ベースとみなす。請求書登録前でも残高計算自体は成立する。
  • 分割入金:WPaymentは状態フィールドを持たず、amountの配列合計で消込を行う。複数回の入金を積み上げるだけで、入金超過(過入金)を検知・警告するロジックはない。
  • 残高の下限:wholesaleBalance()はMath.max(0, ...)により入金超過時も残高0未満にはならない(超過分の扱いはUI・業務運用に委ねられ、コード上は捨てられる)。
  • 0件系:明細0件(items: [])でもwholesaleTotal()等は0を返しエラーにならない。卸価格未登録の品目はcurrentWholesalePrice*系関数がundefinedを返し、UI側は手入力(isManualPrice: true)にフォールバックする設計。
  • 三社間データの部分欠落:threePartySummary()はfinanceReceipts/supplierPayments/dealerSettlementのいずれかが空でも例外を出さず、hasDataフラグでUI側が「三社間データなし」を判定できるようにしている。
  • キャンセル案件:cancelledが立つ案件は一覧・詳細からは消えず、経営集計(売上・実粗利・粗利率・未入金)からのみ除外される。一覧表示からの除外・別扱いの要否は確認事項に記載する。
  • 承認番号の二重管理:WholesaleCase.approvalNo(案件直下・後方互換)とsettlementDetail.approvalNoが両方存在しうる。settlementApproved()はどちらか一方が埋まっていれば承認済みと判定するため、片方だけ更新され両者が食い違う運用リスクがある。
  • 代理店マスタ拡張フィールド(会社情報シート準拠)のうち、enum想定とした項目(雇用形態・稼働状況・契約状況等)の値候補の確定。実データ未確認のため型はいずれも推定。
  • DepartmentHistory.departmentをマスタ参照ID・自由文字列のいずれで持つか、および部署マスタ自体(社員マスタと合わせて後続フェーズで確定)との整合。
  • DepartmentHistory.userIdが参照する社員・アカウントマスタの先行導入要否(本エンティティ単体を先行導入する場合、userIdの参照先が確定するまでの暫定運用)。
  • 代理店側の部門分け(1代理店に自社メンバーと代理店メンバーが混在するケース)の設計、および代理店が登録するデータを自社側と別に変更可能にする方式(Q26)。
  • Product.model(型番)の変更時・重複時の扱い(別型番として再登録するか、履歴を引き継ぐか)。
  • 廃番概念の要否。現状は削除のみで廃番の概念を持たない。
  • 催事開始報告のチェック項目(既定7件)のカスタマイズ可否。
  • 三社間・カードの工程列がinvoice(販売店への請求)を通らない設計の経理確認。
  • DealerIntakeFormの認証方式・公開範囲。
  • WholesaleCaseを元請案件(deals)と別軸で持つ設計の先にある分社化の要否。
  • 正規化(第1段階のID化)を先行リリース第1弾に含めるか、後続で実施するか。
  • Series・催事系エンティティ(EventStore/EventCompany/EventShift/StoreChain/DepartmentHistory)を変更履歴の対象に含めるか。
  • 監査観点(履歴の保持期間、削除時の履歴の扱い、代理店側操作の履歴可視範囲)。
  • EventShift.plannedLeader(責任者名)を自由文字列のまま運用するか、担当者マスタを設けるか。
  • 入金超過(過入金)の検知・警告の要否。現状はwholesaleBalance()が残高を0で下限打ち切りするのみで、超過分の記録・通知は行わない。
  • WholesaleCase.approvalNoとsettlementDetail.approvalNoの二重管理を一本化するか(現状は後方互換のため両方参照)。
  • キャンセル案件(cancelled)を一覧・ボード表示からも除外するか、経営集計のみの除外に留めるか。