コンテンツにスキップ

役割・権限

位置づけ: 新CRM全体設計

先行リリース第1弾(催事・アイテム・商社(卸))の権限は、社内4ロール・外部3ロールの計7ロールで構成するRBAC(ロールベースアクセス制御)で管理する。実装の正本は crm_app/app/src/lib/permissions.ts であり、画面(nav)単位の表示制御と、列・操作単位の可否関数(canSeeCost等)の2層で権限を判定する。

顧客/商談CRM・見積・KPI予実領域の権限(所属・権限スコープ4段・職能ロールの3軸設計)は 役割・権限(全体設計) が扱う。本ページはそのうち先行リリース範囲で実装済みの lib/permissions.ts を対象とする。決定事項の正本は 定例会ログ であり、両者に差異がある場合は定例会ログを優先する。

対象は催事・アイテム(商品マスタ)・商社(卸)の3モジュール。顧客/商談CRM・見積・KPI予実の権限は対象外とし、役割・権限(全体設計) 側で扱う。フェーズ区分の詳細は スコープ・フェーズ分け を参照。

Role 型は7値。社内4ロールと外部3ロールに分かれ、「社内/代理店」の単純な2値ではない。

ロール区分想定ユーザー像主な責務
管理者社内本部・システム管理者全画面・全操作・マスタ管理
営業社内催事・案件を担当する営業案件・顧客・商談結果、催事シフト作成編集(発注・申請・架電は対象外)
事務(BO)社内バックオフィス架電・審査・書類・施工・発注・申請・卸(wholesale)・マスタまで最も広い実務範囲
発注・申請担当社内発注・申請の実務担当発注(/orders)・申請(/applications)・案件閲覧に限定
代理店外部代理店側の管理者自社経由案件と自社の催事シフト。仕切原価は非表示、提案金額は閲覧可
代理店(メンバー)外部代理店の現場スタッフ自社の催事シフト閲覧と開始/終了報告のみ
施工店外部施工パートナー担当案件の施工・チャットのみ(催事・アイテム・卸は対象外)

催事スタッフは独立ロールにせず「代理店(メンバー)」に統合する。個人スタッフはアカウント管理せず、責任者名の文字列と人数のみで管理する方針と整合させる。EventCompany.business(催事/訪販/催事・訪販)は代理店の属性でありロールではないため、権限判定には用いない。

1ユーザーが複数ロールを兼務できるかは確認事項とする。現行実装は1ユーザー=1ロール(サイドバーの表示ロール切替によるデモ運用)を前提とする。

  • テナント境界: 社内ロールは全代理店のデータを横断閲覧する。代理店ロールは eventScopeAgency() が返す所属代理店名により自社にスコープする(シフトの companyId を EventCompany.name に解決して一致判定)。
  • 編集権は原則社内ロールのみに付与する。外部(代理店)には催事の開始/終了報告のみを書き込み許可するadd-only型の設計とする(canManageShifts は管理者・営業・事務(BO)のみtrue)。
  • 権限の適用単位はロールベース(RBAC)とし、「画面(nav)単位の表示制御」と「列・操作単位の可否関数」の2層で構成する。
  • 仕入原価・粗利(canSeeCost)は外部ロール(代理店・代理店(メンバー)・施工店)に非公開とする。提案金額(canSeeProposal)は施工店と代理店(メンバー)のみ非公開とし、代理店は販売当事者として提案金額まで閲覧できる。社内ロール間(管理者・営業・事務(BO)・発注・申請担当)では金額の絞り込みを行わず、全員が原価を閲覧できる。
  • アイテムはフィールド単位の変更履歴(before/after)を /master-history で管理する。変更者(ユーザー紐付け)の記録はログイン実装後に対応する。
  • マスタの編集可否・催事の開始/終了報告の入力可否は、本ページの各表がUI上の想定ポリシーとして示すものであり、それを強制するロールガードは現状未実装である。useRole(表示ロール切替)は変更履歴のactor記録に使うのみで、編集操作自体をブロックする機能を持たない。実装までは運用ルール(周知手順)で統制し、ロールガードの実装は今後の対応とする。
  • 2026-07-22定例会では「代理店の管理者/メンバーは機能差ほぼ無し(枠だけ用意)」とされたが、実装段階では NAV_ALLOW(代理店=/deals・/customers・/chat・/events/代理店(メンバー)=/eventsのみ)、visibleDealTabs(代理店=5タブ/代理店(メンバー)=3タブ)、canSeeProposal(代理店(メンバー)のみ非公開)で明確な差分が生じている。7/22時点の想定と実装の差異は確認事項とする。

