fix(salary): recompute entitled_days on existing ledger rows and record pre-cutover taken days (#1403)
The vacation ledger sync carried entitled_days verbatim on existing open rows while re-deriving accrued and taken, so a stale entitled value (for example the flat 25 stored before Semesterlagen 7 § pro-rating existed) survived every sync. The recompute loop now re-derives entitled the same way the lazy-seed path does, with the opening-balance cutover still outranking recomputation for the year containing cutover_date. Opening balances could also not record paid vacation days already taken in the cutover year under the previous payroll system. New additive column employee_opening_balances.vacation_days_taken_this_year (NUMERIC NOT NULL DEFAULT 0, CHECK 0..40) threaded through the shared service, the Zod schema, the MCP staging tool (schema + mergeable fields), the staged-operation executor, the v1 REST routes, and the employee editor form. Ledger semantics for the cutover year, on both seed and recompute paths: entitled = remaining + taken_this_year, taken = booked-run taken + taken_this_year, so remaining keeps meaning remaining and the seeded value survives every subsequent sync. Fixes #1347 Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Jakob Wennberg
Claude Fable 5
parent
2296c0cd59
commit
9f5a43310b
@@ -27,6 +27,7 @@ interface OpeningBalancesData {
|
||||
ytd_tax: number
|
||||
ytd_net: number
|
||||
vacation_paid_days_remaining: number
|
||||
vacation_days_taken_this_year: number
|
||||
vacation_saved_days_by_year: Record<string, number>
|
||||
opening_semester_liability: number
|
||||
opening_semester_liability_avgifter: number
|
||||
@@ -52,6 +53,7 @@ export function OpeningBalancesPanel({ employeeId, canWrite }: { employeeId: str
|
||||
const [ytdTax, setYtdTax] = useState('')
|
||||
const [ytdNet, setYtdNet] = useState('')
|
||||
const [daysRemaining, setDaysRemaining] = useState('')
|
||||
const [daysTaken, setDaysTaken] = useState('')
|
||||
const [savedByYear, setSavedByYear] = useState<Record<string, string>>({})
|
||||
const [liability, setLiability] = useState('')
|
||||
const [liabilityAvgifter, setLiabilityAvgifter] = useState('')
|
||||
@@ -71,6 +73,7 @@ export function OpeningBalancesPanel({ employeeId, canWrite }: { employeeId: str
|
||||
setYtdTax(String(data.ytd_tax))
|
||||
setYtdNet(String(data.ytd_net))
|
||||
setDaysRemaining(String(data.vacation_paid_days_remaining))
|
||||
setDaysTaken(String(data.vacation_days_taken_this_year ?? 0))
|
||||
setSavedByYear(
|
||||
Object.fromEntries(
|
||||
Object.entries(data.vacation_saved_days_by_year ?? {}).map(([y, d]) => [y, String(d)]),
|
||||
@@ -103,6 +106,7 @@ export function OpeningBalancesPanel({ employeeId, canWrite }: { employeeId: str
|
||||
ytd_tax: parseFloat(ytdTax) || 0,
|
||||
ytd_net: parseFloat(ytdNet) || 0,
|
||||
vacation_paid_days_remaining: parseFloat(daysRemaining) || 0,
|
||||
vacation_days_taken_this_year: parseFloat(daysTaken) || 0,
|
||||
vacation_saved_days_by_year: saved,
|
||||
opening_semester_liability: parseFloat(liability) || 0,
|
||||
opening_semester_liability_avgifter: parseFloat(liabilityAvgifter) || 0,
|
||||
@@ -215,6 +219,12 @@ export function OpeningBalancesPanel({ employeeId, canWrite }: { employeeId: str
|
||||
<Input id="ob-days-remaining" type="number" min={0} max={40} step={0.5} value={daysRemaining}
|
||||
onChange={(e) => setDaysRemaining(e.target.value)} disabled={readOnly} className="tabular-nums" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ob-days-taken">{t('opening_balances_days_taken')}</Label>
|
||||
<Input id="ob-days-taken" type="number" min={0} max={40} step={0.5} value={daysTaken}
|
||||
onChange={(e) => setDaysTaken(e.target.value)} disabled={readOnly} className="tabular-nums" />
|
||||
<p className="text-xs text-muted-foreground">{t('opening_balances_days_taken_hint')}</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ob-liability">{t('opening_balances_liability')}</Label>
|
||||
<Input id="ob-liability" type="number" min={0} value={liability}
|
||||
|
||||
Reference in New Issue
Block a user