- Migration M8 adds payments (cash, TDS, mode, reference). Payment status is derived, never stored: unpaid, partially paid, paid, or overdue. Overpayment is rejected, TDS counts toward settling, and only issued invoices accept payments. list_invoices and get_invoice carry the paid, TDS and balance summary. - New invoice detail view: archived-PDF preview, payments list, Record payment modal, Export, Duplicate (as a draft), Re-render and Cancel. An invoice with payments cannot be cancelled until they are deleted. - History gets search, status, financial-year, month and client filters, sortable columns, pagination, footer totals and an FY summary. Cancelled invoices are excluded from totals. The per-row archive-status calls are gone; archiving is on the detail page. - Rust tests for payments and the list summary; vitest for fiscal-year, money, filter and duplicate helpers. PDF output and fingerprint goldens are unchanged. The new UI has not been run in a webview yet.
49 lines
1.8 KiB
TypeScript
49 lines
1.8 KiB
TypeScript
/** Indian financial year (April to March) helpers. Invoice dates are plain YYYY-MM-DD calendar dates, so they
|
|
* are read from the string; only "now" needs a time zone, and that is always IST (UTC+05:30). */
|
|
|
|
const ISO = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
const IST_OFFSET_MS = (5 * 60 + 30) * 60_000;
|
|
|
|
/** Months in financial-year order, for the month filter. */
|
|
export const FY_MONTHS: { month: number; label: string }[] = [4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3].map((month) => ({
|
|
month,
|
|
label: new Date(2000, month - 1, 1).toLocaleDateString("en-GB", { month: "long" }),
|
|
}));
|
|
|
|
/** Month 1-12 of an ISO date, or null when it is not one. */
|
|
export function monthOf(iso: string): number | null {
|
|
const m = ISO.exec(iso);
|
|
if (!m) return null;
|
|
const month = Number(m[2]);
|
|
return month >= 1 && month <= 12 ? month : null;
|
|
}
|
|
|
|
/** The calendar year in which the financial year containing `iso` starts (2026-03-31 -> 2025, 2026-04-01 -> 2026). */
|
|
export function fyStartYear(iso: string): number | null {
|
|
const m = ISO.exec(iso);
|
|
const month = monthOf(iso);
|
|
if (!m || month === null) return null;
|
|
const year = Number(m[1]);
|
|
return month >= 4 ? year : year - 1;
|
|
}
|
|
|
|
/** 2025 -> "FY 2025-26". */
|
|
export function fyLabel(startYear: number): string {
|
|
return `FY ${startYear}-${String((startYear + 1) % 100).padStart(2, "0")}`;
|
|
}
|
|
|
|
/** First and last day of a financial year. */
|
|
export function fyRange(startYear: number): { from: string; to: string } {
|
|
return { from: `${startYear}-04-01`, to: `${startYear + 1}-03-31` };
|
|
}
|
|
|
|
/** Today's date in IST as YYYY-MM-DD. */
|
|
export function todayIst(now: Date = new Date()): string {
|
|
return new Date(now.getTime() + IST_OFFSET_MS).toISOString().slice(0, 10);
|
|
}
|
|
|
|
/** The financial year that `now` falls in, in IST. */
|
|
export function currentFyStartYear(now: Date = new Date()): number {
|
|
return fyStartYear(todayIst(now)) as number;
|
|
}
|