権限判定関数リファレンス(lib/permissions.ts)

Section titled “権限判定関数リファレンス(lib/permissions.ts)”

列・操作単位の可否は関数として実装されており、いずれも Role 単値から真偽を返す純粋関数である(ユーザーの所属や案件個別の状態は引数に取らない)。

関数シグネチャ判定ロジック
isAgencyRole(role: Role) => boolean代理店 または 代理店(メンバー) のとき true
isExternal(role: Role) => booleanisAgencyRole(role) || role === "施工店"
canSeeCost(role: Role) => boolean!isExternal(role)(仕切原価・粗利)
canSeeProposal(role: Role) => booleanrole !== "施工店" && role !== "代理店(メンバー)"(提案金額)
canSeeInternalMemo(role: Role) => boolean!isExternal(role)(運営メモ)
canEditDeal(role: Role) => boolean管理者 | 営業 | 事務(BO)
canCreateDeal(role: Role) => boolean管理者 | 営業 | 事務(BO)| 代理店
canManageShifts(role: Role) => boolean管理者 | 営業 | 事務(BO)
canExport(role: Role) => boolean!isExternal(role)
isFieldRole / isCoreRole(role: Role) => boolean営業 | 施工店 | isAgencyRole(role) / その否定(レイアウト分岐)
canSeeNav(role: Role, to?: NavPath) => booleanto 未指定は常に true。STYLEGUIDE_PATHS(/rules /style /patterns /theme)は管理者限定。それ以外は NAV_ALLOW[role] が "all" または配列に to を含むかで判定。NAV_ALLOW[role] が存在しない(未知ロール)場合は何も見せず false を返す

ロール横断の可否マトリクス(○=true・×=false。関数の戻り値をそのまま転記):

ロールisExternalisFieldRolecanSeeCostcanSeeProposalcanSeeInternalMemocanEditDealcanCreateDealcanManageShiftscanExport
管理者××○○○○○○○
営業×○○○○○○○○
事務(BO)××○○○○○○○
発注・申請担当××○○○×××○
代理店○○×○××○××
代理店(メンバー)○○×××××××
施工店○○×××××××

canEditDeal は3モジュール(催事・アイテム・卸)を跨いだ「社内のみ編集可」という基本方針の実装上の代表例であり、canManageShifts(催事シフト編集)はこれと同じロール集合(管理者・営業・事務(BO))を対象にする別関数として独立定義されている(同一ロジックを2関数に分けているため、将来一方だけ変更すると乖離するリスクがある)。

案件(Deal)閲覧範囲・タブ・チャットスコープ(対象外・要点のみ)

Section titled “案件(Deal)閲覧範囲・タブ・チャットスコープ(対象外・要点のみ)”

案件(Deal)の閲覧範囲・詳細タブ・チャットルームの可視スコープは、顧客/商談CRM本体の権限であり本ページ(催事・アイテム・商社(卸)の第1弾スコープ)の対象外である。詳細・正本は 役割・権限(全体設計) に委譲する。visibleDeals / visibleDealTabs / visibleScopes はChannel(訪問販売/催事/反響/パートナー)を横断するCRM本体の権限関数であり、催事チャネルに閉じたものではない。

第1弾に関わる範囲の要点のみ以下に記す。

  • 管理者・営業・事務(BO)・発注・申請担当は全件・全8タブ(概要・タスク・アイテム・工程・契約後・申請・チャット・活動)・社内/施工店/代理店の全チャットスコープを閲覧できる。
  • 代理店は自社(代理店名一致)経由の案件のみを閲覧でき、詳細タブは概要・アイテム・工程・チャット・活動の5タブ、チャットスコープは代理店に限定される。
  • 代理店(メンバー)は /deals のnavが非公開であり、案件は催事シフト経由の情報のみ参照する。詳細タブは概要・工程・チャットの3タブに限定される。
  • 施工店は施工が開始している担当案件のみを閲覧でき、詳細タブは概要・工程・チャットの3タブ、チャットスコープは施工店に限定される。

レイアウト前提も権限とあわせて分岐する。営業・代理店・代理店(メンバー)・施工店はモバイル前提の現場ロール、管理者・事務(BO)・発注・申請担当はPC前提のコア運営ロールとする。

