[{"data":1,"prerenderedAt":885},["ShallowReactive",2],{"content-/2026-04-30-consolidated-accounting-vue-implementation":3,"all-pages-for-dir":883,"og-image-/2026-04-30-consolidated-accounting-vue-implementation":884},{"id":4,"title":5,"body":6,"category":864,"description":865,"extension":866,"meta":867,"navigation":836,"path":868,"project_name":869,"published":870,"publishedAt":871,"seo":872,"stem":873,"tags":874,"todo":880,"updatedAt":881,"__hash__":882},"pages/2026-04/2026-04-30/consolidated-accounting-vue-implementation.md","連結会計レッスンを Vue + データ駆動でフルスクラッチ実装",{"type":7,"value":8,"toc":855},"minimark",[9,13,17,22,30,42,210,224,238,485,492,496,499,569,584,605,609,616,619,657,664,678,687,691,699,708,721,724,727,730,736,739,765,768,788,791,822,825,851],[10,11,5],"h1",{"id":12},"連結会計レッスンを-vue-データ駆動でフルスクラッチ実装",[14,15,16],"p",{},"朝の机にHTMLプロトタイプの連結精算表を広げると、セルの数値もハイライトの色も全部直書きで、設例を1本足すたびに数百行をコピペする構造になっていた。今日はこれを Vue コンポーネント + 型付きデータに置き換え、午前中に I-2-1〜I-2-3 の3本、午後に I-3-1〜I-3-3 の3本まで一気に実装した。途中で Codex GPT-5.5 が二重計上の致命点を指摘してきて、計画書を v1 → v2 → v3 まで書き直す羽目になった。",[18,19,21],"h2",{"id":20},"htmlハードコードからデータ駆動へ","HTMLハードコードからデータ駆動へ",[14,23,24,25,29],{},"プロトタイプの ",[26,27,28],"code",{},"consolidated-accounting.html"," は仕訳プールも精算表も、セルのハイライトクラスまで HTML に直書きされていた。設例3本を Vue で動かすには、構造を分解して再構築する必要がある。",[14,31,32,33,41],{},"最初に決めたのは、",[34,35,36,37,40],"strong",{},"データ層を ",[26,38,39],{},"types.ts"," でスキーマ化","することだ。1本のレッスンに必要な情報を以下に分けた。",[43,44,49],"pre",{"className":45,"code":46,"language":47,"meta":48,"style":48},"language-ts shiki shiki-themes vitesse-light vitesse-light","// types.ts のスキーマ概要\ntype Example = {\n  id: string                    // 'I-2-1'\n  title: string\n  premise: PremiseRow[]         // 前提条件（P社・S社の数値）\n  journalEntries: JournalEntry[] // *A *B *C のラベル付き仕訳\n  worksheet: {\n    columns: Column[]           // 個別 / 修正列 / 連結\n    rows: Row[]                 // 科目行（derived 行は依存式を持つ）\n    modifyColumns: ModifyColumn[] // 列ごとの修正仕訳マッピング\n  }\n}\n","ts","",[26,50,51,60,78,94,105,122,139,148,165,182,198,204],{"__ignoreMap":48},[52,53,56],"span",{"class":54,"line":55},"line",1,[52,57,59],{"class":58},"sxvE3","// types.ts のスキーマ概要\n",[52,61,63,67,71,75],{"class":54,"line":62},2,[52,64,66],{"class":65},"stQ0i","type",[52,68,70],{"class":69},"sSkh3"," Example",[52,72,74],{"class":73},"shFtX"," =",[52,76,77],{"class":73}," {\n",[52,79,81,85,88,91],{"class":54,"line":80},3,[52,82,84],{"class":83},"s4oTP","  id",[52,86,87],{"class":73},": ",[52,89,90],{"class":69},"string",[52,92,93],{"class":58},"                    // 'I-2-1'\n",[52,95,97,100,102],{"class":54,"line":96},4,[52,98,99],{"class":83},"  title",[52,101,87],{"class":73},[52,103,104],{"class":69},"string\n",[52,106,108,111,113,116,119],{"class":54,"line":107},5,[52,109,110],{"class":83},"  premise",[52,112,87],{"class":73},[52,114,115],{"class":69},"PremiseRow",[52,117,118],{"class":73},"[]         ",[52,120,121],{"class":58},"// 前提条件（P社・S社の数値）\n",[52,123,125,128,130,133,136],{"class":54,"line":124},6,[52,126,127],{"class":83},"  journalEntries",[52,129,87],{"class":73},[52,131,132],{"class":69},"JournalEntry",[52,134,135],{"class":73},"[] ",[52,137,138],{"class":58},"// *A *B *C のラベル付き仕訳\n",[52,140,142,145],{"class":54,"line":141},7,[52,143,144],{"class":83},"  worksheet",[52,146,147],{"class":73},": {\n",[52,149,151,154,156,159,162],{"class":54,"line":150},8,[52,152,153],{"class":83},"    columns",[52,155,87],{"class":73},[52,157,158],{"class":69},"Column",[52,160,161],{"class":73},"[]           ",[52,163,164],{"class":58},"// 個別 / 修正列 / 連結\n",[52,166,168,171,173,176,179],{"class":54,"line":167},9,[52,169,170],{"class":83},"    rows",[52,172,87],{"class":73},[52,174,175],{"class":69},"Row",[52,177,178],{"class":73},"[]                 ",[52,180,181],{"class":58},"// 科目行（derived 行は依存式を持つ）\n",[52,183,185,188,190,193,195],{"class":54,"line":184},10,[52,186,187],{"class":83},"    modifyColumns",[52,189,87],{"class":73},[52,191,192],{"class":69},"ModifyColumn",[52,194,135],{"class":73},[52,196,197],{"class":58},"// 列ごとの修正仕訳マッピング\n",[52,199,201],{"class":54,"line":200},11,[52,202,203],{"class":73},"  }\n",[52,205,207],{"class":54,"line":206},12,[52,208,209],{"class":73},"}\n",[14,211,212,213,216,217,216,220,223],{},"仕訳には ",[26,214,215],{},"*A"," ",[26,218,219],{},"*B",[26,221,222],{},"*C"," のラベルを持たせ、精算表のセルから「どの仕訳がこの数値に効いたか」を逆引きできるようにした。これで「セルクリック → 仕訳モーダル」の動線が型レベルで保証される。",[14,225,226,229,230,233,234,237],{},[26,227,228],{},"compute.ts"," は精算表の合計値を導出する純粋関数を集めたモジュール。",[26,231,232],{},"derived"," 行（小計や利益）は他の行に依存するので、依存解決を",[34,235,236],{},"複数パスのループ","で処理した。1パスでは「先に合計が出ていない行を参照して NaN になる」問題が起きたためだ。",[43,239,241],{"className":45,"code":240,"language":47,"meta":48,"style":48},"// compute.ts の方針（疑似コード）\nconst computeRows = (rows, columns) => {\n  let resolved = rows.filter(r => r.kind === 'data')\n  while (resolved.length \u003C rows.length) {\n    const next = rows.filter(r => canResolve(r, resolved))\n    if (next.length === 0) throw new Error('循環依存')\n    resolved = [...resolved, ...next.map(compute)]\n  }\n  return resolved\n}\n",[26,242,243,248,279,330,359,396,437,469,473,481],{"__ignoreMap":48},[52,244,245],{"class":54,"line":55},[52,246,247],{"class":58},"// compute.ts の方針（疑似コード）\n",[52,249,250,253,257,259,262,265,268,271,274,277],{"class":54,"line":62},[52,251,252],{"class":65},"const ",[52,254,256],{"class":255},"senZ8","computeRows",[52,258,74],{"class":73},[52,260,261],{"class":73}," (",[52,263,264],{"class":83},"rows",[52,266,267],{"class":73},",",[52,269,270],{"class":83}," columns",[52,272,273],{"class":73},")",[52,275,276],{"class":73}," =>",[52,278,77],{"class":73},[52,280,281,284,287,289,292,295,298,301,304,306,309,311,314,317,321,325,327],{"class":54,"line":80},[52,282,283],{"class":65},"  let ",[52,285,286],{"class":83},"resolved",[52,288,74],{"class":73},[52,290,291],{"class":83}," rows",[52,293,294],{"class":73},".",[52,296,297],{"class":255},"filter",[52,299,300],{"class":73},"(",[52,302,303],{"class":83},"r",[52,305,276],{"class":73},[52,307,308],{"class":83}," r",[52,310,294],{"class":73},[52,312,313],{"class":83},"kind",[52,315,316],{"class":65}," === ",[52,318,320],{"class":319},"sMJiu","'",[52,322,324],{"class":323},"sdGka","data",[52,326,320],{"class":319},[52,328,329],{"class":73},")\n",[52,331,332,336,338,340,342,346,349,351,353,355,357],{"class":54,"line":96},[52,333,335],{"class":334},"sHkkW","  while",[52,337,261],{"class":73},[52,339,286],{"class":83},[52,341,294],{"class":73},[52,343,345],{"class":344},"sz8Xr","length",[52,347,348],{"class":73}," \u003C",[52,350,291],{"class":83},[52,352,294],{"class":73},[52,354,345],{"class":344},[52,356,273],{"class":73},[52,358,77],{"class":73},[52,360,361,364,367,369,371,373,375,377,379,381,384,386,388,390,393],{"class":54,"line":107},[52,362,363],{"class":65},"    const ",[52,365,366],{"class":83},"next",[52,368,74],{"class":73},[52,370,291],{"class":83},[52,372,294],{"class":73},[52,374,297],{"class":255},[52,376,300],{"class":73},[52,378,303],{"class":83},[52,380,276],{"class":73},[52,382,383],{"class":255}," canResolve",[52,385,300],{"class":73},[52,387,303],{"class":83},[52,389,267],{"class":73},[52,391,392],{"class":83}," resolved",[52,394,395],{"class":73},"))\n",[52,397,398,401,403,405,407,409,411,415,417,420,423,426,428,430,433,435],{"class":54,"line":124},[52,399,400],{"class":334},"    if",[52,402,261],{"class":73},[52,404,366],{"class":83},[52,406,294],{"class":73},[52,408,345],{"class":344},[52,410,316],{"class":65},[52,412,414],{"class":413},"sM54T","0",[52,416,273],{"class":73},[52,418,419],{"class":334}," throw",[52,421,422],{"class":65}," new ",[52,424,425],{"class":255},"Error",[52,427,300],{"class":73},[52,429,320],{"class":319},[52,431,432],{"class":323},"循環依存",[52,434,320],{"class":319},[52,436,329],{"class":73},[52,438,439,442,444,447,449,451,454,456,458,461,463,466],{"class":54,"line":141},[52,440,441],{"class":83},"    resolved",[52,443,74],{"class":73},[52,445,446],{"class":73}," [...",[52,448,286],{"class":83},[52,450,267],{"class":73},[52,452,453],{"class":73}," ...",[52,455,366],{"class":83},[52,457,294],{"class":73},[52,459,460],{"class":255},"map",[52,462,300],{"class":73},[52,464,465],{"class":83},"compute",[52,467,468],{"class":73},")]\n",[52,470,471],{"class":54,"line":150},[52,472,203],{"class":73},[52,474,475,478],{"class":54,"line":167},[52,476,477],{"class":334},"  return",[52,479,480],{"class":83}," resolved\n",[52,482,483],{"class":54,"line":184},[52,484,209],{"class":73},[14,486,487,488,491],{},"types / format / compute / example-i-2-1 / test を並列で書いてから ",[26,489,490],{},"pnpm vitest"," を叩くと、12テストが全パスした。データ層が動けばあとは UI を被せるだけ、という確信が得られた瞬間で、ここから実装速度が一気に上がった。",[18,493,495],{"id":494},"phase-3-のコンポーネント分解","Phase 3 のコンポーネント分解",[14,497,498],{},"UI は5つのコンポーネントに切った。",[500,501,502,515],"table",{},[503,504,505],"thead",{},[506,507,508,512],"tr",{},[509,510,511],"th",{},"コンポーネント",[509,513,514],{},"役割",[516,517,518,529,539,549,559],"tbody",{},[506,519,520,526],{},[521,522,523],"td",{},[26,524,525],{},"PatternNav",[521,527,528],{},"I-2-1 / I-2-2 / I-2-3 のタブ切替",[506,530,531,536],{},[521,532,533],{},[26,534,535],{},"RelationDiagram",[521,537,538],{},"P社・S社の支配関係図",[506,540,541,546],{},[521,542,543],{},[26,544,545],{},"PremiseSection",[521,547,548],{},"前提条件テーブル",[506,550,551,556],{},[521,552,553],{},[26,554,555],{},"WorksheetTable",[521,557,558],{},"連結精算表（マーカー＋クリック可能セル）",[506,560,561,566],{},[521,562,563],{},[26,564,565],{},"JournalEntryModal",[521,567,568],{},"セルクリック時の仕訳ポップアップ",[14,570,571,572,575,576,579,580,583],{},"書き終えてChromeで開くと、I-2-1 の営業利益が ",[26,573,574],{},"2,549"," と出た。連結会計入門書の数値と一致している。I-2-2 は ",[26,577,578],{},"2,375","、I-2-3 の売上総利益は ",[26,581,582],{},"2,930","。3本とも書籍の数値と完全一致した。テストも31本全パス。",[14,585,586,587,590,591,593,594,216,597,600,601,604],{},"ここで ",[26,588,589],{},"/simplify"," をかけて3エージェントを並列レビューさせた。",[26,592,565],{}," の ",[26,595,596],{},"groupOf",[26,598,599],{},"labelOf"," を computed の外に出す指摘など3件を採用、回帰テストを再実行して31本全パス。",[26,602,603],{},"feature-slides"," スキルで実装内容を65スライドのHTMLに起こし、午前のセッションをクローズした。",[18,606,608],{"id":607},"codex-再帰レビューで致命点を潰す","Codex 再帰レビューで致命点を潰す",[14,610,611,612,615],{},"午後、別セッションで I-3-1〜I-3-3（連結後の修正仕訳）の計画書を作成した。最初の v1 計画書を Codex GPT-5.5 に投げた段階では「致命点なし」だったのだが、書籍画像を読み直すと",[34,613,614],{},"計画書と画像で数値が食い違っていた","。",[14,617,618],{},"画像の対応関係を確定させる作業を Phase 0 として差し込んだ。",[500,620,621,631],{},[503,622,623],{},[506,624,625,628],{},[509,626,627],{},"設例",[509,629,630],{},"内容",[516,632,633,641,649],{},[506,634,635,638],{},[521,636,637],{},"I-3-1",[521,639,640],{},"機械装置のダウンストリーム未実現利益消去",[506,642,643,646],{},[521,644,645],{},"I-3-2",[521,647,648],{},"商品のアップストリーム＋NCI按分",[506,650,651,654],{},[521,652,653],{},"I-3-3",[521,655,656],{},"ソフトウェアのアップストリーム＋仕掛品振替",[14,658,659,660,663],{},"ここから計画書を v2 に書き直し、再度 ",[26,661,662],{},"codex exec resume --last -m gpt-5.5"," でレビューを走らせると、Codex が一発で致命点を撃ち抜いてきた。",[665,666,667],"blockquote",{},[14,668,669,670,673,674,677],{},"v2 の ",[26,671,672],{},"modifyColumns"," に ",[26,675,676],{},"individual-modify","（個別修正）を含めると、列の合計に個別修正分が二重計上される",[14,679,680,681,683,684,615],{},"確かにその通りだった。",[26,682,672],{}," は「修正仕訳の列だけ」を持ち、個別修正は別のフィールドとして分離するのが正しい。v3 でこの構造を直し、再度 Codex に投げて「致命点なし」を取得してから着手に移った。",[34,685,686],{},"プランレビューを2周回したことで、実装中に発覚すれば数時間ロスする構造ミスを30分で潰せた",[18,688,690],{"id":689},"i-3-1i-3-3-の実装と-hmr-キャッシュ問題","I-3-1〜I-3-3 の実装と HMR キャッシュ問題",[14,692,693,695,696,698],{},[26,694,228],{}," を v3 仕様（",[26,697,672],{}," 形式 + 複数パス依存解決）に書き換えて、I-3-1〜I-3-3 の example ファイルを並列で作成。テストを走らせると 70 本全パス（既存31 + 新規39）。",[14,700,701,704,705,707],{},[26,702,703],{},"pnpm dev"," で立ち上げて Chrome で開くと、画面が古いままだった。データを書き換えても反映されない。HMR が ",[26,706,228],{}," の構造変更を取りこぼしていた。dev サーバを一度落として再起動すると I-3-1 の「未実現損益調整」列が現れ、機械装置 △400 / 減価償却累計額 +40 / 連結後 22,000 / △7,200 / 14,800 / 1,200 / 0 の数値が書籍画像と一致した。",[14,709,710,711,714,715,717,718,720],{},"I-3-2 はNCI按分が絡むので疑っていたが、商品 △500 / 利益剰余金 / 非支配株主持分まで全数値完全一致。I-3-3 は同一セル（ソフトウェア × 未実現損益調整）に *B *C の両方が乗るパターンで、マーカーが ",[26,712,713],{},"*B *C"," の2つ並ぶようになっていた。クリックすると「複数の仕訳がこの数値を構成しています（2本の仕訳の合算）」と表示し、未実現損益消去の ",[26,716,219],{}," と減価償却費調整の ",[26,719,222],{}," を縦並びでモーダルに出した。",[14,722,723],{},"補助科目あり版を3設例分追加してテストを再実行すると、99本全パス（既存31 + I-3-X 39 + 補助 I-3-X 29）。",[18,725,726],{"id":726},"列単位集約モーダルの設計判断",[14,728,729],{},"ユーザーから「機械装置 △400（*A）と減価償却累計額 +40（*B）は同じ機械装置取引の連動した処理なのに、別々のモーダルに分かれているのはおかしい」と指摘が入った。",[14,731,732,733,615],{},"最初の実装は「セル単位のモーダル」だった。*A セルをクリックすると *A の仕訳だけ、*B セルをクリックすると *B の仕訳だけが出る。一見すると素直だが、",[34,734,735],{},"連結ロジックの本質である「列内の仕訳が連動している」という構造が見えなくなる",[14,737,738],{},"設計判断を以下に切り替えた。",[740,741,742,749,759],"ul",{},[743,744,745,748],"li",{},[34,746,747],{},"マーカーはセル単位",": 各セルに乗る仕訳ラベル（*A, *B...）はそのまま表示",[743,750,751,754,755,758],{},[34,752,753],{},"モーダルは列単位の集約",": クリックされたセルの所属する列にぶら下がる仕訳を",[34,756,757],{},"全部","縦並びで表示",[743,760,761,764],{},[34,762,763],{},"ハイライトはクリックしたセルの科目だけ",": モーダル内のハイライトは、クリックした科目に絞る",[14,766,767],{},"I-3-2 の B/S 商品 △500 をクリックすると、モーダルには *A *B *C が縦並びで出て、ハイライトは「商品」の行だけが点灯する。書籍が「列＝概念分類」を採用している構造と一致した。",[14,769,770,772,773,775,776,779,780,783,784,787],{},[26,771,555],{}," のセルクリックハンドラを「セルID渡し」から「列ID渡し」に変更、",[26,774,565],{}," 側で列に紐づく仕訳を全部受け取るように修正。",[26,777,778],{},"align-items"," を ",[26,781,782],{},"flex-start"," から ",[26,785,786],{},"safe center"," に変えてモーダルを画面中央に表示するCSSも入れた。99テスト全パスを確認してこの日の実装をクローズ。",[18,789,790],{"id":790},"今日の収穫",[740,792,793,799,808,816],{},[743,794,795,798],{},[34,796,797],{},"データ層を先に固めると、UI 実装が一気に走る","。Phase 2 のテスト12本が全部通った瞬間に、午後の30倍の速度が出た",[743,800,801,804,805,807],{},[34,802,803],{},"Codex の再帰レビューは計画書段階で2回回す","。v2 で ",[26,806,676],{}," の二重計上を撃ち抜かれた経験は、今日いちばんの節約だった",[743,809,810,615,813,815],{},[34,811,812],{},"HMR を信じない",[26,814,228],{}," の構造変更は dev サーバ再起動で確認する習慣を固める",[743,817,818,821],{},[34,819,820],{},"「列単位集約モーダル」の設計判断は、書籍の構造から逆算した","。セル単位の素直な実装が、必ずしもドメインの本質と一致するわけではない",[18,823,824],{"id":824},"明日やること",[740,826,829,839,845],{"className":827},[828],"contains-task-list",[743,830,833,838],{"className":831},[832],"task-list-item",[834,835],"input",{"disabled":836,"type":837},true,"checkbox"," feature-slides で生成した65スライドを noteの下書きに変換する",[743,840,842,844],{"className":841},[832],[834,843],{"disabled":836,"type":837}," I-3-1〜I-3-3 の補助科目あり版のスクショを計画書に貼る",[743,846,848,850],{"className":847},[832],[834,849],{"disabled":836,"type":837}," 連結精算表のモバイル表示で、横スクロールが発生しないかブラウザ実機で確認する",[852,853,854],"style",{},"html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}html pre.shiki code .sSkh3, html code.shiki .sSkh3{--shiki-default:#2E8F82;--shiki-dark:#2E8F82}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}html pre.shiki code .s4oTP, html code.shiki .s4oTP{--shiki-default:#B07D48;--shiki-dark:#B07D48}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);}html pre.shiki code .senZ8, html code.shiki .senZ8{--shiki-default:#59873A;--shiki-dark:#59873A}html pre.shiki code .sMJiu, html code.shiki .sMJiu{--shiki-default:#B5695977;--shiki-dark:#B5695977}html pre.shiki code .sdGka, html code.shiki .sdGka{--shiki-default:#B56959;--shiki-dark:#B56959}html pre.shiki code .sHkkW, html code.shiki .sHkkW{--shiki-default:#1E754F;--shiki-dark:#1E754F}html pre.shiki code .sz8Xr, html code.shiki .sz8Xr{--shiki-default:#998418;--shiki-dark:#998418}html pre.shiki code .sM54T, html code.shiki .sM54T{--shiki-default:#2F798A;--shiki-dark:#2F798A}",{"title":48,"searchDepth":62,"depth":62,"links":856},[857,858,859,860,861,862,863],{"id":20,"depth":62,"text":21},{"id":494,"depth":62,"text":495},{"id":607,"depth":62,"text":608},{"id":689,"depth":62,"text":690},{"id":726,"depth":62,"text":726},{"id":790,"depth":62,"text":790},{"id":824,"depth":62,"text":824},"dev","HTMLプロトタイプを Vue + 型付きデータに置き換え、設例I-2-1〜I-3-3を全6本実装。Codex GPT-5.5の再帰レビューで二重計上の致命点を潰し、列単位集約モーダルまで設計した1日の記録","md",{},"/2026-04-30-consolidated-accounting-vue-implementation","eurekapu-nuxt4",false,"2026-04-30T00:00:00.000Z",{"title":5,"description":865},"2026-04/2026-04-30/consolidated-accounting-vue-implementation",[875,876,877,878,879],"Vue","連結会計","テスト駆動","Codexレビュー","リファクタリング","memo",null,"aKtOo-N8_16KaXE-8XqTfKGqEq9QN4HB75NS6P2SsuI",[],"https://log.eurekapu.com/favicon.svg",1777617050319]