buhanka-finance-app 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.html +61 -20
  2. package/package.json +1 -1
package/index.html CHANGED
@@ -107,7 +107,14 @@ table.wide tr.tot>td{font-weight:800;background:var(--grandbg);border-top:1px so
107
107
  table.wide tr.sub>td{font-weight:700;background:var(--subbg)}
108
108
  table.wide tr.val td.nm{padding-left:22px;color:var(--muted)}
109
109
  table.wide tr.acc td.nm{padding-left:22px;color:var(--muted2);font-style:italic}
110
+ table.wide tr.acc>td{color:var(--text);font-weight:400}
111
+ table.wide tr.bal>td{background:transparent;color:var(--text);font-weight:400}
112
+ table.wide tr.bal>td.nm{font-weight:700;color:var(--text)}
113
+ table.wide tr.bal.brd>td{border-top:1px solid var(--line2)}
110
114
  table.wide col.plan,table.wide th.plan{background:rgba(232,163,61,.06)}
115
+ table.wide tr.sec>td.nm,table.wide tr.sub>td.nm{background:var(--subbg)}
116
+ table.wide tr.tot>td.nm{background-color:var(--panel);background-image:linear-gradient(var(--grandbg),var(--grandbg))}
117
+ table.wide tr.bal>td.nm,table.wide tr.val>td.nm,table.wide tr.acc>td.nm{background:var(--panel)}
111
118
  table.wide input.p{background:transparent;border:1px solid transparent;text-align:right;width:100%;padding:2px 0;font-size:12.5px;font-variant-numeric:tabular-nums;color:var(--accent)}
112
119
  table.wide input.p:hover{border-color:var(--line2)}
113
120
  table.wide input.p:focus{border-color:var(--accent);background:var(--panel2)}
@@ -228,7 +235,7 @@ tr.subtotal>td:first-child{color:var(--accent);box-shadow:inset 3px 0 0 var(--ac
228
235
 
229
236
  <script>
230
237
  /* ============================================================ БУХАНКА · Финансы v2 ============================================================ */
231
- const APP_VERSION='3.6';
238
+ const APP_VERSION='3.9';
232
239
  const SCHEMA=3; /* версия структуры данных; растёт при доработках */
233
240
  const STORE_KEY='buhanka_finance_v9';/* НЕ МЕНЯТЬ НИКОГДА — иначе данные «слетят» при обновлении файла */
234
241
  const LEGACY_KEYS=['buhanka_finance_v8','buhanka_finance_v7','buhanka_finance_v6','buhanka_finance_v5'];
@@ -599,6 +606,13 @@ function actualUntilOf(mi){ const t=new Date();
599
606
  if(YEAR===t.getFullYear()&&mi===t.getMonth()) return t.getDate();
600
607
  return 0; }
601
608
 
609
+ let _opsIdx=null,_opsIdxKey=null;
610
+ function opsByDate(){
611
+ const k=(ST._ts||0)+':'+(ST.operations||[]).length;
612
+ if(_opsIdxKey===k&&_opsIdx) return _opsIdx;
613
+ const m={}; (ST.operations||[]).forEach(o=>{ if(!isCashOp(o))return; (m[o.date]=m[o.date]||[]).push(o); });
614
+ _opsIdx=m; _opsIdxKey=k; return m;
615
+ }
602
616
  /* ── матрица «статьи × дни» для CF прямого ── */
603
617
  function cfDayMatrix(mi){
604
618
  const mo=MONTHS[mi], N=daysInMonth(mi), au=actualUntilOf(mi), cats=cfCats();
@@ -610,7 +624,7 @@ function cfDayMatrix(mi){
610
624
  const days=[];
611
625
  for(let d=1; d<=N; d++){
612
626
  const ds=`${YEAR}-${String(mi+1).padStart(2,'0')}-${String(d).padStart(2,'0')}`;
613
- const ops=(ST.operations||[]).filter(o=>o.date===ds&&isCashOp(o));
627
+ const ops=opsByDate()[ds]||[];
614
628
  const actual=d<=au;
615
629
  const inc={bank:0,yandex:0,cash:0}, exp={}, fin={}, inv={};
616
630
  ops.forEach(o=>{ const amt=Math.abs(o.amount), g=cfGroup(o.category);
@@ -1283,34 +1297,61 @@ function renderCF(C){
1283
1297
  (()=>{const t=sumOf(x=>x.inv[c]||0);return t>=0?num(t):neg(t);})()); });
1284
1298
  }
1285
1299
 
1286
- html+=row('sub','Деньги на начало периода',M.days.map(d=>cell(fmt(d.openTot))).join(''),fmt(M.days.length?M.days[0].openTot:0));
1300
+ html+=row('bal brd','Деньги на начало периода',M.days.map(d=>cell(fmt(d.openTot))).join(''),fmt(M.days.length?M.days[0].openTot:0));
1287
1301
  M.accs.forEach(a=>{ html+=row('acc',esc(a),M.days.map(d=>cell(fmt(d.open[a]))).join(''),fmt(M.days.length?M.days[0].open[a]:0)); });
1288
1302
  html+=row('tot','Денежный поток за период',M.days.map(d=>cell(d.net>=0?num(d.net):neg(d.net),d.net>=0?'pos':'neg')).join(''),
1289
1303
  (()=>{const t=sumOf(x=>x.net);return `<b class="${t>=0?'pos':'neg'}">${t>=0?num(t):neg(t)}</b>`;})());
1290
- html+=row('sub','Деньги на конец периода',M.days.map(d=>cell(fmt(d.closeTot))).join(''),fmt(M.days.length?M.days[M.days.length-1].closeTot:0));
1304
+ html+=row('bal','Деньги на конец периода',M.days.map(d=>cell(fmt(d.closeTot))).join(''),fmt(M.days.length?M.days[M.days.length-1].closeTot:0));
1291
1305
  M.accs.forEach(a=>{ html+=row('acc',esc(a),M.days.map(d=>cell(fmt(d.close[a]))).join(''),fmt(M.days.length?M.days[M.days.length-1].close[a]:0)); });
1292
1306
 
1293
1307
  html+=`</tbody></table></div></div>
1294
1308
  <p class="hint" style="padding:0 15px 12px;margin:0">Дни до сегодняшнего — факт из операций, дальше — прогноз: подтягиваются постоянные расходы по дню платежа, любую ячейку прогноза можно перебить вручную (оранжевые поля). Плановые суммы списываются с основного банковского счёта — по факту распределение уточнится операциями.</p></div>`;
1295
1309
  }
1296
1310
 
1297
- /* ── 4. ДДС по месяцам ── */
1298
- const cfM=MONTHS.map(mo=>computeCF('cf_direct',mo));
1299
- const struct=structOf('cf_direct');
1300
- html+=`<div class="panel"><div class="hd"><h3>CF по месяцам · ${YEAR}</h3><div class="spacer"></div>
1301
- <span class="muted small">строки — в Конструкторе</span></div>
1302
- <div class="bd" style="padding:0"><div class="tbl-wrap"><table class="wide" style="min-width:${250+13*110}px">
1303
- <colgroup><col style="width:250px">${MONTHS.map(()=>`<col style="width:110px">`).join('')}<col style="width:120px"></colgroup>
1304
- <thead><tr><th class="nm">Статья</th>${MSHORT.map(x=>`<th class="num">${x}</th>`).join('')}<th class="num">Год</th></tr></thead><tbody>`;
1305
- struct.forEach((row,i)=>{
1306
- if(row.role==='header'){ html+=`<tr class="sec"><td class="nm">${esc(row.name)}</td>${MONTHS.map(()=>'<td></td>').join('')}<td></td></tr>`; return; }
1307
- const cls=row.role==='grand'?'tot':row.role==='subtotal'?'sub':'val';
1311
+ /* ── 4. CF по месяцам: та же структура статей, без разбивки по счетам ── */
1312
+ const MM=MONTHS.map((mo,i2)=> i2>=startIdx() ? cfDayMatrix(i2) : null);
1313
+ const catsY=cfCats();
1314
+ const aggM=(M,f)=> M? M.days.reduce((t,x)=>t+f(x),0) : null;
1315
+ const numM=v=>v==null?'':v?fmt(v):'—';
1316
+ const negM=v=>v==null?'':v?'('+fmt(Math.abs(v))+')':'—';
1317
+ const sgnM=v=>v==null?'':v?(v<0?'('+fmt(Math.abs(v))+')':fmt(v)):'—';
1318
+ const rowM=(cls,name,fn,mode)=>{
1308
1319
  let year=0, cells='';
1309
- MONTHS.forEach((mo,mi2)=>{ const x=cfM[mi2][i]; const v=x?x.val:0; year+=v;
1310
- cells+=`<td class="num ${v<0?'neg':v>0?'pos':''}">${v?(v<0?'('+fmt(Math.abs(v))+')':fmt(v)):'—'}</td>`; });
1311
- html+=`<tr class="${cls}"><td class="nm">${esc(row.name)}</td>${cells}<td class="num ${year<0?'neg':year>0?'pos':''}"><b>${year?(year<0?'('+fmt(Math.abs(year))+')':fmt(year)):'—'}</b></td></tr>`;
1312
- });
1313
- html+=`</tbody></table></div></div></div>`;
1320
+ MM.forEach(M=>{ const v=aggM(M,fn); if(v!=null)year+=v;
1321
+ const txt = mode==='neg'?negM(v):mode==='sgn'?sgnM(v):numM(v);
1322
+ const cl = v==null?'':mode==='neg'?(v?'neg':''):mode==='sgn'?(v<0?'neg':v>0?'pos':''):'';
1323
+ cells+=`<td class="num ${cl}">${txt}</td>`; });
1324
+ const yt = mode==='neg'?negM(year):mode==='sgn'?sgnM(year):numM(year);
1325
+ const ycl = mode==='neg'?(year?'neg':''):mode==='sgn'?(year<0?'neg':year>0?'pos':''):'';
1326
+ return `<tr class="${cls}"><td class="nm">${name}</td>${cells}<td class="num ${ycl}"><b>${yt}</b></td></tr>`;
1327
+ };
1328
+ html+=`<div class="panel"><div class="hd"><h3>CF по месяцам · ${YEAR}</h3><div class="spacer"></div>
1329
+ <span class="muted small">те же статьи, что и в таблице по дням</span></div>
1330
+ <div class="bd" style="padding:0"><div class="tbl-wrap"><table class="wide" style="min-width:${250+13*115}px">
1331
+ <colgroup><col style="width:250px">${MONTHS.map(()=>`<col style="width:115px">`).join('')}<col style="width:125px"></colgroup>
1332
+ <thead><tr><th class="nm">Статья</th>${MSHORT.map((x,i2)=>`<th class="num ${i2<startIdx()?'muted':''}">${x}</th>`).join('')}<th class="num">Год</th></tr></thead><tbody>`;
1333
+ html+=rowM('sec','Операционная деятельность',x=>x.opNet,'sgn');
1334
+ html+=rowM('sub','Поступления',x=>x.inSum);
1335
+ CHANNELS.forEach(([k,n])=>{ html+=rowM('val',n,x=>x.inc[k]||0); });
1336
+ html+=rowM('sub','Расходы',x=>x.exSum,'neg');
1337
+ catsY.op.forEach(c=>{ html+=rowM('val',esc(c),x=>x.exp[c]||0,'neg'); });
1338
+ html+=rowM('sec','Финансовая деятельность',x=>x.finSum,'sgn');
1339
+ catsY.fin.forEach(c=>{ html+=rowM('val',esc(c),x=>x.fin[c]||0,'sgn'); });
1340
+ html+=rowM('sec','Инвестиционная деятельность',x=>x.invSum,'sgn');
1341
+ catsY.inv.forEach(c=>{ html+=rowM('val',esc(c),x=>x.inv[c]||0,'sgn'); });
1342
+ {
1343
+ let cells='',first=null,last=null;
1344
+ MM.forEach(M=>{ if(!M){ cells+='<td></td>'; return; }
1345
+ const v=M.days[0].openTot; if(first==null)first=v; cells+=`<td class="num">${fmt(v)}</td>`; });
1346
+ html+=`<tr class="bal brd"><td class="nm">Деньги на начало периода</td>${cells}<td class="num"><b>${first==null?'—':fmt(first)}</b></td></tr>`;
1347
+ html+=rowM('tot','Денежный поток за период',x=>x.net,'sgn');
1348
+ cells='';
1349
+ MM.forEach(M=>{ if(!M){ cells+='<td></td>'; return; }
1350
+ const v=M.days[M.days.length-1].closeTot; last=v; cells+=`<td class="num">${fmt(v)}</td>`; });
1351
+ html+=`<tr class="bal"><td class="nm">Деньги на конец периода</td>${cells}<td class="num"><b>${last==null?'—':fmt(last)}</b></td></tr>`;
1352
+ }
1353
+ html+=`</tbody></table></div></div>
1354
+ <p class="hint" style="padding:0 15px 12px;margin:0">Месяцы до начала учёта (${startMonthGen()}) не считаются. Разбивка по счетам показана только в таблице по дням.</p></div>`;
1314
1355
 
1315
1356
  C.innerHTML=html;
1316
1357
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buhanka-finance-app",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "Buhanka bakery finance dashboard — single-file web app",
5
5
  "license": "MIT",
6
6
  "files": [