操作管理者営業事務(BO)発注・申請担当代理店代理店(メンバー)
シフト(EventShift)作成・編集・削除○○○×××
開始報告(朝礼)・終了報告(現場ファネル)入力××××○○
ShiftStatus(予定/開始済み/終了済み/未開始/終了未報告)の手動変更××××××
シフト閲覧(/events。社内は全代理店横断)○○○×○(自社のみ)○(自社のみ)
店舗(EventStore)・代理店(EventCompany)マスタ画面の閲覧○○○×××
上記マスタの編集○×○×××
催事↔商談の突合結果の閲覧○○○×××

ShiftStatusは基準時刻からの自動導出のため、どのロールも手動変更できない。店舗・代理店マスタの編集権は管理者と事務(BO)に付与する想定だが、これを強制するロールガードは未実装であり運用ルールで統制する(上記「権限モデルの基本方針」参照)。営業への編集権開放(画面自体は営業にも表示済み)は確認事項とする。内部ロールが代理店に代わって開始/終了報告を代行入力する機能は実装しておらず、対応の要否は確認事項とする。代理店(メンバー)には自社の全シフトを表示する(担当シフトのみへの絞り込みは採らない)。

EventShift・開始/終了報告のデータ構造(lib/events.ts)

Section titled “EventShift・開始/終了報告のデータ構造(lib/events.ts)”

書き込み可否(開始/終了報告=代理店・代理店(メンバー)のみ)を裏付けるため、報告対象の型を示す。実装の正本は lib/events.ts であり、状態は保存せず基準時刻から導出する方針(時間軸データモデル)を採る。

型/フィールド型必須既定/備考
EventShift.idstring(S-MMDD-NN)○newShiftId が同日連番で採番
EventShift.date / storeId / companyIdstring○storeId=EventStore.id、companyId=EventCompany.id(代理店=販売店マスタ)への参照
EventShift.startTime / endTimestring(HH:MM)○ShiftStatus導出の基準時刻
EventShift.plannedLeaderstring任意個人アカウント化はしない方針(責任者名の文字列管理)
EventShift.staffCountnumber○人数のみで個人スタッフは管理しない
EventShift.startReportStartReport | undefined任意未設定=開始報告なし
EventShift.endReportEndReport | undefined任意未設定=終了報告なし
StartReport.leader / emergencyContactstring○朝礼報告の記名・緊急連絡先
StartReport.checksRecord<string, boolean>○チェック項目id→済みフラグ。項目自体は useEvents の startChecks(マスタ、既定7項目)から供給し画面編集可能
StartReport.photosReportPhoto[]任意モックは dataURL 保持。Storage連携は未実装
EndReport.lottery / seated / appointmentsFunnelCount({total, both, single})○現場ファネル(抽選→着座→アポ)。both/single は両面/片面提案の内訳

canManageShifts が true のロール(管理者・営業・事務(BO))はシフトの作成・編集・削除操作の対象だが、StartReport/EndReport の入力APIは代理店・代理店(メンバー)側の画面から呼ぶ想定であり、両者は書き込み対象データが排他(社内=シフト実体、外部=報告のみ)という設計になっている。

shiftStatus(shift, now) は startReport/endReport の有無と現在時刻を比較して次のように導出する(優先順位は上から)。

  1. endReport が存在する → 常に「終了済み」(開始報告の有無や時刻を問わない)
  2. startReport が存在し、now >= endTime → 「終了未報告」
  3. startReport が存在し、now < endTime → 「開始済み」
  4. startReport が無く、now >= startTime → 「未開始」
  5. startReport が無く、now < startTime → 「予定」
stateDiagram-v2
    [*] --> 予定
    予定 --> 開始済み: 開始報告入力(now<終了時刻)
    予定 --> 未開始: now>=開始時刻(未報告のまま自動遷移)
    未開始 --> 開始済み: 開始報告入力(now<終了時刻)
    開始済み --> 終了未報告: now>=終了時刻(未報告のまま自動遷移)
    開始済み --> 終了済み: 終了報告入力
    終了未報告 --> 終了済み: 終了報告入力
    未開始 --> 終了済み: 終了報告入力(開始報告なしで終了報告のみ入力された場合。エッジケース)
    終了済み --> [*]

エッジケース: 開始報告を経ずに終了報告のみが記録された場合(現場側の入力漏れ等)でも endReport の存在だけで「終了済み」と判定され、途中経路(開始済み/未開始/終了未報告)を経由しない。開始・終了報告は現状バリデーションで前後関係を強制していない。

アイテム(商品マスタ)モジュールの操作可否

Section titled “アイテム(商品マスタ)モジュールの操作可否”

