import ReceiptPrinterEncoder from '@point-of-sale/receipt-printer-encoder'; import { normalizeEncoderOptions } from '@/lib/thermal-printer/normalize-encoder-options'; import type { PaperSize } from '@/lib/thermal-printer/types'; import type { OrderListItem } from '@/types/order'; export type EncodeOrderReceiptOptions = { storeName: string; storeAddress: string; paperSize: PaperSize; language?: string | null; codepageMapping?: string | null; }; export function getPaperColumns(paperSize: PaperSize): number { return paperSize === '58mm' ? 32 : 48; } function dashedLine(columns: number): string { return '-'.repeat(columns); } export function encodeOrderReceipt( order: OrderListItem, options: EncodeOrderReceiptOptions, ): Uint8Array { const columns = getPaperColumns(options.paperSize); const amountColumnWidth = 14; const labelColumnWidth = columns - amountColumnWidth; const encoderOptions = normalizeEncoderOptions({ language: options.language, codepageMapping: options.codepageMapping, }); const encoder = new ReceiptPrinterEncoder({ language: encoderOptions.language, codepageMapping: encoderOptions.codepageMapping, columns, }); const createdBy = order.created_by?.profile?.full_name ?? order.created_by?.username ?? '-'; encoder.initialize() .align('center') .bold(true) .text('') .newline() .text(options.storeName) .newline() .bold(false) .newline(); for (const addressLine of options.storeAddress.split('\n')) { const trimmed = addressLine.trim(); if (trimmed) { encoder.align('center').text(trimmed).newline(); } } encoder.align('center').line(dashedLine(columns)); encoder.align('left'); encoder.line(`No : ${order.order_number}`); encoder.line(`Tgl : ${order.created_at_formatted}`); if (order.customer) { encoder.line(`Cust : ${order.customer.name}`); } encoder .line(`Kasir: ${createdBy}`) .line(dashedLine(columns)); for (const item of order.items) { const productName = item.product_variant?.product?.name ?? '-'; const variantName = item.product_variant?.name ?? '-'; encoder .line(productName) .line(` ${variantName}`) .table( [ { width: labelColumnWidth, align: 'left' }, { width: amountColumnWidth, align: 'right' }, ], [[ `${item.quantity_formatted} x ${item.unit_price_formatted}`, (rowEncoder) => rowEncoder.bold().text(item.subtotal_formatted).bold(false), ]], ) .newline(); } encoder.line(dashedLine(columns)); const summaryRows: string[][] = [ ['Subtotal', order.subtotal_formatted], ['Diskon', order.discount_formatted], ]; if (order.shipping_cost > 0) { summaryRows.push(['Ongkir', order.shipping_cost_formatted]); } summaryRows.push(['Total', order.total_amount_formatted]); encoder.table( [ { width: labelColumnWidth, align: 'left' }, { width: amountColumnWidth, align: 'right' }, ], summaryRows, ); if (order.notes) { encoder .newline() .line('Catatan:') .line(order.notes); } encoder .newline() .line(dashedLine(columns)) .align('center') .line('Terima kasih atas kunjungannya') .line('Silakan berbelanja kembali') .newline(3) .cut(); return encoder.encode(); }