• #tax-assistant
  • #設計
  • #Codex
  • #GPT-5.3
  • #レビュー
  • #Claude Code
開発tax-assistantメモ

tax-assistant: 新規クライアント登録フロー設計

クライアント切替機能が動くようになった直後、次に必要なのは「UIからの新規クライアント登録」だった。Planエージェントで設計を組み、Codex(GPT-5.3)に4ラウンドのレビューを回した。

背景

これまでクライアントの追加はCLI経由でしかできなかった。add_client() でクライアント情報をUPSERTし、init_client_db() で専用DBを初期化する流れ。開発中はこれで十分だったが、切替UIができた以上、登録もUIからできないと片手落ちになる。

もう一つの問題として、既存の add_client() がUPSERTで実装されていた点がある。新規登録の文脈でUPSERTを使うと、IDの打ち間違いで既存クライアントのデータを上書きしてしまうリスクがある。ここは設計段階で切り分けておく必要があった。

現状分析

既存のコードを確認したところ、以下の状態だった。

機能実装状況備考
add_client()CLI用、UPSERTで実装新規登録には不向き
init_client_db()クライアント専用DB初期化そのまま使える
POST /api/clients未実装API層が丸ごと不在
フロントエンドUI未実装ClientSelectorにモーダルを追加する想定

6タスク構成の設計

Planエージェントで以下の6タスクに分割した。

Task 0: INSERT-only関数の新設

src/clients.pycreate_client() を新設する。既存の add_client()(UPSERT)とは別に、INSERT-onlyの関数を用意する。

UPSERTを避ける理由は明確で、新規登録でINSERTが失敗するなら「そのIDは既に存在する」ことを意味する。UPSERTだとそのエラーが握りつぶされて、既存データが黙って上書きされる。

Task 1: POST /api/clients エンドポイント

フロントエンドからクライアント情報を受け取り、create_client() + init_client_db() を呼ぶ。バリデーションとエラーハンドリングをここに集約する。

Task 2-3: フロントエンドAPI関数 + Piniaストアアクション

API呼び出しの関数と、ストア側のアクションをそれぞれ実装する。クライアント一覧のリロードも含む。

Task 4: ClientSelectorにモーダルUI追加

既存のClientSelectorコンポーネントに「新規追加」ボタンとモーダルダイアログを追加する。入力項目はクライアント名、事業形態、決算月あたり。

Task 5: テスト

INSERT-only関数の正常系・異常系(重複ID、バリデーションエラー)と、APIエンドポイントの統合テスト。

Codexレビュー 4ラウンド

計画をmemoに保存してから、Codexに4回レビューを依頼した。ラウンドを重ねるごとに指摘の粒度が細かくなっていった。

1回目: 7件の指摘

最初のラウンドで出た主な指摘。

  • short_name仕様の統一: クライアントの短縮名の扱いがCLIとAPIで揺れていた。short_name を正式フィールドとして定義し、全箇所で統一
  • FK CASCADE: 子テーブル(仕訳、勘定科目など)にクライアントIDの外部キー制約がなかった。将来のクライアント削除に備えて ON DELETE CASCADE を仕込む
  • ID競合対策: client_NNN の自動採番で、欠番があった場合の挙動を明確化

2回目: 5件の指摘

  • fiscal_year_end型の統一: 決算月の型が箇所によって文字列だったり整数だったり。Optional[int](1-12、NULL=12月デフォルト)に統一
  • 補償処理の原子化: create_client() が成功して init_client_db() が失敗した場合のロールバック処理が不十分だった

3回目: 4件の指摘

  • INSERT-only関数でのUPSERT回避の徹底: add_client() を内部で呼ばないことを明示的にコメントで残す
  • fiscal_year_end境界値: 0や13が入力された場合のバリデーション。1-12の範囲チェックをAPI層で実施

4回目: 3件の指摘

  • 単一トランザクション: create_client()init_client_db() を単一トランザクション内で実行し、途中失敗時に不整合が残らないようにする
  • delete_client改善: 将来の削除機能に備えて、FK CASCADEが正しく機能する前提でのクリーンアップ手順を文書化

合計で20件以上の指摘が出た。1回のレビューでは拾いきれなかった論点が、ラウンドを重ねるごとに浮き上がってきた。

設計決定のまとめ

4ラウンドのレビューを経て固まった主な設計判断。

項目決定内容
ID形式client_NNN 自動採番を維持
新規登録INSERT-only(UPSERTは使わない)
FK制約子テーブルに ON DELETE CASCADE を追加
トランザクションcreate_client + init_client_db を原子的に実行
fiscal_year_endOptional[int](1-12月、NULL時は12月デフォルト)

振り返り

  • UPSERTは便利だが、新規登録の文脈では既存データの上書きリスクがある。INSERT-onlyに分離する判断は正しかった
  • Codexの4ラウンドレビューは、1回目で大きな論点が出て、2回目以降で詳細が詰まる構造になっていた。レビューは複数回回すことで質が上がる
  • FK CASCADEを登録時点で仕込んでおくのは、将来の削除実装をクリーンにするための先行投資。後から追加するとマイグレーションが面倒になる
  • 前回のクライアント切替設計(1ラウンド/7件)と比べて、4ラウンド回すとレビューの深さが段違いだった。設計の複雑さに応じてラウンド数を調整するのがよさそう