Files
beenvoice/src/lib/invoice-status.ts
2025-07-31 18:37:33 -04:00

138 lines
3.2 KiB
TypeScript

import type {
StoredInvoiceStatus,
EffectiveInvoiceStatus,
} from "~/types/invoice";
// Types are now imported from ~/types/invoice
/**
* Calculate the effective status of an invoice including overdue computation
*/
export function getEffectiveInvoiceStatus(
storedStatus: StoredInvoiceStatus,
dueDate: Date | string,
): EffectiveInvoiceStatus {
// If already paid, status is always paid regardless of due date
if (storedStatus === "paid") {
return "paid";
}
// If draft, status is always draft
if (storedStatus === "draft") {
return "draft";
}
// For sent invoices, check if overdue
if (storedStatus === "sent") {
const today = new Date();
const due = new Date(dueDate);
// Set both dates to start of day for accurate comparison
today.setHours(0, 0, 0, 0);
due.setHours(0, 0, 0, 0);
return due < today ? "overdue" : "sent";
}
return storedStatus;
}
/**
* Check if an invoice is overdue
*/
export function isInvoiceOverdue(
storedStatus: StoredInvoiceStatus,
dueDate: Date | string,
): boolean {
return getEffectiveInvoiceStatus(storedStatus, dueDate) === "overdue";
}
/**
* Get days past due (returns 0 if not overdue)
*/
export function getDaysPastDue(
storedStatus: StoredInvoiceStatus,
dueDate: Date | string,
): number {
if (!isInvoiceOverdue(storedStatus, dueDate)) {
return 0;
}
const today = new Date();
const due = new Date(dueDate);
today.setHours(0, 0, 0, 0);
due.setHours(0, 0, 0, 0);
const diffTime = today.getTime() - due.getTime();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return Math.max(0, diffDays);
}
/**
* Status configuration for UI display
*/
export const statusConfig = {
draft: {
label: "Draft",
color: "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300",
description: "Invoice is being prepared",
},
sent: {
label: "Sent",
color: "bg-primary/10 text-primary",
description: "Invoice sent to client",
},
paid: {
label: "Paid",
color: "bg-primary/10 text-primary",
description: "Payment received",
},
overdue: {
label: "Overdue",
color: "bg-destructive/10 text-destructive",
description: "Payment is overdue",
},
} as const;
/**
* Get status configuration for display
*/
export function getStatusConfig(
storedStatus: StoredInvoiceStatus,
dueDate: Date | string,
) {
const effectiveStatus = getEffectiveInvoiceStatus(storedStatus, dueDate);
return statusConfig[effectiveStatus];
}
/**
* Get valid status transitions from current stored status
*/
export function getValidStatusTransitions(
currentStatus: StoredInvoiceStatus,
): StoredInvoiceStatus[] {
switch (currentStatus) {
case "draft":
return ["sent", "paid"]; // Can send or mark paid directly
case "sent":
return ["paid", "draft"]; // Can mark paid or revert to draft
case "paid":
return ["sent"]; // Can revert to sent if needed (rare cases)
default:
return [];
}
}
/**
* Check if a status transition is valid
*/
export function isValidStatusTransition(
from: StoredInvoiceStatus,
to: StoredInvoiceStatus,
): boolean {
const validTransitions = getValidStatusTransitions(from);
return validTransitions.includes(to);
}