[{"data":1,"prerenderedAt":500},["ShallowReactive",2],{"content-/mf-extension-export-enhancements":3,"all-pages-for-dir":498,"og-image-/mf-extension-export-enhancements":499},{"id":4,"title":5,"body":6,"category":479,"description":480,"extension":481,"meta":482,"navigation":205,"ogImage":483,"path":484,"project_name":485,"published":486,"publishedAt":487,"seo":488,"stem":489,"tags":490,"todo":496,"unpublished":486,"updatedAt":483,"__hash__":497},"pages/2026-03/2026-03-24/mf-extension-export-enhancements.md","会計サービス Chrome拡張エクスポート機能強化 - 複数事業者対応・残高試算表スクレイピング・型不一致バグとの格闘",{"type":7,"value":8,"toc":459},"minimark",[9,14,23,26,31,37,40,42,46,53,71,74,76,79,84,91,101,104,107,129,144,267,270,272,276,280,283,286,290,305,308,312,318,326,328,332,338,348,350,354,357,361,382,388,392,409,418,422,431,433,436,448,455],[10,11,13],"h1",{"id":12},"会計サービス-chrome拡張エクスポート機能強化","会計サービス Chrome拡張エクスポート機能強化",[15,16,17,18,22],"p",{},"朝、Codexのレビューコメントを読んで ",[19,20,21],"code",{},"switchYearByCti"," のcatch漏れを直すところから始まった。テスト53件が緑になってPR #1をマージした後、そのまま勢いで5つの機能追加とバグ修正を積み重ねた。途中、スプレッドシート上で金額が文字列扱いになったり、重複チェックが同一データを「別物」と判定したりと、型の罠に何度もはまった一日の記録。",[24,25],"hr",{},[27,28,30],"h2",{"id":29},"pr-1マージ-multi-business-export","PR #1マージ: multi-business-export",[15,32,33,34,36],{},"Codexレビューで指摘された ",[19,35,21],{}," の問題は、catch節が年度切替失敗を握りつぶしていた点。エラー時にそのまま次の処理に進んでしまい、違う年度のデータをエクスポートする危険があった。",[15,38,39],{},"修正後にテスト53件を流して全パス。PR #1をマージして、複数事業者の一括エクスポート機能が本線に入った。",[24,41],{},[27,43,45],{"id":44},"スプレッドシートurl分離","スプレッドシートURL分離",[15,47,48,49,52],{},"もともと ",[19,50,51],{},"exportUrl"," という1つのURLに全データを書き込んでいたが、仕訳帳と連携明細では構造が違うため、シートを分けたくなった。",[15,54,55,56,58,59,62,63,66,67,70],{},"データ構造を変更して、",[19,57,51],{},"（明細用）と ",[19,60,61],{},"journalExportUrl","（仕訳帳専用）の2つに分離。",[19,64,65],{},"lib.js"," のエクスポート関数に ",[19,68,69],{},"urlKey"," パラメータを追加し、どちらのURLを参照するかを呼び出し元が指定する形にした。",[15,72,73],{},"設定UIにも入力欄を追加。hidden inputで既存値を保持し、auto-saveロジックで変更時にchrome.storageへ即時保存する。地味だが、ここの配線を間違えると片方のURLが消えるので、DevToolsでstorage変更イベントを監視しながら動作確認した。",[24,75],{},[27,77,78],{"id":78},"金額の数値化とカンマ区切り書式",[80,81,83],"h3",{"id":82},"stringをやめる","String()をやめる",[15,85,86,87,90],{},"借方・貸方金額をスプレッドシートに書き込む際、",[19,88,89],{},"String()"," でラップしていたためSheets上で文字列になっていた。SUM関数が効かない。",[15,92,93,94,97,98,100],{},"Sheets APIの ",[19,95,96],{},"RAW"," モードは、送信するJSONの値の型をそのまま反映する。数値型で送れば数値、文字列で送れば文字列。修正はシンプルで、",[19,99,89],{}," を外してJavaScriptの数値型のまま渡すだけだった。",[80,102,103],{"id":103},"カンマ区切り書式の設定",[15,105,106],{},"数値にしたら今度は「1234567」のように桁区切りなしで表示される。読みにくい。",[15,108,109,112,113,116,117,120,121,124,125,128],{},[19,110,111],{},"background.js"," に ",[19,114,115],{},"formatSheetColumns"," アクションを追加し、Google Sheets APIの ",[19,118,119],{},"repeatCell"," リクエストで ",[19,122,123],{},"numberFormat"," を設定する形にした。フォーマットパターンは ",[19,126,127],{},"#,##0","。",[15,130,131,132,135,136,139,140,143],{},"ここでバグを踏んだ。ヘッダー行に背景色を設定するエントリも同じリクエストバッチに含まれていて、そのエントリには ",[19,133,134],{},"fmt.columns"," プロパティが存在しない。",[19,137,138],{},"fmt.columns.forEach(...)"," が ",[19,141,142],{},"undefined"," に対して呼ばれてクラッシュした。",[145,146,151],"pre",{"className":147,"code":148,"language":149,"meta":150,"style":150},"language-javascript shiki shiki-themes vitesse-light vitesse-light","// クラッシュする: ヘッダー背景色エントリにはcolumnsがない\nfmt.columns.forEach(col => { ... });\n\n// ガード追加\nif (fmt.columns) {\n  fmt.columns.forEach(col => { ... });\n}\n","javascript","",[19,152,153,162,200,207,213,235,261],{"__ignoreMap":150},[154,155,158],"span",{"class":156,"line":157},"line",1,[154,159,161],{"class":160},"sxvE3","// クラッシュする: ヘッダー背景色エントリにはcolumnsがない\n",[154,163,165,169,173,176,178,182,185,188,191,194,197],{"class":156,"line":164},2,[154,166,168],{"class":167},"s4oTP","fmt",[154,170,172],{"class":171},"shFtX",".",[154,174,175],{"class":167},"columns",[154,177,172],{"class":171},[154,179,181],{"class":180},"senZ8","forEach",[154,183,184],{"class":171},"(",[154,186,187],{"class":167},"col",[154,189,190],{"class":171}," =>",[154,192,193],{"class":171}," {",[154,195,196],{"class":171}," ...",[154,198,199],{"class":171}," });\n",[154,201,203],{"class":156,"line":202},3,[154,204,206],{"emptyLinePlaceholder":205},true,"\n",[154,208,210],{"class":156,"line":209},4,[154,211,212],{"class":160},"// ガード追加\n",[154,214,216,220,223,225,227,229,232],{"class":156,"line":215},5,[154,217,219],{"class":218},"sHkkW","if",[154,221,222],{"class":171}," (",[154,224,168],{"class":167},[154,226,172],{"class":171},[154,228,175],{"class":167},[154,230,231],{"class":171},")",[154,233,234],{"class":171}," {\n",[154,236,238,241,243,245,247,249,251,253,255,257,259],{"class":156,"line":237},6,[154,239,240],{"class":167},"  fmt",[154,242,172],{"class":171},[154,244,175],{"class":167},[154,246,172],{"class":171},[154,248,181],{"class":180},[154,250,184],{"class":171},[154,252,187],{"class":167},[154,254,190],{"class":171},[154,256,193],{"class":171},[154,258,196],{"class":171},[154,260,199],{"class":171},[154,262,264],{"class":156,"line":263},7,[154,265,266],{"class":171},"}\n",[15,268,269],{},"エラーメッセージだけ見ると「columns is not iterable」で、最初はデータ構造のバグかと疑った。実際にはフォーマット設定のリクエスト配列に異なる構造のエントリが混在していただけ。ログに各エントリの中身を出して気づいた。",[24,271],{},[27,273,275],{"id":274},"残高試算表エクスポート-htmlスクレイピング方式","残高試算表エクスポート: HTMLスクレイピング方式",[80,277,279],{"id":278},"なぜjson-apiが使えないか","なぜJSON APIが使えないか",[15,281,282],{},"仕訳帳や連携明細にはJSON APIが存在するが、残高試算表（TB）には内部APIがなかった。会計サービスの残高試算表画面はサーバーサイドレンダリングされたHTMLテーブルをそのまま表示している。",[15,284,285],{},"方針を切り替えて、HTMLテーブルをDOMからスクレイピングする方式で実装した。",[80,287,289],{"id":288},"cssクラスから階層情報を読む","CSSクラスから階層情報を読む",[15,291,292,293,296,297,300,301,304],{},"会計サービスの残高試算表テーブルでは、勘定科目の階層（大分類→中分類→科目→補助科目）を ",[19,294,295],{},"label-indent-1",", ",[19,298,299],{},"label-indent-2"," のようなCSSクラスで表現している。このクラス名からindentレベルを数値で取得し、スプレッドシート上では ",[19,302,303],{},"padding.left"," でインデントを再現した（level × 12px）。",[15,306,307],{},"BS（貸借対照表）とPL（損益計算書）を1つのシートに統合し、合計行にはグレー背景を付けて視覚的に区別した。「〜の部合計」行はインデント0固定で出力。",[80,309,311],{"id":310},"会計サービスのcss-indent値が信用できない問題","会計サービスのCSS indent値が信用できない問題",[15,313,314,315,317],{},"ここで想定外の問題にぶつかった。会計サービスのCSSクラスが示すインデント値が、論理的な階層と一致しない。たとえば勘定科目と補助科目で同じ ",[19,316,299],{}," が付いているケースがある。",[15,319,320,321,325],{},"CSSの値をそのまま使うとスプレッドシート上で親子関係が崩れる。対策として、CSSのindent値ではなく",[322,323,324],"strong",{},"論理的な階層レベルを自前で決定するロジック","を組んだ。「勘定科目は常にlevel 2、補助科目は常にlevel 3」のように、科目の種類に応じて固定値を割り当てる。会計サービス側のCSS表現に依存しない形にできた。",[24,327],{},[27,329,331],{"id":330},"年度フィルタバグ-全年度が展開されていた","年度フィルタバグ: 全年度が展開されていた",[15,333,334,337],{},[19,335,336],{},"buildEntityExportTasks"," 関数が全年度のCTI（事業者コンテキストID）をタスクリストに展開していた。ユーザーが2024年度だけを選択しても、2021〜2025年の全年度分のエクスポートが走る。",[15,339,340,341,344,345,347],{},"原因は ",[19,342,343],{},"selectedYears"," パラメータが渡されていなかったこと。関数のシグネチャに ",[19,346,343],{}," を追加し、タスク生成時にフィルタするように修正。",[24,349],{},[27,351,353],{"id":352},"重複チェックの型不一致-3段階で踏んだ罠","重複チェックの型不一致: 3段階で踏んだ罠",[15,355,356],{},"既存データとの重複チェックで、同一レコードを「別データ」と判定するバグが出た。原因を追っていくと、型の不一致が3段階で潜んでいた。",[80,358,360],{"id":359},"第1段階-数値-vs-文字列","第1段階: 数値 vs 文字列",[15,362,363,366,367,370,371,374,375,378,379,128],{},[19,364,365],{},"normalizeRow"," で行データを正規化する際、Sheets APIから取得した既存データは文字列（",[19,368,369],{},"\"241729\"","）、新規データはJavaScript数値型（",[19,372,373],{},"241729","）で入ってくる。",[19,376,377],{},"==="," で比較すると常に ",[19,380,381],{},"false",[15,383,384,385,387],{},"対策として、比較前に全フィールドを ",[19,386,89],{}," で統一した。",[80,389,391],{"id":390},"第2段階-カンマ付きformatted_value","第2段階: カンマ付きFORMATTED_VALUE",[15,393,394,396,397,400,401,404,405,408],{},[19,395,89],{}," 変換で解決したと思ったら、まだ重複が検出されない。Sheets APIの ",[19,398,399],{},"FORMATTED_VALUE"," モードで取得すると、数値がカンマ付き（",[19,402,403],{},"\"-19,958\"","）で返ってくる。一方、新規データは ",[19,406,407],{},"\"-19958\""," のようにカンマなし。",[15,410,411,413,414,417],{},[19,412,399],{}," ではなく ",[19,415,416],{},"UNFORMATTED_VALUE"," で取得するか、比較時にカンマを除去するかの二択。カンマ除去の方がシンプルだったので、正規化関数内でカンマをstripする処理を追加した。",[80,419,421],{"id":420},"coercenumericcolumns","coerceNumericColumns",[15,423,424,425,427,428,430],{},"これらの型変換処理を ",[19,426,421],{}," として ",[19,429,65],{}," に切り出した。数値カラムのインデックスを受け取り、該当カラムの値をカンマ除去→数値変換する。エクスポート前と重複チェック前の両方で呼ぶ。",[24,432],{},[27,434,435],{"id":435},"振り返り",[15,437,438,439,441,442,441,444,447],{},"一日で触ったファイルは ",[19,440,111],{},"、",[19,443,65],{},[19,445,446],{},"content_script.js","、設定画面のHTML、テストファイルと広範囲に渡った。朝のPRマージから夜の重複チェック修正まで、ほぼ全ての問題が「型」に起因していた。Sheets APIが返す値の型、JavaScriptの暗黙的な型変換、CSSクラス名が示す値と論理的な意味のズレ。",[15,449,450,451,454],{},"特に重複チェックのバグは、修正して動かすたびに新しい不一致パターンが顔を出して、3回「直った」と思って3回裏切られた。ログに ",[19,452,453],{},"typeof"," と実際の値を並べて出力するようにしてから、やっと全体像が見えた。型を疑うなら最初から型を可視化すべきだった。",[456,457,458],"style",{},"html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .s4oTP, html code.shiki .s4oTP{--shiki-default:#B07D48;--shiki-dark:#B07D48}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}html pre.shiki code .senZ8, html code.shiki .senZ8{--shiki-default:#59873A;--shiki-dark:#59873A}html pre.shiki code .sHkkW, html code.shiki .sHkkW{--shiki-default:#1E754F;--shiki-dark:#1E754F}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":150,"searchDepth":164,"depth":164,"links":460},[461,462,463,467,472,473,478],{"id":29,"depth":164,"text":30},{"id":44,"depth":164,"text":45},{"id":78,"depth":164,"text":78,"children":464},[465,466],{"id":82,"depth":202,"text":83},{"id":103,"depth":202,"text":103},{"id":274,"depth":164,"text":275,"children":468},[469,470,471],{"id":278,"depth":202,"text":279},{"id":288,"depth":202,"text":289},{"id":310,"depth":202,"text":311},{"id":330,"depth":164,"text":331},{"id":352,"depth":164,"text":353,"children":474},[475,476,477],{"id":359,"depth":202,"text":360},{"id":390,"depth":202,"text":391},{"id":420,"depth":202,"text":421},{"id":435,"depth":164,"text":435},"dev","会計サービス連携Chrome拡張のエクスポート機能を一日で6件追加・修正。PR初マージ、スプレッドシートURL分離、金額の数値化、残高試算表HTMLスクレイピング、重複チェックの型不一致バグ修正までの記録","md",{},null,"/mf-extension-export-enhancements","tax-assistant",false,"2026-03-24T00:00:00.000Z",{"title":5,"description":480},"2026-03/2026-03-24/mf-extension-export-enhancements",[491,492,493,494,495],"Chrome拡張機能","クラウド会計","Google Sheets API","HTMLスクレイピング","型変換","memo","dD9e09qzMXSr5tyGIHpZc-7XgHP7qL-pfCFlb7VFukM",[],"https://log.eurekapu.com/og/blog/mf-extension-export-enhancements.png?v=2026-03-24T00%3A00%3A00.000Z&title=%E4%BC%9A%E8%A8%88%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%20Chrome%E6%8B%A1%E5%BC%B5%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E6%A9%9F%E8%83%BD%E5%BC%B7%E5%8C%96%20-%20%E8%A4%87%E6%95%B0%E4%BA%8B%E6%A5%AD%E8%80%85%E5%AF%BE%E5%BF%9C%E3%83%BB%E6%AE%8B%E9%AB%98%E8%A9%A6%E7%AE%97%E8%A1%A8%E3%82%B9%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%94%E3%83%B3%E3%82%B0%E3%83%BB%E5%9E%8B%E4%B8%8D%E4%B8%80%E8%87%B4%E3%83%90%E3%82%B0%E3%81%A8%E3%81%AE%E6%A0%BC%E9%97%98&author=Kei%20Komatsu&sig=8d3a34766481284a",1782528820449]