/master-products 等のマスタ画面は管理者と事務(BO)のみに開放する。営業は /master-history(変更履歴)のみアクセスでき、発注・申請担当はアイテムマスタ関連画面に一切アクセスできない。

操作管理者営業事務(BO)発注・申請担当代理店
Product(型番/kind/maker/spec等)の画面閲覧○×○××
Product編集○×○××
在庫(潤沢/少/欠品)・納期(leadTimeDays)更新○×○××
周辺マスタ(Series/Supplier/Vendor/LoanCompany/Manufacturer)編集○×○××
変更履歴(/master-history、フィールド単位before/after)閲覧○○○××

Series・催事は変更履歴の対象外とする(プロダクト全体仕様)。外部ロール(代理店)へのアイテム公開は第1弾では行わない(在庫・納期の代理店向け公開は将来拡張の対象)。価格は商品マスタに持たず卸金額(CRM)側で保持するため、アイテムマスタの権限は原価情報を含まず単純化されている。営業・発注・申請担当へのアイテムマスタ本体の開放範囲は確認事項とする。上表のProduct編集可否も、店舗・代理店マスタと同様にロールガード未実装のため運用ルールで統制する。

アイテムマスタのデータ構造(lib/masters.ts)

Section titled “アイテムマスタのデータ構造(lib/masters.ts)”

管理者・事務(BO)のみが閲覧・編集する対象を明確にするため、主要マスタの型を示す。実装の正本は lib/masters.ts。価格(仕入価格・卸価格)はここには含まれず、卸金額(lib/wholesalePricing.ts)側で保持する設計と整合する。

型主キーフィールド必須制約/備考
Product(商品)model(型番)kind / maker / series? / name? / spec? / leadTimeDays? / stock? / note?model・kind・makerは必須、他は任意stock は "潤沢" | "少" | "欠品" のenum。leadTimeDays 未設定時はメーカー単位の値へのフォールバックを想定(実装コメント上の方針、フォールバック関数自体は未確認)
Series(シリーズ)idname / maker? / note?id・name必須検索の名寄せ用(例:「UFO」で関連商品をヒット)
Package(パッケージ)id(codeが主キー相当)code / name / maker? / series? / capacity? / systemType? / warrantyYears? / spec? / note?id・code・name必須systemType は "全負荷" | "特定負荷" のenum。ValueOS packages 準拠
Supplier(仕入先)idname / type / note? / closingDay? / paymentSite? / creditLimit?id・name・type必須type は "商社" | "メーカー直送" のenum。creditLimit(与信枠)は掛売の判定材料になり得るが wholesaleFlow の算出には未接続
LoanCompany(信販)idname / daysDirect / daysViaTrader全項目必須2社間/3社間の入金予定営業日数。三社間マネー(WFinanceReceipt)の予定日算出には現状未接続(別ロジック)
Vendor(施工店)idname / type / tool / area?id・name・type・tool必須typeは"自社"|"他社"、toolは"CRMチャット"|"ANDPAD"|"LINE"のenum

商社(卸)モジュールの操作可否と工程担当

Section titled “商社(卸)モジュールの操作可否と工程担当”

/wholesale のnav可視性は管理者と事務(BO)のみとする。営業・発注・申請担当・代理店3ロールはいずれも画面非表示とする。工程STEP単位の担当ロール紐付けは lib/permissions.ts には実装しておらず、ドメイン設計(WholesaleTask.assignee等)側の対応関係として管理する。

工程STEP想定担当掛売三社間カード前金その他
settlement(決済条件確定)経理○--○○
approval(承認番号確認)経理-○○--
prepay(前金請求入金)経理---○-
order(商社・メーカーへ発注)発注担当○○○○○
deliver(納品確認)発注担当○○○○○
invoice(販売店へ請求)経理○---○
payment(入金消込)経理○○○-○

「想定担当」は実装 lib/wholesale.ts の STEP_META.assignee に準拠する自由文字列(経理/発注担当)であり、Role 型の値そのものではない。経理は事務(BO)、発注担当は発注・申請担当に対応すると想定されるが、現行ロールには /wholesale のnavアクセスがない。ロール名と画面権限の不一致は確認事項とし、発注・申請担当に /wholesale を開放するか事務(BO)に一本化するかを決める。前金(FLOW: [settlement, prepay, order, deliver])はinvoice・payment工程を持たない点に注意する(三社間・カードも請求工程invoiceを持たない)。決済区分(前金/掛売/三社間/カード/その他)ごとのSTEP_ORDER分岐はドメイン仕様として定義済みだが、権限側とは未接続である。

