seahaven-ap/packages/shared/src/pay-date.ts
Adam Moussa 46b570352a
feat(shared): add payment ladder and CSV domain package (AP-13) (#11)
* feat(shared): add payment ladder and CSV domain package

Introduce @seahaven-ap/shared via npm workspaces so FE and future API share
payments-dashboard-compatible status ladders, pay-date helpers, and CSV headers.

* fix(web): build shared package before vite dev

Ensure fresh npm ci && npm run dev (and Playwright webServer) can resolve
@seahaven-ap/shared dist exports that are gitignored.
2026-08-10 19:28:02 -04:00

53 lines
1.8 KiB
TypeScript

/**
* Shared date parsing for Stampli M/D/YY and MM/DD/YYYY formats.
* Ported from payments-dashboard/src/dates.js — both formats must parse;
* anything else returns null (never pass through garbage).
*/
export type ParsedMDY = {
year: number;
month: number;
day: number;
};
/** Parse "M/D/YY" or "MM/DD/YYYY" into { year, month, day }, or null. */
export function parseMDY(value: string | null | undefined): ParsedMDY | null {
const match = /^(\d{1,2})\/(\d{1,2})\/(\d{2}|\d{4})$/.exec(String(value ?? "").trim());
if (!match) return null;
const month = Number.parseInt(match[1]!, 10);
const day = Number.parseInt(match[2]!, 10);
let year = Number.parseInt(match[3]!, 10);
if (match[3]!.length === 2) year += 2000;
const dt = new Date(year, month - 1, day);
if (dt.getFullYear() !== year || dt.getMonth() !== month - 1 || dt.getDate() !== day) {
return null;
}
return { year, month, day };
}
/** "YYYY-MM-DD" or null. */
export function toISODate(value: string | null | undefined): string | null {
const p = parseMDY(value);
if (!p) return null;
return `${p.year}-${String(p.month).padStart(2, "0")}-${String(p.day).padStart(2, "0")}`;
}
/** Canonical "MM/DD/YYYY" or null. Stored form for send_payment_on / CSV. */
export function toCanonicalMDY(value: string | null | undefined): string | null {
const p = parseMDY(value);
if (!p) return null;
return `${String(p.month).padStart(2, "0")}/${String(p.day).padStart(2, "0")}/${p.year}`;
}
/**
* Plausibility window for ingested send dates.
* Relative to the current year (7 years back, 2 years forward).
*/
export function isPlausibleSendYear(
value: string | null | undefined,
nowYear: number = new Date().getFullYear(),
): boolean {
const p = parseMDY(value);
if (p === null) return false;
return p.year >= nowYear - 7 && p.year <= nowYear + 2;
}