/* ================================================================ 合并财务报表智能工作台 v3.0 引擎:权益法调整 → 长投与权益抵销 → 投资收益与利润分配抵销 → 内部交易智能矩阵(含递延所得税/坏账/少数股东分摊) 支持报表数手工覆盖(override)并自动向上汇总传播 ================================================================ */ /* ============ 报表项目定义(财会〔2019〕6号一般企业格式) ============ */ const FC=(...ks)=>ks.map(k=>[k,1]); const BS_ITEMS=[ {k:'sec_a',sec:'流动资产:'}, {k:'monetary',s:'A',n:'货币资金'},{k:'tfa',s:'A',n:'交易性金融资产'},{k:'dfa',s:'A',n:'衍生金融资产'}, {k:'nr',s:'A',n:'应收票据'},{k:'ar',s:'A',n:'应收账款'},{k:'arf',s:'A',n:'应收款项融资'}, {k:'prepay',s:'A',n:'预付款项'},{k:'othRec',s:'A',n:'其他应收款'},{k:'inv',s:'A',n:'存货'}, {k:'contractA',s:'A',n:'合同资产'},{k:'hfs',s:'A',n:'持有待售资产'},{k:'nca1y',s:'A',n:'一年内到期的非流动资产'},{k:'othCA',s:'A',n:'其他流动资产'}, {k:'tca',s:'A',n:'流动资产合计',sub:1,f:FC('monetary','tfa','dfa','nr','ar','arf','prepay','othRec','inv','contractA','hfs','nca1y','othCA')}, {k:'sec_na',sec:'非流动资产:'}, {k:'debtInv',s:'A',n:'债权投资'},{k:'othDebtInv',s:'A',n:'其他债权投资'},{k:'ltRec',s:'A',n:'长期应收款'}, {k:'ltip',s:'A',n:'长期股权投资'},{k:'othEqInv',s:'A',n:'其他权益工具投资'},{k:'othNcfA',s:'A',n:'其他非流动金融资产'}, {k:'invProp',s:'A',n:'投资性房地产'},{k:'fa',s:'A',n:'固定资产'},{k:'cip',s:'A',n:'在建工程'}, {k:'ba',s:'A',n:'生产性生物资产'},{k:'oga',s:'A',n:'油气资产'},{k:'rou',s:'A',n:'使用权资产'}, {k:'ia',s:'A',n:'无形资产'},{k:'devExp',s:'A',n:'开发支出'},{k:'gw',s:'A',n:'商誉'}, {k:'ltDef',s:'A',n:'长期待摊费用'},{k:'dta',s:'A',n:'递延所得税资产'},{k:'othNCA',s:'A',n:'其他非流动资产'}, {k:'tnca',s:'A',n:'非流动资产合计',sub:1,f:FC('debtInv','othDebtInv','ltRec','ltip','othEqInv','othNcfA','invProp','fa','cip','ba','oga','rou','ia','devExp','gw','ltDef','dta','othNCA')}, {k:'ta',s:'A',n:'资产总计',grand:1,f:[['tca',1],['tnca',1]]}, {k:'sec_l',sec:'流动负债:'}, {k:'stb',s:'L',n:'短期借款'},{k:'tfl',s:'L',n:'交易性金融负债'},{k:'dfl',s:'L',n:'衍生金融负债'}, {k:'np',s:'L',n:'应付票据'},{k:'ap',s:'L',n:'应付账款'},{k:'adv',s:'L',n:'预收款项'},{k:'contractL',s:'L',n:'合同负债'}, {k:'payrol',s:'L',n:'应付职工薪酬'},{k:'taxP',s:'L',n:'应交税费'},{k:'othPay',s:'L',n:'其他应付款'}, {k:'hfsL',s:'L',n:'持有待售负债'},{k:'ncl1y',s:'L',n:'一年内到期的非流动负债'},{k:'othCL',s:'L',n:'其他流动负债'}, {k:'tcl',s:'L',n:'流动负债合计',sub:1,f:FC('stb','tfl','dfl','np','ap','adv','contractL','payrol','taxP','othPay','hfsL','ncl1y','othCL')}, {k:'sec_nl',sec:'非流动负债:'}, {k:'ltb',s:'L',n:'长期借款'},{k:'bond',s:'L',n:'应付债券'},{k:'leaseL',s:'L',n:'租赁负债'},{k:'ltPay',s:'L',n:'长期应付款'}, {k:'prov',s:'L',n:'预计负债'},{k:'defInc',s:'L',n:'递延收益'},{k:'dtl',s:'L',n:'递延所得税负债'},{k:'othNCL',s:'L',n:'其他非流动负债'}, {k:'tncl',s:'L',n:'非流动负债合计',sub:1,f:FC('ltb','bond','leaseL','ltPay','prov','defInc','dtl','othNCL')}, {k:'tl',s:'L',n:'负债合计',grand:1,f:[['tcl',1],['tncl',1]]}, {k:'sec_e',sec:'所有者权益(或股东权益):'}, {k:'paidIn',s:'E',n:'实收资本(或股本)'},{k:'othEq',s:'E',n:'其他权益工具'}, {k:'pref',s:'E',n:'其中:优先股',memo:1},{k:'perp',s:'E',n:'    永续债',memo:1}, {k:'capRes',s:'E',n:'资本公积'},{k:'treas',s:'E',n:'减:库存股',minus:1},{k:'oci',s:'E',n:'其他综合收益'}, {k:'spres',s:'E',n:'专项储备'},{k:'surRes',s:'E',n:'盈余公积'},{k:'retEarn',s:'E',n:'未分配利润',sp:1}, {k:'tparEq',s:'E',n:'归属于母公司所有者权益合计',sub:1,f:[['paidIn',1],['othEq',1],['capRes',1],['oci',1],['spres',1],['surRes',1],['retEarn',1],['treas',-1]]}, {k:'minEq',s:'E',n:'少数股东权益',sp:1}, {k:'teq',s:'E',n:'所有者权益合计',grand:1,f:[['tparEq',1],['minEq',1]]}, {k:'tle',s:'L',n:'负债和所有者权益总计',grand:1,f:[['tl',1],['teq',1]]} ]; const IS_ITEMS=[ {k:'rev',s:'I',n:'一、营业收入'},{k:'cost',s:'X',n:'减:营业成本'},{k:'tax',s:'X',n:'  税金及附加'}, {k:'sell',s:'X',n:'  销售费用'},{k:'adm',s:'X',n:'  管理费用'},{k:'rd',s:'X',n:'  研发费用'},{k:'fin',s:'X',n:'  财务费用'}, {k:'intexp',s:'X',n:'    其中:利息费用',memo:1},{k:'intinc',s:'I',n:'      利息收入',memo:1}, {k:'othInc',s:'I',n:'加:其他收益'},{k:'invInc',s:'I',n:'  投资收益(损失以“-”号填列)'}, {k:'assocInc',s:'I',n:'    其中:对联营企业和合营企业的投资收益',memo:1}, {k:'hedge',s:'I',n:'  净敞口套期收益(损失以“-”号填列)'}, {k:'fvInc',s:'I',n:'  公允价值变动收益(损失以“-”号填列)'}, {k:'credImp',s:'X',n:'  信用减值损失(损失金额以“+”填列)'},{k:'imp',s:'X',n:'  资产减值损失(损失金额以“+”填列)'}, {k:'dispInc',s:'I',n:'  资产处置收益(损失以“-”号填列)'}, {k:'opProfit',s:'I',n:'二、营业利润(亏损以“-”号填列)',sub:1,f:[['rev',1],['cost',-1],['tax',-1],['sell',-1],['adm',-1],['rd',-1],['fin',-1],['othInc',1],['invInc',1],['hedge',1],['fvInc',1],['credImp',-1],['imp',-1],['dispInc',1]]}, {k:'nonOpInc',s:'I',n:'加:营业外收入'},{k:'nonOpExp',s:'X',n:'减:营业外支出'}, {k:'totProfit',s:'I',n:'三、利润总额(亏损总额以“-”号填列)',sub:1,f:[['opProfit',1],['nonOpInc',1],['nonOpExp',-1]]}, {k:'incTax',s:'X',n:'减:所得税费用'}, {k:'netProfit',s:'I',n:'四、净利润(净亏损以“-”号填列)',sub:1,f:[['totProfit',1],['incTax',-1]]}, {k:'npCont',s:'I',n:'(一)持续经营净利润(净亏损以“-”号填列)',memo:1},{k:'npDisc',s:'I',n:'(二)终止经营净利润(净亏损以“-”号填列)',memo:1}, {k:'parInt',s:'I',n:'(一)归属于母公司所有者的净利润',sub:1,f:[['netProfit',1],['minInt',-1]]}, {k:'minInt',s:'I',n:'(二)少数股东损益',sp:1}, {k:'ociNet',s:'I',n:'五、其他综合收益的税后净额'}, {k:'tci',s:'I',n:'六、综合收益总额',grand:1,f:[['netProfit',1],['ociNet',1]]}, {k:'tciP',s:'I',n:'  归属于母公司所有者的综合收益总额',sub:1,f:[['tci',1],['tciM',-1]]}, {k:'tciM',s:'I',n:'  归属于少数股东的综合收益总额',sp:1}, {k:'epsB',s:'I',n:'七、基本每股收益(元/股)',memo:1},{k:'epsD',s:'I',n:'  稀释每股收益(元/股)',memo:1} ]; const CF_ITEMS=[ {k:'sec1',sec:'一、经营活动产生的现金流量:'}, {k:'cashSales',s:'I',n:'销售商品、提供劳务收到的现金'},{k:'taxRefund',s:'I',n:'收到的税费返还'},{k:'othOpIn',s:'I',n:'收到其他与经营活动有关的现金'}, {k:'opIn',s:'I',n:'经营活动现金流入小计',sub:1,f:FC('cashSales','taxRefund','othOpIn')}, {k:'cashPurch',s:'X',n:'购买商品、接受劳务支付的现金'},{k:'cashEmpl',s:'X',n:'支付给职工以及为职工支付的现金'},{k:'cashTax',s:'X',n:'支付的各项税费'},{k:'othOpOut',s:'X',n:'支付其他与经营活动有关的现金'}, {k:'opOut',s:'X',n:'经营活动现金流出小计',sub:1,f:FC('cashPurch','cashEmpl','cashTax','othOpOut')}, {k:'opNet',s:'I',n:'经营活动产生的现金流量净额',sub:1,f:[['opIn',1],['opOut',-1]]}, {k:'sec2',sec:'二、投资活动产生的现金流量:'}, {k:'invRecov',s:'I',n:'收回投资收到的现金'},{k:'invIncRecv',s:'I',n:'取得投资收益收到的现金'},{k:'faDisp',s:'I',n:'处置固定资产、无形资产和其他长期资产收回的现金净额'},{k:'subDisp',s:'I',n:'处置子公司及其他营业单位收到的现金净额'},{k:'othInvIn',s:'I',n:'收到其他与投资活动有关的现金'}, {k:'invIn',s:'I',n:'投资活动现金流入小计',sub:1,f:FC('invRecov','invIncRecv','faDisp','subDisp','othInvIn')}, {k:'faPurch',s:'X',n:'购建固定资产、无形资产和其他长期资产支付的现金'},{k:'invPaid',s:'X',n:'投资支付的现金'},{k:'subAcq',s:'X',n:'取得子公司及其他营业单位支付的现金净额'},{k:'othInvOut',s:'X',n:'支付其他与投资活动有关的现金'}, {k:'invOut',s:'X',n:'投资活动现金流出小计',sub:1,f:FC('faPurch','invPaid','subAcq','othInvOut')}, {k:'invNet',s:'I',n:'投资活动产生的现金流量净额',sub:1,f:[['invIn',1],['invOut',-1]]}, {k:'sec3',sec:'三、筹资活动产生的现金流量:'}, {k:'capRecv',s:'I',n:'吸收投资收到的现金'},{k:'loanRecv',s:'I',n:'取得借款收到的现金'},{k:'othFinIn',s:'I',n:'收到其他与筹资活动有关的现金'}, {k:'finIn',s:'I',n:'筹资活动现金流入小计',sub:1,f:FC('capRecv','loanRecv','othFinIn')}, {k:'minCapRecv',s:'I',n:'  其中:子公司吸收少数股东投资收到的现金',sp:1,memo:1}, {k:'debtRepay',s:'X',n:'偿还债务支付的现金'},{k:'divPaid',s:'X',n:'分配股利、利润或偿付利息支付的现金'},{k:'othFinOut',s:'X',n:'支付其他与筹资活动有关的现金'}, {k:'finOut',s:'X',n:'筹资活动现金流出小计',sub:1,f:FC('debtRepay','divPaid','othFinOut')}, {k:'minDivPaid',s:'X',n:'  其中:子公司支付给少数股东的股利、利润',sp:1,memo:1}, {k:'finNet',s:'I',n:'筹资活动产生的现金流量净额',sub:1,f:[['finIn',1],['finOut',-1]]}, {k:'fxEffect',s:'I',n:'四、汇率变动对现金及现金等价物的影响'}, {k:'netInc',s:'I',n:'五、现金及现金等价物净增加额',grand:1,f:[['opNet',1],['invNet',1],['finNet',1],['fxEffect',1]]}, {k:'begCash',s:'I',n:'加:期初现金及现金等价物余额'}, {k:'endCash',s:'I',n:'六、期末现金及现金等价物余额',grand:1,f:[['netInc',1],['begCash',1]]} ]; const LIST={bs:BS_ITEMS,is:IS_ITEMS,cf:CF_ITEMS}; const RPT_NAME={bs:'资产负债表',is:'利润表',cf:'现金流量表'}; const IM={bs:{},is:{},cf:{}}; Object.keys(LIST).forEach(r=>LIST[r].forEach(it=>{if(!it.sec)IM[r][it.k]=it;})); /* 特殊合并行(由引擎计算,自动分录不可直接命中) */ const SPSET={retEarn:1,minEq:1,minInt:1,tciM:1,minCapRecv:1,minDivPaid:1}; /* 个别报表/底稿分公司列显示为“—”的行(retEarn 在个别报表中为正常录入项,不在此列) */ const SPFMT={minEq:1,parInt:1,minInt:1,tciP:1,tciM:1,minCapRecv:1,minDivPaid:1}; /* 工作底稿分公司列与合计列额外隐藏未分配利润(其合并数由引擎轧算) */ const WS_SP={retEarn:1,minEq:1,parInt:1,minInt:1,tciP:1,tciM:1,minCapRecv:1,minDivPaid:1}; function signOf(rpt,it){if(rpt==='cf')return it.s==='I'?1:-1;return (it.s==='A'||it.s==='X'||it.minus)?1:-1;} /* ============ 工具 ============ */ function num(v){const n=parseFloat(v);return isNaN(n)?0:n;} function r2(x){return Math.round((num(x)+Number.EPSILON)*100)/100;} function esc(s){return String(s==null?'':s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"');} function fmtP(n){return num(n).toLocaleString('zh-CN',{minimumFractionDigits:2,maximumFractionDigits:2});} function uid(){return 'id'+(++state.seq)+'_'+Math.random().toString(36).slice(2,8);} const UNIT_F={'元':1,'千元':1e3,'万元':1e4,'百万元':1e6}; function UF(){return UNIT_F[state.meta.unit]||1;} function dnum(n){return num(n)/UF().toLocaleString?num(n)/UF():0;} function dstr(n){const v=num(n)/UF();return v.toLocaleString('zh-CN',{minimumFractionDigits:2,maximumFractionDigits:2});} function dcell(n,opt){opt=opt||{};if(Math.abs(num(n))<0.005)return opt.zero!=null?opt.zero:'0.00';const s=dstr(n);return num(n)<0?''+s+'':s;} function itemName(rpt,k){const it=IM[rpt]&&IM[rpt][k];return it?it.n.replace(/^[\s ]+/,''):k;} function todayStr(){const t=new Date();return t.getFullYear()+'-'+String(t.getMonth()+1).padStart(2,'0')+'-'+String(t.getDate()).padStart(2,'0');} /* ============ 状态与持久化 ============ */ const LS_DATA='consol_pro_data_v3',LS_UI='consol_pro_ui_v3'; let state=null,ui={theme:'light',accent:'indigo',collapsed:false}; function defState(){return{ meta:{co:'',period:'',date:todayStr(),unit:'元',taxRate:25,defTax:true,showMemo:true,showSign:true,showParent:true,showZero:true,editMode:true}, companies:[],data:{},tx:[],entries:[],ovr:{},stData:0,stGen:0,seq:0};} let saveTimer=null; function save(){try{localStorage.setItem(LS_DATA,JSON.stringify(state));const el=document.getElementById('saveSt');if(el)el.textContent='已自动保存 '+new Date().toLocaleTimeString('zh-CN',{hour12:false});}catch(e){}} function saveSoon(){clearTimeout(saveTimer);saveTimer=setTimeout(save,400);} function markData(){state.stData=Date.now();saveSoon();} function loadData(){try{const s=localStorage.getItem(LS_DATA);if(s){const o=JSON.parse(s);if(o&&o.meta){state=Object.assign(defState(),o);state.meta=Object.assign(defState().meta,o.meta||{});return;}}}catch(e){}state=defState();} function loadUi(){try{const s=localStorage.getItem(LS_UI);if(s){const o=JSON.parse(s);if(o)ui=Object.assign(ui,o);}}catch(e){}} function saveUi(){try{localStorage.setItem(LS_UI,JSON.stringify(ui));}catch(e){}} function applyUi(){document.body.classList.toggle('dark',ui.theme==='dark');document.documentElement.dataset.ac=ui.accent;document.body.classList.toggle('edit-on',!!state.meta.editMode);applySide();const b=document.getElementById('themeBtn');if(b)b.textContent=ui.theme==='dark'?'☀️':'🌙';renderAccents();} /* ---- 侧栏折叠(状态随主题偏好一起持久化) ---- */ function applySide(){document.body.classList.toggle('side-min',!!ui.collapsed); const t=document.getElementById('sideToggle');if(t)t.title=(ui.collapsed?'展开':'折叠')+'侧边栏(Ctrl+B)';} function toggleSide(){ui.collapsed=!ui.collapsed;saveUi();applySide();} function setTheme(t){ui.theme=t;saveUi();applyUi();} function toggleTheme(){setTheme(ui.theme==='dark'?'light':'dark');} function setAccent(a){ui.accent=a;saveUi();applyUi();} function renderAccents(){const box=document.getElementById('accents');if(!box)return; const arr=[['indigo','#6366f1'],['blue','#0ea5e9'],['teal','#14b8a6'],['rose','#f43f5e'],['amber','#f59e0b'],['graphite','#64748b']]; box.innerHTML=arr.map(a=>``).join('');} /* ============ toast ============ */ let toastTimer=null; function toast(t,type){const el=document.getElementById('toast');if(!el)return;el.textContent=t;el.className='show '+(type||'');clearTimeout(toastTimer);toastTimer=setTimeout(()=>{el.className='';},3200);} /* ============ 公司 ============ */ function parentCo(){return state.companies.find(c=>c.type==='parent')||null;} function subCos(){return state.companies.filter(c=>c.type==='sub'&&c.inScope!==false);} function allCos(){const p=parentCo();return p?[p].concat(subCos()):subCos();} function coById(id){return state.companies.find(c=>c.id===id)||null;} function r(c){return Math.min(1,Math.max(0,num(c.ratio))/100);} function gv(cid,rpt,k){const d=state.data[cid];return d&&d[rpt]?num(d[rpt][k]):0;} function setV(cid,rpt,k,v){if(!state.data[cid])state.data[cid]={bs:{},is:{},cf:{}};if(!state.data[cid][rpt])state.data[cid][rpt]={};state.data[cid][rpt][k]=num(v);} function subEq(c){return ['paidIn','othEq','capRes','oci','spres','surRes','retEarn'].reduce((a,k)=>a+gv(c.id,'bs',k),0)-gv(c.id,'bs','treas');} function npOf(c){return calcSheet(c.id,'is').netProfit;} function gwOf(c){if(c.goodwill!=null&&c.goodwill!=='')return num(c.goodwill);return Math.max(0,num(c.cost)-num(c.acqEquity)*r(c));} function adjAmt(c){return (npOf(c)-num(c.div))*r(c);} /* ============ 个别报表计算 ============ */ function calcSheet(cid,rpt){ const out={};const items=LIST[rpt]; function val(k){if(out[k]!==undefined)return out[k];out[k]=0; const it=IM[rpt][k];if(!it)return 0; if(it.sp&&k!=='retEarn'){return 0;} if(it.f){let s=0;it.f.forEach(t=>{s+=t[1]*val(t[0]);});out[k]=r2(s);return out[k];} out[k]=gv(cid,rpt,k);return out[k];} items.forEach(it=>{if(!it.sec)val(it.k);}); return out;} /* ============ 内部交易矩阵 → 抵销分录 ============ */ function saleU(t){if(t.mode==='profit')return Math.max(0,num(t.v2));return Math.max(0,(num(t.v1)-num(t.v2))*num(t.v3||0)/100);} function buildTxEntry(t){ const from=coById(t.from),to=coById(t.to); if(!from||!to||from.id===to.id)return null; const TR=state.meta.defTax?num(state.meta.taxRate)/100:0; const lines=[];const tag=from.name+' → '+to.name; const A=(rpt,k,side,amt,note)=>{if(Math.abs(num(amt))>0.005)lines.push({rpt,k,side,amt:r2(num(amt)),note:note||''});}; if(t.type==='debt'){ const bal=num(t.v1);if(bal<=0.005)return null; const P={ar:['ap','ar'],othRec:['othPay','othRec'],nr:['np','nr'],prepay:['contractL','prepay']}[t.kind||'ar']; A('bs',P[0],'dr',bal,'债务方账面');A('bs',P[1],'cr',bal,'债权方账面'); const bad=num(t.v2); if(bad>0.005){A('bs',P[1],'dr',bad,'冲回内部坏账准备');A('is','credImp','cr',bad,'冲回信用减值损失'); if(TR>0){A('is','incTax','dr',bad*TR,'转回递延所得税');A('bs','dta','cr',bad*TR,'冲回递延所得税资产');}} return {name:'内部债权债务抵销:'+tag,lines}; } if(t.type==='sale'){ const rev=num(t.v1);if(rev<=0.005)return null; const u=saleU(t); A('is','rev','dr',rev,'内部收入');A('is','cost','cr',rev-u,'内部成本'); if(u>0.005){A('bs','inv','cr',u,'未实现内部利润'); const T=u*TR;if(T>0.005){A('bs','dta','dr',T,'未实现利润递延所得税');A('is','incTax','cr',T,'递延所得税');}} const cf=num(t.v4);if(cf>0.005){A('cf','cashPurch','dr',cf,'购货方付现');A('cf','cashSales','cr',cf,'销货方收现');} const dir=from.type==='sub'?'逆流':'顺流'; return {name:'内部商品购销抵销('+dir+'):'+tag+(u>0.005?',未实现利润 '+fmtP(u):''),lines}; } if(t.type==='fa'){ const G=num(t.v1),D=num(t.v2); if(Math.abs(G)<=0.005&&Math.abs(D)<=0.005)return null; if(G>0.005){A('is','dispInc','dr',G,'未实现处置收益');A('bs','fa','cr',G,'调减固定资产');} if(G<-0.005){A('is','dispInc','cr',-G,'未实现处置损失');A('bs','fa','dr',-G,'调增固定资产');} if(D>0.005){A('bs','fa','dr',D,'冲回多提折旧');A('is','adm','cr',D,'冲减多提折旧费用');} const net=G-D,T=Math.max(0,net)*TR; if(T>0.005){A('bs','dta','dr',T,'未实现损益递延所得税');A('is','incTax','cr',T,'递延所得税');} const cf=num(t.v3);if(cf>0.005){A('cf','faPurch','dr',cf,'购建方付现');A('cf','faDisp','cr',cf,'处置方收现');} const dir=from.type==='sub'?'逆流':'顺流'; return {name:'内部固定资产交易抵销('+dir+'):'+tag,lines}; } if(t.type==='loan'){ const it=num(t.v1),cf=num(t.v2); if(it>0.005){A('is','invInc','dr',it,'内部利息收入');A('is','fin','cr',it,'内部利息支出');} if(cf>0.005){A('cf','othInvOut','dr',cf,'借出方付现');A('cf','loanRecv','cr',cf,'借入方收现');} if(!lines.length)return null; return {name:'内部资金借贷及利息抵销:'+tag,lines}; } return null; } /* 汇总全部交易的未实现损益信息(顺流/逆流/坏账) */ function txInfo(){ const TR=state.meta.defTax?num(state.meta.taxRate)/100:0; const info={TR,downU:0,downT:0,up:[],badP:0,badUp:[]}; state.tx.forEach(t=>{ const from=coById(t.from);if(!from||!coById(t.to)||t.from===t.to)return; if(t.type==='sale'){const u=saleU(t);if(u<=0.005)return;const T=u*TR; if(from.type==='sub')info.up.push({r:r(from),u:u,T:T});else{info.downU+=u;info.downT+=T;}} else if(t.type==='fa'){const net=num(t.v1)-num(t.v2);if(Math.abs(net)<=0.005)return;const T=Math.max(0,net)*TR; if(from.type==='sub')info.up.push({r:r(from),u:net,T:T});else{info.downU+=net;info.downT+=T;}} else if(t.type==='debt'){const bad=num(t.v2);if(bad<=0.005)return;const bn=bad*(1-TR); if(from.type==='sub')info.badUp.push({r:r(from),bn:bn});else info.badP+=bn;} }); return info; } /* ============ 一键生成全部分录 ============ */ function genAll(silent){ const P=parentCo(),subs=subCos(); if(!P){toast('请先在「① 集团与参数」中确认母公司','err');return false;} const warns=[]; subs.forEach(c=>{ const has=state.data[c.id]&&Object.keys(state.data[c.id].bs||{}).length>0; if(!has)warns.push(c.name+':尚未录入个别资产负债表'); else if(Math.abs(subEq(c))<0.005)warns.push(c.name+':所有者权益合计为0,请核对报表'); if(Math.abs(num(c.cost))<0.005)warns.push(c.name+':未填写母公司长期股权投资成本'); }); if(!silent&&warns.length&&!confirm('智能校验发现以下问题:\n\n· '+warns.join('\n· ')+'\n\n仍要继续生成分录吗?'))return false; state.entries=state.entries.filter(e=>e.manual); const push=(kind,name,lines)=>{const ls=lines.filter(l=>Math.abs(l.amt)>0.005);if(ls.length)state.entries.push({id:uid(),auto:1,kind,name,lines:ls});}; /* 1 权益法调整(成本法→权益法) */ subs.forEach(c=>{const a=adjAmt(c);if(Math.abs(a)<=0.005)return; push('调整','权益法调整:确认对'+c.name+'投资的净损益份额(调增长投与投资收益)',[ {rpt:'bs',k:'ltip',side:'dr',amt:r2(a),note:'长投调增'}, {rpt:'is',k:'invInc',side:'cr',amt:r2(a),note:'按比例确认投资收益'}]);}); /* 2 长投与子公司所有者权益抵销 */ subs.forEach(c=>{ const lines=[]; ['paidIn','othEq','capRes','oci','spres','surRes'].forEach(k=>{const v=gv(c.id,'bs',k);if(Math.abs(v)>0.005)lines.push({rpt:'bs',k,side:'dr',amt:r2(v),note:'子公司账面'});}); const re=gv(c.id,'bs','retEarn');if(Math.abs(re)>0.005)lines.push({rpt:'bs',k:'retEarn',side:'dr',amt:r2(re),note:'子公司未分配利润'}); const tr=gv(c.id,'bs','treas');if(Math.abs(tr)>0.005)lines.push({rpt:'bs',k:'treas',side:'cr',amt:r2(tr),note:'库存股'}); const g=gwOf(c);if(g>0.005)lines.push({rpt:'bs',k:'gw',side:'dr',amt:r2(g),note:'合并商誉'}); lines.push({rpt:'bs',k:'ltip',side:'cr',amt:r2(num(c.cost)+adjAmt(c)),note:'权益法调整后长投余额'}); const mi=r2(subEq(c)*(1-r(c)));if(Math.abs(mi)>0.005)lines.push({rpt:'bs',k:'minEq',side:'cr',amt:mi,note:'少数股东权益'}); let dr=0,cr=0;lines.forEach(l=>{if(l.side==='dr')dr+=l.amt;else cr+=l.amt;}); const d=r2(dr-cr);if(Math.abs(d)>0.005)lines.push({rpt:'bs',k:'capRes',side:d>0?'cr':'dr',amt:Math.abs(d),note:'合并差额(商誉为手工指定时产生)'}); push('抵销','母公司长期股权投资与'+c.name+'所有者权益抵销'+(g>0.005?'(含商誉 '+fmtP(g)+')':''),lines);}); /* 3 投资收益与子公司利润分配抵销 */ subs.forEach(c=>{ const p=npOf(c),rr=r(c),lines=[]; if(Math.abs(p*rr)>0.005)lines.push({rpt:'is',k:'invInc',side:'dr',amt:r2(p*rr),note:'母公司份额'}); if(Math.abs(p*(1-rr))>0.005)lines.push({rpt:'is',k:'minInt',side:'dr',amt:r2(p*(1-rr)),note:'少数股东损益'}); if(Math.abs(num(c.begRE))>0.005)lines.push({rpt:'bs',k:'retEarn',side:'dr',amt:r2(num(c.begRE)),note:'年初未分配利润'}); if(Math.abs(num(c.curSC))>0.005)lines.push({rpt:'bs',k:'retEarn',side:'cr',amt:r2(num(c.curSC)),note:'提取盈余公积'}); if(Math.abs(num(c.div))>0.005)lines.push({rpt:'bs',k:'retEarn',side:'cr',amt:r2(num(c.div)),note:'对所有者的分配'}); const end=gv(c.id,'bs','retEarn');if(Math.abs(end)>0.005)lines.push({rpt:'bs',k:'retEarn',side:'cr',amt:r2(end),note:'年末未分配利润'}); push('抵销','母公司对'+c.name+'投资收益与子公司利润分配抵销',lines);}); /* 4 内部交易智能矩阵 */ state.tx.forEach(t=>{const e=buildTxEntry(t);if(e)push('抵销',e.name,e.lines);}); /* 5 子公司向母公司分红收付现金抵销(少数股东部分保留) */ subs.forEach(c=>{const d=num(c.div)*r(c);if(d<=0.005)return; push('抵销','内部股利收付现金抵销:'+c.name+' → 母公司',[ {rpt:'cf',k:'divPaid',side:'dr',amt:r2(d),note:'母公司应得份额'}, {rpt:'cf',k:'invIncRecv',side:'cr',amt:r2(d),note:'母公司投资收益收现'}]);}); state.stGen=Date.now();save(); toast('已生成 '+state.entries.filter(e=>e.auto).length+' 笔自动分录(手工分录已保留)','ok'); if(document.getElementById('entriesBox')!==null||document.querySelector('.page.on')&&document.querySelector('.page.on').id==='p3')renderP3(); return true; } function oneKey(){if(genAll(true))switchTab('p5');} /* ============ 核心合并引擎 ============ */ function computeAll(){ const cos=allCos(),P=parentCo(),subs=subCos(); const per={};cos.forEach(c=>{per[c.id]={bs:calcSheet(c.id,'bs'),is:calcSheet(c.id,'is'),cf:calcSheet(c.id,'cf')};}); const sums={bs:{},is:{},cf:{}}; ['bs','is','cf'].forEach(rpt=>{LIST[rpt].forEach(it=>{if(it.sec||it.f||it.sp)return; let s=0;cos.forEach(c=>{s+=num(per[c.id][rpt][it.k]);});sums[rpt][it.k]=r2(s);});}); /* 分录聚合:调整(权益法)与抵销分开列示;自动分录不可命中公式行 */ const adjD={bs:{},is:{},cf:{}},adjC={bs:{},is:{},cf:{}},eliD={bs:{},is:{},cf:{}},eliC={bs:{},is:{},cf:{}}; state.entries.forEach(e=>{ const isAdj=e.kind==='调整';const DD=isAdj?adjD:eliD,CC=isAdj?adjC:eliC; e.lines.forEach(l=>{ if(SPSET[l.k]&&e.auto)return; if(!IM[l.rpt]||!IM[l.rpt][l.k])return; if(l.side==='dr')DD[l.rpt][l.k]=(DD[l.rpt][l.k]||0)+num(l.amt); else CC[l.rpt][l.k]=(CC[l.rpt][l.k]||0)+num(l.amt);});}); function manualNet(rpt,k){let n=0;state.entries.forEach(e=>{if(!e.manual)return; e.lines.forEach(l=>{if(l.rpt===rpt&&l.k===k)n+=(l.side==='dr'?1:-1)*num(l.amt);});}); const it=IM[rpt][k];return it?signOf(rpt,it)*n:0;} const info=txInfo(); /* 特殊行:未分配利润 / 少数股东权益 / 少数股东损益 / 少数股东综合收益 / 少数股东现金流 */ let retEarnC=P?gv(P.id,'bs','retEarn'):0; subs.forEach(c=>{retEarnC+=(subEq(c)-num(c.acqEquity))*r(c);}); retEarnC-=(info.downU-info.downT); info.up.forEach(x=>{retEarnC-=(x.u-x.T)*x.r;}); retEarnC+=info.badP; info.badUp.forEach(x=>{retEarnC+=x.bn*x.r;}); retEarnC=r2(retEarnC+manualNet('bs','retEarn')); let minEqC=0;subs.forEach(c=>{minEqC+=subEq(c)*(1-r(c));}); info.up.forEach(x=>{minEqC-=(x.u-x.T)*(1-x.r);}); info.badUp.forEach(x=>{minEqC-=x.bn*(1-x.r);}); minEqC=r2(minEqC+manualNet('bs','minEq')); let minIntC=0;subs.forEach(c=>{minIntC+=npOf(c)*(1-r(c));}); info.up.forEach(x=>{minIntC-=(x.u-x.T)*(1-x.r);}); info.badUp.forEach(x=>{minIntC-=x.bn*(1-x.r);}); minIntC=r2(minIntC+manualNet('is','minInt')); let tciMC=minIntC;subs.forEach(c=>{tciMC+=gv(c.id,'is','ociNet')*(1-r(c));}); tciMC=r2(tciMC+manualNet('is','tciM')); const minCapRecvC=r2(subs.reduce((a,c)=>a+num(c.minCapCash),0)+manualNet('cf','minCapRecv')); const minDivPaidC=r2(subs.reduce((a,c)=>a+num(c.div)*(1-r(c)),0)+manualNet('cf','minDivPaid')); const spv={bs:{retEarn:retEarnC,minEq:minEqC},is:{minInt:minIntC,tciM:tciMC},cf:{minCapRecv:minCapRecvC,minDivPaid:minDivPaidC}}; const cons={bs:{},is:{},cf:{}},fin={bs:{},is:{},cf:{}}; ['bs','is','cf'].forEach(rpt=>{ const c=cons[rpt]; const resolve=k=>{ if(c[k]!==undefined)return c[k]; c[k]=0; const it=IM[rpt][k];if(!it)return 0; if(it.sp){c[k]=spv[rpt][k]||0;return c[k];} if(it.f){let s=0;it.f.forEach(t=>{s+=t[1]*resolve(t[0]);});c[k]=r2(s);return c[k];} c[k]=r2(num(sums[rpt][k])+signOf(rpt,it)*((adjD[rpt][k]||0)-(adjC[rpt][k]||0)+(eliD[rpt][k]||0)-(eliC[rpt][k]||0))); return c[k];}; LIST[rpt].forEach(it=>{if(!it.sec)resolve(it.k);}); /* 手工覆盖(override)沿公式链向上传播 */ const dm={}; const dl=k=>{if(dm[k]!==undefined)return dm[k];dm[k]=0; const it=IM[rpt][k];let d=0;const ov=state.ovr[rpt+'.'+k]; if(ov!=null&&ov!==''&&isFinite(num(ov)))d+=num(ov)-num(c[k]); if(it&&it.f)it.f.forEach(t=>{d+=t[1]*dl(t[0]);}); dm[k]=d;return d;}; const f=fin[rpt]; LIST[rpt].forEach(it=>{if(!it.sec)f[it.k]=r2(num(c[it.k])+dl(it.k));}); }); return {cos,per,sums,adjD,adjC,eliD,eliC,cons,fin,info}; } /* ============ 智能校验 ============ */ function checksList(){ const C=computeAll();const out=[]; let bad=0;state.entries.forEach(e=>{let d=0,c=0;e.lines.forEach(l=>{if(l.side==='dr')d+=num(l.amt);else c+=num(l.amt);});if(Math.abs(d-c)>0.01)bad++;}); out.push({lvl:bad?'err':(state.entries.length?'ok':'warn'),t:bad?('有 '+bad+' 笔分录借贷不平衡,请修正'):(state.entries.length?('全部 '+state.entries.length+' 笔抵销与调整分录借贷平衡'):'尚未生成抵销分录,请一键生成'),p:'p3'}); const cos=allCos(); cos.forEach(c=>{ const v=C.per[c.id].bs,d=num(v.ta)-num(v.tle); if(Math.abs(num(v.ta))>0.005&&Math.abs(d)>0.01)out.push({lvl:'err',t:c.name+' 个别资产负债表不平衡(差额 '+fmtP(d)+')',p:'p2'}); const cd=num(C.per[c.id].cf.endCash)-num(v.monetary); if(Math.abs(num(v.ta))>0.005&&Math.abs(cd)>0.01)out.push({lvl:'warn',t:c.name+' 现金流量表期末现金与货币资金差异 '+fmtP(cd),p:'p2'});}); subCos().forEach(c=>{ if(Math.abs(subEq(c))<0.005)out.push({lvl:'warn',t:c.name+' 所有者权益为0(个别报表可能未录入)',p:'p2'}); if(Math.abs(num(c.cost))<0.005)out.push({lvl:'warn',t:c.name+' 未填写母公司长期股权投资成本',p:'p1'});}); const dta=num(C.fin.bs.ta)-num(C.fin.bs.tle); out.push({lvl:Math.abs(dta)>0.01?'err':'ok',t:'合并资产负债表:资产总计 '+fmtP(C.fin.bs.ta)+' = 负债和所有者权益总计 '+fmtP(C.fin.bs.tle)+(Math.abs(dta)>0.01?('(差额 '+fmtP(dta)+',请检查是否有未平衡的手工调整)'):'(平衡)'),p:'p5'}); const dnp=num(C.fin.is.netProfit)-num(C.fin.is.parInt)-num(C.fin.is.minInt); out.push({lvl:Math.abs(dnp)>0.01?'err':'ok',t:'合并净利润 '+fmtP(C.fin.is.netProfit)+' = 归母净利润 '+fmtP(C.fin.is.parInt)+' + 少数股东损益 '+fmtP(C.fin.is.minInt),p:'p5'}); const dcash=num(C.fin.cf.endCash)-num(C.fin.bs.monetary); out.push({lvl:Math.abs(dcash)>0.01?(Math.abs(num(C.fin.bs.ta))>0.005?'warn':'ok'):'ok',t:'合并期末现金 '+fmtP(C.fin.cf.endCash)+' | 合并货币资金 '+fmtP(C.fin.bs.monetary)+(Math.abs(dcash)>0.01?('(差异 '+fmtP(dcash)+')'):'(一致)'),p:'p5'}); const chain=num(C.fin.cf.opNet)+num(C.fin.cf.invNet)+num(C.fin.cf.finNet)+num(C.fin.cf.fxEffect)-num(C.fin.cf.netInc); out.push({lvl:Math.abs(chain)>0.01?'err':'ok',t:'现金流量内部勾稽:经营/投资/筹资净额及汇率影响合计 = 现金净增加额 '+(Math.abs(chain)>0.01?('(差额 '+fmtP(chain)+')'):'(通过)'),p:'p5'}); const ovc=Object.keys(state.ovr||{}).length; if(ovc)out.push({lvl:'warn',t:'存在 '+ovc+' 处报表手工调整数,导出与报出前请复核',p:'p5'}); if(state.stGen'+(cs.length?cs.map(c=>`
${esc(c.t)}前往 ›
`).join(''):'
暂无校验项
')+''; } /* ============ 页面框架 ============ */ const TITLES={p0:['工作台总览','集团合并财务报表一站式智能编制'],p1:['① 集团与参数','母子公司架构、持股比例与编制参数'],p2:['② 个别报表录入','母子公司三大报表 · 支持Excel模板批量导入'],p3:['③ 抵销与调整','智能交易矩阵 + 一键生成分录 + 手工调整'],p4:['④ 合并工作底稿','个别数 → 合计数 → 调整抵销 → 合并数 全过程透明'],p5:['⑤ 合并报表输出','三大合并报表 · 可手工修改 · 多格式导出']}; let curTab='p0'; function switchTab(p){curTab=p; document.querySelectorAll('#nav a').forEach(a=>a.classList.toggle('on',a.dataset.p===p)); document.querySelectorAll('.page').forEach(pg=>pg.classList.toggle('on',pg.id===p)); const t=TITLES[p];document.getElementById('topTitle').textContent=t[0];document.getElementById('topSub').textContent=t[1]; renderCur();window.scrollTo(0,0);} function renderCur(){const f={p0:renderP0,p1:renderP1,p2:renderP2,p3:renderP3,p4:renderP4,p5:renderP5};(f[curTab]||renderP0)(); const sel=document.getElementById('unitTop');if(sel){sel.innerHTML=['元','千元','万元','百万元'].map(u=>``).join('');}} /* ============ ⓪ 工作台 ============ */ function renderP0(){ const C=computeAll();const P=parentCo();const subs=subCos();const cos=allCos(); const filled=cos.filter(c=>Math.abs(num(C.per[c.id].bs.ta))>0.005).length; const balanced=Math.abs(num(C.fin.bs.ta)-num(C.fin.bs.tle))<0.01&&Math.abs(num(C.fin.bs.ta))>0.005; const fresh=!state.stGen||state.stGen>=state.stData; const st=(ok,okT,todoT)=>ok?`✔ ${okT}`:`${todoT}`; const h=`

${esc(state.meta.co||'我的企业集团')}合并财务报表智能工作台

📅 ${esc(state.meta.period||'未设置报告期间')}🗓 资产负债表日 ${esc(state.meta.date)}💰 单位:${esc(state.meta.unit)}🧾 法定税率 ${esc(state.meta.taxRate)}%${state.meta.defTax?'(含递延所得税智能计算)':'(未启用递延所得税)'}
🏢
${cos.length}纳入合并主体(母公司1+子公司${subs.length})
🤝
${state.tx.length}已声明内部交易事项
🧮
${state.entries.length}抵销与调整分录
📈
${Math.abs(num(C.fin.bs.ta))>0.005?dstr(C.fin.bs.ta):'—'}合并资产总计(${esc(state.meta.unit)})
${balanced?'✅':'⚠️'}
${Math.abs(num(C.fin.bs.ta))>0.005?(balanced?'平衡':'不平衡'):'待录入'}报表勾稽状态
${fresh?'🔄':'📌'}
${fresh?'分录为最新':'数据已变更'}${fresh?'抵销分录与基础数据同步':'建议重新一键生成分录'}

合并编制流程 · 点击直达

1

集团与参数

${st(subs.length>0,'已纳入 '+subs.length+' 家子公司','待添加子公司')}

设置母子公司、持股比例、长投成本与商誉、宣告股利等合并基础数据。

2

个别报表录入

${st(filled>=cos.length&&cos.length>0,'已录入 '+filled+'/'+cos.length+' 家','已录入 '+filled+'/'+cos.length+' 家')}

三大报表在线录入,或下载Excel模板分发给子公司填报后一键批量导入。

3

抵销与调整

${st(state.entries.length>0&&fresh,'已生成 '+state.entries.length+' 笔','待一键生成')}

声明内部交易事实,系统按准则自动生成权益、利润、递延税与现金流抵销分录。

4

合并工作底稿

${st(state.entries.length>0,'底稿就绪','待生成分录')}

个别数、合计数、调整与抵销、合并数全过程透明可溯源。

5

合并报表输出

${st(balanced,'三大报表已配平','待完成编制')}

正式格式三大报表,支持数字手工修改、Excel/HTML导出与打印。

智能校验中心

自动检测数据完整性与勾稽关系
${checksBoxHTML(checksList())}

数据管理

所有数据自动保存在本机浏览器,建议定期导出JSON备份。
`; document.getElementById('p0').innerHTML=h;} /* ============ ① 集团与参数 ============ */ function updMeta(k,v){if(k==='taxRate')state.meta.taxRate=num(v);else state.meta[k]=v;markData();} function addSub(){state.companies.push({id:uid(),type:'sub',name:'子公司'+(state.companies.filter(c=>c.type==='sub').length+1),ratio:100,method:'nonsame',cost:0,goodwill:null,acqEquity:0,div:0,begRE:0,curSC:0,minCapCash:0,inScope:true});markData();renderP1();} function updCo(id,f,v){const c=coById(id);if(!c)return; if(f==='name'||f==='method')c[f]=v; else if(f==='inScope')c[f]=!!v; else c[f]=(v===''||v==null)?(f==='goodwill'?null:0):num(v); markData();if(f==='ratio'||f==='inScope')renderP1();} function delCo(id){const c=coById(id);if(!c)return; if(!confirm('确定删除「'+c.name+'」?其个别报表数据将一并删除。'))return; state.companies=state.companies.filter(x=>x.id!==id);delete state.data[id]; state.tx=state.tx.filter(t=>t.from!==id&&t.to!==id); markData();renderP1();} function renderP1(){ const P=parentCo();const subs=state.companies.filter(c=>c.type==='sub'); let h=`

集团基础信息与编制参数

💡 录入一律以为单位;上方“报表展示单位”仅影响报表展示与导出换算。启用递延所得税后,内部交易未实现损益将按法定税率自动生成“借:递延所得税资产 / 贷:所得税费用”抵销分录。

母公司(合并主体)

📌 母公司个别报表中「长期股权投资」请按成本法账面余额填列(系统将自动完成成本法→权益法调整)。

纳入合并范围的子公司

`; if(!subs.length)h+=''; subs.forEach(c=>{ const gAuto=gwOf(c); h+=``;}); h+=`
子公司名称持股比例%取得方式母公司长投成本合并商誉(空=自动)取得日权益合计本期宣告股利年初未分配利润提取盈余公积少数股东增资现金纳入合并操作
尚未添加子公司,点击右上角「+ 添加子公司」开始建立合并范围。
📌 权益抵销智能算法:系统按“持股比例、长投成本、商誉”自动生成 ①成本法→权益法调整 ②长投与所有者权益抵销(含商誉与少数股东权益) ③投资收益与利润分配抵销 ④内部股利现金流抵销。
💡 取消勾选“纳入合并”可临时将子公司移出合并范围(数据保留)。
`; document.getElementById('p1').innerHTML=h;} function setParentName(v){let p=parentCo(); if(!p){p={id:uid(),type:'parent',name:v};state.companies.unshift(p);} else p.name=v;markData();} function gotoP2(cid){curCo2=cid;switchTab('p2');} /* ============ ② 个别报表 ============ */ let curCo2=null,curRpt2='bs'; function selCo2(cid){curCo2=cid;renderP2();} function selRpt2(r){curRpt2=r;renderP2();} function renderP2(){ const el=document.getElementById('p2'); if(!allCos().length){el.innerHTML='

个别报表录入

请先在「① 集团与参数」中设置母公司或添加子公司。
';return;} if(!coById(curCo2)||!allCos().some(c=>c.id===curCo2))curCo2=allCos()[0].id; const cid=curCo2,rpt=curRpt2,c=coById(cid); let h=`

填报工作区 · ${esc(c.name)} · ${RPT_NAME[rpt]}

🚀 推荐做法:下载Excel模板 → 发给各子公司填写 → 返回后批量选中多个文件导入,系统按公司名称智能匹配并自动映射三表数据。在线录入时,合计行实时自动计算。
${allCos().map(x=>``).join('')}
${['bs','is','cf'].map(r=>``).join('')}
`; LIST[rpt].forEach(it=>{ if(it.sec){h+=``;return;} if((it.sp&&it.k!=='retEarn')||it.k==='parInt'||it.k==='tciP'){h+=``;return;} if(it.f){h+=``;return;} const v=gv(cid,rpt,it.k); h+=``;}); h+=`
${esc(it.n)}
${esc(it.n)}— (合并层面自动计算)
${esc(it.n)}0.00
${esc(it.n)}${it.memo?' (备注行)':''}
`; el.innerHTML=h;refreshSheetTotals();updBar2();} function refreshSheetTotals(){ const cid=curCo2,rpt=curRpt2;if(!coById(cid))return; const v=calcSheet(cid,rpt); LIST[rpt].forEach(it=>{if(it.f){const td=document.getElementById('tv_'+it.k);if(td)td.textContent=(Math.abs(num(v[it.k]))<0.005?'0.00':fmtP(v[it.k]));}});} function updBar2(){ const bar=document.getElementById('chkBar');if(!bar)return; const cid=curCo2,rpt=curRpt2;const v=calcSheet(cid,rpt);const c=coById(cid); let h=''; if(rpt==='bs'){const d=num(v.ta)-num(v.tle); h=`${Math.abs(num(v.ta))<0.005?'尚未录入':(Math.abs(d)<0.01?'✔ 资产 '+fmtP(v.ta)+' = 负债和权益 '+fmtP(v.tle)+'(平衡)':'✘ 不平衡:差额 '+fmtP(d))}`;} else if(rpt==='is'){h=`净利润 ${fmtP(v.netProfit)}归母/少数拆分在合并层面自动完成`;} else{const d=num(v.endCash)-gv(cid,'bs','monetary'); h=`期末现金 ${fmtP(v.endCash)}${Math.abs(d)<0.01?'✔ 与资产负债表货币资金一致':'⚠ 与货币资金差异 '+fmtP(d)}`;} bar.innerHTML='
'+h+'
';} function onCell(cid,rpt,k,v){setV(cid,rpt,k,v);state.stData=Date.now();saveSoon();refreshSheetTotals();updBar2();} function clearSheet(){ const cid=curCo2,rpt=curRpt2,c=coById(cid); if(!c||!confirm('确定清零「'+c.name+'」的'+RPT_NAME[rpt]+'全部数据?'))return; if(!state.data[cid])state.data[cid]={bs:{},is:{},cf:{}}; state.data[cid][rpt]={};markData();renderP2();toast('已清零','ok');} /* ============ ③ 抵销与调整 ============ */ const MTX_TYPE={debt:{n:'内部债权债务',ic:'🤝'},sale:{n:'内部商品购销',ic:'📦'},fa:{n:'内部固定资产交易',ic:'🏗'},loan:{n:'内部资金借贷',ic:'💰'}}; function addTx(type){const cos=allCos();if(cos.length<1){toast('请先设置公司','err');return;} state.tx.push({id:uid(),type,from:cos[0].id,to:(cos[1]||cos[0]).id,kind:'ar',mode:'ratio',v1:0,v2:0,v3:0,v4:0}); markData();renderP3();} function delTx(id){state.tx=state.tx.filter(t=>t.id!==id);markData();renderP3();} function updTx(id,k,v){const t=state.tx.find(x=>x.id===id);if(!t)return; t[k]=(k==='from'||k==='to'||k==='kind'||k==='mode')?v:num(v); if(k==='mode'&&t.mode==='profit'&&t.type==='sale'){/* 切换口径时清空比例 */t.v3=0;} markData();renderP3();} function txPreviewHtml(t){ const e=buildTxEntry(t); if(!e)return ''; let dr=0,cr=0;e.lines.forEach(l=>{if(l.side==='dr')dr+=l.amt;else cr+=l.amt;}); let s=''; return s;} function renderP3(){ const el=document.getElementById('p3');const cos=allCos(); const fresh=!state.stGen||state.stGen>=state.stData; let h=`

智能分录引擎

系统将按准则自动生成:①成本法→权益法调整 ②长投与所有者权益抵销(商誉/少数股东权益) ③投资收益与利润分配抵销 ④下方内部交易矩阵全部分录 ⑤内部股利现金流抵销。手工分录始终保留,一键生成只刷新自动分录。${fresh?'':'(基础数据已变更,建议重新生成)'}

内部交易智能矩阵 · 像陈述业务一样申报

`; if(!state.tx.length)h+='
暂无内部交易事项。请按业务事实逐项添加,系统将自动判定顺流/逆流、计算未实现损益、计提递延所得税并分摊少数股东损益。
'; else h+='
'+state.tx.map((t,i)=>txCardHTML(t,i)).join('')+'
'; h+='
'; /* 智能分录助手 */ h+=`

智能分录助手 · 自定义借贷

支持选择合并公式行(未分配利润、少数股东权益等)用于跨期调整;系统实时校验借贷平衡。

分录工作底稿(${state.entries.length} 笔)

自动分录修改任意一行后将自动转为手工分录,不会被一键生成覆盖
${entriesHTML()}
`; el.innerHTML=h; renderSmartLines();} function txCardHTML(t,i){ const td=MTX_TYPE[t.type]; const coSel=(k,label)=>`
`; let body=''; if(t.type==='debt'){ body=coSel('from','债权方(应收方)')+coSel('to','债务方(应付方)')+ `
`; }else if(t.type==='sale'){ body=coSel('from','销售方')+coSel('to','购货方')+ `
`+ (t.mode==='profit' ?`
` :`
`)+ `
`; }else if(t.type==='fa'){ body=coSel('from','资产出售方')+coSel('to','资产购入方')+ `
`; }else if(t.type==='loan'){ body=coSel('from','资金借出方')+coSel('to','资金借入方')+ `
`; } const dir=t.type==='sale'||t.type==='fa'?((coById(t.from)||{}).type==='sub'?'逆流':'顺流'):''; return `
${td.ic} 事项 ${i+1} · ${td.n} ${dir}
${body}
${txPreviewHtml(t)}
`;} /* ---- 手工分录 ---- */ function addManual(){state.entries.push({id:uid(),manual:1,kind:'抵销',name:'手工分录 '+(state.entries.filter(e=>e.manual).length+1),lines:[ {rpt:'bs',k:'ap',side:'dr',amt:0,note:''},{rpt:'bs',k:'ar',side:'cr',amt:0,note:''}]}); saveSoon();renderP3();} function convManual(id){const e=state.entries.find(x=>x.id===id);if(!e)return;e.auto=0;e.manual=1;saveSoon();renderP3();toast('已转为手工分录,一键生成不再覆盖它','ok');} function delEntry(id){state.entries=state.entries.filter(x=>x.id!==id);saveSoon();renderP3();} function editEntryName(id,v){const e=state.entries.find(x=>x.id===id);if(e){e.name=v;saveSoon();}} function toManual(e){if(e.auto){e.auto=0;e.manual=1;}} function addLineE(id,side){const e=state.entries.find(x=>x.id===id);if(!e)return; toManual(e);e.lines.push({rpt:'bs',k:'monetary',side,amt:0,note:''});saveSoon();renderP3();} function delLineE(id,idx){const e=state.entries.find(x=>x.id===id);if(!e)return; toManual(e);e.lines.splice(idx,1);saveSoon();renderP3();} function updLineE(id,idx,f,v){const e=state.entries.find(x=>x.id===id);if(!e)return; toManual(e);const l=e.lines[idx];if(!l)return; if(f==='rpt'){l.rpt=v;l.k=(v==='bs'?'monetary':v==='is'?'rev':'cashSales');} else if(f==='k')l.k=v; else if(f==='side')l.side=v; else l.amt=num(v); saveSoon();renderP3();} function itemOptsFor(rpt,sel){ let h='';LIST[rpt].forEach(it=>{ if(it.sec||it.f||it.memo)return; const lbl=(it.sp?'【公式行】':'')+it.n.replace(/^[\s ]+/,''); h+=``;}); return h;} function entriesHTML(){ if(!state.entries.length)return '
尚无分录。点击上方「⚡ 一键生成」由系统智能编制,或「+ 新增手工分录」。
'; let h=''; state.entries.forEach((e,idx)=>{ let dr=0,cr=0;e.lines.forEach(l=>{if(l.side==='dr')dr+=num(l.amt);else cr+=num(l.amt);}); const ok=Math.abs(dr-cr)<0.01; const tag=e.manual?`手工${e.kind==='调整'?'·调整':'·抵销'}`:(e.kind==='调整'?'自动·调整':'自动·抵销'); h+=`
${tag} 借 ${fmtP(dr)} / 贷 ${fmtP(cr)}${ok?' ✓':' ✘'} ${e.auto?``:''}
`; e.lines.forEach((l,i)=>{ const spNote=(SPSET[l.k]&&!e.manual)?' [公式行·不过入合并数,由引擎轧算]':''; if(e.manual){ h+=``; }else{ h+=``;} }); h+='
科目所属报表方向金额(元)备注
${RPT_NAME[l.rpt]}
${l.side==='cr'?'贷:':'借:'}${esc(itemName(l.rpt,l.k))}${spNote}${l.note?' ('+esc(l.note)+')':''} ${RPT_NAME[l.rpt]}${l.side==='dr'?'借':'贷'} ${l.side==='dr'?fmtP(l.amt):''}${l.side==='cr'?fmtP(l.amt):''}
'; if(e.manual)h+=`
`; h+='
'; }); return h;} /* ---- 智能分录助手 ---- */ let smartOpen=false,smartRows=[]; function toggleSmart(){smartOpen=!smartOpen;if(smartOpen&&smartRows.length<2)smartRows=[{rpt:'bs',k:'ap',side:'dr',amt:''},{rpt:'bs',k:'ar',side:'cr',amt:''}];renderP3();} function smartAddLine(){smartRows.push({rpt:'bs',k:'monetary',side:'dr',amt:''});renderSmartLines();} function smartUpd(i,f,v){smartRows[i][f]=f==='amt'?v:v;} function smartDel(i){smartRows.splice(i,1);renderSmartLines();} function renderSmartLines(){ const box=document.getElementById('smartLines');if(!box)return; box.innerHTML=smartRows.map((r,i)=>`
`).join('')+ `
${smartBalText()}
`;} function smartBal(){let d=0,c=0;smartRows.forEach(r=>{const a=num(r.amt);if(!a)return;if(r.side==='dr')d+=a;else c+=a;});return{d:r2(d),c:r2(c)};} function smartBalText(){const b=smartBal();if(!b.d&&!b.c)return '填写借贷金额,系统实时校验平衡';const ok=Math.abs(b.d-b.c)<0.01;return '借方合计 '+fmtP(b.d)+' / 贷方合计 '+fmtP(b.c)+(ok?' ✓ 平衡':' ✘ 差额 '+fmtP(b.d-b.c));} function smartCommit(){ const kind=document.getElementById('smartKind')?document.getElementById('smartKind').value:'抵销'; const lines=smartRows.filter(r=>num(r.amt)>0.005).map(r=>({rpt:r.rpt,k:r.k,side:r.side,amt:r2(num(r.amt)),note:''})); if(lines.length<2){toast('至少需要两行有效分录','err');return;} const b=smartBal(); if(Math.abs(b.d-b.c)>0.01){toast('借贷不平衡:借 '+fmtP(b.d)+' / 贷 '+fmtP(b.c),'err');return;} state.entries.push({id:uid(),manual:1,kind,name:'智能助手·'+kind+'分录 '+(state.entries.filter(e=>e.manual).length+1),lines}); saveSoon();renderP3();toast('分录已加入工作底稿','ok');} /* ============ ④ 合并工作底稿 ============ */ let wsR='bs'; function wsSel(r){wsR=r;renderP4();} function renderP4(){ const el=document.getElementById('p4'); if(!allCos().length){el.innerHTML='
请先设置公司结构。
';return;} const C=computeAll();const cos=C.cos; const csum=(k)=>r2(cos.reduce((a,c)=>a+num(C.per[c.id][wsR][k]),0)); let h=`

合并工作底稿 · ${RPT_NAME[wsR]}

${['bs','is','cf'].map(r=>``).join('')}
${cos.map(c=>``).join('')} `; LIST[wsR].forEach(it=>{ if(it.sec){h+=``;return;} const isSp=WS_SP[it.k]; const cls=it.grand?'grand':(it.sub?'sub':(it.memo?'memo':'')); const aD=C.adjD[wsR][it.k]||0,aC=C.adjC[wsR][it.k]||0,eD=C.eliD[wsR][it.k]||0,eC=C.eliC[wsR][it.k]||0; const finV=C.fin[wsR][it.k]; h+=``; cos.forEach(c=>{h+=isSp?'':'';}); h+=(isSp?'':'') +'' +'' +'';}); h+=`
项目${esc(c.name)}${c.type==='sub'?'
'+(c.ratio==null?100:c.ratio)+'%':''}
合计数调整借方调整贷方抵销借方抵销贷方合并数
${esc(it.n)}
${esc(it.n)}${it.sp?' [公式]':''}'+dcell(C.per[c.id][wsR][it.k],{zero:'—'})+''+dcell(csum(it.k),{zero:'—'})+''+dcell(aD,{zero:'—'})+''+dcell(aC,{zero:'—'})+''+dcell(eD,{zero:'—'})+''+dcell(eC,{zero:'—'})+''+dcell(finV,{zero:'0.00'})+'
💡 合并数 = 合计数 ± 调整与抵销;利润表分录对未分配利润的影响已由引擎在合并层面自动轧算,跨表勾稽无需手工结转。手动修改的报表数字(如有)已反映在“合并数”列。

底稿勾稽校验

${checksBoxHTML(checksList())}
`; el.innerHTML=h;} /* ============ ⑤ 合并报表输出 ============ */ let outR='bs'; function outSel(r){outR=r;renderP5();} function togOpt(k,el){state.meta[k]=!!el.checked;saveSoon();renderP5();} function togEditMode(el){state.meta.editMode=!!el.checked;document.body.classList.toggle('edit-on',!!state.meta.editMode);saveSoon();} function clearOvrs(){const n=Object.keys(state.ovr).length;if(!n){toast('当前没有手工调整数','ok');return;} if(!confirm('确定清除全部 '+n+' 处报表手工调整数,恢复引擎计算结果?'))return; state.ovr={};saveSoon();renderP5();toast('已恢复全部引擎计算数','ok');} function rowNoMap(rpt){const m={};let i=0;LIST[rpt].forEach(it=>{if(!it.sec)m[it.k]=++i;});return m;} function cellTd(rpt,k,fin,cons,edit,extraCls){ const ov=state.ovr[rpt+'.'+k]!=null; const v=fin[k]; return `${dcell(v,{zero:''})}`;} function reportSheetHTML(rpt,edit){ const C=computeAll();const fin=C.fin[rpt];const cons=C.cons[rpt]; const P=parentCo();const rn=rowNoMap(rpt); const coLine='编制单位:'+esc(state.meta.co||'—')+'(合并)'; const unitLine='金额单位:'+esc(state.meta.unit); let h='
'; if(rpt==='bs'){ h+=`
合并资产负债表
编制单位:${esc(state.meta.co||'—')}(合并)
报告日期:${esc(state.meta.date||'—')}
金额单位:${esc(state.meta.unit)}
${state.meta.showParent?``:''}${state.meta.showParent?``:''}`; const L=[],R=[];let side=0; LIST.bs.forEach(it=>{if(it.k==='sec_l')side=1;(side?R:L).push(it);}); const n=Math.max(L.length,R.length); /* 两侧总计数行底部对齐(账户式规范排法) */ const padBottom=arr=>{const d=n-arr.length;if(d<=0)return arr;const out=arr.slice(0,-1);for(let i=0;i{if(!it)return true;if(it.sec)return true; if(it.memo&&!state.meta.showMemo)return false; if(!state.meta.showZero&&(it.f||it.sp||it.memo===undefined)){} if(!state.meta.showZero&&!it.f&&!it.sp&&!it.memo&&Math.abs(num(fin[it.k]))<0.005)return false; return true;}; const cellHtml=(it,isA)=>{ if(!it)return ''+(state.meta.showParent?'':''); if(it.sec)return ''; const cls=it.grand?'grand':(it.sub?'sub':(it.memo?'memo':'')); const no=isA?++lnA:++lnB; const pv=state.meta.showParent?(P?C.per[P.id].bs[it.k]:0):0; const pvCell=state.meta.showParent?``:''; return ``+cellTd('bs',it.k,fin,cons,true,cls)+pvCell;}; for(let i=0;i';} h+='
行次资产期末合并数母公司数行次负债和所有者权益期末合并数母公司数
'+esc(it.n)+'${isSpf('bs',it.k)?'':dcell(pv,{zero:''})}${no}${esc(it.n)}
'; }else{ const ttl=rpt==='is'?'合并利润表':'合并现金流量表'; const perLine=rpt==='is'?'报告期间:'+esc(state.meta.period||'—'):'报告期间:'+esc(state.meta.period||'—'); h+=`
${ttl}
编制单位:${esc(state.meta.co||'—')}(合并)
报告期间:${esc(state.meta.period||state.meta.date||'—')}
金额单位:${esc(state.meta.unit)}
`; LIST[rpt].forEach(it=>{ if(it.sec){h+=``;return;} if(it.memo&&!state.meta.showMemo)return; if(!state.meta.showZero&&!it.f&&!it.sp&&!it.memo&&Math.abs(num(fin[it.k]))<0.005)return; const cls=it.grand?'grand':(it.sub?'sub':(it.memo?'memo':'')); h+=``+cellTd(rpt,it.k,fin,cons,true,cls)+'';}); h+='
行次项目本期合并金额
${esc(it.n)}
${rn[it.k]}${esc(it.n)}
';} if(state.meta.showSign)h+=`
法定代表人签字:财务总监/主管会计签字:制表人签字:
`; h+='
'; return h;} function isSpf(rpt,k){return !!SPFMT[k];} function renderP5(){ const el=document.getElementById('p5'); if(!allCos().length){el.innerHTML='
请先设置公司结构。
';return;} const C=computeAll();window._OUT=C; const ovc=Object.keys(state.ovr).length; let h=`

输出中心

显示选项: ${ovc?`✎ 已手工调整 ${ovc} 处(点击清除)`:'无手工调整数'}
${['bs','is','cf','all'].map(r=>``).join('')}
${outR==='bs'||outR==='all'?reportSheetHTML('bs',true):''} ${outR==='is'||outR==='all'?reportSheetHTML('is',true):''} ${outR==='cf'||outR==='all'?reportSheetHTML('cf',true):''}

报表勾稽校验

${checksBoxHTML(checksList())}
${state.meta.editMode?'
✏️ 编辑模式已开启:直接点击报表中的任意数字即可手工修改(留空保存则恢复系统自动计算)。手工调整数将以 ✎ 标识并自动汇总至合计行,导出文件中以底色标出。
':''}`; el.innerHTML=h;} function cellEdit(td){ if(!state.meta.editMode)return; if(td.querySelector('input'))return; const rpt=td.dataset.rpt,k=td.dataset.k; const cur=num(td.dataset.v); const C=window._OUT;if(!C)return; const f=UF(); const inp=document.createElement('input');inp.type='number';inp.step='any';inp.className='cellin'; inp.value=Math.abs(cur)<0.005?'':String(r2(cur/f)); td.textContent='';td.appendChild(inp);inp.focus();if(inp.value)inp.select(); let done=false; const commit=ok=>{ if(done)return;done=true; if(ok){ const raw=inp.value.trim(); if(raw===''){delete state.ovr[rpt+'.'+k];} else{const v=num(raw)*f; if(Math.abs(v-num(C.cons[rpt][k]))<0.005)delete state.ovr[rpt+'.'+k]; else state.ovr[rpt+'.'+k]=v;} state.stData=Date.now();saveSoon();} renderP5();}; inp.addEventListener('keydown',e=>{if(e.key==='Enter')commit(true);else if(e.key==='Escape')commit(false);}); inp.addEventListener('blur',()=>commit(true));} /* ============ Excel 导出(原生 XLSX,打开无格式警告) ============ */ function xEsc(s){return String(s==null?'':s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"');} function xCell(v,st,ma){return {v:v==null?'':v,s:st||'',ma:ma||0};} /* --- ZIP(store)与 CRC32 --- */ const CRC_T=(function(){const t=new Uint32Array(256);for(let n=0;n<256;n++){let c=n;for(let k=0;k<8;k++)c=(c&1)?(0xEDB88320^(c>>>1)):(c>>>1);t[n]=c;}return t;})(); function crc32(u8){let c=0xFFFFFFFF;for(let i=0;i>>8);return (c^0xFFFFFFFF)>>>0;} function zipStore(files){ const enc=new TextEncoder();const parts=[];const central=[];let offset=0; const dosTime=12*2048,dosDate=((2026-1980)<<9)|(6<<5)|30; files.forEach(f=>{ const nameB=enc.encode(f.name),crc=crc32(f.u8),sz=f.u8.length; const lh=new Uint8Array(30+nameB.length);const lv=new DataView(lh.buffer); lv.setUint32(0,0x04034b50,true);lv.setUint16(4,20,true);lv.setUint16(6,0x0800,true);lv.setUint16(8,0,true); lv.setUint16(10,dosTime,true);lv.setUint16(12,dosDate,true); lv.setUint32(14,crc,true);lv.setUint32(18,sz,true);lv.setUint32(22,sz,true); lv.setUint16(26,nameB.length,true); lh.set(nameB,30); parts.push(lh,f.u8); const ch=new Uint8Array(46+nameB.length);const cv=new DataView(ch.buffer); cv.setUint32(0,0x02014b50,true);cv.setUint16(4,20,true);cv.setUint16(6,20,true);cv.setUint16(8,0x0800,true);cv.setUint16(10,0,true); cv.setUint16(12,dosTime,true);cv.setUint16(14,dosDate,true); cv.setUint32(16,crc,true);cv.setUint32(20,sz,true);cv.setUint32(24,sz,true); cv.setUint16(28,nameB.length,true); cv.setUint32(42,offset,true); ch.set(nameB,46); central.push(ch); offset+=lh.length+sz;}); let cdSize=0;central.forEach(c=>cdSize+=c.length); const out=new Uint8Array(offset+cdSize+22);let p=0; parts.concat(central).forEach(u=>{out.set(u,p);p+=u.length;}); const ev=new DataView(out.buffer,p); ev.setUint32(0,0x06054b50,true);ev.setUint16(8,files.length,true);ev.setUint16(10,files.length,true); ev.setUint32(12,cdSize,true);ev.setUint32(16,offset,true); return out;} /* --- XLSX 样式 --- */ const XLSX_STYLES=(function(){ const fonts=[{sz:10},{sz:10,b:1},{sz:14,b:1},{sz:9,c:'FF697386'}, {sz:10,b:1,c:'FF047857'},{sz:10,b:1,c:'FFB91C1C'},{sz:10,b:1,c:'FF1E40AF'}]; const fills=[{t:'none'},{t:'gray125'}, {t:'solid',bg:'FFEEF2FB'},{t:'solid',bg:'FFDBE7FF'},{t:'solid',bg:'FFF1F5FB'},{t:'solid',bg:'FFE0E7FF'}, {t:'solid',bg:'FFF8FAFC'},{t:'solid',bg:'FFDBEAFE'},{t:'solid',bg:'FFE8F6FE'},{t:'solid',bg:'FFFDF3D3'}, {t:'solid',bg:'FFD9F5E6'},{t:'solid',bg:'FFFDE3E3'}]; const defs=[ ['xT',2,0,0,{h:'center'}],['xSub',3,0,0,{h:'center',wrap:1}], ['xH',1,2,1,{h:'center'}],['xH2',1,3,1,{h:'center'}], ['xSec',1,4,1,{h:'left'}],['xBan',1,5,1,{h:'left'}], ['xI',0,0,1,{h:'left',wrap:1}],['xC',0,0,1,{h:'center'}],['xE',0,0,0,{h:'left'}], ['xIB',1,6,1,{h:'left',wrap:1}],['xIG',1,7,1,{h:'left',wrap:1}],['xIM',3,0,1,{h:'left',wrap:1}], ['xN',0,0,1,{h:'right',num:1}],['xNB',1,6,1,{h:'right',num:1}],['xNG',1,7,1,{h:'right',num:1}],['xNM',3,0,1,{h:'right',num:1}], ['xNC',1,8,1,{h:'right',num:1}],['xOV',1,9,1,{h:'right',num:1}], ['xOk',4,10,1,{h:'left'}],['xBad',5,11,1,{h:'left'}],['xInfo',6,8,1,{h:'left'}]]; const ix={};defs.forEach((d,i)=>ix[d[0]]=i); return {fonts:fonts,fills:fills,defs:defs,ix:ix}; })(); function xlsxStylesXml(){ const S=XLSX_STYLES; const fonts=''+S.fonts.map(f=>''+(f.b?'':'')+''+(f.c?'':'')+'').join('')+''; const fills=''+S.fills.map(f=>''+(f.bg?'':'')+'').join('')+''; const borders='' +''; const xfs=S.defs.map(d=>{const o=d[4]; return '';}).join(''); return '' +'' +'' +fonts+fills+borders +'' +''+xfs+'' +'' +'';} /* --- 工作表组装 --- */ function xSheet(name,widths,rows){return {name:name,widths:widths,rows:rows};} function colRef(i){let s='';i++;while(i>0){const m=(i-1)%26;s=String.fromCharCode(65+m)+s;i=(i-m-1)/26;}return s;} function sheetXml(sh){ let body='',merges='',mc=0; sh.rows.forEach((r,ri)=>{ let col=0,rowXml=''; (r||[]).forEach(c0=>{ const c=c0||{v:'',s:'',ma:0}; const span=1+(c.ma||0); if(c.ma>0){merges+='';mc++;} const hasS=c.s&&c.s!=='xE'&&XLSX_STYLES.ix[c.s]!=null; const sId=hasS?XLSX_STYLES.ix[c.s]:null; const v=c.v; if((v===''||v==null)&&!hasS){col+=span;return;} const ref=colRef(col)+(ri+1); if(typeof v==='number'&&isFinite(v))rowXml+=''+v+''; else if(v==='')rowXml+=''; else rowXml+=''+xEsc(String(v))+''; col+=span;}); const isSub=r&&r[0]&&r[0].s==='xSub'; body+=''+rowXml+'';}); const cols=sh.widths.map((w,i)=>'').join(''); return '' +'' +''+cols+''+body+'' +(mc?''+merges+'':'') +''; } function xlsxFromSheets(sheets){ const enc=new TextEncoder(); const sheetsXml=sheets.map((sh,i)=>({name:'xl/worksheets/sheet'+(i+1)+'.xml',u8:enc.encode(sheetXml(sh))})); const wb='' +'' +sheets.map((sh,i)=>'').join('') +''; const wbRels='' +'' +sheets.map((sh,i)=>'').join('') +'' +''; const ct='' +'' +'' +'' +'' +sheets.map((sh,i)=>'').join('') +'' +''; const rootRels='' +'' +'' +''; const files=[{name:'[Content_Types].xml',u8:enc.encode(ct)},{name:'_rels/.rels',u8:enc.encode(rootRels)}, {name:'xl/workbook.xml',u8:enc.encode(wb)},{name:'xl/_rels/workbook.xml.rels',u8:enc.encode(wbRels)}, {name:'xl/styles.xml',u8:enc.encode(xlsxStylesXml())}].concat(sheetsXml); return zipStore(files);} function sheetParamsX(){ const rows=[]; rows.push([xCell('集团架构与编制参数','xT',9)]); rows.push([xCell('编制单位:'+(state.meta.co||'—')+' | 报告期间:'+(state.meta.period||'—')+' | 资产负债表日:'+state.meta.date+' | 导出时间:'+new Date().toLocaleString('zh-CN')+' | 金额单位:元','xSub',9)]); rows.push([xCell('公司名称','xH'),xCell('类型','xH'),xCell('持股比例%','xH'),xCell('取得方式','xH'),xCell('母公司长投成本','xH'),xCell('合并商誉','xH'),xCell('取得日权益','xH'),xCell('本期宣告股利','xH'),xCell('年初未分配利润','xH'),xCell('提取盈余公积','xH'),xCell('少数股东增资现金','xH'),xCell('纳入合并','xH')]); state.companies.forEach(c=>{ if(c.type==='parent')rows.push([xCell(c.name,'xI'),xCell('母公司','xC'),xCell('—','xC'),xCell('—','xC'),xCell('','xI'),xCell('','xI'),xCell('','xI'),xCell('','xI'),xCell('','xI'),xCell('','xI'),xCell('','xI'),xCell('—','xC')]); else rows.push([xCell(c.name,'xI'),xCell(c.ratio>=100?'全资':'控股','xC'),xCell(num(c.ratio),'xN'),xCell(c.method==='same'?'同一控制':'非同一控制','xC'),xCell(num(c.cost),'xN'),xCell(gwOf(c),'xN'),xCell(num(c.acqEquity),'xN'),xCell(num(c.div),'xN'),xCell(num(c.begRE),'xN'),xCell(num(c.curSC),'xN'),xCell(num(c.minCapCash),'xN'),xCell(c.inScope!==false?'是':'否','xC')]);}); rows.push([xCell('','xE')]); rows.push([xCell('内部交易智能矩阵','xBan',9)]); state.tx.forEach((t,i)=>{ const from=coById(t.from),to=coById(t.to); rows.push([xCell('事项'+(i+1)+':'+MTX_TYPE[t.type].n+'('+(from?from.name:'?')+' → '+(to?to.name:'?')+')','xI'),xCell('','xE')]); const fields=t.type==='sale'?[['销售收入',t.v1],[t.mode==='profit'?'未实现利润':'销售成本/未售比例',t.mode==='profit'?t.v2:(t.v2+' / '+t.v3+'%')],['现金结算',t.v4]] :t.type==='debt'?[['往来余额',t.v1],['坏账准备',t.v2]] :t.type==='fa'?[['未实现损益',t.v1],['多提折旧',t.v2],['收付现金',t.v3]] :[['内部利息',t.v1],['收付现金',t.v2]]; fields.forEach(f=>rows.push([xCell(' '+f[0],'xI'),xCell(typeof f[1]==='number'?num(f[1]):String(f[1]||0),typeof f[1]==='number'?'xN':'xC')]));}); if(!state.tx.length)rows.push([xCell('(无内部交易事项)','xIM')]); return xSheet('集团参数',[26,9,11,12,15,15,15,15,15,15,16,10],rows);} function sheetIndivX(){ const rows=[]; allCos().forEach((c,ci)=>{ if(ci)rows.push([xCell('','xE')]); rows.push([xCell(c.name+'(个别报表)','xT',1)]); ['bs','is','cf'].forEach(rpt=>{ rows.push([xCell(RPT_NAME[rpt],'xBan',1)]); rows.push([xCell('项目','xH'),xCell('金额(元)','xH')]); const v=calcSheet(c.id,rpt); LIST[rpt].forEach(it=>{ if(it.sec){rows.push([xCell(it.n,'xSec'),xCell('','xSec')]);return;} if(it.sp&&it.k!=='retEarn'){rows.push([xCell(it.n,'xIM'),xCell('(合并层面计算)','xIM')]);return;} if(it.k==='parInt'||it.k==='tciP'){rows.push([xCell(it.n,'xIM'),xCell('(合并层面计算)','xIM')]);return;} if(it.f){rows.push([xCell(it.n,'xIB'),xCell(r2(num(v[it.k])),'xNB')]);return;} rows.push([xCell(it.n,'xI'),xCell(gv(c.id,rpt,it.k),'xN')]);}); });}); return xSheet('个别报表',[50,16],rows);} function sheetEntriesX(){ const rows=[]; rows.push([xCell('合并调整与抵销分录','xT',3)]); rows.push([xCell('工作底稿分录,不过入个别账簿 | 编制单位:'+(state.meta.co||'—'),'xSub',3)]); if(!state.entries.length)rows.push([xCell('(尚未生成分录)','xIM')]); state.entries.forEach(e=>{ let dr=0,cr=0;e.lines.forEach(l=>{if(l.side==='dr')dr+=l.amt;else cr+=l.amt;}); rows.push([xCell('第'+(e.no||'')+'笔【'+(e.manual?'手工':'自动')+'·'+e.kind+'】'+e.name,'xBan',2),xCell(Math.abs(dr-cr)<0.01?'借 '+fmtP(dr)+' / 贷 '+fmtP(cr)+' ✓':'借 '+fmtP(dr)+' / 贷 '+fmtP(cr)+' ✘',Math.abs(dr-cr)<0.01?'xOk':'xBad')]); rows.push([xCell('科目','xH'),xCell('报表','xH'),xCell('借方','xH'),xCell('贷方','xH')]); e.lines.forEach(l=>{ const nm=(l.side==='cr'?' ':'')+(l.side==='cr'?'贷:':'借:')+itemName(l.rpt,l.k)+(l.note?'('+l.note+')':''); rows.push([xCell(nm,'xI'),xCell(RPT_NAME[l.rpt],'xC'),l.side==='dr'?xCell(num(l.amt),'xN'):xCell('','xN'),l.side==='cr'?xCell(num(l.amt),'xN'):xCell('','xN')]);}); rows.push([xCell('','xE')]);}); return xSheet('抵销分录',[46,12,14,14],rows);} function sheetWSX(rpt){ const C=computeAll();window._XC=C;const cos=C.cos; const rows=[]; const span=cos.length+6; rows.push([xCell('合并工作底稿 · '+RPT_NAME[rpt],'xT',span)]); rows.push([xCell('编制单位:'+(state.meta.co||'—')+' | 报告期间:'+(state.meta.period||state.meta.date)+' | 金额单位:'+state.meta.unit,'xSub',span)]); const hd=[xCell('项目','xH')];cos.forEach(c=>hd.push(xCell(c.name,'xH')));hd.push(xCell('合计数','xH2'),xCell('调整借','xH'),xCell('调整贷','xH'),xCell('抵销借','xH'),xCell('抵销贷','xH'),xCell('合并数','xH2')); rows.push(hd); const csum=k=>r2(cos.reduce((a,c)=>a+num(C.per[c.id][rpt][k]),0)); LIST[rpt].forEach(it=>{ if(it.sec){const r0=[xCell(it.n,'xSec')];for(let i=0;irow.push(isSp?xCell('—','xI'):xCell(r2(num(C.per[c.id][rpt][it.k])/UF()),ns))); row.push(isSp?xCell('—','xI'):xCell(r2(csum(it.k)/UF()),ns)); row.push(xCell(r2(num(C.adjD[rpt][it.k]||0)/UF()),'xN'),xCell(r2(num(C.adjC[rpt][it.k]||0)/UF()),'xN'),xCell(r2(num(C.eliD[rpt][it.k]||0)/UF()),'xN'),xCell(r2(num(C.eliC[rpt][it.k]||0)/UF()),'xN')); row.push(xCell(r2(num(C.fin[rpt][it.k])/UF()),state.ovr[rpt+'.'+it.k]!=null?'xOV':'xNC')); rows.push(row);}); return xSheet('底稿-'+RPT_NAME[rpt],[50].concat(cos.map(()=>20)).concat([20,11,11,11,11,20]),rows);} function sheetBSX(){ const C=computeAll();window._XC=C;const fin=C.fin.bs,P=parentCo(); const rows=[]; rows.push([xCell('合并资产负债表','xT',state.meta.showParent?7:3)]); rows.push([xCell('编制单位:'+(state.meta.co||'—')+'(合并) | 报告日:'+state.meta.date+' | 金额单位:'+state.meta.unit,'xSub',state.meta.showParent?7:3)]); const hd=[xCell('资产','xH'),xCell('期末余额','xH')];if(state.meta.showParent)hd.push(xCell('母公司数','xH'));hd.push(xCell('负债和所有者权益','xH'),xCell('期末余额','xH'));if(state.meta.showParent)hd.push(xCell('母公司数','xH')); rows.push(hd); const L=[],R=[];let side=0;LIST.bs.forEach(it=>{if(it.k==='sec_l')side=1;(side?R:L).push(it);}); const n=Math.max(L.length,R.length); const padBottom=arr=>{const d=n-arr.length;if(d<=0)return arr;const out=arr.slice(0,-1);for(let i=0;i{ if(!it)return [xCell('','xI'),xCell('','xN')].concat(state.meta.showParent?[xCell('','xN')]:[]); if(it.sec)return [xCell('','xSec'),xCell(it.n,'xSec')].concat(state.meta.showParent?[xCell('','xSec')]:[]); const ls=it.grand?'xIG':(it.sub?'xIB':(it.memo?'xIM':'xI')); const ns=it.grand?'xNG':(it.sub?'xNB':(it.memo?'xNM':'xN')); const arr=[xCell(it.n,ls),xCell(r2(num(fin[it.k])/UF()),state.ovr['bs.'+it.k]!=null?'xOV':ns)]; if(state.meta.showParent)arr.push(xCell(P?r2(num(C.per[P.id].bs[it.k])/UF()):0,SPFMT[it.k]?'xIM':ns)); return arr;}; for(let i=0;i{ if(it.sec){rows.push([xCell(it.n,'xSec'),xCell('','xSec')]);return;} if(it.memo&&!state.meta.showMemo)return; const ls=it.grand?'xIG':(it.sub?'xIB':(it.memo?'xIM':'xI')); const ns=it.grand?'xNG':(it.sub?'xNB':(it.memo?'xNM':'xN')); rows.push([xCell(it.n,ls),xCell(r2(num(fin[it.k])/UF()),state.ovr[rpt+'.'+it.k]!=null?'xOV':ns)]);}); return xSheet(rpt==='is'?'合并利润表':'合并现金流量表',[50,15],rows);} function sheetChecksX(){ const rows=[]; rows.push([xCell('合并勾稽关系校验表','xT',2)]); rows.push([xCell('编制单位:'+(state.meta.co||'—')+' | 导出时间:'+new Date().toLocaleString('zh-CN'),'xSub',2)]); rows.push([xCell('校验项目','xH'),xCell('结果','xH')]); checksList().forEach(c=>rows.push([xCell(c.t,'xI'),xCell(c.lvl==='ok'?'✔ 通过':(c.lvl==='warn'?'⚠ 提醒':'✘ 异常'),c.lvl==='ok'?'xOk':(c.lvl==='warn'?'xInfo':'xBad'))])); return xSheet('勾稽校验',[64,10],rows);} function buildExcelXML(){ window._XC=computeAll(); return xlsxFromSheets([sheetParamsX(),sheetIndivX(),sheetEntriesX(),sheetWSX('bs'),sheetWSX('is'),sheetWSX('cf'),sheetBSX(),sheetSimpleX('is'),sheetSimpleX('cf'),sheetChecksX()]);} function exportExcel(){ if(!allCos().length){toast('请先设置公司结构','err');return;} try{ const bytes=buildExcelXML(); const blob=new Blob([bytes],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}); const a=document.createElement('a');a.href=URL.createObjectURL(blob); a.download='合并财务报表全套_'+(state.meta.co||'集团')+'_'+state.meta.date+'.xlsx';a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),4000); toast('Excel全套工作簿已导出(原生xlsx,含参数/个别报表/分录/底稿/三表/校验)','ok'); }catch(e){toast('导出失败:'+e.message,'err');}} /* ============ HTML 报表导出 ============ */ function exportHTML(){ if(!allCos().length){toast('请先设置公司结构','err');return;} const oldMode=state.meta.editMode;state.meta.editMode=false; const keep=window._OUT; let body=''; ['bs','is','cf'].forEach(r=>{body+=reportSheetHTML(r,false);}); state.meta.editMode=oldMode;window._OUT=keep; const checks=checksList().map(c=>`
${c.lvl==='ok'?'✔':(c.lvl==='warn'?'⚠':'✘')} ${esc(c.t)}
`).join(''); const html='合并财务报表 · '+(state.meta.co||'')+''+body+'
勾稽校验'+checks+'
'; const blob=new Blob(['\ufeff'+html],{type:'text/html;charset=utf-8'}); const a=document.createElement('a');a.href=URL.createObjectURL(blob); a.download='合并财务报表_'+(state.meta.co||'集团')+'_'+state.meta.date+'.html';a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),4000); toast('HTML报表已导出,可直接浏览器打开或打印为PDF','ok');} /* ============ Excel 模板下载与导入 ============ */ function buildTemplateXML(co){ const rows=[]; const sheets=['bs','is','cf'].map(rpt=>{ const rs=[]; rs.push([xCell('【合并报表工作台】个别报表填写模板 — 公司名称:'+co.name,'xT',1)]); rs.push([xCell('公司名称:'+co.name,'xSub',1)]); rs.push([xCell('报表项目','xH'),xCell('金额(元,合计行自动计算无需填写)','xH')]); LIST[rpt].forEach(it=>{ if(it.sec){rs.push([xCell(it.n,'xSec'),xCell('','xSec')]);return;} if(it.f||(it.sp&&it.k!=='retEarn')||it.k==='parInt'||it.k==='tciP'){rs.push([xCell(it.n,'xIB'),xCell('(不填)','xIB')]);return;} rs.push([xCell(it.n,'xI'),xCell(gv(co.id,rpt,it.k)||'','xN')]);}); return xSheet(RPT_NAME[rpt]+'-'+co.name.slice(0,8),[50,36],rs);}); return xlsxFromSheets(sheets); } function downloadTpl(){ const c=coById(curCo2);if(!c){toast('请先选择公司','err');return;} const bytes=buildTemplateXML(c); const blob=new Blob([bytes],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}); const a=document.createElement('a');a.href=URL.createObjectURL(blob); a.download='个别报表模板_'+c.name+'.xlsx';a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),4000); toast('xlsx模板已下载(含三大报表),填写后可在本页批量导入','ok');} function normName(s){return String(s||'').replace(/[ \s]+/g,'').replace(/^[一二三四五六七八九十]+、/,'').replace(/^(减|加):/,'').replace(/^其中:/,'').replace(/([^)]*)$/,'');} /* --- 轻量 XML 解析(用于 xlsx 部件与旧版模板) --- */ function xmlDec(s){return String(s).replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,'&');} function xmlParse(s){ s=String(s).replace(/<\?[\s\S]*?\?>/g,'').replace(//g,''); const root={tag:'#root',attrs:{},children:[],text:''}; const stack=[root]; let i=0; while(i',lt)+1;if(stack.length>1)stack.pop();continue;} if(s[lt+1]==='!'){i=s.indexOf('>',lt)+1;continue;} let j=lt+1; while(j]/.test(s[j]))j++; const el={tag:s.slice(lt+1,j),attrs:{},children:[],text:''}; let k=j,selfClose=false; while(k'){selfClose=true;k+=2;break;} if(s[k]==='>'){k++;break;} const a0=k; while(k]/.test(s[k]))k++;av=s.slice(a1,k);}} if(an)el.attrs[an]=xmlDec(av);} i=k; stack[stack.length-1].children.push(el); if(!selfClose)stack.push(el);} return root;} function xEls(node,tag){const out=[];(function walk(n){(n.children||[]).forEach(c=>{if(c.tag===tag)out.push(c);walk(c);});})(node);return out;} function xText(el){return el?(el.text||''):'';} function xDeepText(el){let t=el.text||'';(el.children||[]).forEach(c=>{t+=xDeepText(c);});return t;} function colIdxOf(ref){const m=/^([A-Za-z]+)/.exec(ref||'');if(!m)return 0;let s=0;for(const ch of m[1])s=s*26+(ch.toUpperCase().charCodeAt(0)-64);return s-1;} /* --- ZIP 读取(支持 store 与 deflate) --- */ async function inflateRaw(u8){ if(typeof DecompressionStream==='undefined')throw new Error('当前浏览器不支持解压,请使用新版 Edge/Chrome'); const ds=new DecompressionStream('deflate-raw'); const ab=await new Response(new Blob([u8]).stream().pipeThrough(ds)).arrayBuffer(); return new Uint8Array(ab);} async function readZipU8(buf){ const dv=new DataView(buf);const u8=new Uint8Array(buf); let e=-1; for(let i=buf.byteLength-22;i>=0&&i>=buf.byteLength-22-65535;i--){ if(dv.getUint32(i,true)===0x06054b50){e=i;break;}} if(e<0)throw new Error('不是有效的xlsx(zip)文件'); const cnt=dv.getUint16(e+10,true);let off=dv.getUint32(e+16,true); const dec=new TextDecoder();const pend={}; for(let i=0;ibuf.byteLength||dv.getUint32(off,true)!==0x02014b50)break; const method=dv.getUint16(off+10,true),csize=dv.getUint32(off+20,true); const nlen=dv.getUint16(off+28,true),elen=dv.getUint16(off+30,true),clen=dv.getUint16(off+32,true); const lho=dv.getUint32(off+42,true); const name=dec.decode(u8.subarray(off+46,off+46+nlen)); const lnlen=dv.getUint16(lho+26,true),lelen=dv.getUint16(lho+28,true); const dstart=lho+30+lnlen+lelen; const raw=u8.subarray(dstart,dstart+csize); pend[name]=method===0?Promise.resolve(raw):inflateRaw(raw); off+=46+nlen+elen+clen;} const out={}; for(const name of Object.keys(pend))out[name]=await pend[name]; return out;} /* --- 导入:xlsx(含真实Excel文件)与旧版SpreadsheetML --- */ function resolveImportCo(coName,fname,log){ let co=state.companies.find(c=>normName(c.name)===normName(coName)) ||(coName&&state.companies.find(c=>c.name.indexOf(coName)>=0||coName.indexOf(c.name)>=0)); if(co)return co; const base=normName(fname.replace(/\.[^.]+$/,'')); co=base&&state.companies.find(c=>base.indexOf(normName(c.name))>=0); if(co)return co; if(!coName){log.push('✘ '+fname+':未识别到公司名称');return null;} co={id:uid(),type:'sub',name:coName,ratio:100,method:'nonsame',cost:0,goodwill:null,acqEquity:0,div:0,begRE:0,curSC:0,minCapCash:0,inScope:true}; state.companies.push(co);log.push('+ 自动新增子公司:'+coName); return co;} function cellTextOf(c,shared){ const t=c.attrs.t; if(t==='inlineStr'){const is=xEls(c,'is')[0];return xDeepText(is||c).trim();} const vEl=xEls(c,'v')[0]; let v=xText(vEl); if(t==='s')v=shared[parseInt(v,10)]||''; return String(v).trim();} async function importXlsxBuffer(buf,fname,log){ const td=new TextDecoder(); let ab=buf; if(ab&&ab.buffer&&ab.byteLength!==undefined)ab=ab.buffer.slice(ab.byteOffset||0,(ab.byteOffset||0)+ab.byteLength); const files=await readZipU8(ab); const shared=[]; if(files['xl/sharedStrings.xml']){ xEls(xmlParse(td.decode(files['xl/sharedStrings.xml'])),'si').forEach(si=>shared.push(xDeepText(si)));} const wbRoot=xmlParse(td.decode(files['xl/workbook.xml']||'')); const rels={}; if(files['xl/_rels/workbook.xml.rels']){ xEls(xmlParse(td.decode(files['xl/_rels/workbook.xml.rels'])),'Relationship').forEach(r=>{rels[r.attrs.Id]=r.attrs.Target;});} const sheetEls=xEls(wbRoot,'sheet'); const docs=sheetEls.map((sh,i)=>{ const rid=sh.attrs['r:id']||sh.attrs.id||''; let t=rels[rid]||rels['rId'+(i+1)]||('worksheets/sheet'+(i+1)+'.xml'); t=String(t).replace(/^\//,''); if(!/^xl\//.test(t))t='xl/'+t; const raw=files[t]||files['xl/worksheets/sheet'+(i+1)+'.xml']; return {name:sh.attrs.name||'',root:raw?xmlParse(td.decode(raw)):null};}); let coName=''; docs.forEach(d=>{ if(coName||!d.root)return; const cs=xEls(d.root,'c').slice(0,60); for(const c of cs){const m=cellTextOf(c,shared).match(/公司名称[::]\s*(.+)$/);if(m){coName=m[1].trim();break;}}}); const co=resolveImportCo(coName,fname,log); if(!co)return; let total=0; docs.forEach(d=>{ if(!d.root)return; const rpt=/资产负债/.test(d.name)?'bs':(/利润/.test(d.name)?'is':(/现金流/.test(d.name)?'cf':null)); if(!rpt)return; const idx={};LIST[rpt].forEach(it=>{ if(it.sec||it.f||(it.sp&&it.k!=='retEarn')||it.k==='parInt'||it.k==='tciP')return; idx[normName(it.n)]=it.k;}); xEls(d.root,'row').forEach(row=>{ const arr=[]; xEls(row,'c').forEach(c=>{arr[colIdxOf(c.attrs.r)]=cellTextOf(c,shared);}); let key='',keyCol=-1; for(let i=0;i{if(!coName){const m=xDeepText(d).match(/公司名称[::]\s*(.+)$/);if(m)coName=m[1].trim();}}); const co=resolveImportCo(coName,fname,log); if(!co)return; let total=0; xEls(root,'Worksheet').forEach(ws=>{ const nm=ws.attrs['ss:Name']||ws.attrs.name||''; const rpt=/资产负债/.test(nm)?'bs':(/利润/.test(nm)?'is':(/现金流/.test(nm)?'cf':null)); if(!rpt)return; const idx={};LIST[rpt].forEach(it=>{ if(it.sec||it.f||(it.sp&&it.k!=='retEarn')||it.k==='parInt'||it.k==='tciP')return; idx[normName(it.n)]=it.k;}); let cnt=0; xEls(ws,'Row').forEach(row=>{ const byCol={};let pos=0; xEls(row,'Cell').forEach(c=>{ const ia=c.attrs['ss:Index'];if(ia)pos=parseInt(ia,10)-1; const d=xEls(c,'Data')[0];byCol[pos]=d?xDeepText(d):'';pos++;}); const key=idx[normName(byCol[0])]; if(!key)return; const v=parseFloat(String(byCol[1]).replace(/,/g,'')); if(!isNaN(v)){setV(co.id,rpt,key,v);cnt++;}}); total+=cnt;}); log.push('✔ '+fname+' → '+co.name+':共导入 '+total+' 项');} function importStmts(inp){ const files=Array.from(inp.files||[]);if(!files.length)return; const log=[];let done=0; const finish=()=>{markData();save();renderCur(); alert('批量导入结果:\n\n'+log.join('\n')+'\n\n提示:可在“① 集团与参数”中核对新增公司的持股比例等信息。'); inp.value='';}; files.forEach(f=>{ (async()=>{ try{ const buf=await f.arrayBuffer(); const u8=new Uint8Array(buf); if(u8[0]===0x50&&u8[1]===0x4b)await importXlsxBuffer(buf,f.name,log); else importXmlText(new TextDecoder('utf-8').decode(buf),f.name,log); }catch(err){log.push('✘ '+f.name+':解析失败('+err.message+')');} if(++done===files.length)finish(); })();}); } /* ============ 备份与恢复 ============ */ function backupJSON(){ const blob=new Blob([JSON.stringify(state,null,1)],{type:'application/json;charset=utf-8'}); const a=document.createElement('a');a.href=URL.createObjectURL(blob); a.download='合并报表备份_'+(state.meta.co||'集团')+'_'+todayStr()+'.json';a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),4000); toast('JSON备份已导出','ok');} function restoreJSON(inp){ const f=inp.files[0];if(!f)return; const rd=new FileReader(); rd.onload=e=>{try{ const o=JSON.parse(e.target.result); if(!o||!o.meta||!o.companies)throw new Error('格式不正确'); state=Object.assign(defState(),o);state.meta=Object.assign(defState().meta,o.meta||{}); save();renderCur();toast('备份已恢复:共 '+state.companies.length+' 家公司','ok'); }catch(err){toast('恢复失败:'+err.message,'err');} inp.value='';}; rd.readAsText(f,'UTF-8');} function clearAllData(){ if(!confirm('⚠ 将清空全部数据(集团、个别报表、内部交易、分录、手工调整)。\n建议先导出JSON备份。\n\n确定继续?'))return; state=defState(); state.companies.push({id:uid(),type:'parent',name:'母公司'}); save();renderCur();toast('已重置','ok');} /* ============ 演示数据 ============ */ function loadDemo(){ if(state.entries.length||state.tx.length||Object.keys(state.data).length){ if(!confirm('载入演示数据将覆盖当前全部数据(可先备份)。确定继续?'))return;} state=defState(); state.meta.co='华信控股集团有限公司';state.meta.period='2026年1-6月';state.meta.date='2026-06-30'; state.seq=1; const P={id:'cP',type:'parent',name:'华信控股(母公司)'}; const A={id:'cA',type:'sub',name:'华信智造(全资子公司)',ratio:100,method:'same',cost:3150000,goodwill:null,acqEquity:3150000,div:400000,begRE:0,curSC:150000,minCapCash:0,inScope:true}; const B={id:'cB',type:'sub',name:'华信新材(控股子公司)',ratio:80,method:'nonsame',cost:2300000,goodwill:null,acqEquity:2575000,div:500000,begRE:0,curSC:125000,minCapCash:0,inScope:true}; state.companies=[P,A,B]; state.data={ cP:{bs:{monetary:4700000,ar:3000000,inv:4000000,othCA:1000000,ltip:5450000,fa:6000000,ia:400000,stb:2000000,ap:2500000,payrol:500000,taxP:1000000,ltb:3000000,paidIn:10000000,capRes:2000000,surRes:1600000,retEarn:1950000}, is:{rev:20000000,cost:12000000,tax:200000,sell:1000000,adm:1800000,fin:300000,invInc:800000,incTax:1375000}, cf:{cashSales:22000000,cashPurch:13000000,cashEmpl:2000000,cashTax:1800000,othOpOut:200000,invIncRecv:800000,faPurch:1000000,loanRecv:1000000,debtRepay:1500000,divPaid:300000,begCash:700000}}, cA:{bs:{monetary:1750000,ar:1000000,inv:1500000,fa:2500000,ap:1200000,taxP:300000,ltb:1000000,paidIn:3000000,surRes:300000,retEarn:950000}, is:{rev:8000000,cost:5000000,tax:80000,adm:920000,incTax:500000}, cf:{cashSales:8500000,cashPurch:5000000,cashEmpl:800000,cashTax:500000,othOpOut:200000,faPurch:500000,debtRepay:500000,divPaid:400000,begCash:1150000}}, cB:{bs:{monetary:1000000,ar:600000,inv:900000,fa:1600000,ia:100000,ap:700000,taxP:300000,paidIn:2000000,capRes:500000,surRes:200000,retEarn:500000}, is:{rev:5000000,cost:3000000,tax:50000,adm:450000,incTax:375000}, cf:{cashSales:5200000,cashPurch:2800000,cashEmpl:600000,cashTax:400000,othOpOut:100000,faPurch:200000,divPaid:500000,begCash:400000}}}; state.tx=[ {id:uid(),type:'debt',from:'cA',to:'cP',kind:'ar',v1:500000,v2:0,v3:0,v4:0}, {id:uid(),type:'sale',from:'cP',to:'cB',mode:'ratio',v1:1000000,v2:700000,v3:30,v4:1000000}]; state.stData=Date.now(); genAll(true); save(); renderCur(); alert('演示数据已载入并自动生成全部分录。\n\n集团架构:华信控股持有「华信智造」100%(同一控制)、「华信新材」80%(非同一控制,自动确认商誉 240,000)。\n内部交易:A对母公司应收账款 500,000;母公司向B销售商品 1,000,000(成本 700,000,未售出30%,未实现利润 90,000,含递延所得税自动计算);内部购销收付现金 1,000,000;子公司宣告股利自动抵销。\n\n预期结果(递延所得税开启、税率25%):\n· 合并资产总计 = 负债和所有者权益总计 = 29,722,500.00 元\n· 归母净利润 5,657,500.00 + 少数股东损益 225,000.00 = 合并净利润 5,882,500.00\n· 合并未分配利润 3,482,500.00;少数股东权益 640,000.00;递延所得税资产 22,500.00\n· 经营活动净额 8,300,000.00;期末现金 7,450,000.00 = 合并货币资金\n\n可在「⑤ 合并报表输出」中查看并点击任意数字手工调整体验。');} /* ============ 启动 ============ */ function init(){ loadUi();loadData(); if(!state.companies.length)state.companies.push({id:uid(),type:'parent',name:'母公司'}); if(!parentCo()){const s=state.companies.find(c=>c.type==='sub');if(s)s.type='parent';} if(!state.meta.date)state.meta.date=todayStr(); applyUi();renderCur(); document.addEventListener('keydown',e=>{ const k=e.key.toLowerCase(); if((e.ctrlKey||e.metaKey)&&k==='s'){e.preventDefault();save();toast('已保存','ok');return;} if((e.ctrlKey||e.metaKey)&&k==='b'){e.preventDefault();toggleSide();}}); window.addEventListener('beforeunload',save);} window.addEventListener('DOMContentLoaded',init);