• #Chrome拡張機能
  • #クラウド会計
  • #自動仕訳ルール
  • #NFKC正規化
  • #UI設計
  • #バグ修正
開発misc-devメモ

Chrome拡張 自動仕訳ルールの独立タブ化とルールID照合バグ修正

自動仕訳ルールの管理UIがエクスポート/インポートの中に埋もれていて、毎回タブを切り替えてスクロールしていた。「設定/エクスポート/インポート/自動仕訳ルール/ログ」の5タブに再編し、ルールを独立タブに引き出した。その過程で、ルールID照合が29件分失敗しているバグを発見し、原因を3つ潰して全件解消した一日の記録。


タブ構成の再編: 3タブから5タブへ

もともと「設定/エクスポート/ログ」の3タブ構成で、エクスポートタブの中にインポートと自動仕訳ルールの機能がすべて詰まっていた。事業者が増えるにつれスクロール量が膨らみ、目的の操作にたどり着くまでに手が止まる。

新しいタブ構成:

  1. 設定 -- 認証情報とグローバル設定
  2. エクスポート -- データ取得(プル系操作)
  3. インポート -- データ書き込み(プッシュ系操作)
  4. 自動仕訳ルール -- ルールのドライラン・同期
  5. ログ -- 実行履歴

タブ位置は 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. 1パス目: NFKC正規化済みキーで完全一致
  2. 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照合は、コード変更量は少ないのに原因特定に時間を食った。「動かない」より「ほぼ動くが一部だけ合わない」問題のほうが、調査に手間がかかることを改めて実感した。