2026-06-07 / スキーマ追加・一覧/詳細/編集画面・計算ロジック・再計算の設計合意。
型チェック(tsc --noEmit)通過。新規ファイルに lint 警告なし。マイグレーションは未適用(後述)。
| 対象 | ファイル | 状態 |
|---|---|---|
| Prisma スキーマ | prisma/schema/billing-record.prisma(+ organization / contract-plan にリレーション) | ✅ |
| マイグレーション SQL | prisma/migrations/20260607053512_billing_records/ | ✅ 作成(未適用) |
| 型エクスポート | packages/database/index.ts | ✅ |
| 計算共有関数 | lib/billing-record/calculate.ts(前月集計+プランスナップショット) | ✅ |
| 実効値ヘルパ | lib/billing-record/effective.ts / previous-month.ts | ✅ |
| サーバーアクション | actions/billing-record/{list,get,list-…-year-month} + 詳細/編集の recalculate / update | ✅ |
| 一覧画面 | app/billing-records/page.tsx + 複数選択検索フォーム | ✅ |
| 詳細画面 | [billingRecordId]/page.tsx + view + 再計算ボタン(確認ダイアログ) | ✅ |
| 編集画面 | [billingRecordId]/edit/ + 手動10フィールド | ✅ |
| ナビ | app/_components/header-navigation/index.tsx に「課金履歴」追加 | ✅ |
マイグレーション未適用について
開発DBに既存のドリフト(ai_instructions / system_configs の unique index が DB 側で欠落)があり、
prisma migrate dev がリセットを要求するため自動適用していない。
billing_records の CREATE TABLE SQL は prisma migrate diff の出力と一致確認済み。
ドリフト解消後に適用すること。
(Y年 M月) レコード = M月分の基本月額 + M-1月の生成実績に対する超過課金。計算対象画像生成数は前月(M-1)の生成数。initialPlanRatio は今回も未使用(課題のまま据え置き)。contractPlanId から自動値を再取得し、手動フィールドはすべて破棄(NULL リセット)。実行前に確認ダイアログを表示する方針。BillingRecord、テーブル billing_records、ルート /billing-records、用語「課金履歴」。year:Int / month:Int(1-12)、deletedAt ソフトデリート。DB のユニーク制約は設けない(論理削除済み行とのタプル衝突を避けるため。組織×年月の一意性はアプリ側で担保)。amount)。 一覧表示はこの永続値を使用。生成・再計算・編集の各書き込み経路で実効値ベースに再計算して保つ(共有関数 calculateBillingAmount)。contractPlanName。note)を追加。 手動入力(編集画面)で更新する場合は必須。POST /api/billing-records/generate。対象月の未生成組織にレコードを一括作成。更新(動作確認後)
一括生成 API・再計算とも catch-all 集計・Asia/Tokyo 境界・DateTime へ統一(経路間の差異は解消、calculate.ts は削除)。
区分1 は「区分2/3 以外すべて(renovation 含む)」を正と確定。
基本月額は当月(M)分・超過分は前月(M-1)分の表示に確定。
日時は LocalDateTime を非推奨化し DateTime へ統一(規約: 日時・タイムゾーン)。
残りは集計ロジックの重複解消のみ → 課題。
例:3月のレコード。基本月額は3月分、超過分は2月の生成実績に対して課金される。
| 基本月額(3月分) | 実効(月額) | ¥50,000 |
| 超過 区分1(2月分) 超過 20 × ¥200 |
max(0, 120 − 100) × 200 | ¥4,000 |
| 超過 区分2 画像キレイ(2月分) 超過 8 × ¥500 |
max(0, 58 − 50) × 500 | ¥4,000 |
| 超過 区分3 3D間取り(2月分) 超過 0(上限内) |
max(0, 12 − 20) × 800 | ¥0 |
※ 再計算は手動入力をすべて破棄し、現在のプランと実測値で再計算します。
/// 課金履歴
///
/// @namespace 課金
model BillingRecord {
id String @id @default(cuid(2)) @db.VarChar(30)
organizationId String @map("organization_id") @db.VarChar(30)
contractPlanId String? @map("contract_plan_id") @db.VarChar(30)
planName String? @map("plan_name")
year Int @map("year")
month Int @map("month")
// --- 自動計算値(プラン由来のスナップショット) ---
monthlyCharge Int @map("monthly_charge")
monthlyGenerationQuota1 Int @map("monthly_generation_quota_1")
monthlyGenerationQuota2 Int @map("monthly_generation_quota_2")
monthlyGenerationQuota3 Int @map("monthly_generation_quota_3")
extraGenerationCharge1 Int @map("extra_generation_charge_1")
extraGenerationCharge2 Int @map("extra_generation_charge_2")
extraGenerationCharge3 Int @map("extra_generation_charge_3")
// --- 自動計算値(前月実測の計算対象生成数) ---
targetGenerationCount1 Int @map("target_generation_count_1")
targetGenerationCount2 Int @map("target_generation_count_2")
targetGenerationCount3 Int @map("target_generation_count_3")
// --- 手動上書き(NULL のとき上の自動値を使用) ---
manualMonthlyCharge Int? @map("manual_monthly_charge")
manualMonthlyGenerationQuota1 Int? @map("manual_monthly_generation_quota_1")
manualMonthlyGenerationQuota2 Int? @map("manual_monthly_generation_quota_2")
manualMonthlyGenerationQuota3 Int? @map("manual_monthly_generation_quota_3")
manualExtraGenerationCharge1 Int? @map("manual_extra_generation_charge_1")
manualExtraGenerationCharge2 Int? @map("manual_extra_generation_charge_2")
manualExtraGenerationCharge3 Int? @map("manual_extra_generation_charge_3")
manualTargetGenerationCount1 Int? @map("manual_target_generation_count_1")
manualTargetGenerationCount2 Int? @map("manual_target_generation_count_2")
manualTargetGenerationCount3 Int? @map("manual_target_generation_count_3")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
organization Organization @relation(fields: [organizationId], references: [id])
contractPlan ContractPlan? @relation(fields: [contractPlanId], references: [id])
// 論理削除運用のため @@unique は設けない(削除済み行とのタプル衝突を避ける)。
@@map("billing_records")
}
※ Organization / ContractPlan 側にもリレーション(billingRecords BillingRecord[])を追加。プラン名・型・命名は実装時に最終確認。
DateTime 使用)。
※ 当初の共有関数 calculate.ts は削除し、現状は一括生成 API と再計算アクションにロジックを実装(重複は 課題)。list-billing-record(年月・組織で絞り込み、年月降順)/get-billing-record/recalculate-billing-record(手動破棄)/update-billing-record(手動上書き)。/billing-records(一覧・複数選択ドロップダウン)//billing-records/[id](詳細)//billing-records/[id]/edit(編集)。権限はシステム管理者のみ。手動 ?? 自動 と課金合計算出を一覧・詳細で共用。