決済区分別ワークフロー(FLOW)の状態遷移

Section titled “決済区分別ワークフロー(FLOW)の状態遷移”

wholesaleFlow(case) は決済区分ごとの FLOW 配列(STEPキー列)を先頭から走査し、未完了の最初のSTEPを currentStep(次アクション)とする。全STEP完了で done=true・bucket="完了"。

flowchart LR
    subgraph 前金
        A1[settlement 決済条件確定] --> A2[prepay 前金入金確認] --> A3[order 発注登録] --> A4[deliver 納品確認]
    end
    subgraph 掛売
        B1[settlement 決済条件確定] --> B2[order 発注登録] --> B3[deliver 納品確認] --> B4[invoice 請求登録] --> B5[payment 入金消込]
    end
    subgraph 三社間
        C1[approval 承認番号確認] --> C2[order 発注登録] --> C3[deliver 納品確認] --> C4[payment 入金消込]
    end
    subgraph カード
        D1[approval 承認番号確認] --> D2[order 発注登録] --> D3[deliver 納品確認] --> D4[payment 入金消込]
    end
    subgraph その他
        E1[settlement 決済条件確定] --> E2[order 発注登録] --> E3[deliver 納品確認] --> E4[invoice 請求登録] --> E5[payment 入金消込]
    end

各STEPの完了判定(stepDone)と業務ボードの分類(WBucket):

STEP完了判定対応バケット
settlementsettlementConfirmed === true決済待ち
approvalsettlementApproved(case)(下記)決済待ち
prepaypaymentReceived === true入金待ち
orderordered === true発注待ち
deliverdelivered === true納品待ち
invoiceinvoiced === true請求待ち
paymentpaymentReceived === true入金待ち

発注可否(canOrder)・請求可否(canInvoice)はゲート条件として個別に算出される(currentStep とは独立した判定関数)。

  • canOrder = FLOW に order が含まれ、かつ order より前の全STEPが完了済み、かつ ordered === false。前工程未完了時は orderBlockedReason に未完了STEPのラベルを文言化して表示する。
  • canInvoice = FLOW に invoice が含まれ、かつ invoice より前の全STEPが完了済み、かつ invoiced === false。三社間・カードは FLOW に invoice を持たないため canInvoice は常に false(請求ではなく入金消込のみで完結する設計)。

発注/請求/入金の登録シーケンス(掛売の例)

Section titled “発注/請求/入金の登録シーケンス(掛売の例)”

画面操作(WholesaleDetail の「次へ進める」相当の各ボタン)から状態がどう連動するかを示す。ロールガードは未実装のため、下記は「想定担当」(STEP_META.assignee)に基づく想定シーケンスであり、実際にはどのロールでも同じ操作が可能である。

sequenceDiagram
    actor BO as 事務(BO)=経理相当
    actor 発注 as 発注・申請担当=発注担当相当
    participant UI as WholesaleDetail
    participant Store as useWholesale(状態)

    BO->>UI: 決済条件を確定
    UI->>Store: updateCase(settlementConfirmed=true)
    発注->>UI: 発注を登録
    UI->>Store: addOrder(amount=卸金額合計, status="発注済")
    Store-->>UI: ordered=true(bucket→納品待ち)
    発注->>UI: 納品を確認
    UI->>Store: markDelivered(該当order)
    Store-->>UI: delivered=true(bucket→請求待ち)
    BO->>UI: 請求を登録
    UI->>Store: addInvoice(amount=卸金額合計, status="請求済")
    Store-->>UI: invoiced=true(bucket→入金待ち)
    BO->>UI: 入金を登録
    UI->>Store: addPayment(amount)
    Store->>Store: reconcile():入金合計と請求合計を比較し invoice.status / paymentReceived を再計算
    Store-->>UI: 入金合計>=請求合計なら bucket→完了

卸ドメインのデータ構造(lib/wholesale.ts / lib/wholesalePricing.ts / lib/wholesaleTask.ts)

