会計サービス Chrome拡張エクスポート機能強化
朝、Codexのレビューコメントを読んで switchYearByCti のcatch漏れを直すところから始まった。テスト53件が緑になってPR #1をマージした後、そのまま勢いで5つの機能追加とバグ修正を積み重ねた。途中、スプレッドシート上で金額が文字列扱いになったり、重複チェックが同一データを「別物」と判定したりと、型の罠に何度もはまった一日の記録。
PR #1マージ: multi-business-export
Codexレビューで指摘された switchYearByCti の問題は、catch節が年度切替失敗を握りつぶしていた点。エラー時にそのまま次の処理に進んでしまい、違う年度のデータをエクスポートする危険があった。
修正後にテスト53件を流して全パス。PR #1をマージして、複数事業者の一括エクスポート機能が本線に入った。
スプレッドシートURL分離
もともと exportUrl という1つのURLに全データを書き込んでいたが、仕訳帳と連携明細では構造が違うため、シートを分けたくなった。
データ構造を変更して、exportUrl(明細用)と journalExportUrl(仕訳帳専用)の2つに分離。lib.js のエクスポート関数に urlKey パラメータを追加し、どちらのURLを参照するかを呼び出し元が指定する形にした。
設定UIにも入力欄を追加。hidden inputで既存値を保持し、auto-saveロジックで変更時にchrome.storageへ即時保存する。地味だが、ここの配線を間違えると片方のURLが消えるので、DevToolsでstorage変更イベントを監視しながら動作確認した。
金額の数値化とカンマ区切り書式
String()をやめる
借方・貸方金額をスプレッドシートに書き込む際、String() でラップしていたためSheets上で文字列になっていた。SUM関数が効かない。
Sheets APIの RAW モードは、送信するJSONの値の型をそのまま反映する。数値型で送れば数値、文字列で送れば文字列。修正はシンプルで、String() を外してJavaScriptの数値型のまま渡すだけだった。
カンマ区切り書式の設定
数値にしたら今度は「1234567」のように桁区切りなしで表示される。読みにくい。
background.js に formatSheetColumns アクションを追加し、Google Sheets APIの repeatCell リクエストで numberFormat を設定する形にした。フォーマットパターンは #,##0。
ここでバグを踏んだ。ヘッダー行に背景色を設定するエントリも同じリクエストバッチに含まれていて、そのエントリには fmt.columns プロパティが存在しない。fmt.columns.forEach(...) が undefined に対して呼ばれてクラッシュした。
// クラッシュする: ヘッダー背景色エントリにはcolumnsがない
fmt.columns.forEach(col => { ... });
// ガード追加
if (fmt.columns) {
fmt.columns.forEach(col => { ... });
}
エラーメッセージだけ見ると「columns is not iterable」で、最初はデータ構造のバグかと疑った。実際にはフォーマット設定のリクエスト配列に異なる構造のエントリが混在していただけ。ログに各エントリの中身を出して気づいた。
残高試算表エクスポート: HTMLスクレイピング方式
なぜJSON APIが使えないか
仕訳帳や連携明細にはJSON APIが存在するが、残高試算表(TB)には内部APIがなかった。会計サービスの残高試算表画面はサーバーサイドレンダリングされたHTMLテーブルをそのまま表示している。
方針を切り替えて、HTMLテーブルをDOMからスクレイピングする方式で実装した。
CSSクラスから階層情報を読む
会計サービスの残高試算表テーブルでは、勘定科目の階層(大分類→中分類→科目→補助科目)を label-indent-1, label-indent-2 のようなCSSクラスで表現している。このクラス名からindentレベルを数値で取得し、スプレッドシート上では padding.left でインデントを再現した(level × 12px)。
BS(貸借対照表)とPL(損益計算書)を1つのシートに統合し、合計行にはグレー背景を付けて視覚的に区別した。「〜の部合計」行はインデント0固定で出力。
会計サービスのCSS indent値が信用できない問題
ここで想定外の問題にぶつかった。会計サービスのCSSクラスが示すインデント値が、論理的な階層と一致しない。たとえば勘定科目と補助科目で同じ label-indent-2 が付いているケースがある。
CSSの値をそのまま使うとスプレッドシート上で親子関係が崩れる。対策として、CSSのindent値ではなく論理的な階層レベルを自前で決定するロジックを組んだ。「勘定科目は常にlevel 2、補助科目は常にlevel 3」のように、科目の種類に応じて固定値を割り当てる。会計サービス側のCSS表現に依存しない形にできた。
年度フィルタバグ: 全年度が展開されていた
buildEntityExportTasks 関数が全年度のCTI(事業者コンテキストID)をタスクリストに展開していた。ユーザーが2024年度だけを選択しても、2021〜2025年の全年度分のエクスポートが走る。
原因は selectedYears パラメータが渡されていなかったこと。関数のシグネチャに selectedYears を追加し、タスク生成時にフィルタするように修正。
重複チェックの型不一致: 3段階で踏んだ罠
既存データとの重複チェックで、同一レコードを「別データ」と判定するバグが出た。原因を追っていくと、型の不一致が3段階で潜んでいた。
第1段階: 数値 vs 文字列
normalizeRow で行データを正規化する際、Sheets APIから取得した既存データは文字列("241729")、新規データはJavaScript数値型(241729)で入ってくる。=== で比較すると常に false。
対策として、比較前に全フィールドを String() で統一した。
第2段階: カンマ付きFORMATTED_VALUE
String() 変換で解決したと思ったら、まだ重複が検出されない。Sheets APIの FORMATTED_VALUE モードで取得すると、数値がカンマ付き("-19,958")で返ってくる。一方、新規データは "-19958" のようにカンマなし。
FORMATTED_VALUE ではなく UNFORMATTED_VALUE で取得するか、比較時にカンマを除去するかの二択。カンマ除去の方がシンプルだったので、正規化関数内でカンマをstripする処理を追加した。
coerceNumericColumns
これらの型変換処理を coerceNumericColumns として lib.js に切り出した。数値カラムのインデックスを受け取り、該当カラムの値をカンマ除去→数値変換する。エクスポート前と重複チェック前の両方で呼ぶ。
振り返り
一日で触ったファイルは background.js、lib.js、content_script.js、設定画面のHTML、テストファイルと広範囲に渡った。朝のPRマージから夜の重複チェック修正まで、ほぼ全ての問題が「型」に起因していた。Sheets APIが返す値の型、JavaScriptの暗黙的な型変換、CSSクラス名が示す値と論理的な意味のズレ。
特に重複チェックのバグは、修正して動かすたびに新しい不一致パターンが顔を出して、3回「直った」と思って3回裏切られた。ログに typeof と実際の値を並べて出力するようにしてから、やっと全体像が見えた。型を疑うなら最初から型を可視化すべきだった。