商品・アイテムマスタ
位置づけ: Phase 1(コア)のマスタ。案1 で継続(価格系・催事系マスタは削除済み)
アイテムマスタ(商品マスタ)は、型番を主キーとする Product と、メーカー・シリーズ・仕入先・施工店・ローン会社の周辺5マスタ、および複数商品を束ねたセット販売商品を管理する Package(パッケージ商品マスタ)で構成する。Product は価格フィールドを持たない。価格は卸金額・価格表・販売価格・見積の4系統側で管理する(価格4系統との関係を参照)。
画面はマスタ画面の定型(一覧テーブル+編集ドロワー+変更履歴ペイン、サイドバー「マスタ」集約)に準拠する。定型仕様そのものは本ページでは扱わない。
顧客・商談CRM本実装、見積本実装、KPI予実、AIエージェントは対象外(後続フェーズ)。本ページは商品マスタと周辺マスタの構造・業務ルールのみを扱う。段階分けは 段階的スコープ を参照。
Product(商品)— 型番=主キー
Section titled “Product(商品)— 型番=主キー”Product.model(型番)を主キーとし、重複登録はできない。型番が変わった場合は別レコード(別商品)として扱う。型番変更時、変更履歴は新規レコードに引き継がれず分断される。
| フィールド | 型・選択肢 | 必須 | 既定 | 備考 |
|---|---|---|---|---|
model | 文字列(型番) | 必須・主キー | なし | 表記例: CS-340B81。編集ドロワーでも変更可能なテキスト項目(型番変更のエッジケース参照) |
kind | PV / BT / EQ / IH / AC / 付帯 | 必須 | PV※ | 種別6区分を参照。※編集ドロワーの入力項目は未選択を許容し、未選択のまま保存すると PV に暗黙フォールバックする(エッジケース参照) |
maker | 文字列(メーカー名) | 必須 | なし | 編集画面ではプルダウン。メーカーマスタの名称と商品側の既存値をマージして候補にする |
series | 文字列(シリーズ名) | 任意 | なし | 編集画面ではプルダウン。シリーズマスタと名称一致で対応付ける |
name | 文字列(商品名) | 任意 | なし | 例: 「蓄電池」等の一般名 |
spec | 文字列(容量・枚数等) | 任意 | なし | legacy。自由記述1列の旧表記。新規登録は下記 capacity/unit/specification を用いる(自動移行・同期はせず併存させる) |
status | 取扱中 / 廃番 | 任意 | 取扱中※ | ValueOS is_active 相当。廃番=販売終了。廃番管理を参照。※編集ドロワーの入力項目は未選択を許容し、未選択のまま保存すると 取扱中 に暗黙フォールバックする(型定義上の既定値と一致するため実害は小さい) |
capacity | 文字列(容量表記) | 任意 | なし | 例: 5.10-5.78kW。ValueOS capacity |
unit | 文字列(単位) | 任意 | なし | 例: kW/kWh/L/cm。ValueOS unit |
specification | 文字列(構造化仕様) | 任意 | なし | 例: 「単結晶PERC・積雪地域対応」。ValueOS specification |
defaultSupplierId | 文字列(Supplier.id 参照) | 任意 | なし | 既定仕入先。maker/series と異なり名称一致ではなく Supplier.id への直接ID参照(階層構造と名称一致参照)。ValueOS default_supplier_id |
memo | 文字列(メモ) | 任意 | なし | ValueOS memo 相当。既存の note(備考)とは別項目として併存する |
leadTimeDays | 数値(日) | 任意 | なし(undefined) | 商品単位の納期目安。未設定時は納期解決ルールでメーカー目安にフォールバックする |
stock | 潤沢 / 少 / 欠品 | 任意 | なし(undefined、フォールバックなし) | 数量在庫は持たない3状態管理。kind と異なり未選択時の既定値フォールバックはない |
note | 文字列(備考) | 任意 | なし | 自由記述 |
2026-08、ValueOS products(is_active/capacity/unit/specification/default_supplier_id/memo)に合わせて status・capacity・unit・specification・defaultSupplierId・memo を追加した(試作crm_appに反映済み)。いずれも任意項目で既存項目・後方互換を崩さず、主キーは引き続き model(型番)である。
在庫は「潤沢/少/欠品」の3状態のみを持ち、数量在庫は管理しない。実在庫はメーカー・商社側にあり、CRM側で数量を保持しても実態と乖離するため、状態区分に限定する。
Product は価格フィールドを一切持たない。価格の実体は価格4系統との関係を参照。
型番の全角/半角・ハイフン・スペースなどの表記ゆれを正規化するルールは未定義である。階層構造と名称一致の課題と同根であり、確認事項に記載する。
種別6区分(kind)
Section titled “種別6区分(kind)”kind は PV / BT / EQ / IH / AC / 付帯 の6区分で固定する。区分の追加はコード上の列挙値変更を伴うため、リリース後の変更管理対象として扱う。
| 区分コード | 名称 | 想定商材 |
|---|---|---|
| PV | 太陽光パネル | 太陽光モジュール一式 |
| BT | 蓄電池 | 蓄電システム一式 |
| EQ | エコキュート | 給湯システム一式 |
| IH | IHクッキングヒーター | IH機器 |
| AC | エアコン | 空調機器 |
| 付帯 | 付帯部材 | 単体販売しない部材・工事付属品 |
「付帯」区分は境界事例(単体販売するかどうか判断が割れる部材)を含む。判断基準は運用側の判断に委ねる。
kind は商品マスタ画面のフィルタ軸であり、商社モジュールの WholesalePrice.itemKind(商品 / パッケージ)とは別概念である。名称が似ているため混同しやすいが、前者は商材カテゴリ、後者は卸金額行が単品かセットかの区分である。
周辺マスタ5種
Section titled “周辺マスタ5種”いずれも一覧+ドロワーの定型画面(マスタ画面)で構成する。
| マスタ | 主要フィールド | 用途 |
|---|---|---|
Manufacturer(メーカー) | name | Product.maker の候補元。価格表の manufacturerId からも参照される |
Series(シリーズ) | name, maker, note | 商品を束ねる系列。商品検索の名寄せに使う |
Supplier(仕入先) | name, type: 商社 / メーカー直送, note, closingDay(締日), paymentSite(支払サイト), creditLimit(与信枠) | 仕入先の種別・支払条件管理。卸金額の supplier(商社名)と名称で対応する。締日・支払サイト・与信枠はValueOS suppliers の支払条件相当として実装済み(試作crm_appに反映済み) |
Vendor(施工店) | name, type: 自社 / 他社, tool: CRMチャット / ANDPAD / LINE, area | 施工店の種別・連絡ツール・エリア管理 |
LoanCompany(ローン会社) | name, daysDirect(2社間営業日数), daysViaTrader(3社間営業日数) | 審査〜入金の営業日数管理 |
代理店マスタ(旧 Agency)は独立マスタとして廃止し、催事モジュールの会社マスタに統合済みである。催事会社と代理店を別マスタで分けない方針による。詳細は 催事モジュール仕様 を参照。
催事モジュールの会社マスタ EventCompany(代理店=販売店/卸先マスタ)に paymentType(既定支払区分)・defaultProfitAmount(既定利益額)を実装済みである(試作crm_appに反映済み)。ValueOSの dealers(販売店=卸先マスタ)が持つ卸属性に相当し、卸金額の販売店(dealer)軸との関係は価格4系統との関係を参照。
補足事項:
Supplierと商社モジュールのWholesalePrice.supplier(商社名・フリーテキスト)は名称一致で結びついており、ID参照ではない。- 仕入先支払予定日の自動算出(
lib/supplierPayment.tsのsupplierPaymentDueDate(supplierName, orderDate))を実装済みである(試作crm_appに反映済み)。Supplier.closingDay(締日)・paymentSite(支払サイト)を名称一致で引き当て、発注日を締日で締めた月から支払サイト分の月数だけ後ろにずらした支払予定日を算出する。締日・支払サイトが未設定、または表記を解釈できない場合はundefinedを返し、呼び出し側で支払予定日の手入力にフォールバックする。 Vendor.tool(連絡ツール区分)は施工進捗の通知経路の出し分けに使う想定である。LoanCompany.daysDirect / daysViaTraderは2社間・3社間で別々に営業日数を持つ。Manufacturer.nameとProduct.makerの紐付けも名称一致である。
パッケージ商品マスタ(Package)
Section titled “パッケージ商品マスタ(Package)”Product(単品・型番=主キー)とは別に、複数商品を束ねたセット販売商品を管理する Package マスタを実装済みである(試作crm_appに反映済み)。ValueOS packages(package_code/manufacturer_id/series_id/system_type/warranty_years)に対応するマスタが当方に無い、という従来の差分を解消するもので、ValueOS実体との突合の商品マスタ突合表も本追加に合わせて更新済みである。
| フィールド | 型・選択肢 | 必須 | 既定 | 備考 |
|---|---|---|---|---|
id | 文字列 | 必須・主キー | nid("PKG", packages.length) で自動採番 | シード採番(例: PKG01)。連番は配列長ベースで、削除機能が無いため現状は重複しない |
code | 文字列(パッケージコード) | 必須 | なし | 主キー相当。ValueOS package_code に対応 |
name | 文字列(パッケージ名) | 必須 | なし | 例: 「UFOシリーズ 蓄電システムセット」 |
maker | 文字列(メーカー名) | 任意 | なし | Product.maker と同様に名称一致。ValueOS manufacturer_id 相当 |
series | 文字列(シリーズ名) | 任意 | なし | Product.series と同様に名称一致。ValueOS series_id 相当 |
capacity | 文字列(容量表記) | 任意 | なし | 例: 9.8kWh |
systemType | 全負荷 / 特定負荷 | 任意 | なし(undefined、フォールバックなし) | ValueOS system_type 相当。Product.kind と異なり未選択時の既定値フォールバックはない |
warrantyYears | 数値(年) | 任意 | なし | ValueOS warranty_years 相当 |
spec | 文字列(構成一式の説明) | 任意 | なし | 例: 「本体+パワコン+リモコン一式」 |
status | 取扱中 / 廃番 | 任意 | 取扱中※ | Product.status と平仄を合わせた型定義(ValueOS is_active 相当)。パッケージ商品マスタ画面(PackagesMasterPage)の状態列(ProductStatusBadge)・絞り込みチップ(filterKey="status")・編集ドロワーの状態select(取扱中/廃番)に実装済みである(廃番管理参照)。※編集ドロワーの入力項目は未選択を許容し、未選択のまま保存すると 取扱中 に暗黙フォールバックする(Product.status と同型のパターン) |
note | 文字列(備考) | 任意 | なし | 自由記述 |
Package も他マスタと同様に useMasters の packages スライスで保持し、専用画面 PackagesMasterPage(マスタ画面の定型)をルート /master-packages として実装し、サイドバー「マスタ」集約に登録済みである。
Package は卸金額(WholesalePrice)の itemKind: パッケージ 行が持つ itemName(フリーテキスト)とは現時点でID連携していない。パッケージ行の型番参照化は確認事項に残タスクとして記載する。
階層構造と名称一致
Section titled “階層構造と名称一致”メーカー─シリーズ─商品の階層は、すべて名称の文字列一致で解決する。ID参照ではない。
flowchart LR M1["Manufacturer.name<br/>(文字列)"] -.文字列一致.-> P1["Product.maker<br/>(文字列)"] S1["Series.name<br/>(文字列)"] -.文字列一致.-> P1b["Product.series<br/>(文字列)"]
名称一致が切れる代表的なシナリオ:
- メーカーが社名変更・ブランド統合した際、
Manufacturer.nameを更新してもProduct.makerの既存レコードが旧名のまま残り、名称一致が切れる。 - シリーズ名がリブランドされた場合、旧シリーズ名で登録済みの商品が新シリーズ名の
Seriesレコードから外れて見えなくなる。 - 全角/半角・スペース・中黒(・)などの表記ゆれにより、同一のメーカー・シリーズが別名称として扱われ、名寄せが漏れる。
seriesId/makerId へのID参照化は、確認事項に記載する未決の論点である。
上記の名称一致は maker/series に限る。Product.defaultSupplierId(既定仕入先)は例外的に Supplier.id への直接ID参照であり、名称一致ではない(データのProduct表を参照)。
商品マスタと価格の分離
Section titled “商品マスタと価格の分離”商品マスタ(Product、型番=主キー)は型番・種別・納期・在庫などの識別情報のみを保持する。価格・仕切りは価格・見積ドメイン(PriceBook/Part/PackageSet/SalesPrice)側で保持し、商品マスタからは参照しない。
納期解決ルール(resolveLeadtime)
Section titled “納期解決ルール(resolveLeadtime)”発注(Order)画面・工事パネルは、型番・種別・メーカーから納期目安と在庫状態を都度解決する。解決は商品マスタ(Product.leadTimeDays)を最優先し、無ければメーカー単位の目安マスタ LEADTIME_MASTER(kind×maker)にフォールバックする2段構成である(lib/crm.ts の resolveLeadtime/leadtimeFor)。
flowchart TD
A["発注/工事パネルで型番・種別・メーカーを保持"] --> B{"型番が Product に一致し<br/>leadTimeDays が設定済みか"}
B -- はい --> C["商品マスタの leadTimeDays / stock / note を採用<br/>source: 商品マスタ"]
B -- いいえ --> D{"kind × maker が<br/>LEADTIME_MASTER に一致するか<br/>(maker は部分一致)"}
D -- はい --> E["メーカー目安の leadTimeDays / stock / note を採用<br/>source: メーカー目安"]
D -- いいえ --> F["納期情報なし(undefined)"]
C --> G["orderDeadline = 着工日 − leadTimeDays<br/>(着工日未定なら算出しない)"]
E --> G
leadtimeFor のメーカー一致は完全一致ではなく maker.includes(l.maker)(部分一致)である。Product.stock(在庫3状態)も同じ優先順位で解決され、商品マスタ側に値があればメーカー目安より優先される。発注期限 orderDeadline は着工日からカレンダー日で leadTimeDays 分だけ逆算する(営業日換算ではない)。
Product.status(取扱中 / 廃番)を実装済みである(試作crm_appに反映済み)。ValueOS is_active 相当で、廃番(生産終了)を「レコードのある/ない」の二値ではなく状態として表現し、既存参照(卸金額の productModel、過去の卸案件明細)を保ったまま販売終了扱いにできる。既定値は 取扱中。編集ドロワーで未選択のまま保存した場合も 取扱中 に暗黙フォールバックする(kind 未選択時の PV フォールバックと同型のパターン。エッジケース/異常系参照)。
商品マスタ画面(ProductsMasterPage)は filterKey="status" で「すべて / 取扱中 / 廃番」の絞り込みチップを持ち、一覧の「状態」列は廃番のみ淡色バッジ(ProductStatusBadge、過去/無効トーン)で明示し、取扱中は控えめなテキスト表示にとどめる。色の役割を「状態=有効/無効」の1意味に固定し、種別(CategoryBadge)・在庫バッジの配色とは混同させない。
廃番後の後継型番案内は専用機能を持たない。シード例(EQ46XFV を廃番、memo: "後継機種 EQ46YFV へ切替済み")が示すとおり、現状は memo(自由記述)に手書きで残す運用にとどまる。後継型番への構造化リンクの要否は確認事項に記載する。
Package.status も実装済みである(試作crm_appに反映済み)。パッケージ商品マスタ画面(PackagesMasterPage)は filterKey="status" の絞り込みチップ・状態列(Productと共通の ProductStatusBadge)・編集ドロワーの状態select(既定値取扱中)を備え、Product(商品マスタ)と同一の見せ方で廃番管理する。
マスタ画面定型の履歴ペインで、フィールド単位の before/after(変更者・日時付き)を記録・閲覧できる。
| 対象エンティティ | 履歴記録 | 粒度 |
|---|---|---|
Product(商品) | あり | フィールド単位 before/after |
Supplier(仕入先) | あり | フィールド単位 before/after |
Vendor(施工店) | あり | フィールド単位 before/after |
LoanCompany(ローン会社) | あり | フィールド単位 before/after |
Series(シリーズ) | なし | 詳細ページが専用UIで、定型の履歴経路(マスタ画面のドロワー編集)を通らないため |
EventStore(催事店舗)・EventCompany(代理店) | あり | MasterScreen経由でフィールド単位 before/after。記録・閲覧は催事モジュール仕様側 |
| 催事シフト等(MasterScreenを使わない催事内データ) | なし | 催事モジュール仕様 の管轄範囲であり対象外 |
Product の型番変更は別レコード扱いのため、旧型番側の変更履歴は新レコードに引き継がれない。
価格4系統との関係
Section titled “価格4系統との関係”Product は価格フィールドを持たない。価格は次の4系統で管理する。
flowchart LR Product["Product<br/>(型番・種別・在庫等)"] WP["① 卸金額<br/>WholesalePrice"] PB["② 価格表<br/>PriceBook / Part / PackageSet"] SP["③ 販売価格<br/>SalesPrice"] QT["④ 見積<br/>Quote"] Product -.ID連携(productModel)/名称一致(itemName).-> WP Product -.名称一致(productNo等).-> PB Product -.名称一致(productNo).-> SP Product -.名称一致.-> QT
| 価格系統 | 保持モジュール | アイテムとの紐付け方法 | 改定管理 |
|---|---|---|---|
| 卸金額 | 商社モジュール(WholesalePrice) | itemKind: 商品 は productModel(Product.model にID連携)+ supplier(商社名)で保持。itemKind: パッケージ は itemName(フリーテキスト)+ supplierのまま | startDate(反映日)で最新を導出 |
| 価格表 | 価格・見積ドメイン(PriceBook/Part/PackageSet) | Part.productNo 等の名称的な参照 | effectiveDate(反映日)で最新を導出 |
| 販売価格 | 価格・見積ドメイン(SalesPrice) | productNo(型番)+ channel(商流/代理店)で保持 | startDate(反映日)で最新を導出 |
| 見積 | 価格・見積ドメイン(Quote) | 明細行(QuoteItem)に型番・数量・金額を都度入力 | 見積単位で個別 |
卸金額(WholesalePrice)は itemKind(商品 / パッケージ)・itemName(フリーテキスト)・productModel(型番、任意)・supplier(商社名)・purchasePrice(仕入)・wholesalePrice(卸)・startDate(反映日)を持ち、反映日で改定を管理する。
itemKind: 商品 の行には productModel を追加済みである。productModel は Product.model(型番)と直接対応するID連携のキーであり、itemName(フリーテキスト)による名称一致に代わる正規化ルートである。卸金額マスタ画面には「型番」列・入力欄を設けている。型番引き当ては currentWholesalePriceByModel(prices, productModel, baseDate) で行い、従来の品名一致版 currentWholesalePrice(prices, itemName, kind, baseDate) は itemKind: パッケージ の行など型番を持たない行のために引き続き残す。
シード9件のうち itemKind: 商品 の全6行(WP03/WP04/WP05/WP07/WP08/WP09)は実在型番(CS-340B81/ESS-U4X1/EGS-LM0500/S40ZTEP/CS6.1/CS-340B81)で productModel を連携済みである(型番マップの残タスクは解消済み)。パッケージ行(WP01/WP02/WP06)は単品の型番を持たないため対象外である。
現在価格の引き当ては、型番優先・反映日降順・同日は卸価格が安い順、という統一ロジックで導出する。dealer(販売店)指定がある場合はさらに「販売店専用行を優先し、無ければ標準行にフォールバック」という軸が重なる。
flowchart TD
A["引当キー: productModel と/または itemName・kind、dealer(任意)"] --> B{"dealer 指定あり?"}
B -- はい --> C["dealer が一致する行だけに母集団を絞り込み<br/>下の絞り込み(G〜J)を実行"]
C --> X{"結果が見つかったか"}
X -- はい --> K["現在の卸金額(dealer専用価格)"]
X -- いいえ --> D
B -- いいえ --> D["dealer 未設定(標準価格)の行だけに絞り込み<br/>下の絞り込み(G〜J)を実行"]
D --> G{"productModel を指定しているか"}
G -- はい --> H["productModel 一致 かつ<br/>startDate<=基準日<=endDate(endDate任意)"]
G -- いいえ --> I["itemName(+kind)一致 かつ<br/>startDate<=基準日<=endDate(endDate任意)"]
H --> J["startDate 降順 → 同日は wholesalePrice 昇順で先頭1件"]
I --> J
J --> K2["現在の卸金額(標準価格。無ければ undefined)"]
型番指定時に productModel 側の該当が無くても itemName へは自動フォールバックしない(currentWholesalePriceForDealer は呼び出し側が渡した productModel/itemName のどちらか一方のみで絞り込む)。呼び出し側が型番と品名の両方を渡した場合は型番指定を優先する。
卸金額マスタで反映日を改定しても、既に受注済みの卸案件明細(WItem)には影響しない。案件明細は受注時点の卸価格を snapshotWItem() でスナップショットコピーするためである。
sequenceDiagram
participant U as 担当者(卸案件明細作成)
participant WP as WholesalePrice(卸金額マスタ)
participant Fn as currentWholesalePriceByModel /<br/>currentWholesalePriceForDealer
participant WI as WItem(案件明細)
U->>WP: 型番/品名・区分・(任意)dealer を指定
U->>Fn: 基準日(発注日等)で現在価格を引当
Fn-->>U: 該当する WholesalePrice(purchasePrice / wholesalePrice)
U->>WI: snapshotWItem(base, { wholesalePrice, purchasePrice, manual?, fetchedAt? })
Note over WI: unitPrice = wholesalePrice<br/>wholesalePriceSnapshot / purchasePriceSnapshot に固定
Note over WP,WI: 以後 WholesalePrice の startDate を改定しても<br/>この WItem のスナップ値は追随しない
WItem(卸案件明細)自体は現時点で name(品名)と kind(商品/パッケージ)のみを保持し、Product.model(型番)や WholesalePrice.productModel への直接参照は持たない。マスタ間の型番ID連携(Product ↔ WholesalePrice.productModel)は導入済みだが、案件明細レベルへの型番伝播は未実装である。
卸金額(WholesalePrice)は dealer(販売店名、任意。空=標準価格)を実装済みである(試作crm_appに反映済み)。ValueOSの sales_prices における dealer_id 軸に相当し、同一品目・同一仕入先でも販売店ごとに卸金額を分けられる。dealer の実体は催事モジュールの EventCompany.name(代理店=販売店/卸先マスタ)を想定し、Product.maker/series と同様にID参照ではなく名称一致で対応する。
販売店ごとの現在価格は currentWholesalePriceForDealer(prices, { productModel?, itemName?, kind?, dealer? }, baseDate) で引き当てる。指定した dealer の価格が存在すればそれを優先し、無ければ dealer 未設定(標準価格)の行にフォールバックする。内部では型番指定時に currentWholesalePriceByModel、品名指定時に currentWholesalePrice を、それぞれ dealer で絞り込んだ母集団に対して呼び出す。
シードでは WP09(長州産業 CS-340B81、dealer: フィールドクリエイト)が販売店専用の卸単価(55,000円)を持ち、同一型番の標準価格 WP03(58,000円)より安い価格を代理店限定で提供する例になっている。これにより確認事項No.56「卸金額は販売店ごとに違うか」=YESに対する設計上の答えとしている。
価格表・販売価格・見積の3データは価格・見積ドメイン内で保持し、商品マスタとは名称的な参照(型番文字列の一致)で結びつく。ID参照ではない。
価格改定(各系統の反映日)と、アイテム側の leadTimeDays(納期)・stock(在庫3状態)は独立した概念であり連動しない。価格が変わっても在庫・納期の表示は自動更新されない。
商品マスタ・パッケージ商品マスタの画面は、マスタ画面定型(一覧テーブル+編集ドロワー+変更履歴ペイン)を用いる。定型の操作感(検索・履歴の記録方式など)そのものは扱わないが、本ページのマスタに固有のパラメータ化は次のとおりである。
| 項目 | 商品マスタ(ProductsMasterPage) | パッケージ商品マスタ(PackagesMasterPage) |
|---|---|---|
| ルート | /master-products | /master-packages |
| 詳細ページルート(2026-08追加) | /master-products/$model(一覧行のEyeアイコンから遷移) | /master-packages/$id(同左) |
| 一覧列の並び | メーカー・シリーズ・商品名・型番・容量・種別・状態・納期・在庫(2026-08、状態列を種別の直後に追加) | コード・名称・メーカー・システム種別・容量・状態(2026-08、状態列を末尾に追加) |
絞り込みチップの軸(filterKey) | status(状態:取扱中/廃番。2026-08、kindから変更) | status(状態:取扱中/廃番。2026-08、systemTypeから変更) |
| 行クリック時の操作 | 編集ドロワーを開く(一覧行に削除操作はない) | 同左 |
maker の選択肢 | メーカーマスタ(Manufacturer.name)と商品側の既存 maker 値を統合した集合 | 自由入力(プルダウン化されていない) |
series の選択肢 | シリーズマスタ(Series.name)と商品側の既存 series 値を統合した集合 | 自由入力(プルダウン化されていない) |
defaultSupplierId の選択肢(2026-08追加) | 仕入先マスタ(Supplier.id→name表示)から選択 | フィールド自体なし |
| ドロワーの追加項目(2026-08) | 状態・容量(構造化)・単位・仕様・既定仕入先・メモ | 状態select(取扱中/廃番、既定値取扱中) |
| 検索対象 | 一覧列+入力項目の全キー(例: シリーズ名「UFO」で該当商品がヒット) | 同左 |
| 新規追加時の主キー確定方法 | ユーザーが型番を直接入力 | id は自動採番、code はユーザー入力 |
廃番バッジ・状態フィルタの詳しい挙動は廃番管理を参照。一覧・編集ドロワーは商品・パッケージの識別情報のみを扱い、卸金額(WholesalePrice)・価格表(PriceBook/Part)・販売価格(SalesPrice)は表示しない。価格の参照は下記の詳細ページ、または各画面(卸金額マスタ・価格表・販売価格一覧)側で行う。
商品詳細ページ(/master-products/$model)
Section titled “商品詳細ページ(/master-products/$model)”商品マスタ一覧の各行にEyeアイコン(renderRowAction、行クリックによる編集ドロワー起動とは独立した操作列)を実装済みである(試作crm_appに反映済み)。クリックすると商品詳細ページ ProductDetailPage(/master-products/$model)に遷移する。ValueOS products/[id] の画面構成(商品情報+関連価格の集約)を踏襲したものである。
詳細ページは1画面に次を集約する。
- 商品情報: 型番・メーカー・シリーズ・種別・状態・在庫のStat帯、品名・容量(構造化含む)・単位・仕様・既定仕入先・納期・備考・メモのフィールド一覧。
- 卸価格:
WholesalePrice.productModel(型番のID連携)で当該型番に紐づく行を仕入先・仕入価格・卸価格・粗利(卸価格−仕入価格)・反映日つきで一覧表示し、currentWholesalePriceByModelで導出した現在有効な行を「現在有効」ピル+背景色で強調する。0件時は「この型番の卸価格は未登録です。」と表示し、卸金額マスタへの遷移リンクを併設する。 - 販売価格:
SalesPrice.productNo(型番)で紐づく行を商流(channel)別に一覧表示し、商流ごとの現在有効値(currentSalesPrice。channel未設定=標準を含む)を同様に強調する。0件時は「この型番の販売価格は未登録です。」と表示し、販売価格マスタへの遷移リンクを併設する。
編集は一覧側と同じ商品マスタ編集ドロワー(productFields.ts定義)を再利用し、変更履歴(useMasterHistory)も一覧側の編集と同じ記録経路である。
パッケージ詳細ページ(/master-packages/$id)
Section titled “パッケージ詳細ページ(/master-packages/$id)”パッケージ商品マスタ一覧にも同型のEyeアイコン導線を実装済みである(試作crm_appに反映済み)。詳細ページ PackageDetailPage(ValueOS packages/[id] 相当)は次を集約する。
- パッケージ情報: パッケージコード・メーカー・シリーズ・容量・システム種別・状態のStat帯、名称・規格・保証年数・備考のフィールド一覧。
- 構成品: 価格表側の
PackageSet(usePricingのpackages)をパッケージ名の文字列一致(PackageSet.name === Package.name)で1件引き当て、一致すればそのmembers(No・名称・数量・区分)とsetPrice(セット価格)を表示する。Package(マスタ)とPackageSet(価格表)は別概念・別ID体系であり、名称一致でしか結びつかない点に注意。 - 卸価格:
WholesalePrice(itemKind: パッケージ)をパッケージ名の文字列一致(itemName === Package.name)で集約表示する。商品詳細ページのproductModelのようなID連携は未整備で、名称一致にとどまる。
現状のシードデータはパッケージ名と PackageSet/卸金額の名称が完全一致しないため、構成品・卸価格の両セクションが「未登録です」表示になる箇所がある。これはマスタ整備待ちの状態であり実装の不具合ではなく、空セクションでも崩れないUIにしてある。編集は一覧側と同じパッケージ商品マスタ編集ドロワー(packageFields.ts定義)を再利用する。
価格の一括登録(TSV貼付)
Section titled “価格の一括登録(TSV貼付)”卸金額・販売価格の両マスタに、既存の取込キット(lib/csvImport.ts の makeTsvParser と features/crm/MasterImportSheet.tsx の MasterImportSheet)を用いたTSV貼付の一括登録を実装済みである(試作crm_appに反映済み)。ValueOSの prices/bulk-by-supplier(仕入先別の一括登録)・sales-prices/bulk-by-dealer(販売店別の一括登録)に相当する。
| 対象 | 列構成(タブ区切り貼付・1行目はヘッダ) | 型番・仕入先の突合 |
|---|---|---|
卸金額(WholesalePriceImportSheet) | 型番/品名(任意)/区分(商品|パッケージ、既定「商品」)/仕入先/仕入価/卸価/販売店(任意、空=標準)/反映日 | 型番→Product.model、仕入先→Supplier.name とそれぞれ一致検証(不一致は警告のみで取込は続行) |
販売価格(SalesPriceImportSheet) | 型番/商流(任意、空=標準)/価格/反映日 | 型番→Product.modelと一致検証(不一致は警告のみで取込は続行、一致時は商品名を自動補完) |
必須項目(卸金額は「型番または品名」+仕入価+卸価+反映日、販売価格は価格+反映日)が欠けた行は取込対象からスキップし、理由をエラー一覧に表示する。取込確定時のID採番は nextSequentialIds(WP/SP連番、既存件数と衝突しない番号)で行い、確定後は通常の登録と同じ変更履歴(useMasterHistory)に「一括取込」として1件(件数付き)で記録される。パース結果はブラウザ内で保持するのみでサーバ送信はしない(動作確認用のランタイム機能であり、実データ投入はAccess制限環境でのCSV取込運用に委ねる)。
現行の権限モック(lib/permissions.ts)は、サイドバー・モバイルタブの表示可否(canSeeNav)としてのみ機能し、ルート自体への到達を遮断するガードではない(同ファイルの冒頭コメントに「実装では Supabase の RLS + アプリ層のガードで担保する」と明記されている)。
| ロール | /master-products(商品) | /master-packages(パッケージ) | /master-series /master-suppliers /master-loans /master-vendors /master-agencies | /master-wholesale-price(卸金額) |
|---|---|---|---|---|
| 管理者 | 見える(all) | 見える(all) | 見える(all) | 見える(all) |
| 事務(BO) | 見える | 見えない(未許可) | 見える | 見えない(未許可) |
| 営業 | 見えない | 見えない | 見えない | 見えない |
| 発注・申請担当 | 見えない | 見えない | 見えない | 見えない |
| 代理店・代理店(メンバー)・施工店 | 見えない | 見えない | 見えない | 見えない |
事務(BO) は商品・シリーズ・仕入先・ローン会社・施工店・代理店の各マスタにはアクセスできるが、/master-packages(パッケージ商品マスタ)と /master-wholesale-price(卸金額マスタ)は許可リストに含まれておらず、ナビ上は管理者しか到達できない。両マスタとも比較的新しい追加(パッケージ商品マスタ、卸金額の型番/dealer拡張)であるため、意図的な絞り込みか許可リスト更新漏れかは実装からは判別できない(確認事項参照)。なお、ナビ非表示は画面遷移UIを隠すのみで、URLを直接開いた場合の到達を防ぐルートガードは現時点で実装されていない。
エッジケース/異常系
Section titled “エッジケース/異常系”kind未選択時の暗黙フォールバック: 商品マスタのデータ型ではkindは必須だが、編集ドロワーの入力項目定義にはrequired指定がなく、未選択のまま保存するとPV(太陽光パネル)に暗黙的にフォールバックする((v.kind || "PV") as ItemKind)。BT/EQ/IH/AC/付帯の商品を種別未選択のまま登録すると、意図せず太陽光パネルとして分類・フィルタされる。同様のパターンは仕入先のtype(未選択時商社)、施工店のtype(未選択時他社)・tool(未選択時CRMチャット)、商品マスタのstatus(未選択時取扱中。こちらは元々任意項目かつ型定義上の既定値と一致するため実害は小さい)にもある。一方でパッケージ商品マスタのsystemTypeにはこのフォールバックがなく、未選択時はundefinedのまま保存される(statusは取扱中への暗黙フォールバックを実装済み。廃番管理参照)。- 数値項目のバリデーション欠如:
leadTimeDays・warrantyYears・creditLimit・daysDirect・daysViaTraderはドロワー上ではテキスト入力として保持され、送信時にNumber(v)で変換するのみで型・範囲チェックはない。数値以外の文字列を入力するとNaNが保存され得る。 - 型番(
model)の編集: 商品マスタの編集ドロワーは主キーであるmodelも編集可能な必須項目として扱う。ストア実装(updateProduct)は旧型番で一致した配列要素のフィールドを上書きする「インプレース更新」のため配列上は同一レコードのままだが、変更履歴(MasterHistoryEntry.targetId)には旧型番がそのまま記録され、以後の更新は新型番をtargetIdとして記録される。型番でレコードを追跡する運用では、型番変更の前後で連続性が失われる(廃番管理、および冒頭の型番変更=別レコード扱いという記載と符合する)。加えて、卸金額側のWholesalePrice.productModel(旧型番の文字列)は自動追随しないため、型番変更後は卸金額の型番引当が手動更新なしに切れる。 - 卸金額の型番/品名の二重指定:
productModelとitemNameを両方保持する行(シード6商品行はすべて該当)は、呼び出し側がproductModelを渡す限り型番一致ロジックのみで引き当てられ、itemName側は無視される。呼び出し側が誤って旧品名だけを渡すと、型番連携済みの行であっても品名一致でしか引けない。 dealer専用価格が期限切れ/未来日の場合:currentWholesalePriceForDealerはdealer一致の母集団内で期間条件(startDate〜endDate)を満たす行が無いと、dealer指定自体は「一致行が存在した」かどうかに関わらず標準価格(dealer未設定)へフォールバックする。販売店専用価格の設定ミス(反映日が未来など)に気づきにくい。- 未マップの周辺マスタ参照:
Product.maker/series、Package.maker/series、WholesalePrice.supplier/dealerはいずれも名称文字列一致であり、周辺マスタ側の名称が変わる/表記ゆれがあると静かに対応が切れる(階層構造と名称一致参照)。エラーやワーニングは出ない。 - 0件表示: 検索・絞り込みチップで一致0件のときは「まだ{マスタ名}がありません」または「『{検索語}』に一致する{マスタ名}がありません」を一覧に表示し、テーブル自体は空行で崩れない(マスタ画面定型の共通挙動)。
ValueOS実体との突合
Section titled “ValueOS実体との突合”ValueOS(正本 github.com/stanaka-blip/valueos)の商品・仕入価格・卸価格・施工店に関するテーブル実体(lib/database.types.ts・マイグレーション・各登録画面コードで確認済み)と、本ページの商品マスタ・周辺マスタとの対応を示す。テーブル一覧の全体像は ValueOSデータモデル、機能差分の全体像は ValueOS差分・項目突合 を参照。
商品マスタ(products / packages ↔ Product)
Section titled “商品マスタ(products / packages ↔ Product)”| ValueOS | 当方 Product | 突合 |
|---|---|---|
id(UUID主キー)、model_no(属性列・NULL可) | model(型番=主キー) | 主キー設計が異なる。ValueOSはUUID固定で型番変更を列更新のみで扱えるが、当方は型番自体が主キーのため型番変更=別レコード扱いになる(廃番管理参照) |
category / product_type | kind(PV/BT/EQ/IH/AC/付帯) | 概ね一致(区分の持ち方が2列⇔1列) |
manufacturer_id(FK・NULL可) | maker(名称直持ち・必須) | ValueOSはID参照・任意、当方は名称一致・必須 |
series_id(FK) | series(名称・任意) | 同上 |
name | name | 一致 |
is_active | status(取扱中 / 廃番) | 実装済み(試作crm_appに反映済み)。ValueOSは真偽値、当方は列挙値(取扱中=true相当/廃番=false相当)で意味は一致(廃番管理参照) |
specification + capacity + unit | specification・capacity・unit(旧 spec はlegacyとして併存) | 実装済み(試作crm_appに反映済み)。当方も3列構成となり一致。従来の自由記述1列(spec)は自動移行せずlegacyとして残す |
memo | memo | 実装済み(試作crm_appに反映済み)。一致。既存の note(備考)とは別項目として併存する |
default_supplier_id(既定仕入先) | defaultSupplierId(Supplier.id参照) | 実装済み(試作crm_appに反映済み)。ValueOSと同様のID参照に揃えた。maker/seriesの名称一致とは異なる例外(階層構造と名称一致参照) |
| なし | leadTimeDays(納期)、stock(在庫3状態)、note(備考) | 差:当方固有。ValueOSに対応列なし |
パッケージ商品マスタ(packages ↔ Package)
Section titled “パッケージ商品マスタ(packages ↔ Package)”ValueOS packages(複数商品をまとめたセット販売商品。package_code/system_type/warranty_years/pricing_method等を持つ)に対応するマスタは、パッケージ商品マスタ(Package)として実装済みである(試作crm_appに反映済み)。
| ValueOS | 当方 Package | 突合 |
|---|---|---|
id(UUID主キー) | id | 突合方式が異なる(当方はシード採番の文字列ID) |
package_code | code | 一致 |
manufacturer_id(FK) | maker(名称・任意) | ValueOSはID参照、当方は Product と同様に名称一致 |
series_id(FK) | series(名称・任意) | 同上 |
name | name | 一致 |
capacity | capacity | 一致 |
system_type | systemType(全負荷 / 特定負荷) | 一致 |
warranty_years | warrantyYears | 一致 |
specification 等 | spec | ValueOSは列分解、当方は自由記述1列に集約(Product.spec と同方針) |
memo | note | 一致 |
is_active(想定。products.is_active と同様の列がある前提) | status(取扱中 / 廃番) | Product.status と平仄を合わせて実装済み(試作crm_appに反映済み)。PackagesMasterPage の状態列・絞り込みチップ・編集ドロワーにも反映済み(廃番管理参照) |
pricing_method | なし | 差:当方はパッケージ単位の価格算出方式を持たない。価格は卸金額(WholesalePrice)の itemKind: パッケージ 行が itemName(フリーテキスト)で別管理し、Package.code とのID連携は未実装(確認事項参照) |
| ValueOS | 当方 | 突合 |
|---|---|---|
manufacturers | Manufacturer | ほぼ1:1。メーカー→シリーズ→商品の階層構造は当方と同型 |
product_series(実テーブル名。「series」ではない) | Series | name↔name、manufacturer_id↔maker。description/is_active は当方に無い |
suppliers | Supplier | name↔name、supplier_type↔type。closing_day↔closingDay(締日)・payment_site↔paymentSite(支払サイト)・credit_limit↔creditLimit(与信枠)は実装済み(試作crm_appに反映済み)。ValueOSの order_method(発注方法)のみ当方に対応列なし |
contractors(migration 20260808120000_create_contractors.sql) | Vendor(施工店) | masterKinds.ts で contractor =施工店と明記され概念は一致。ただしValueOSは postal_code/address/delivery_name/delivery_address/delivery_phone/receiver_name など納品先住所・受取人の物流属性中心。当方の type(自社/他社)・tool(連絡ツール)・area(対応エリア)に相当する列はValueOSに無く、逆に納品先系は当方に無い |
contractors はValueOSの案件テーブル(cases等)から外部キー参照を受けない独立マスタである。当方の Vendor も案件(WholesaleCase)から直接参照せず名称管理にとどまる点は一致する。
価格(purchase_prices / sales_prices ↔ WholesalePrice)
Section titled “価格(purchase_prices / sales_prices ↔ WholesalePrice)”ValueOS の価格は、アプリが実際に読み書きする purchase_prices(仕入価格・supplier_id 軸)と sales_prices(販売価格・dealer_id 軸=販売店ごと)が正本である(lib/purchasePrices.ts/lib/salesPrices.ts が from("purchase_prices")/from("sales_prices") を実行)。supplier_purchase_prices/dealer_sales_prices も database.types.ts に型として存在する(ビュー相当)。同名の supplier_purchase_price_bulk_requests / dealer_sales_price_bulk_requests は一括登録RPCの冪等リクエスト台帳であり、マスタではない。
| ValueOS | 当方 WholesalePrice | 突合 |
|---|---|---|
purchase_prices:supplier_id、purchase_price、start_date/end_date、product_id/package_id(price_target_type で排他) | 仕入側:supplier、purchasePrice、startDate/endDate、productModel | supplier_id↔supplier、purchase_price↔purchasePrice、期間列は一致。対象指定はValueOSが product_id(UUID FK)、当方は productModel(型番文字列でのID連携)で発想は同じ |
有効判定(lib/purchasePrices.ts):is_active=true かつ start_date<=基準日<=end_date(NULL可)を満たす start_date 降順先頭1件 | 有効判定(currentWholesalePriceByModel/currentWholesalePrice):startDate 降順(同日は卸価格が安い順) | 期間で現在価格を導出する発想が一致 |
sales_prices:dealer_id、sales_price、product_id/package_id | 卸売側:wholesalePrice(同一レコード内)+dealer(販売店名、任意) | sales_price↔wholesalePrice、dealer_id↔dealer。構造差は残る:当方は仕入値・卸値を1レコードに同居させるが、ValueOSは仕入(purchase_prices=仕入先軸)と販売(sales_prices=販売店(dealer)軸)を別テーブルに分離する。販売店(dealer)軸自体は実装済み(試作crm_appに反映済み):WholesalePrice.dealer を追加し、催事モジュールの EventCompany(代理店=販売店/卸先マスタ)を ValueOS dealers 相当として流用する。dealer_idのようなFKではなく EventCompany.name との名称一致 |
case_products(ValueOSの案件明細)は sales_price_id/purchase_price_id でマスタ価格のIDを保持しつつ金額を明細へスナップショットコピーし、is_manual_price/price_fetched_at で手入力・取得時点を記録する。このパターンは当方の卸案件明細 WItem(lib/wholesale.ts)に purchasePriceSnapshot(purchase_price 相当)・wholesalePriceSnapshot(受注時点の卸価スナップ)・isManualPrice(is_manual_price 相当)・priceFetchedAt(price_fetched_at 相当)として実装済みである(試作crm_appに反映済み)。ヘルパー snapshotWItem() が受注時点の価格をこれらのフィールドへ固定し、以後の卸金額マスタ改定の影響を受けないようにする。
販売店別軸(卸先ごとの卸価格)は実装済みである(試作crm_appに反映済み)。ValueOSの dealers(販売店=卸先マスタ。sales_prices.dealer_id の親)に相当するマスタを新設する代わりに、既存の催事モジュール会社マスタ EventCompany(代理店=販売店/卸先マスタ、周辺マスタ5種参照)を流用し、paymentType(既定支払区分)・defaultProfitAmount(既定利益額)を追加して ValueOS dealers の卸属性に近づけた。WholesalePrice.dealer は EventCompany.name との名称一致で対応し、currentWholesalePriceForDealer で「販売店指定価格を優先→無ければ標準価格にフォールバック」を引き当てる(価格4系統との関係参照)。なお LoanCompany(2社間/3社間営業日数)に直接対応するValueOSマスタは無く、three_party_money_requests/finance_receipts/dealer_settlements の3社間金流フローとして実装されている。
seriesId/makerIdによるID参照化の要否・移行手順(既存レコードの名称→ID変換方法、価格表・卸金額など他モジュールへの波及範囲を含む)。- 型番の表記ゆれ(全角/半角・ハイフン・スペース)を正規化するルールの要否。
- 卸金額
itemKind: 商品のproductModelID連携は実装済みで、シード全6商品行への付与も完了済み(試作crm_appに反映済み)。残るのはitemKind: パッケージ行の型番参照化(パッケージは単品の型番を持たないため、Package.codeとの連携など別途構成の要否含む)。 - 卸案件明細(
WItem)はname/kindのみを保持し、Product.model/WholesalePrice.productModelへの型番参照を持たない。マスタ間のID連携を案件明細まで伝播させるかは未定。 - 商品状態(
status: 取扱中 / 廃番。確認事項Q52「商品状態『販売中/廃番』を追加すべきか」)・規格の構造化(capacity/unit/specification。Q44「規格は自由入力でよいか、構造化するか」)・既定仕入先(defaultSupplierId。対応する設問はないがValueOSdefault_supplier_idを根拠とする)の3点は、8/19定例の確認事項仕分けにおいてクライアント回答を待たず「ValueOSの実装に合わせて設計として確定」と判断し、いずれも実装済みである(試作crm_appに反映済み。廃番管理・ValueOS実体との突合参照)。廃番時に新規選択候補(各種プルダウン)から除外する仕組みは未実装で、卸金額・過去案件など既存参照は名称一致のまま保持される。 - 旧
spec(自由記述1列)を廃止するか、構造化したcapacity/unit/specificationと併存させ続けるかの運用ルールの要否。 - 廃番(恒久的な状態)と
stockの「欠品」(一時的な状態)の使い分け運用ルールの要否。 - 廃番品から後継型番への案内リンク機能の要否(現状は
memoへの手書きのみ。廃番管理参照)。 - 変更履歴の保存期間・CSV等へのエクスポート要否。
- 販売店(dealer)軸の卸金額は
WholesalePrice.dealer+催事モジュールEventCompanyの卸属性拡張(paymentType/defaultProfitAmount)で実装済み(試作crm_appに反映済み)。ValueOSのようにdealersを独立の正式マスタとして分離するか、当方のように催事モジュールの会社マスタ流用のままとするかは運用判断であり、ValueOS実体との突合参照。 Supplierへの締日・支払サイト・与信枠は実装済み(試作crm_appに反映済み)。ValueOSのorder_method(発注方法)は当方に対応列がなく、追加要否は未定。- 商品マスタ・仕入先マスタ・施工店マスタの編集ドロワーで
kind/status/type/toolを未選択のまま保存すると暗黙の既定値(PV/取扱中/商社/他社/CRMチャット)で保存される現行実装(エッジケース/異常系参照)を、入力必須化に是正するかの要否。 /master-packages(パッケージ商品マスタ)・/master-wholesale-price(卸金額マスタ)が事務(BO)のナビ許可リストに含まれていない現行実装(権限参照)が意図的な絞り込みか許可リスト更新漏れかの確認。