[{"data":1,"prerenderedAt":494},["ShallowReactive",2],{"content-/journal-rules-export-import":3,"all-pages-for-dir":492,"og-image-/journal-rules-export-import":493},{"id":4,"title":5,"body":6,"category":470,"description":471,"extension":472,"meta":473,"navigation":474,"ogImage":475,"path":476,"project_name":477,"published":478,"publishedAt":479,"seo":480,"stem":481,"tags":482,"todo":490,"unpublished":478,"updatedAt":475,"__hash__":491},"pages/2026-03/2026-03-27/journal-rules-export-import.md","自動仕訳ルールのエクスポート・インポート・同期機能を実装した記録",{"type":7,"value":8,"toc":443},"minimark",[9,13,17,20,25,28,32,60,67,69,72,76,79,159,163,166,169,172,175,196,207,209,212,215,221,224,227,230,233,240,242,245,248,277,294,296,299,302,309,311,314,318,321,338,341,344,346,350,357,367,370,372,376,379,429,432,434,437,440],[10,11,5],"h1",{"id":12},"自動仕訳ルールのエクスポートインポート同期機能を実装した記録",[14,15,16],"p",{},"Chrome DevToolsで会計ソフトAの内部APIを3本掘り当て、自動仕訳ルールのエクスポート・インポート・同期機能を6ファイルにわたって組み上げた。UIを2回作り直し、CTIクロスコンタミネーションというバグを踏み抜き、Codexレビューを3回回して着地させるまでの一日。",[18,19],"hr",{},[21,22,24],"h2",{"id":23},"chrome-devtoolsでの内部api調査","Chrome DevToolsでの内部API調査",[14,26,27],{},"前日の調査で公開APIに自動仕訳ルールのエンドポイントが存在しないことは確認済みだった。今日はChrome DevToolsのNetworkタブに張り付いて、実際に画面を操作しながら内部APIを捕まえにいった。",[29,30,31],"h3",{"id":31},"掘り当てた3つのエンドポイント",[33,34,35,43,49],"ul",{},[36,37,38,42],"li",{},[39,40,41],"strong",{},"journal_rules",": ルール一覧の取得。ページネーション付きでルールID・条件・仕訳内容がJSONで返る",[36,44,45,48],{},[39,46,47],{},"attribute_options",": 勘定科目・補助科目・税区分などのマスタデータ。ルールが参照するIDを名前に変換するために必要だった",[36,50,51,54,55,59],{},[39,52,53],{},"Search API",": ",[56,57,58],"code",{},"POST /api/v1/office_journal_rules/search"," で条件付き検索。後の同期機能でルールのマッチングに使うことになる",[14,61,62,63,66],{},"Search APIへのPOSTで最初に422エラーが返ってきた。リクエストヘッダを見比べて、",[56,64,65],{},"X-CSRF-Token","ヘッダが必須だと突き止めた。CSRFトークンはHTMLのmetaタグから取得する方式に落ち着いた。",[18,68],{},[21,70,71],{"id":71},"エクスポート機能の実装",[29,73,75],{"id":74},"_6ファイルの変更","6ファイルの変更",[14,77,78],{},"エクスポート機能は以下の6ファイルに手を入れた。",[80,81,82,95],"table",{},[83,84,85],"thead",{},[86,87,88,92],"tr",{},[89,90,91],"th",{},"ファイル",[89,93,94],{},"役割",[96,97,98,109,119,129,139,149],"tbody",{},[86,99,100,106],{},[101,102,103],"td",{},[56,104,105],{},"lib.js",[101,107,108],{},"API呼び出し・データ変換のユーティリティ",[86,110,111,116],{},[101,112,113],{},[56,114,115],{},"bridge.js",[101,117,118],{},"Chrome拡張のバックグラウンドとコンテンツスクリプトの通信橋渡し",[86,120,121,126],{},[101,122,123],{},[56,124,125],{},"export.js",[101,127,128],{},"スプレッドシートへの書き出しロジック",[86,130,131,136],{},[101,132,133],{},[56,134,135],{},"import.js",[101,137,138],{},"スプレッドシートからの読み込みロジック",[86,140,141,146],{},[101,142,143],{},[56,144,145],{},"content.js",[101,147,148],{},"DOM操作・UI制御",[86,150,151,156],{},[101,152,153],{},[56,154,155],{},"manifest.json",[101,157,158],{},"パーミッション追加",[29,160,162],{"id":161},"uiの試行錯誤独立タブからチェックボックスへ","UIの試行錯誤：独立タブからチェックボックスへ",[14,164,165],{},"最初は「ルール管理」という独立タブを設けて、エクスポート・インポートのボタンを並べた。動くものはできたが、実際に触ってみると操作の流れが途切れる。仕訳データをエクスポートするついでにルールも一緒に出したい場面がほとんどだった。",[14,167,168],{},"ユーザーフィードバックを受けて方針を変えた。既存のエクスポート画面に「自動仕訳ルールも含める」チェックボックスを1つ追加する形に統合した。コード量は減り、操作ステップも1つ減った。",[29,170,171],{"id":171},"スプレッドシートの書式設計",[14,173,174],{},"エクスポート先のスプレッドシートは、視認性を上げるために3つの工夫を入れた。",[176,177,178,184,190],"ol",{},[36,179,180,183],{},[39,181,182],{},"3エリアの縦ボーダー区切り",": ルール条件 / 仕訳設定 / 借方貸方の3ブロックを縦ボーダーで区切り、どの列がどのブロックに属するかを一目で判別できるようにした",[36,185,186,189],{},[39,187,188],{},"Material Designカラー",": ヘッダー行にMaterial Designのカラーパレットを適用。ルール条件は青系、仕訳は緑系、貸借は紫系",[36,191,192,195],{},[39,193,194],{},"金融機関ごとの色分けと濃淡",": 銀行・クレジットカード・電子マネーなど金融機関の種別で背景色を変え、さらに同じ金融機関内でも口座ごとに濃淡を付けた。100行を超えるルール一覧でも、目的の口座のルールがどこにあるか色で追える",[14,197,198,199,202,203,206],{},"シート名は ",[56,200,201],{},"ルール_エクスポート"," とした。後述のインポート用シート ",[56,204,205],{},"ルール_インポート"," とヘッダー書式を共通化し、両シート間でコピー&ペーストしても列がずれない設計にした。",[18,208],{},[21,210,211],{"id":211},"インポート機能の実装",[29,213,214],{"id":214},"インポートシートの自動作成",[14,216,217,218,220],{},"エクスポートを実行すると、同じスプレッドシートに ",[56,219,205],{}," シートが自動で作成される。ヘッダー行はエクスポートシートと同一書式で、データ行は空。ユーザーはエクスポートシートから必要な行をコピーし、編集してからインポートを実行する。",[14,222,223],{},"インポートシートのURLは設定画面に自動保存される。次回以降はワンクリックでシートに飛べる。",[29,225,226],{"id":226},"事業者ごとの個別インポートボタン",[14,228,229],{},"当初は全事業者を一括でインポートするボタンを置いていたが、事業者Aのルールだけ更新したい場面のほうが多かった。一括ボタンを廃止し、各事業者の横に個別のインポートボタンを配置した。",[29,231,232],{"id":232},"ローカル保存ログ",[14,234,235,236,239],{},"インポート実行時、CSVデータをローカルにも保存するようにした。ファイル名は ",[56,237,238],{},"{SSタイトル}_rules_import_{YYYYMMDD_HHmmss}.csv"," の形式。スプレッドシートのデータを誤って消しても、ローカルにCSVが残っているので復元できる。",[18,241],{},[21,243,244],{"id":244},"エラー判定バグの修正",[14,246,247],{},"インポート結果の成功/失敗判定で、会計ソフトAの画面構造に起因するバグを踏んだ。",[14,249,250,251,254,255,254,258,261,262,265,266,269,270,273,274,276],{},"会計ソフトAのインポート結果画面には ",[56,252,253],{},"alert-success","、",[56,256,257],{},"alert-warning",[56,259,260],{},"alert-danger"," の3つのdivが",[39,263,264],{},"常に存在","していて、",[56,267,268],{},"hidden","クラスの付け外しで表示を切り替える仕様だった。最初の実装では「",[56,271,272],{},"alert","を含むdivを正規表現で探し、可視のものを判定する」というロジックにしていたが、3つ全てにマッチしてしまい、常に最初の ",[56,275,253],{}," を拾って成功と判定していた。",[14,278,279,280,254,282,254,284,286,287,289,290,293],{},"修正後は ",[56,281,253],{},[56,283,257],{},[56,285,260],{}," それぞれを個別にセレクタで取得し、",[56,288,268],{},"クラスが",[39,291,292],{},"付いていない","ものだけを結果として採用するようにした。",[18,295],{},[21,297,298],{"id":298},"設定画面の改善",[14,300,301],{},"設定画面に保存されたスプレッドシートURLが、プレーンテキストのまま表示されていた。URLをコピーしてブラウザのアドレスバーに貼り付ける手間が毎回発生していた。",[14,303,304,305,308],{},"URLをアンカータグに変換し、クリックで直接スプレッドシートに遷移できるようにした。",[56,306,307],{},"target=\"_blank\""," で新しいタブに開く。",[18,310],{},[21,312,313],{"id":313},"ルール同期機能",[29,315,317],{"id":316},"設計csv-search-apiハイブリッド","設計：CSV + Search APIハイブリッド",[14,319,320],{},"エクスポート・インポートは手動操作だが、「会計ソフトA上のルールを最新のスプレッドシートと一致させたい」という要望があった。同期機能の設計は以下の方針で組んだ。",[176,322,323,326,329,335],{},[36,324,325],{},"スプレッドシートからルール一覧をCSVとして読み込む",[36,327,328],{},"会計ソフトAのSearch APIで既存ルールを取得する",[36,330,331,334],{},[39,332,333],{},"ルールIDでマッチング","して、スプレッドシートにあって会計ソフトAにないルールは追加、会計ソフトAにあってスプレッドシートにないルールは削除候補としてリストアップ",[36,336,337],{},"差分削除は確認ダイアログを挟む（誤削除防止）",[29,339,340],{"id":340},"フォールバックルールの仕様",[14,342,343],{},"調査中に「他のルールにマッチしない場合」に適用されるフォールバックルールの存在を確認した。このルールにはルールIDが振られず、Search APIのレスポンスにも含まれない。同期処理ではフォールバックルールを除外するロジックを入れ、仕様をドキュメントとして残した。",[18,345],{},[21,347,349],{"id":348},"ctiクロスコンタミネーションバグ","CTIクロスコンタミネーションバグ",[14,351,352,353,356],{},"同期機能のテスト中に、事業者Aのルールが事業者Bのデータとして保存される現象が起きた。原因を追うと ",[56,354,355],{},"extractCti()"," 関数にたどり着いた。",[14,358,359,360,362,363,366],{},"CTI（Company/Tenant Identifier）はURLのパスから抽出する。しかし、同期処理が非同期で走る間にユーザーが別の事業者のページに遷移すると、",[56,361,355],{}," が",[39,364,365],{},"現在のURL","からCTIを取得してしまう。結果として、事業者Aのデータが事業者BのCTIに紐付いて保存される——これがクロスコンタミネーションの正体だった。",[14,368,369],{},"修正は、同期処理の開始時にCTIをキャプチャして変数に保持し、以降の処理ではキャプチャ済みの値を使う方式に変えた。URLから毎回取得するのをやめたことで、ページ遷移の影響を受けなくなった。",[18,371],{},[21,373,375],{"id":374},"codexレビュー3回","Codexレビュー3回",[14,377,378],{},"実装の区切りごとにCodexレビューを回した。3回のレビューで受けた致命的指摘と対応は以下の通り。",[80,380,381,394],{},[83,382,383],{},[86,384,385,388,391],{},[89,386,387],{},"回",[89,389,390],{},"指摘内容",[89,392,393],{},"対応",[96,395,396,407,418],{},[86,397,398,401,404],{},[101,399,400],{},"1回目",[101,402,403],{},"CSRFトークンの取得タイミングがリクエストと離れすぎており、トークン失効のリスクがある",[101,405,406],{},"トークン取得をリクエスト直前に移動",[86,408,409,412,415],{},[101,410,411],{},"2回目",[101,413,414],{},"CTI取得が非同期処理中にURLから再取得される設計になっている",[101,416,417],{},"上述のCTIキャプチャ方式に修正",[86,419,420,423,426],{},[101,421,422],{},"3回目",[101,424,425],{},"差分削除で確認なしに削除が走る可能性がある",[101,427,428],{},"確認ダイアログの追加と、削除対象のプレビュー表示を実装",[14,430,431],{},"2回目の指摘でCTIバグに気づけたのは大きかった。手動テストでは再現条件が限られるため、レビューなしでは本番で踏むまで気づかなかった可能性が高い。",[18,433],{},[21,435,436],{"id":436},"振り返り",[14,438,439],{},"朝のAPI調査から始めて、エクスポート → インポート → 同期と段階的に機能を積み上げた。UIを2回作り直す回り道はあったが、結果的にチェックボックス1つに統合した現在のUIのほうが操作が自然に流れる。",[14,441,442],{},"CTIクロスコンタミネーションは、非同期処理で「今のURL」を都度参照する設計の危うさを身をもって体験した。状態は開始時にキャプチャして閉じ込める——関数型の原則がそのまま当てはまるバグだった。",{"title":444,"searchDepth":445,"depth":445,"links":446},"",2,[447,451,456,461,462,463,467,468,469],{"id":23,"depth":445,"text":24,"children":448},[449],{"id":31,"depth":450,"text":31},3,{"id":71,"depth":445,"text":71,"children":452},[453,454,455],{"id":74,"depth":450,"text":75},{"id":161,"depth":450,"text":162},{"id":171,"depth":450,"text":171},{"id":211,"depth":445,"text":211,"children":457},[458,459,460],{"id":214,"depth":450,"text":214},{"id":226,"depth":450,"text":226},{"id":232,"depth":450,"text":232},{"id":244,"depth":445,"text":244},{"id":298,"depth":445,"text":298},{"id":313,"depth":445,"text":313,"children":464},[465,466],{"id":316,"depth":450,"text":317},{"id":340,"depth":450,"text":340},{"id":348,"depth":445,"text":349},{"id":374,"depth":445,"text":375},{"id":436,"depth":445,"text":436},"dev","Chrome拡張のクラウド会計連携で自動仕訳ルールのエクスポート・インポート・同期機能を構築。内部API調査からUI試行錯誤、CSVハイブリッド同期、CTIバグ修正までの全工程","md",{},true,null,"/journal-rules-export-import","claude-code-tools",false,"2026-03-27T00:00:00.000Z",{"title":5,"description":471},"2026-03/2026-03-27/journal-rules-export-import",[483,484,485,486,487,488,489],"Chrome拡張機能","クラウド会計","自動仕訳ルール","エクスポート","インポート","同期","内部API","memo","Ys0k-dHwO7KJd9MTMzJqo7fEwObD3-M8svKr-20O-j0",[],"https://log.eurekapu.com/og/blog/journal-rules-export-import.png?v=2026-03-27T00%3A00%3A00.000Z&title=%E8%87%AA%E5%8B%95%E4%BB%95%E8%A8%B3%E3%83%AB%E3%83%BC%E3%83%AB%E3%81%AE%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%83%BB%E3%82%A4%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%88%E3%83%BB%E5%90%8C%E6%9C%9F%E6%A9%9F%E8%83%BD%E3%82%92%E5%AE%9F%E8%A3%85%E3%81%97%E3%81%9F%E8%A8%98%E9%8C%B2&author=Kei%20Komatsu&sig=2a4eedc3604a9396",1782528821510]