buhanka-finance-app 1.44.0 → 1.45.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 +72 -10
- package/package.json +1 -1
package/index.html
CHANGED
|
@@ -286,7 +286,7 @@ tr.subtotal>td:first-child{color:var(--accent);box-shadow:inset 3px 0 0 var(--ac
|
|
|
286
286
|
|
|
287
287
|
<script>
|
|
288
288
|
/* ============================================================ БУХАНКА · Финансы v2 ============================================================ */
|
|
289
|
-
const APP_VERSION='7.
|
|
289
|
+
const APP_VERSION='7.7';
|
|
290
290
|
const SCHEMA=8; /* версия структуры данных; растёт при доработках */
|
|
291
291
|
const STORE_KEY='buhanka_finance_v9';/* НЕ МЕНЯТЬ НИКОГДА — иначе данные «слетят» при обновлении файла */
|
|
292
292
|
const LEGACY_KEYS=['buhanka_finance_v8','buhanka_finance_v7','buhanka_finance_v6','buhanka_finance_v5'];
|
|
@@ -1430,16 +1430,84 @@ function taxCfgSet(p,field,v){ ST.taxConfig=ST.taxConfig||{}; ST.taxConfig[p]=ST
|
|
|
1430
1430
|
function taxDataSet(mo,field,v){ ST.taxData=ST.taxData||{}; ST.taxData[mo]=ST.taxData[mo]||{}; const n=parseNum(v); if(n==null)delete ST.taxData[mo][field]; else ST.taxData[mo][field]=n; save(); render(); }
|
|
1431
1431
|
|
|
1432
1432
|
/* ---------- Запасы (по дням) ---------- */
|
|
1433
|
+
/* цепочка остатков по месяцам: конец месяца = начало следующего */
|
|
1434
|
+
function invSeries(point){
|
|
1435
|
+
const inv=(ST.inventory||{})[point]||{}; const out={};
|
|
1436
|
+
let prev=Number((ST.invOpening||{})[point]||0);
|
|
1437
|
+
for(let mi=startIdx();mi<12;mi++){
|
|
1438
|
+
const mo=MONTHS[mi], N=daysInMonth(mi); const open=prev;
|
|
1439
|
+
let pur=0,cogs=0,wof=0,other=0;
|
|
1440
|
+
for(let d=1;d<=N;d++){
|
|
1441
|
+
const ds=`${YEAR}-${String(mi+1).padStart(2,'0')}-${String(d).padStart(2,'0')}`;
|
|
1442
|
+
const r=inv[ds]; if(!r) continue;
|
|
1443
|
+
const start=(r.start!=null)?r.start:prev;
|
|
1444
|
+
const p=Number(r.purchase||0), c=Number(r.cogs||0), w=Number(r.writeoff||0);
|
|
1445
|
+
const calc=start+p-c-w; pur+=p; cogs+=c; wof+=w;
|
|
1446
|
+
if(r.end!=null){ other+=r.end-calc; prev=r.end; } else prev=calc;
|
|
1447
|
+
}
|
|
1448
|
+
out[mo]={open,pur,cogs,wof,other,end:prev};
|
|
1449
|
+
}
|
|
1450
|
+
return out;
|
|
1451
|
+
}
|
|
1452
|
+
function invAgg(point,months){
|
|
1453
|
+
const ser=invSeries(point); const mm=months.filter(m=>MONTHS.indexOf(m)>=startIdx());
|
|
1454
|
+
if(!mm.length) return {open:0,pur:0,cogs:0,wof:0,other:0,end:0};
|
|
1455
|
+
const first=ser[mm[0]]||{open:0}, last=ser[mm[mm.length-1]]||{end:0};
|
|
1456
|
+
const acc=mm.reduce((a,m)=>{ const x=ser[m]||{}; a.pur+=x.pur||0; a.cogs+=x.cogs||0; a.wof+=x.wof||0; a.other+=x.other||0; return a; },
|
|
1457
|
+
{pur:0,cogs:0,wof:0,other:0});
|
|
1458
|
+
return {open:first.open||0,...acc,end:last.end||0};
|
|
1459
|
+
}
|
|
1460
|
+
function invSummaryTable(months,title){
|
|
1461
|
+
let html=`<div class="panel"><div class="hd"><h3>${title}</h3></div>
|
|
1462
|
+
<div class="bd" style="padding:0"><div class="tbl-wrap"><table class="bld" style="min-width:940px">
|
|
1463
|
+
<colgroup><col style="width:220px"><col style="width:130px"><col style="width:130px"><col style="width:140px">
|
|
1464
|
+
<col style="width:120px"><col style="width:130px"><col></colgroup>
|
|
1465
|
+
<thead><tr><th>Точка</th><th class="num">Начало</th><th class="num">Закупки</th><th class="num">Себестоимость</th>
|
|
1466
|
+
<th class="num">Списания</th><th class="num">Конец</th><th class="num">Расхождение</th></tr></thead><tbody>`;
|
|
1467
|
+
const t={open:0,pur:0,cogs:0,wof:0,other:0,end:0};
|
|
1468
|
+
if(!ST.points.length) html+=`<tr><td colspan="7"><div class="empty">Точек нет</div></td></tr>`;
|
|
1469
|
+
ST.points.forEach(p=>{ const a=invAgg(p,months);
|
|
1470
|
+
Object.keys(t).forEach(k=>t[k]+=a[k]||0);
|
|
1471
|
+
html+=`<tr><td class="nm" style="padding-left:12px">${esc(p)}</td>
|
|
1472
|
+
<td class="num">${fmt(a.open)}</td><td class="num">${fmt(a.pur)}</td><td class="num">${fmt(a.cogs)}</td>
|
|
1473
|
+
<td class="num">${fmt(a.wof)}</td><td class="num" style="font-weight:600">${fmt(a.end)}</td>
|
|
1474
|
+
<td class="num ${Math.abs(a.other)>0.5?'neg':'muted'}">${a.other?fmt(a.other):'—'}</td></tr>`; });
|
|
1475
|
+
html+=`<tr class="r-sub"><td class="nm">Итого</td><td class="num">${fmt(t.open)}</td><td class="num">${fmt(t.pur)}</td>
|
|
1476
|
+
<td class="num">${fmt(t.cogs)}</td><td class="num">${fmt(t.wof)}</td><td class="num">${fmt(t.end)}</td>
|
|
1477
|
+
<td class="num ${Math.abs(t.other)>0.5?'neg':''}">${t.other?fmt(t.other):'—'}</td></tr>
|
|
1478
|
+
</tbody></table></div></div></div>`;
|
|
1479
|
+
return html;
|
|
1480
|
+
}
|
|
1481
|
+
function invCards(a,sub){
|
|
1482
|
+
return `<div class="cards">
|
|
1483
|
+
<div class="card"><div class="k">Остаток на конец</div><div class="v">${fmt(a.end)} ₽</div><div class="d muted">${esc(sub)}</div></div>
|
|
1484
|
+
<div class="card"><div class="k">Закупки</div><div class="v">${fmt(a.pur)} ₽</div></div>
|
|
1485
|
+
<div class="card"><div class="k">Себестоимость</div><div class="v">${fmt(a.cogs)} ₽</div></div>
|
|
1486
|
+
<div class="card"><div class="k">Списания</div><div class="v">${fmt(a.wof)} ₽</div></div>
|
|
1487
|
+
<div class="card" style="border-color:${Math.abs(a.other)>0.5?'var(--accent)':'var(--line)'}"><div class="k">Прочее (расхождение)</div>
|
|
1488
|
+
<div class="v ${Math.abs(a.other)>0.5?'neg':''}">${fmt(a.other)} ₽</div></div></div>`;
|
|
1489
|
+
}
|
|
1433
1490
|
function renderWcap(C){
|
|
1491
|
+
const P=ST.ui.period;
|
|
1492
|
+
if(isAggregate(P)){
|
|
1493
|
+
document.getElementById('pageSub').textContent=`${P} · сводно по точкам`;
|
|
1494
|
+
const mm=periodMonths(P);
|
|
1495
|
+
const tot=ST.points.reduce((a,p)=>{ const x=invAgg(p,mm);
|
|
1496
|
+
a.pur+=x.pur; a.cogs+=x.cogs; a.wof+=x.wof; a.other+=x.other; a.end+=x.end; return a; },{pur:0,cogs:0,wof:0,other:0,end:0});
|
|
1497
|
+
C.innerHTML=invCards(tot,`все точки · ${P}`)+invSummaryTable(mm,`Запасы по точкам · ${P}`);
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1434
1500
|
const m=primaryMonth(), mi=MONTHS.indexOf(m), N=daysInMonth(mi), lk=isLocked(m);
|
|
1435
1501
|
ST.ui.invPoint = ST.points.includes(ST.ui.invPoint)? ST.ui.invPoint : ST.points[0];
|
|
1436
1502
|
const point=ST.ui.invPoint;
|
|
1437
1503
|
document.getElementById('pageSub').textContent=`${m} · учёт запасов по дням`;
|
|
1438
1504
|
ST.inventory[point]=ST.inventory[point]||{};
|
|
1439
|
-
|
|
1440
|
-
|
|
1505
|
+
const aggPt=invAgg(point,[m]);
|
|
1506
|
+
let html=invCards(aggPt,`${point} · ${m}`);
|
|
1507
|
+
html+=invSummaryTable([m],`Свод по точкам · ${m}`);
|
|
1508
|
+
html+=`<div class="row" style="margin:18px 0 14px">`;
|
|
1509
|
+
ST.points.forEach(p=>html+=`<span class="pill ${p===point?'on':''}" onclick="invPoint('${p}')">${esc(p)}</span>`); html+=`</div>`;
|
|
1441
1510
|
if(lk)html+=`<div class="note">🔒 ${m} закрыт — только просмотр.</div>`;
|
|
1442
|
-
html+=`<div class="note">Ведите запасы по дням: остаток на начало (авто = конец прошлого дня), закупка, себестоимость (ушло в продажу), списания и фактический остаток на конец. Если факт ≠ расчёту — разница уходит в «Прочее».</div>`;
|
|
1443
1511
|
let sumPur=0,sumCogs=0,sumWof=0,sumOther=0; let prevEnd=Number(ST.invOpening[point]||0);
|
|
1444
1512
|
html+=`<div class="panel"><div class="hd"><h3>Запасы · ${point} · ${m}</h3><div class="spacer"></div><span class="tag">вход. остаток <input class="money" ${lk?'disabled':''} value="${fmtNum(ST.invOpening[point]||0)}" onchange="invOpenSet('${point}',this.value)" style="width:90px"> ₽</span></div>
|
|
1445
1513
|
<div class="bd" style="padding:0"><div class="tbl-wrap"><table style="font-size:12.5px">
|
|
@@ -1458,12 +1526,6 @@ function renderWcap(C){
|
|
|
1458
1526
|
}
|
|
1459
1527
|
html+=`<tr class="subtotal"><td>Итого</td><td></td><td class="num">${fmt(sumPur)}</td><td class="num">${fmt(sumCogs)}</td><td class="num">${fmt(sumWof)}</td><td colspan="2"></td><td class="num ${Math.abs(sumOther)>0.5?'neg':''}">${fmt(sumOther)}</td></tr>`;
|
|
1460
1528
|
html+=`</tbody></table></div></div></div>`;
|
|
1461
|
-
html+=`<div class="cards">
|
|
1462
|
-
<div class="card"><div class="k">Остаток на конец ${m}</div><div class="v">${fmt(prevEnd)} ₽</div><div class="d muted">${point}</div></div>
|
|
1463
|
-
<div class="card"><div class="k">Закупки за месяц</div><div class="v">${fmt(sumPur)} ₽</div></div>
|
|
1464
|
-
<div class="card"><div class="k">Себестоимость</div><div class="v">${fmt(sumCogs)} ₽</div></div>
|
|
1465
|
-
<div class="card"><div class="k">Списания</div><div class="v">${fmt(sumWof)} ₽</div></div>
|
|
1466
|
-
<div class="card" style="border-color:${Math.abs(sumOther)>0.5?'var(--accent)':'var(--line)'}"><div class="k">Прочее (расхождение)</div><div class="v ${Math.abs(sumOther)>0.5?'neg':''}">${fmt(sumOther)} ₽</div></div></div>`;
|
|
1467
1529
|
C.innerHTML=html;
|
|
1468
1530
|
}
|
|
1469
1531
|
function invPoint(p){ ST.ui.invPoint=p; save(); render(); }
|