Chrome拡張 自動仕訳ルールの独立タブ化とルールID照合バグ修正
自動仕訳ルールの管理UIがエクスポート/インポートの中に埋もれていて、毎回タブを切り替えてスクロールしていた。「設定/エクスポート/インポート/自動仕訳ルール/ログ」の5タブに再編し、ルールを独立タブに引き出した。その過程で、ルールID照合が29件分失敗しているバグを発見し、原因を3つ潰して全件解消した一日の記録。
タブ構成の再編: 3タブから5タブへ
もともと「設定/エクスポート/ログ」の3タブ構成で、エクスポートタブの中にインポートと自動仕訳ルールの機能がすべて詰まっていた。事業者が増えるにつれスクロール量が膨らみ、目的の操作にたどり着くまでに手が止まる。
新しいタブ構成:
- 設定 -- 認証情報とグローバル設定
- エクスポート -- データ取得(プル系操作)
- インポート -- データ書き込み(プッシュ系操作)
- 自動仕訳ルール -- ルールのドライラン・同期
- ログ -- 実行履歴
タブ位置は localStorage に保存し、拡張を開き直しても前回のタブが復元される。
UIデザインの試行錯誤
レイアウト遍歴
最初は3行の縦並びレイアウトを試した。エクスポートとインポートが同じ見た目で並ぶと、どちらのボタンを押しているのか目が迷う。
次に2カラム5行のグリッドレイアウトに変えた。情報密度は上がったが、事業者ごとのカードが横に広がりすぎてスクロールが横方向にも発生した。
最終的に落ち着いたのは、設定画面風のカードスタイル。枠線と角丸で領域を区切り、エクスポート領域とインポート領域を背景色で視覚的に分離した。
ボタンと入力欄の細かい調整
- エクスポートボタンに
↓、同期実行ボタンに↑の矢印アイコンを追加。プル/プッシュの方向が一目で伝わる - URL入力欄のフォントを
9px Consolasに変更。スプレッドシートURLの構造(/d/{id}/edit)がそのまま読める - ヘルプモーダルの記述を修正 -- 会計サービスの仕様上「ルール変更」APIは存在せず、削除+追加で対応する旨を明記
ルールID照合: 29件未特定バグの解消
自動仕訳ルールの同期処理では、CSVからエクスポートしたルール一覧とAPIから取得したルール一覧を突合し、各ルールにIDを紐付ける。このID照合で29件が「未特定」のまま残っていた。
原因1: 同一マッチキーの複数ルール
ルールの照合キーは「勘定科目+摘要+取引先」の組み合わせで構成していた。ところが同じ組み合わせで複数のルールが存在するケースがあり、Map にセットすると後勝ちで上書きされていた。
// Before: 上書きで消える
ruleIdMap.set(matchKey, rule.id);
// After: 1つのキーに複数IDを保持
if (!ruleIdMap.has(matchKey)) {
ruleIdMap.set(matchKey, []);
}
ruleIdMap.get(matchKey).push(rule.id);
マルチマップに変えて、消費済みIDを除外しながらマッチングする形にした。
原因2: 全角/半角の文字コード差異
CSVに含まれるカタカナや記号が全角で、APIレスポンスでは半角になっているケースがあった。"カ" と "カ" が別文字として扱われ、キーが一致しない。
// NFKC正規化で全角/半角を統一
const normalize = (s) => s.normalize('NFKC');
const matchKey = normalize(`${account}|${summary}|${partner}`);
NFKC正規化を照合キー生成時に適用することで、文字コードの揺れを吸収した。
原因3: CSVの商品名途中切れ
CSVの摘要欄に入る商品名が長い場合、途中で切れていた。会計サービス側で自動登録されたルールは商品名がフルで入るため、完全一致では引っかからない。
2パスアルゴリズムで対処した:
- 1パス目: NFKC正規化済みキーで完全一致
- 2パス目: 1パス目で未特定のルールに対し、摘要の先頭15文字での前方一致
当初は3パス目のフォールバック(さらに緩い条件)も実装したが、NFKC正規化の導入で2パス目までに全件がマッチするようになったため、3パス目は削除した。
結果
29件未特定 → 0件。照合ログを出力して、全ルールにIDが紐付いていることを確認した。
グローバルボタンロック
処理中に別の事業者のボタンを押すと、会計サービスのセッション単位でCTI(事業者コンテキスト)が切り替わり、実行中の処理と競合する。
全事業者のボタンをグローバルにdisableするロック機構を入れた。処理開始時にロックを取得し、完了(成功/失敗問わず)時に解放する。
function setGlobalLock(locked) {
document.querySelectorAll('[data-lockable]')
.forEach(btn => { btn.disabled = locked; });
}
CLAUDE.md にも「新しいボタンを追加する際は data-lockable 属性を付与すること」の注意書きを追記した。
その他の修正
- 設定画面の整理: 自動仕訳ルールURL欄を設定タブから削除。ルール管理は専用タブに一本化し、情報の重複を排除
- URL空欄時の挙動修正: エクスポートURL欄を空にして保存すると、既存のスプレッドシートURLが消えた上に新規SSが自動作成されるバグを修正。空欄保存時はURL消去のみ行い、自動作成は走らせない
- ドライラン/同期ボタンの制御: ルール管理URLが空欄のとき、ドライランと同期ボタンをdisableに。押しても何も起きないボタンが活性化しているのは混乱の元
- プレースホルダー改善: URL入力欄に「未設定時はスプレッドシートを自動作成します」の説明を追加
振り返り
29件の未特定バグは、ログに照合キーのペアを並べて出力したことで原因の切り分けが進んだ。目で見比べて「同じに見えるのに不一致」となった瞬間に文字コードを疑い、charCodeAt でバイト列を確認して全角/半角の差異を突き止めた。NFKC正規化という一手で根本から片付いたのは気持ちがよかった。
タブの再編は、コードの移動量こそ多かったが判断は単純だった。一方でID照合は、コード変更量は少ないのに原因特定に時間を食った。「動かない」より「ほぼ動くが一部だけ合わない」問題のほうが、調査に手間がかかることを改めて実感した。