134 lines
3.7 KiB
TypeScript
134 lines
3.7 KiB
TypeScript
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();
|
|
}
|