buhanka-finance-app 1.6.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 +55 -18
  2. package/package.json +1 -1
package/index.html CHANGED
@@ -112,6 +112,9 @@ table.wide tr.bal>td{background:transparent;color:var(--text);font-weight:400}
112
112
  table.wide tr.bal>td.nm{font-weight:700;color:var(--text)}
113
113
  table.wide tr.bal.brd>td{border-top:1px solid var(--line2)}
114
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)}
115
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)}
116
119
  table.wide input.p:hover{border-color:var(--line2)}
117
120
  table.wide input.p:focus{border-color:var(--accent);background:var(--panel2)}
@@ -232,7 +235,7 @@ tr.subtotal>td:first-child{color:var(--accent);box-shadow:inset 3px 0 0 var(--ac
232
235
 
233
236
  <script>
234
237
  /* ============================================================ БУХАНКА · Финансы v2 ============================================================ */
235
- const APP_VERSION='3.7';
238
+ const APP_VERSION='3.9';
236
239
  const SCHEMA=3; /* версия структуры данных; растёт при доработках */
237
240
  const STORE_KEY='buhanka_finance_v9';/* НЕ МЕНЯТЬ НИКОГДА — иначе данные «слетят» при обновлении файла */
238
241
  const LEGACY_KEYS=['buhanka_finance_v8','buhanka_finance_v7','buhanka_finance_v6','buhanka_finance_v5'];
@@ -603,6 +606,13 @@ function actualUntilOf(mi){ const t=new Date();
603
606
  if(YEAR===t.getFullYear()&&mi===t.getMonth()) return t.getDate();
604
607
  return 0; }
605
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
+ }
606
616
  /* ── матрица «статьи × дни» для CF прямого ── */
607
617
  function cfDayMatrix(mi){
608
618
  const mo=MONTHS[mi], N=daysInMonth(mi), au=actualUntilOf(mi), cats=cfCats();
@@ -614,7 +624,7 @@ function cfDayMatrix(mi){
614
624
  const days=[];
615
625
  for(let d=1; d<=N; d++){
616
626
  const ds=`${YEAR}-${String(mi+1).padStart(2,'0')}-${String(d).padStart(2,'0')}`;
617
- const ops=(ST.operations||[]).filter(o=>o.date===ds&&isCashOp(o));
627
+ const ops=opsByDate()[ds]||[];
618
628
  const actual=d<=au;
619
629
  const inc={bank:0,yandex:0,cash:0}, exp={}, fin={}, inv={};
620
630
  ops.forEach(o=>{ const amt=Math.abs(o.amount), g=cfGroup(o.category);
@@ -1298,23 +1308,50 @@ function renderCF(C){
1298
1308
  <p class="hint" style="padding:0 15px 12px;margin:0">Дни до сегодняшнего — факт из операций, дальше — прогноз: подтягиваются постоянные расходы по дню платежа, любую ячейку прогноза можно перебить вручную (оранжевые поля). Плановые суммы списываются с основного банковского счёта — по факту распределение уточнится операциями.</p></div>`;
1299
1309
  }
1300
1310
 
1301
- /* ── 4. ДДС по месяцам ── */
1302
- const cfM=MONTHS.map(mo=>computeCF('cf_direct',mo));
1303
- const struct=structOf('cf_direct');
1304
- html+=`<div class="panel"><div class="hd"><h3>CF по месяцам · ${YEAR}</h3><div class="spacer"></div>
1305
- <span class="muted small">строки — в Конструкторе</span></div>
1306
- <div class="bd" style="padding:0"><div class="tbl-wrap"><table class="wide" style="min-width:${250+13*110}px">
1307
- <colgroup><col style="width:250px">${MONTHS.map(()=>`<col style="width:110px">`).join('')}<col style="width:120px"></colgroup>
1308
- <thead><tr><th class="nm">Статья</th>${MSHORT.map(x=>`<th class="num">${x}</th>`).join('')}<th class="num">Год</th></tr></thead><tbody>`;
1309
- struct.forEach((row,i)=>{
1310
- if(row.role==='header'){ html+=`<tr class="sec"><td class="nm">${esc(row.name)}</td>${MONTHS.map(()=>'<td></td>').join('')}<td></td></tr>`; return; }
1311
- 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)=>{
1312
1319
  let year=0, cells='';
1313
- MONTHS.forEach((mo,mi2)=>{ const x=cfM[mi2][i]; const v=x?x.val:0; year+=v;
1314
- cells+=`<td class="num ${v<0?'neg':v>0?'pos':''}">${v?(v<0?'('+fmt(Math.abs(v))+')':fmt(v)):'—'}</td>`; });
1315
- 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>`;
1316
- });
1317
- 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>`;
1318
1355
 
1319
1356
  C.innerHTML=html;
1320
1357
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buhanka-finance-app",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Buhanka bakery finance dashboard — single-file web app",
5
5
  "license": "MIT",
6
6
  "files": [