Section titled “卸ドメインのデータ構造(lib/wholesale.ts / lib/wholesalePricing.ts / lib/wholesaleTask.ts)”
型/フィールド型必須既定/制約
WholesaleCase.idstring(W-0001形式)○newWholesaleId が全案件横断の連番で採番
WholesaleCase.settlementWSettlement enum(前金/掛売/三社間/カード/その他)○FLOW・STEP_METAの分岐キー
WholesaleCase.settlementConfirmed / paymentReceived / ordered / delivered / invoicedboolean○(既定false)ワークフローのゲートフラグ。paymentReceived は prepay・payment 両STEPの完了判定に兼用される
WholesaleCase.cancelledboolean任意「集計対象外」という設計意図のコメントのみで、実装上どの集計関数(wholesaleTotal系・wholesaleFlow・一覧表示)もこのフラグを参照していない(後述エッジケース)
WholesaleCase.orders / invoices / paymentsWOrder[] / WInvoice[] / WPayment[]任意(配列)複数登録可。合計・消込は配列全体を集計
WOrder.status"発注済" | "納品済"○markDelivered で”納品済”へ遷移
WInvoice.status"請求済" | "一部入金" | "入金済"○reconcile() が入金額に応じて自動更新(手動変更不可)
WSettlementDetail{feeRate?, feeAmount?, depositRate?, depositAmount?, paymentTerms?, cardBrand?, cardStatus?, financeCompany?, loanStatus?, approvalNo?}任意決済区分ごとに SETTLEMENT_FIELDS が表示すべき項目を規定(ValueOS case_settlements 準拠)
WFinanceReceipt / WSupplierPayment / WDealerSettlementオブジェクト任意(settlement==="三社間"のときのみ使用)信販→VE入金/VE→仕入先支払/VE→販売店精算の3実体。WDealerSettlement.payout は creditReceived − veShare + adjustment の算出式
WholesalePrice.itemKind"商品" | "パッケージ"○
WholesalePrice.supplierstring○Supplier.name と一致させる自由文字列(外部キー制約なし)
WholesalePrice.purchasePrice / wholesalePricenumber○税抜。丸め処理・端数処理は未実装(整数円をそのまま保持)
WholesalePrice.startDate / endDatestring(YYYY-MM-DD)/ 任意startDate必須反映日。endDate未設定=現在も有効
WholesalePrice.dealerstring / 任意任意未設定=標準価格。設定時は当該販売店専用価格(下記フォールバック参照)
WholesaleTask.caseIdstring | null○null=卸案件に紐付かない単独タスク(例: 与信枠相談)
WholesaleTask.status"未対応" | "対応中" | "完了"○既定”未対応”
WholesaleTask.priority"高" | "中" | "低"○
WholesaleTask.assigneestring○自由文字列(Role型と非連動。STEP_METAと同じ想定担当の文言を使用)
WholesaleTask.dueDatestring(YYYY-MM-DD)○(空文字許容)taskOverdue()=status!=="完了" && dueDate!=="" && dueDate < today(期限未設定は超過扱いにしない)
  • 卸金額合計: wholesaleTotal(case) = Σ(item.unitPrice × item.qty)。税抜。消費税・端数処理のロジックは実装になく、円単位の整数をそのまま合計する。
  • 請求ベース金額: wholesaleBilled(case) = 請求書(invoices)があればその合計、無ければ wholesaleTotal。
  • 残高: wholesaleBalance(case) = max(0, wholesaleBilled − wholesalePaid)。入金超過(過入金)が発生してもマイナス表示にはならず、超過分の警告表示もない(下記エッジケース)。
  • 原価: wholesaleCost(case) = Σ(order.amount)。発注登録(registerOrder)時の既定値は amount = wholesaleTotal(case)(=販売店への卸金額そのもの)であり、実際の仕入原価ではない。画面上で手動修正しない限り wholesaleGrossProfit は常に0円になる(下記エッジケース)。
  • 粗利: wholesaleGrossProfit(case) = wholesaleTotal − wholesaleCost。
  • 入金消込(reconcile): 入金登録(addPayment)・請求登録(addInvoice)のたびに paid = Σpayments.amount と billed = wholesaleBilled を比較し、billed > 0 && paid >= billed なら全請求書のstatusを”入金済”かつ paymentReceived=true、0 < paid < billed なら”一部入金”、paid === 0 なら”請求済”に一括更新する。個別請求書ごとの消込(充当順)は行わず、案件単位の合算で判定する。
  • 承認判定(settlementApproved): approvalNo(旧フィールド)または settlementDetail.approvalNo に値がある、または loanStatus が”承認済”/“実行済”、または cardStatus が”承認済”/“決済済”のいずれかを満たせばtrue。三社間・カードの approval STEPのゲート判定に使う。
  • 卸価格の解決フォールバック(currentWholesalePriceForDealer): (1) dealer 指定時はその販売店専用価格(dealer一致)を先に検索し、ヒットすればそれを採用。(2) ヒットしなければ標準価格(dealer未設定のレコード群)にフォールバック。(3) いずれの母集団でも startDate <= 基準日 <= endDate(or 無期限) で絞り込み、複数該当時は反映日が新しい順→同日なら卸価格が安い順でソートし先頭を採用。該当なしは undefined(呼び出し側でのエラー処理は未確認)。
  • 発注・請求/入金マイルストーンのリードタイム(wholesaleMilestones、タイムライン表示用): createdAt を起点に決済区分別の発注リードタイム(前金5日/掛売1日/三社間2日/カード2日/その他3日)を加算して発注予定日を算出。前金のみ「入金(createdAt+2日)→発注」の2件で早期リターンし、納品後のマイルストーンは持たない。それ以外は発注→納品10日、納品→請求/入金3日を固定値として加算して2件目を置き、三社間・カードは請求STEPを持たないためこの2件目が「請求」でなく「入金」ラベルになる(掛売・その他は「請求」)。
  • 番号採番: 発注番号 PO-0001〜・請求番号 INV-0001〜は全案件横断の最大値+1(nextSeq)。案件ID W-0001〜・タスクID WT-0001〜も同様の連番方式。
  • /wholesale(WholesalePage)は「業務ボード(次にやること軸)」「決済フロー(かんばん)」「カレンダー(タイムライン)」「サマリー」「タスク」の5つの見せ方をタブ切替で提供し、販売店名・案件ID・決済区分のフリーワード検索(デバウンス付き)を業務ボード/決済フロー/カレンダーの3タブに適用する。サマリー・タスクタブには検索欄を表示しない。
  • 案件詳細(WholesaleDetail)はSTEPごとの操作ボタン(決済条件確定・発注登録・納品確認・請求登録・入金登録)を提供し、currentStep(未完了の先頭STEP)に応じて次に押すべき操作を強調する。各操作は即時に状態更新しトースト通知を表示する(確認ダイアログや取消操作はない=一度登録した発注/請求/入金の削除・編集UIは未確認)。
  • 発注登録時の金額は既定で wholesaleTotal(case)(卸金額合計=販売店への請求額)が自動セットされ、仕入先への実際の支払額(原価)を別途入力する導線は明示的なフォームフィールドとしては提供されていない(上記「原価」算出式のエッジケース参照)。
  • DealerIntakeForm(/dealer-order)は販売店が自身で入力するフルページフォームで、wholesaleItemOptions・currentWholesalePriceForDealer を通じて卸金額マスタから品目と単価を引き当てる。送信すると newWholesaleId で受付番号を発行し WholesaleCase として即時起票する(承認・審査ステップはなく、送信=案件化)。

