fix(dates): @db.Date filters built from local midnight queried the wrong day
Prisma truncates a JS Date used in a WHERE filter on a @db.Date column to its UTC date part. Under TZ=Europe/Prague a local-midnight boundary (new Date(y,m,d) = 22:00/23:00Z of the previous day) therefore filtered as the PREVIOUS calendar date. Same class as the dashboard fix; full sweep of every @db.Date filter in the codebase. New shared helper utcMidnightOfLocalDay() in src/utils/date.ts. Fixed (all previously off by one day): - attendance.service: getStatus today+month windows; getWorkfund (last day of each month was double-counted across months); getPrintData (monthly print included prev month's last day); listAttendance (admin month view included prev month's last day AND dropped the selected month's last day); createAttendance duplicate/overlap validation (checked only the PREVIOUS day - same-day duplicates were never caught, neighbors falsely rejected) - invoice-alerts: the 'splatnost za 3 dny' advance alert had NEVER fired (due==today+3 was outside the fetched window); window computation extracted as computeAlertWindow() + pure tests - invoices.service: month list/totals filter dropped invoices issued on the month's last day; getInvoiceStats month/year bounds; markOverdueInvoices boundary; auto paid_date could store yesterday during 00:00-02:00 - received-invoices auto paid_date, issued-orders default order_date: same night-window write hazard, now via the helper - attendance.schema: shift_date hardened to isoDateString (bare YYYY-MM-DD) +9 regression tests (month boundaries, same-day duplicate, last-day-of-month invoice in list+totals, alert window). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
||||
} from "../../schemas/received-invoices.schema";
|
||||
import { nasInvoicesManager } from "../../services/nas-financials-manager";
|
||||
import { toCzk } from "../../services/exchange-rates";
|
||||
import { utcMidnightOfLocalDay } from "../../utils/date";
|
||||
import path from "path";
|
||||
|
||||
const VALID_STATUSES = ["unpaid", "paid"] as const;
|
||||
@@ -561,14 +562,17 @@ export default async function receivedInvoicesRoutes(
|
||||
// `amount` is the GROSS total (VAT included); VAT is the portion within it.
|
||||
const computedVat = vatFromGross(finalAmount, finalVatRate);
|
||||
|
||||
// Auto-set paid_date when status transitions to paid (matching PHP)
|
||||
// Auto-set paid_date when status transitions to paid (matching PHP).
|
||||
// paid_date is @db.Date (truncated to the UTC date part) —
|
||||
// utcMidnightOfLocalDay keeps the LOCAL calendar day even during the
|
||||
// 00:00–02:00 Prague window (a bare new Date() stored yesterday there).
|
||||
const newStatus =
|
||||
body.status !== undefined
|
||||
? String(body.status)
|
||||
: String(existing.status);
|
||||
const paidDate =
|
||||
newStatus === "paid" && String(existing.status) !== "paid"
|
||||
? new Date()
|
||||
? utcMidnightOfLocalDay()
|
||||
: body.paid_date !== undefined
|
||||
? body.paid_date
|
||||
? new Date(String(body.paid_date))
|
||||
|
||||
Reference in New Issue
Block a user