feat(attendance): 'Chybějící hodiny' (manko) row — real hours below the fond

manko = max(0, fond − (worked_total + dovolená + nemoc + neplacené volno))
using REAL clock time, not the paušál. Supplement next to Přesčas — the
verified binding formulas are untouched.

Why: the paušál Odpracováno can nominally cover the fond while dozens of
real hours are missing (Dominik 6/2026: 21 days × 8 + 8h dovolená = 176 =
fond exactly, thanks to weekend shifts compensating 3 recordless weekdays —
yet real worked time was 136,5h). Přesčas = 0 correctly, and the 31,5h gap
vanished from every total. Now it's a permanent summary line, red+bold when
positive, plus a 'Chybí: Xh' chip on the admin screen cards.

+4 tests incl. the real June dataset as a pinned acceptance case (also
pins noc 6,75 — early shifts starting 05:30/05:45 legitimately count
night minutes before 06:00).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-07-08 00:12:20 +02:00
parent 3cd7cfcc34
commit 996012e3d3
5 changed files with 113 additions and 0 deletions

View File

@@ -97,6 +97,7 @@ interface UserTotal {
weekend_hours?: number;
night_minutes?: number;
worked_holiday_hours?: number; // sum of hours worked ON a holiday
manko?: number; // Chybějící hodiny: real clock time below the fond
records?: AttendanceRecord[];
}
@@ -243,6 +244,7 @@ function computeUserTotals(
t.weekend_hours = s.weekendHours;
t.night_minutes = s.nightMinutes;
t.worked_holiday_hours = s.svatekHours;
t.manko = s.manko;
}
return totals;
@@ -427,6 +429,10 @@ export function buildUserSectionHtml(
<span class="summary-label">Přesčas:</span>
<span class="summary-value">${formatHoursDecimal(s.prescas * 60)}h</span>
</div>
<div class="summary-row${s.manko > 0 ? " summary-alert" : ""}">
<span class="summary-label">Chybějící hodiny:</span>
<span class="summary-value">${formatHoursDecimal(s.manko * 60)}h</span>
</div>
<div class="summary-row">
<span class="summary-label">Svátek:</span>
<span class="summary-value">${formatHoursDecimal(s.svatekHours * 60)}h</span>
@@ -547,6 +553,9 @@ export function buildPrintHtml(
}
.user-summary .summary-label { color: #555; }
.user-summary .summary-value { font-weight: 600; }
/* Chybějící hodiny > 0 — must jump out of the sheet, not hide in it. */
.user-summary .summary-alert .summary-label,
.user-summary .summary-alert .summary-value { color: #b00020; font-weight: 700; }
.user-summary .summary-fund {
border-top: 1px solid #ddd; margin-top: 4px; padding-top: 6px;
}