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