会計システムの技術書を読み解き、動くMVPアプリまで実装した一日
会計システムの技術書を読み解き、動くMVPアプリまで実装した一日
読んで終わりにするか、動かして終わりにするか。積んでいたエンジニア向けの会計システム解説書を書籍データベースに入れたところまでは、いつもの巡回だった。違ったのは、その先だった。「これを元にちょっとコンテンツ作ってほしいんですよ。実際に動くやつ。」——そう言って、別のリポジトリを切ることになった。
Kindleからの取り込みと、自分の目で拾ったノイズ
蔵書ライブラリを開いてもらい、目当ての書籍を指定した。Kindle Cloud Readerではなく、すでにKindle for PCアプリ側で開いていたので、そのままアプリ側の巡回撮影ルートに切り替えた。396ページの本を先頭から最終ページまで撮り続け、199枚のスクリーンショットが揃った。OCR、Turso DBへの投入、目次整形と続けて、いったんは取り込み完了のはずだった。
念のため、取り込んだ本を書籍ビューアで自分の目で開いてもらった。表紙ページの末尾に「Location 1 of 3737 . 0%」という余計な文字列が残っている。Kindleの位置インジケータがOCRに混ざり込んだものだった。全76チャンク中74チャンクに同じ種類のノイズ(送りアイコンとページ表示のペア)が散っていることが分かり、除去スクリプトを組んで一括で消した。FTSインデックスは更新トリガーで自動同期される仕組みだったので、明示的な再構築は不要だと分かり、検索結果にノイズ文字列が出てこないことまで確認して、ようやく取り込みが完了した。
「実際に動くやつ」の計画と、1時間48分止まっていたCodex
書籍はすでにTursoに構造化済みだったので、実装イメージをつかむために該当章のチャンクを取り出し、要約をサブエージェントに任せた。要約ができたところで、実装計画をCodex(gpt-5.6-sol)に依頼した。
順調に進んでいるに違いない、と思っていた。ところが「これ本当に動いてる?」と気になって出力ファイルの更新時刻を確認させると、最終更新から1時間48分、何も変化していなかった。プロセスを強制終了して再実行しても症状は同じ。原因は、作業ディレクトリの親フォルダがgitリポジトリではなく、Codexの安全チェックに「信頼されていないディレクトリ」と判定されて止まっていたことだった。--skip-git-repo-check を付けて再実行すると、今度は本当に動き出し、質の高い計画書ができあがった。
Phase1 — 基盤とマスタ画面
新規リポジトリにNuxt3・TypeScript・Nuxt UI・Drizzle ORM・libSQLの構成を組み、21テーブルのマイグレーションを適用した。確定済み(POSTED)の仕訳は更新も削除もできないよう、DBトリガーで縛った。
-- 確定済みの仕訳行は、金額を1円たりとも触らせない
CREATE TRIGGER prevent_posted_update
BEFORE UPDATE ON journal_lines
WHEN OLD.status = 'POSTED'
BEGIN
SELECT RAISE(ABORT, 'Cannot modify posted journal entry');
END;
検証用に投入したPOSTEDレコードが本当に消せない状態でDBに残ってしまい、それ自体がトリガーの動作証拠になった。パスワードはNode標準のscryptでハッシュ化し、シードスクリプトで期首残高の貸借一致まで確認。勘定科目・部門・期首残高のマスタ画面を実装し、ブラウザで新規登録モーダルから「1008 仮払金」を追加できることまで確かめた。
途中、composablesディレクトリを新規作成した直後にページルーティングが認識されなくなる不具合と、期首残高画面がSSR時のレースコンディションで500エラーを吐く不具合に当たったが、どちらもdev server再起動とガード条件の追加で片づいた。型チェック・Vitest・ブラウザ確認がすべて緑になったことを確かめ、Phase1完了の報告を受けた。
「会計仕分けはどこで登録するんですか?」
報告を受けた直後、核心を突く一言をぶつけてみた。「会計仕分けはどこで登録するんですか?」
すぐには答えが返ってこなかった。今回できていたのはマスタ管理までで、仕訳を登録する画面はまだ存在していなかったのだ。「会計仕分けを見れる場所がないと思うんですけど」と重ねて指摘すると、その日はいったんセッションを切り替えることになった。
続き — ログインと仕訳のワークフロー
翌セッションでは、まず伝票番号(voucherNo)の採番タイミングを見直した。DRAFT作成時点では番号を振らず、承認されてPOSTEDになった瞬間に採番する設計に変更した。破棄されるかもしれない下書きに番号を先に振ってしまうと、欠番や採番順のズレが起きるためだ。
続けてログイン機能一式(セッション管理・認証API・ログイン画面)を実装し、レイアウトに仕訳入力・承認へのナビゲーションリンクを追加した。前のセッションで「見れる場所がない」と言われていた導線が、ここでようやくできた。
一気通貫の動作確認をした。起票担当でログインし、借方に現金、貸方に売上高を選んで部門と金額を入力する。ここでNuxt UIのUSelectが空文字列の選択肢を許容しないバグに当たり、「指定なし」の値を空文字列以外に変更して直した。同じパターンが仕訳帳のステータスフィルタにも残っていたので、そちらも直した。保存・提出・承認担当への切り替え・承認、と流すと伝票番号1が採番されPOSTEDになり、仕訳帳にも反映された。起票者本人が自分の仕訳を承認しようとすると権限エラーになることも確かめた。
E2Eテストで踏んだ小さな穴
Playwright E2Eテストを安定させる過程では、細かい踏み外しが続いた。Vueのhydrationが終わる前にログインボタンを押してネイティブのフォーム送信が走ってしまう競合、USelectのポップオーバーが開くタイミングのずれ、data-testid がラッパー要素に付いて実際の<input>に伝わっていない問題。最後に見つかったのが一番厄介で、SQLiteのWALファイル書き込みをViteのファイル監視が検知し、開発中のページを勝手にHMRリロードしていた。テスト用DBの配置をOSの一時ディレクトリに移し、data/ディレクトリを監視対象から外して、ようやく2回連続で安定して通るところまで持っていった。Windows特有のglobマッチング不具合も見つかり、正規表現関数形式に書き換えた。
学習ゲートと一日の締め
型チェック・Vitest・E2Eすべてを最終確認してからコミットした。学習ゲートのクイズでは、伝票番号を承認時まで振らない理由を問われて1問誤答した。伝票番号を主キーだと勘違いしていたが、実際の主キーはUUIDで別に管理されており、番号を遅らせるのは欠番と採番順ズレを防ぐためだと解説を読み直して納得し、受領証を発行してもらった。計画書の進捗ログにPhase2完了を書き足したところで、「今日はもうセッション終わりにしたいんで、また明日開始できるようにドキュメントちゃんと残しておいてくださいね」と言われた。動いていたdev serverを止め、git statusがクリーンなことを確かめてから、次セッション用の引き継ぎプロンプトを用意してその日を締めた。
積み残し
- 次セッションでPhase3以降の実装を引き継ぎプロンプトから再開する
- 実際の日々の仕訳入力にどこまで使えるか、明日以降試す
学び
- Codexが止まって見えたら、まず疑うのは処理の重さではなく「作業ディレクトリが信頼済みか」
- 確定済みデータの不可変性は、テストデータが消せずに残ってしまうこと自体が一番の証拠になる
- 「完了しました」の直後に核心を突く質問が来ることがある。マスタ画面が動くことと、仕訳を登録・承認できることは別物だった
伝票番号1件だけが刻まれた元帳を、明日どこまで育てられるかはまだ分からない。