feat: ui changes and pdf setup on the clients

This commit is contained in:
Nathnael
2026-07-01 06:56:58 +00:00
parent 94982d3c5a
commit e5aab84f51
16 changed files with 857 additions and 354 deletions

View File

@@ -31,9 +31,6 @@ export interface WarehouseHandoverPdfContext {
const escapePdfText = (value: string) =>
value.replace(/\\/g, '\\\\').replace(/\(/g, '\\(').replace(/\)/g, '\\)');
const money = (amount: unknown, currency = 'USD') =>
`${Number(amount ?? 0).toLocaleString()} ${currency === 'ETB' ? 'Birr (ETB)' : currency}`;
const fmtDate = (value: unknown) => {
if (!value) return '-';
const date = new Date(value as string | Date);
@@ -96,12 +93,6 @@ const textOp = (
color = '0 0 0',
) => `BT\n${color} rg\n/${bold ? 'F2' : 'F1'} ${size} Tf\n${x} ${y} Td\n(${escapePdfText(text)}) Tj\nET`;
const buildAuthorizationBand = (label: 'PAID' | 'CLEARED') => [
lineOp(60, 242, 535, 242),
textOp('AUTHORIZED SEAL', 382, 218, 9, true, GREEN),
buildCircularSeal(452, 155, label),
];
const buildWarehouseOfficerSealBand = () => [
lineOp(60, 218, 535, 218),
textOp('WAREHOUSE OFFICER SEAL', 92, 194, 9, true, GREEN),
@@ -146,57 +137,6 @@ function buildSimplePdf(lines: PdfLine[], rawOps: string[] = []): Blob {
return new Blob([pdf], { type: 'application/pdf' });
}
export function buildWarehouseInvoicePdf(invoice: WarehouseFeeInvoice, kind: 'INVOICE' | 'RECEIPT') {
const paid = kind === 'RECEIPT' || invoice.status === 'PAID';
const title = `Warehouse Fee ${kind === 'RECEIPT' ? 'Receipt' : 'Invoice'}`;
const bookingReference = firstText(invoice.bookingReference);
const customerName = firstText(invoice.customerName);
const inventoryReference = firstText(invoice.inventoryReference);
const inventoryInfo = firstText(invoice.inventoryInfo, invoice.containerNumber, invoice.cargoDescription);
const clearanceStatus = firstText(
invoice.clearanceStatus,
paid ? 'FEE PAID - READY FOR RELEASE' : 'PENDING PAYMENT',
);
const lines: PdfLine[] = [
{ text: 'Ethio-Djibouti Railway S.C.', size: 12, bold: true, yGap: 0, align: 'center' },
{ text: title, size: 23, bold: true, yGap: 28, align: 'center' },
{ text: `Document No: ${invoice.invoiceNumber}`, size: 12, bold: true, yGap: 32, align: 'center' },
{ text: `Status: ${invoice.status.replace(/_/g, ' ')} Type: ${invoice.invoiceType.replace(/_/g, ' ')}`, align: 'center' },
{ text: `Booking Reference: ${bookingReference} Customer: ${customerName}`, align: 'center' },
{ text: `Inventory Reference: ${inventoryReference} Inventory Info: ${inventoryInfo}`, align: 'center' },
{ text: `Clearance: ${clearanceStatus}`, align: 'center' },
{ text: `Issued: ${fmtDate(invoice.issuedAt)} Paid At: ${fmtDate(invoice.paidAt)}`, align: 'center' },
{ text: 'ITEMS', size: 13, bold: true, yGap: 30, align: 'center' },
...(invoice.items ?? []).flatMap((item) => [
{ text: item.description, bold: true, align: 'center' as const },
{
text: `${item.feeType.replace(/_/g, ' ')} | Qty ${Number(item.quantity ?? 0).toLocaleString()} | Rate ${money(item.unitRate, item.currency)} | Amount ${money(item.amount, item.currency)}`,
yGap: 13,
align: 'center' as const,
},
]),
{ text: 'TOTALS', size: 13, bold: true, yGap: 30, align: 'center' },
{ text: `Subtotal: ${money(invoice.subtotalAmount, invoice.currency)}`, align: 'center' },
{ text: `Tax: ${money(invoice.taxAmount, invoice.currency)}`, align: 'center' },
{ text: `Total: ${money(invoice.totalAmount, invoice.currency)}`, bold: true, align: 'center' },
{ text: `Paid: ${money(invoice.paidAmount, invoice.currency)}`, align: 'center' },
{ text: `Balance: ${money(invoice.balanceAmount, invoice.currency)}`, bold: true, align: 'center' },
];
const authorizationOps = [
...buildAuthorizationBand('PAID'),
textOp('Prepared by EDR warehouse finance', 72, 196, 10),
textOp('Finance officer name / signature / date:', 72, 164, 10),
lineOp(245, 162, 360, 162, '0 0 0'),
];
const invoiceOps = [
lineOp(60, 242, 535, 242),
textOp('Prepared by EDR warehouse finance', 72, 196, 10),
textOp('Finance officer name / signature / date:', 72, 164, 10),
lineOp(245, 162, 360, 162, '0 0 0'),
];
return buildSimplePdf(lines, paid ? authorizationOps : invoiceOps);
}
const firstText = (...values: Array<unknown>) => {
for (const value of values) {
if (value !== null && value !== undefined && String(value).trim()) return String(value);