自動仕訳ルールのエクスポート・インポート・同期機能を実装した記録
Chrome DevToolsで会計ソフトAの内部APIを3本掘り当て、自動仕訳ルールのエクスポート・インポート・同期機能を6ファイルにわたって組み上げた。UIを2回作り直し、CTIクロスコンタミネーションというバグを踏み抜き、Codexレビューを3回回して着地させるまでの一日。
Chrome DevToolsでの内部API調査
前日の調査で公開APIに自動仕訳ルールのエンドポイントが存在しないことは確認済みだった。今日はChrome DevToolsのNetworkタブに張り付いて、実際に画面を操作しながら内部APIを捕まえにいった。
掘り当てた3つのエンドポイント
- journal_rules: ルール一覧の取得。ページネーション付きでルールID・条件・仕訳内容がJSONで返る
- attribute_options: 勘定科目・補助科目・税区分などのマスタデータ。ルールが参照するIDを名前に変換するために必要だった
- Search API:
POST /api/v1/office_journal_rules/searchで条件付き検索。後の同期機能でルールのマッチングに使うことになる
Search APIへのPOSTで最初に422エラーが返ってきた。リクエストヘッダを見比べて、X-CSRF-Tokenヘッダが必須だと突き止めた。CSRFトークンはHTMLのmetaタグから取得する方式に落ち着いた。
エクスポート機能の実装
6ファイルの変更
エクスポート機能は以下の6ファイルに手を入れた。
| ファイル | 役割 |
|---|---|
lib.js | API呼び出し・データ変換のユーティリティ |
bridge.js | Chrome拡張のバックグラウンドとコンテンツスクリプトの通信橋渡し |
export.js | スプレッドシートへの書き出しロジック |
import.js | スプレッドシートからの読み込みロジック |
content.js | DOM操作・UI制御 |
manifest.json | パーミッション追加 |
UIの試行錯誤:独立タブからチェックボックスへ
最初は「ルール管理」という独立タブを設けて、エクスポート・インポートのボタンを並べた。動くものはできたが、実際に触ってみると操作の流れが途切れる。仕訳データをエクスポートするついでにルールも一緒に出したい場面がほとんどだった。
ユーザーフィードバックを受けて方針を変えた。既存のエクスポート画面に「自動仕訳ルールも含める」チェックボックスを1つ追加する形に統合した。コード量は減り、操作ステップも1つ減った。
スプレッドシートの書式設計
エクスポート先のスプレッドシートは、視認性を上げるために3つの工夫を入れた。
- 3エリアの縦ボーダー区切り: ルール条件 / 仕訳設定 / 借方貸方の3ブロックを縦ボーダーで区切り、どの列がどのブロックに属するかを一目で判別できるようにした
- Material Designカラー: ヘッダー行にMaterial Designのカラーパレットを適用。ルール条件は青系、仕訳は緑系、貸借は紫系
- 金融機関ごとの色分けと濃淡: 銀行・クレジットカード・電子マネーなど金融機関の種別で背景色を変え、さらに同じ金融機関内でも口座ごとに濃淡を付けた。100行を超えるルール一覧でも、目的の口座のルールがどこにあるか色で追える
シート名は ルール_エクスポート とした。後述のインポート用シート ルール_インポート とヘッダー書式を共通化し、両シート間でコピー&ペーストしても列がずれない設計にした。
インポート機能の実装
インポートシートの自動作成
エクスポートを実行すると、同じスプレッドシートに ルール_インポート シートが自動で作成される。ヘッダー行はエクスポートシートと同一書式で、データ行は空。ユーザーはエクスポートシートから必要な行をコピーし、編集してからインポートを実行する。
インポートシートのURLは設定画面に自動保存される。次回以降はワンクリックでシートに飛べる。
事業者ごとの個別インポートボタン
当初は全事業者を一括でインポートするボタンを置いていたが、事業者Aのルールだけ更新したい場面のほうが多かった。一括ボタンを廃止し、各事業者の横に個別のインポートボタンを配置した。
ローカル保存ログ
インポート実行時、CSVデータをローカルにも保存するようにした。ファイル名は {SSタイトル}_rules_import_{YYYYMMDD_HHmmss}.csv の形式。スプレッドシートのデータを誤って消しても、ローカルにCSVが残っているので復元できる。
エラー判定バグの修正
インポート結果の成功/失敗判定で、会計ソフトAの画面構造に起因するバグを踏んだ。
会計ソフトAのインポート結果画面には alert-success、alert-warning、alert-danger の3つのdivが常に存在していて、hiddenクラスの付け外しで表示を切り替える仕様だった。最初の実装では「alertを含むdivを正規表現で探し、可視のものを判定する」というロジックにしていたが、3つ全てにマッチしてしまい、常に最初の alert-success を拾って成功と判定していた。
修正後は alert-success、alert-warning、alert-danger それぞれを個別にセレクタで取得し、hiddenクラスが付いていないものだけを結果として採用するようにした。
設定画面の改善
設定画面に保存されたスプレッドシートURLが、プレーンテキストのまま表示されていた。URLをコピーしてブラウザのアドレスバーに貼り付ける手間が毎回発生していた。
URLをアンカータグに変換し、クリックで直接スプレッドシートに遷移できるようにした。target="_blank" で新しいタブに開く。
ルール同期機能
設計:CSV + Search APIハイブリッド
エクスポート・インポートは手動操作だが、「会計ソフトA上のルールを最新のスプレッドシートと一致させたい」という要望があった。同期機能の設計は以下の方針で組んだ。
- スプレッドシートからルール一覧をCSVとして読み込む
- 会計ソフトAのSearch APIで既存ルールを取得する
- ルールIDでマッチングして、スプレッドシートにあって会計ソフトAにないルールは追加、会計ソフトAにあってスプレッドシートにないルールは削除候補としてリストアップ
- 差分削除は確認ダイアログを挟む(誤削除防止)
フォールバックルールの仕様
調査中に「他のルールにマッチしない場合」に適用されるフォールバックルールの存在を確認した。このルールにはルールIDが振られず、Search APIのレスポンスにも含まれない。同期処理ではフォールバックルールを除外するロジックを入れ、仕様をドキュメントとして残した。
CTIクロスコンタミネーションバグ
同期機能のテスト中に、事業者Aのルールが事業者Bのデータとして保存される現象が起きた。原因を追うと extractCti() 関数にたどり着いた。
CTI(Company/Tenant Identifier)はURLのパスから抽出する。しかし、同期処理が非同期で走る間にユーザーが別の事業者のページに遷移すると、extractCti() が現在のURLからCTIを取得してしまう。結果として、事業者Aのデータが事業者BのCTIに紐付いて保存される——これがクロスコンタミネーションの正体だった。
修正は、同期処理の開始時にCTIをキャプチャして変数に保持し、以降の処理ではキャプチャ済みの値を使う方式に変えた。URLから毎回取得するのをやめたことで、ページ遷移の影響を受けなくなった。
Codexレビュー3回
実装の区切りごとにCodexレビューを回した。3回のレビューで受けた致命的指摘と対応は以下の通り。
| 回 | 指摘内容 | 対応 |
|---|---|---|
| 1回目 | CSRFトークンの取得タイミングがリクエストと離れすぎており、トークン失効のリスクがある | トークン取得をリクエスト直前に移動 |
| 2回目 | CTI取得が非同期処理中にURLから再取得される設計になっている | 上述のCTIキャプチャ方式に修正 |
| 3回目 | 差分削除で確認なしに削除が走る可能性がある | 確認ダイアログの追加と、削除対象のプレビュー表示を実装 |
2回目の指摘でCTIバグに気づけたのは大きかった。手動テストでは再現条件が限られるため、レビューなしでは本番で踏むまで気づかなかった可能性が高い。
振り返り
朝のAPI調査から始めて、エクスポート → インポート → 同期と段階的に機能を積み上げた。UIを2回作り直す回り道はあったが、結果的にチェックボックス1つに統合した現在のUIのほうが操作が自然に流れる。
CTIクロスコンタミネーションは、非同期処理で「今のURL」を都度参照する設計の危うさを身をもって体験した。状態は開始時にキャプチャして閉じ込める——関数型の原則がそのまま当てはまるバグだった。