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.
Files changed (2) hide show
  1. package/index.html +35 -13
  2. 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.4';
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
- function empPaid(month,empId){ return ST.operations.filter(o=>o.month===month&&o.employeeId===empId&&o.type==='Расход').reduce((s,o)=>s+Math.abs(o.amount),0); }
696
- const HALF1_LAST=15;
697
- /* за какую половину платим: выплата должна уменьшать долг того периода, за который сделана.
698
- 1–5 числа — расчёт за вторую половину прошлого периода; 6–16 — аванс и расчёт за первую;
699
- с 17-го — уже аванс под вторую. Явная метка o.half всегда сильнее. */
700
- const HALF2_FROM=17;
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
- return (d<=5||d>=HALF2_FROM)?2:1; }
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.month===month&&o.employeeId===empId&&o.type==='Расход'&&opPayHalf(o)===half)
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 190px" title="За какую половину месяца эта выплата">За половину<select id="op-half">
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 числа — расчёт за вторую половину, 6–16 — за первую, с 17-го — аванс под вторую. Половину можно поправить в самой операции">разнесение выплат — авто</span></div>
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
- Если выплата отнесена не к тому периоду, поправьте «За половину» в самой операции.</p></div>
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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buhanka-finance-app",
3
- "version": "1.71.0",
3
+ "version": "1.72.0",
4
4
  "description": "Buhanka bakery finance dashboard — single-file web app",
5
5
  "license": "MIT",
6
6
  "files": [