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.
- package/index.html +55 -18
- 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.
|
|
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=(
|
|
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
|
|
1303
|
-
const
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
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
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
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
|
}
|