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.
- package/index.html +61 -20
- 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.
|
|
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=(
|
|
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('
|
|
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('
|
|
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
|
|
1299
|
-
const
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
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
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
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
|
}
|