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:
@@ -1,7 +1,11 @@
|
||||
import prisma from "../config/database";
|
||||
import { config } from "../config/env";
|
||||
import { sendMail } from "./mailer";
|
||||
import { localDateCzStr, localDateStr } from "../utils/date";
|
||||
import {
|
||||
localDateCzStr,
|
||||
localDateStr,
|
||||
utcMidnightOfLocalDay,
|
||||
} from "../utils/date";
|
||||
import { getSystemSettings } from "./system-settings";
|
||||
|
||||
interface AlertInvoice {
|
||||
@@ -33,17 +37,35 @@ function formatAmount(n: number | { toNumber?: () => number }): string {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Alert window [today, today+3] for the due-date queries + the local date
|
||||
* strings the classifier matches against.
|
||||
*
|
||||
* due_date is @db.Date: Prisma truncates the filter Dates to their UTC date
|
||||
* part, so the window boundaries must be UTC midnights of the LOCAL calendar
|
||||
* day. A local midnight (22:00/23:00 UTC of the previous day) shifted the
|
||||
* whole window a day early — the "splatnost za 3 dny" advance alert
|
||||
* (due == today+3) could then never fire.
|
||||
*
|
||||
* Exported (with an injectable `now`) so the window math is unit-testable.
|
||||
*/
|
||||
export function computeAlertWindow(now: Date = new Date()) {
|
||||
const today = utcMidnightOfLocalDay(now);
|
||||
const todayStr = localDateStr(now);
|
||||
const in3days = new Date(today);
|
||||
in3days.setUTCDate(in3days.getUTCDate() + 3);
|
||||
const in3daysStr = localDateStr(
|
||||
new Date(now.getFullYear(), now.getMonth(), now.getDate() + 3),
|
||||
);
|
||||
return { today, todayStr, in3days, in3daysStr };
|
||||
}
|
||||
|
||||
export async function checkInvoiceAlerts(): Promise<void> {
|
||||
const settings = await getSystemSettings();
|
||||
const alertEmail = settings.invoice_alert_email || config.email.invoiceAlert;
|
||||
if (!alertEmail) return;
|
||||
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const todayStr = localDateStr(today);
|
||||
const in3days = new Date(today);
|
||||
in3days.setDate(in3days.getDate() + 3);
|
||||
const in3daysStr = localDateStr(in3days);
|
||||
const { today, todayStr, in3days, in3daysStr } = computeAlertWindow();
|
||||
|
||||
// Classify a due date into an alert type/label, or null if it doesn't match.
|
||||
const classify = (
|
||||
|
||||
Reference in New Issue
Block a user