buhanka-finance-app 1.71.0 → 1.72.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 +35 -13
- package/package.json +1 -1
package/index.html
CHANGED
|
@@ -309,7 +309,7 @@ tr.subtotal>td:first-child{color:var(--accent);box-shadow:inset 3px 0 0 var(--ac
|
|
|
309
309
|
|
|
310
310
|
<script>
|
|
311
311
|
/* ============================================================ БУХАНКА · Финансы v2 ============================================================ */
|
|
312
|
-
const APP_VERSION='10.
|
|
312
|
+
const APP_VERSION='10.6';
|
|
313
313
|
const SCHEMA=8; /* версия структуры данных; растёт при доработках */
|
|
314
314
|
const STORE_KEY='buhanka_finance_v9';/* НЕ МЕНЯТЬ НИКОГДА — иначе данные «слетят» при обновлении файла */
|
|
315
315
|
const LEGACY_KEYS=['buhanka_finance_v8','buhanka_finance_v7','buhanka_finance_v6','buhanka_finance_v5'];
|
|
@@ -692,17 +692,27 @@ function empAdjBase(month,empId){ const a=(ST.payAdj[month]&&ST.payAdj[month][em
|
|
|
692
692
|
return Number(a.bonus1||0)+Number(a.bonus2||0)-Number(a.penalty1||0)-Number(a.penalty2||0); }
|
|
693
693
|
function empAdjSum(month,empId){ return empAdjBase(month,empId)+mgrBonusOf(empId); }
|
|
694
694
|
function empAccrued(month,empId){ let s=0; ST.points.forEach(p=>{ s+=empDaysAt(month,empId,p); }); return s+empAdjSum(month,empId); }
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
function opPayHalf(o){ if(o.half===1||o.half===2)return o.half;
|
|
695
|
+
const HALF1_LAST=15, HALF2_FROM=17;
|
|
696
|
+
/* Выплата закрывает тот период, ЗА который сделана, а не тот, в котором прошли деньги.
|
|
697
|
+
1–5 числа — расчёт за вторую половину ПРОШЛОГО месяца;
|
|
698
|
+
6–16 — за первую половину текущего; с 17-го — аванс под вторую половину текущего.
|
|
699
|
+
Ручные метки o.perM (месяц) и o.half (половина) всегда сильнее авто. */
|
|
700
|
+
function opPayPeriod(o){
|
|
702
701
|
const d=Number(String(o.date||'').slice(8,10));
|
|
703
|
-
|
|
702
|
+
const own=MONTHS.indexOf(o.month)>=0?MONTHS.indexOf(o.month):new Date(o.date).getMonth();
|
|
703
|
+
let mi=own, half=(d<=5||d>=HALF2_FROM)?2:1;
|
|
704
|
+
if(d<=5) mi=own>0?own-1:own; /* расчёт за прошлый месяц */
|
|
705
|
+
if(o.perM&&MONTHS.indexOf(o.perM)>=0) mi=MONTHS.indexOf(o.perM);
|
|
706
|
+
if(o.half===1||o.half===2) half=o.half;
|
|
707
|
+
return {month:MONTHS[mi], half};
|
|
708
|
+
}
|
|
709
|
+
function opPayHalf(o){ return opPayPeriod(o).half; }
|
|
710
|
+
function opPayMonth(o){ return opPayPeriod(o).month; }
|
|
711
|
+
function empPaid(month,empId){ return (ST.operations||[])
|
|
712
|
+
.filter(o=>o.employeeId===empId&&o.type==='Расход'&&opPayMonth(o)===month)
|
|
713
|
+
.reduce((s,o)=>s+Math.abs(o.amount),0); }
|
|
704
714
|
function empPaidHalf(month,empId,half){ return (ST.operations||[])
|
|
705
|
-
.filter(o=>o.
|
|
715
|
+
.filter(o=>o.employeeId===empId&&o.type==='Расход'&&(()=>{const p=opPayPeriod(o);return p.month===month&&p.half===half;})())
|
|
706
716
|
.reduce((s,o)=>s+Math.abs(o.amount),0); }
|
|
707
717
|
function empDaysHalf(month,empId,firstHalf){ let s=0; ST.points.forEach(p=>{ const d=ST.timesheet[month]&&ST.timesheet[month][p]&&ST.timesheet[month][p][empId]; if(d)Object.entries(d).forEach(([day,a])=>{ if((Number(day)<=HALF1_LAST)===firstHalf)s+=Number(a||0); }); }); return s; }
|
|
708
718
|
const isMgrEmp=e=>!!(e&&e.isManager);
|
|
@@ -1159,6 +1169,10 @@ function svgBars(vals,labels,opts,W){ opts=opts||{}; const fmtV=opts.fmt||fmtAxi
|
|
|
1159
1169
|
|
|
1160
1170
|
/* ============================================================ RENDER ============================================================ */
|
|
1161
1171
|
function render(){
|
|
1172
|
+
/* правка одной строки перерисовывает страницу целиком — прокрутку надо вернуть,
|
|
1173
|
+
иначе в длинных списках (выписка, операции) отбрасывает в начало */
|
|
1174
|
+
const SCEL=document.scrollingElement||document.documentElement;
|
|
1175
|
+
const SY=SCEL.scrollTop, SX=[...document.querySelectorAll('.tbl-wrap')].map(e=>e.scrollLeft);
|
|
1162
1176
|
applyTheme(); buildNav();
|
|
1163
1177
|
document.querySelectorAll('#nav a').forEach(a=>a.classList.toggle('active',a.dataset.id===CUR));
|
|
1164
1178
|
renderPeriodBar();
|
|
@@ -1166,6 +1180,9 @@ function render(){
|
|
|
1166
1180
|
CHARTQ=[];
|
|
1167
1181
|
({dash:renderDash,capital:renderCapital,audit:renderAudit,todo:renderTodo,checks:renderChecks,pnl:renderPnL,points:renderPoints,cf:renderCF,cfi:renderCFI,recon:renderRecon,taxes:renderTaxes,wcap:renderWcap,loans:renderLoans,ops:renderOps,fixed:renderFixed,sched:renderSched,settings:renderSettings,backup:renderBackup,import:renderImport}[CUR])(document.getElementById('content'));
|
|
1168
1182
|
drawCharts();
|
|
1183
|
+
if(SY){ SCEL.scrollTop=SY; }
|
|
1184
|
+
if(SX.length){ const w=document.querySelectorAll('.tbl-wrap');
|
|
1185
|
+
SX.forEach((v,i)=>{ if(v&&w[i]) w[i].scrollLeft=v; }); }
|
|
1169
1186
|
}
|
|
1170
1187
|
function renderPeriodBar(){
|
|
1171
1188
|
const P=ST.ui.period, mode=periodModeOf(P); let chips='';
|
|
@@ -2212,7 +2229,10 @@ function opModal(id){ const ed=id?ST.operations.find(o=>o.id===id):null;
|
|
|
2212
2229
|
CHANNELS.map(([k,n])=>`<option value="${k}" ${ed&&ed.ch===k?'selected':''}>${n}</option>`).join('')}</select></label></div>
|
|
2213
2230
|
<div class="row" id="op-cfrow"><label class="fld" style="flex:1">Тип для CF<select id="op-cash"><option value="1" ${!ed||ed.cash!==false?'selected':''}>Денежный (касса)</option><option value="0" ${ed&&ed.cash===false?'selected':''}>Неденежный (только начисление)</option></select></label>
|
|
2214
2231
|
<label class="fld" style="flex:1">Сотрудник (для ФОТ)<select id="op-emp"><option value="">—</option>${(ST.employees||[]).map(e=>`<option value="${e.id}" ${ed&&ed.employeeId===e.id?'selected':''}>${esc(e.name)}</option>`).join('')}</select></label>
|
|
2215
|
-
<label class="fld" style="flex:0 0
|
|
2232
|
+
<label class="fld" style="flex:0 0 150px" title="За какой месяц эта выплата — по умолчанию определяется по дате">За месяц<select id="op-perm">
|
|
2233
|
+
<option value="" ${!ed||!ed.perM?'selected':''}>авто${ed&&ed.employeeId?' · '+opPayMonth(ed):''}</option>
|
|
2234
|
+
${MONTHS.map(m=>`<option ${ed&&ed.perM===m?'selected':''}>${m}</option>`).join('')}</select></label>
|
|
2235
|
+
<label class="fld" style="flex:0 0 150px" title="За какую половину месяца эта выплата">За половину<select id="op-half">
|
|
2216
2236
|
<option value="" ${!ed||!ed.half?'selected':''}>авто${ed&&ed.employeeId?' · '+(opPayHalf(ed)===1?'1–15':'16–конец'):''}</option>
|
|
2217
2237
|
<option value="1" ${ed&&ed.half===1?'selected':''}>1–15</option>
|
|
2218
2238
|
<option value="2" ${ed&&ed.half===2?'selected':''}>16–конец</option></select></label></div>
|
|
@@ -2224,6 +2244,7 @@ function opModal(id){ const ed=id?ST.operations.find(o=>o.id===id):null;
|
|
|
2224
2244
|
amount:Math.abs(Number(val('op-amt'))||0),cash:type==='Перевод'?true:val('op-cash')==='1',
|
|
2225
2245
|
employeeId:type==='Перевод'?null:(val('op-emp')||null),
|
|
2226
2246
|
half:type==='Перевод'?null:(Number(val('op-half'))||null),
|
|
2247
|
+
perM:type==='Перевод'?null:(val('op-perm')||null),
|
|
2227
2248
|
ch:type==='Перевод'?null:(val('op-ch')||null),note:val('op-note'),comment:val('op-note')};
|
|
2228
2249
|
if(type==='Перевод'&&rec.account===rec.account2){ alert('Счёт списания и счёт зачисления совпадают'); return; }
|
|
2229
2250
|
if(ed)Object.assign(ed,rec); else ST.operations.push({id:uid(),...rec}); save(); render(); });
|
|
@@ -2347,7 +2368,7 @@ function renderSched(C){
|
|
|
2347
2368
|
/* ---- сверка по половинам: где уже рассчитались, а где ещё нет ---- */
|
|
2348
2369
|
const dcell=(v)=>`<td class="num ${Math.abs(v)<1?'pos':(v>0?'neg':'')}" style="font-weight:600">${Math.abs(v)<1?'ровно':fmt(v)}</td>`;
|
|
2349
2370
|
html+=`<div class="panel"><div class="hd"><h3>Сверка по половинам · ${m}</h3><div class="spacer"></div>
|
|
2350
|
-
<span class="tag" title="1–5 числа —
|
|
2371
|
+
<span class="tag" title="Выплата закрывает период, за который сделана: 1–5 числа — вторая половина прошлого месяца, 6–16 — первая половина текущего, с 17-го — вторая половина текущего. Месяц и половину можно поправить в самой операции">разнесение выплат — авто</span></div>
|
|
2351
2372
|
<div class="bd" style="padding:0"><div class="tbl-wrap"><table class="gt" style="font-size:12.5px;min-width:900px">
|
|
2352
2373
|
<colgroup><col style="width:180px"><col style="width:100px"><col style="width:100px"><col style="width:100px">
|
|
2353
2374
|
<col style="width:100px"><col style="width:100px"><col style="width:100px"><col style="width:110px"></colgroup>
|
|
@@ -2368,7 +2389,8 @@ function renderSched(C){
|
|
|
2368
2389
|
<td class="num" style="${bl}">${fmt(a2t)}</td><td class="num">${fmt(p2t)}</td>${dcell(a2t-p2t)}
|
|
2369
2390
|
<td class="num" style="${bl}">${fmt((a1t+a2t)-(p1t+p2t))}</td></tr></tbody></table></div>
|
|
2370
2391
|
<div class="bd"><p class="hint" style="margin:0">Δ — сколько ещё должны за эту половину. Минус значит выдали вперёд.
|
|
2371
|
-
|
|
2392
|
+
Выплата засчитывается в тот период, за который сделана: расчёт 1–5 числа закрывает вторую половину прошлого месяца.
|
|
2393
|
+
Если авто ошиблось, поправьте «За месяц» и «За половину» в самой операции.</p></div>
|
|
2372
2394
|
</div></div>`;
|
|
2373
2395
|
|
|
2374
2396
|
ST.points.forEach(p=>{ const pemps=emps.filter(e=>(e.points||[]).includes(p));
|