卸金額(WholesalePrice: 仕入価格・卸価格・反映日)の登録・編集、WholesaleTask(未対応/対応中/完了、担当者・期限・優先度)の作成・アサインは、事務(BO)・発注・申請担当・管理者に限定する設計上の想定である。ただし現行実装(lib/wholesale.ts・lib/wholesalePricing.ts・lib/wholesaleTask.ts)は発注フォーム・案件操作に認証・権限ガードを持たず、社内(VE)担当による代行入力を前提に動作する。3ファイルとも lib/permissions.ts の Role・可否関数を一切importしておらず、ロールに応じた表示・入力の出し分けは実装されていない。これらの制限は未実装であり、実際のガード実装は今後の対応とする。営業への閲覧範囲開放は確認事項とする。DealerIntakeForm(販売店発注フォーム)は外部の販売店が入力し案件化される唯一の外部起点であり、代理店ロールとは別枠のアクセス経路とする。アクセス方式(認証なしフォームか専用トークンURLか)は確認事項とする。

別軸案件(元請=販売店の deals と分離、将来分社化を視野)のため、卸モジュール全体を社内でも権限で閉じられる構造にする。現状のnav絞り込みはこの方針と整合する。

  • cancelledフラグの未接続: WholesaleCase.cancelled はコメント上「集計対象外」と意図されているが、wholesaleTotal系の算出関数・wholesaleFlow・業務ボード(BoardView)・サマリ(WholesaleDashboard)のいずれもこのフラグでフィルタしていない。キャンセル案件を立てても通常案件と同様に一覧・集計に表示され続ける。
  • 原価の既定値が卸金額と同額: 発注登録の既定金額が卸金額合計のため、発注登録直後は wholesaleGrossProfit = 0。実際の仕入原価を反映するには画面上で金額を手動修正する運用が前提になるが、その修正を促すUI上の注記はない。
  • 入金超過: wholesaleBalance は0未満にならないよう max(0, …) でフロアされるため、請求額を超える入金があっても残高はマイナス表示にならず、過入金アラートも出ない。
  • 価格未ヒット時のundefined: currentWholesalePriceForDealerは該当価格がない場合undefinedを返す。呼び出し側(DealerIntakeForm・WholesalePages)でのフォールバック表示(0円扱いか入力ブロックか)は未確認。
  • /master-wholesale-price・/dealer-orderは管理者以外nav非表示: NAV_ALLOWのどのロール配列にもこの2パスは含まれない(/wholesaleのみ列挙。管理者は"all"のため両パスともtrue)。サイドバーには「価格・見積>卸金額」「商社/卸>発注フォーム(受付)」として登録済みで、管理者にのみ表示される。ただしcanSeeNavはSidebar/MobileTabBar/Dashboardの表示制御にのみ使われ、TanStack RouterのbeforeLoad等でのルートガードは実装されていないため、URLを直接知っていれば(施工店・代理店など/wholesale非表示ロールを含め)どのロールでもアクセスできる。「権限外URLへの直アクセス時の挙動」の確認事項と直結する具体例である。

画面・ナビゲーションと権限の対応

Section titled “画面・ナビゲーションと権限の対応”

NAV_ALLOW を第1弾3モジュールに絞って整理する。

画面管理者営業事務(BO)発注・申請担当代理店代理店(メンバー)施工店
/events(催事シフト)○○○×○○×
/events/stores, /events/companies(店舗・代理店マスタ)○○○××××
/master-products 等(アイテムマスタ本体)○×○××××
/master-history(変更履歴)○○○××××
/wholesale(商社・卸)○×○××××

マスタ画面はMasterScreen定型(テーブル+ドロワー+変更履歴)に統一する。編集権のないロールにはドロワーを閲覧専用で表示する。金額カラム(仕入・卸価格)は該当列自体を消す方式(canSeeCost)を採用し、マスキング表示(***)は採らない。権限外URLへの直アクセス時の挙動(404型で存在を隠すか403型で権限不足を示すか)は確認事項とする。

招待制ログインとアカウントライフサイクル

Section titled “招待制ログインとアカウントライフサイクル”

第1弾では認証基盤・招待フローは未着手であり、サイドバーの「表示ロール(デモ)」スイッチで7ロールを手動切替してデモする段階にある。

本実装では自己登録なしの完全招待制とする。管理者が社内メンバーと代理店を招待する。招待フローはメール招待→初回パスワード設定→ロール自動付与(招待時に指定)の3ステップとする。代理店の契約終了時は一括無効化し、過去の開始/終了報告データは保持する。

代理店に自社メンバー(代理店(メンバー))の招待権を付与するか、本部管理者のみが招待する一本化にするかは確認事項とする。認証基盤の選定、パスワードリセット・多要素認証・セッション有効期限などのセキュリティ要件も確認事項とする。

  • 発注・申請担当に /wholesale を開放するか、事務(BO)に一本化するか。
  • 営業・発注・申請担当へアイテムマスタ本体を開放する範囲(参照のみ/編集込み/現状維持)。
  • 卸金額(仕入原価)の社内閲覧範囲を事務(BO)・管理者限定のままとするか、営業にも開放するか。
  • 催事スタッフを将来個人アカウント化するか、責任者名+人数のまま維持するか。
  • 代理店に自社メンバーの招待権を付与するか、本部管理者のみに一本化するか。
  • 担当外STEPの操作を通知にとどめるか、ハードブロックにするか。
  • 内部ロールによる開始/終了報告の代行入力を実装するか。
  • 1ユーザーが複数ロールを兼務できるか(例: 事務(BO)と発注・申請担当)。
  • 店舗・代理店マスタの編集権を営業にも開放するか。
  • DealerIntakeFormへの外部起点アクセス方式(認証なしフォーム/専用トークンURL)。
  • 権限外URLへの直アクセス時の挙動(404型/403型)。/master-wholesale-price・/dealer-orderは管理者以外nav非表示のみでルートガードが無く、この論点が最も先鋭化する対象である。
  • 卸案件のキャンセル(WholesaleCase.cancelled)を一覧・集計から除外する仕様にするか、フラグ自体を廃止するか。
  • 発注登録時の既定金額(卸金額=販売価格の仮置き)を仕入原価の手入力必須に変えるか、仕入先マスタの参照価格から自動算出する仕組みを別途設けるか。

論点の一次情報・詳細な背景整理は 未確定事項一覧 側に展開する。本ページの一覧はそのうち催事・アイテム・商社(卸)領域の抜粋である。