Created
February 12, 2024 09:32
-
-
Save ANNASBlackHat/9d7e4d454380e71b7749112c819825f7 to your computer and use it in GitHub Desktop.
Receipt Note Format
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:core'; | |
import 'package:intl/intl.dart'; | |
void main() { | |
// Create dummy data | |
SalesEntity sales = dummySales(); | |
Outlet outlet = dummyOutlet(); | |
Employee employee = Employee( | |
name: 'Jane Doe', | |
); | |
var notaFormat = getPrintNotaFormat(sales, outlet, employee); | |
print(notaFormat); | |
} | |
String getPrintNotaFormat(SalesEntity sales, Outlet outlet, Employee employee, | |
{bool isBill = false, | |
int paperSize = 58, | |
bool isReprint = false, | |
String language = "id"}) { | |
StringBuffer teks = StringBuffer(); | |
StringBuffer header = StringBuffer('[CUT]\n'); | |
int MAX = getMaxCharacter(paperSize); | |
try { | |
header.writeln(outlet.name.trim().center(MAX)); | |
header.writeln(outlet.receiptAddress?.trim()?.center(MAX)); | |
if (outlet.receiptPhone != null) { | |
header.writeln(outlet.receiptPhone!.trim().center(MAX)); | |
} | |
header.writeln("=" * MAX); | |
String paymentDetail = sales.payments.first.bank.name != null | |
? "(${sales.payments.first.bank.name})" | |
: ""; | |
if (sales.salesTag != null) { | |
SalesTagEntity tag = sales.salesTag!; | |
teks.writeln("#${tag.name.toUpperCase()}"); | |
} | |
ReceiptText lng = receiptLanguage(language); | |
teks.writeln("${lng.receiptNumber} : ${sales.displayNota}".padRight(MAX)); | |
teks.writeln("${lng.customer} : ${sales.customer}".padRight(MAX)); | |
teks.writeln( | |
"${lng.date} : ${sales.timeCreated.dateTimeFormat()}".padRight(MAX)); | |
teks.writeln("${lng.cashier} : ${employee.name}".padRight(MAX)); | |
teks.writeln( | |
"${lng.payment} : ${sales.payment} $paymentDetail".padRight(MAX)); | |
if (sales.table.isNotEmpty) { | |
teks.writeln("${lng.table} : ${sales.table}".padRight(MAX)); | |
} | |
String? voucherCode; | |
if (sales.promotions.isNotEmpty) { | |
voucherCode = sales.promotions.first.displayVoucher; | |
} | |
if (voucherCode != null) { | |
teks.writeln("Voucher: ${voucherCode.padRight(MAX)}"); | |
} | |
teks.writeln("\n\n"); | |
int disc = 0; | |
sales.orderList.sort((a, b) { | |
return a.product.productId.compareTo(b.product.productId); | |
}); | |
sales.orderList.forEach((Order orders) { | |
double total = 0; | |
int promoItemValue = orders.promotion?.promotionValue ?? 0; | |
if (sales.status == "Refund") { | |
orders.qty = orders.qty * -1; | |
orders.subTotal = orders.subTotal * -1; | |
} | |
total += orders.subTotal; | |
String name; | |
if (orders.isItemVoid) { | |
name = "VOID: ${orders.product.name}"; | |
} else { | |
name = orders.product.name ?? ''; | |
} | |
teks.write(name.padRight(MAX - 4 - 8)); | |
teks.write(orders.qty.toString().center(4)); | |
teks.writeln((orders.subTotal + promoItemValue).toCurrency().padLeft(8)); | |
orders.extra.forEach((Order extra) { | |
String name; | |
if (orders.isItemVoid) { | |
name = "-VOID: ${extra.product.name}"; | |
} else { | |
name = "-${extra.product.name}"; | |
} | |
teks.write(name.padRight(MAX - 4 - 8)); | |
teks.write(extra.qty.toString().width(4)); | |
teks.writeln(extra.subTotal.toCurrency().padLeft(8)); | |
total += extra.subTotal; | |
}); | |
// Print promotion details | |
if (orders.discount.discountNominal != 0) { | |
disc += orders.discount.discountNominal; | |
String txtDisc = "-Discount"; | |
if (orders.discount.discountType == Constant.TYPE_PERCENT) { | |
txtDisc = "-Discount (${orders.discount.discount}%)"; | |
} | |
teks | |
..write(txtDisc.padRight(MAX - 8)) | |
..writeln( | |
("-${orders.discount.discountNominal.toCurrency()}").padLeft(8)) | |
..writeln("( ${orders.discount.discountInfo} )".padRight(MAX)); | |
} | |
if (!isBill && orders.note != null) { | |
teks.writeln(orders.note!.widthRight(MAX)); | |
} | |
}); | |
int extrasSubtotal = 0; | |
var extras = sales.orderList.expand((order) => order.extra).cast<Order>(); | |
if (extras.isNotEmpty) { | |
extrasSubtotal = extras.map((e) => e.subTotal).reduce((a, b) => a + b); | |
} | |
var orderListSubtotal = | |
sales.orderList.map((order) => order.subTotal).reduce((a, b) => a + b); | |
var subtotal = orderListSubtotal + extrasSubtotal - disc; | |
// Print divider | |
teks.writeln('-' * MAX); | |
// Print subtotal | |
teks | |
..write('Total'.padRight(MAX - 13)) | |
..writeln(subtotal.toCurrency().padLeft(13)); | |
// Print discounts | |
if (sales.discount != null) { | |
var discount = sales.discount!; | |
if (discount.discount > 0) { | |
teks | |
..write('Discount'.padRight(MAX - 13)) | |
..writeln(discount.discountNominal.toCurrency().padLeft(13)); | |
} | |
if (discount.voucher > 0) { | |
var voucher = discount.voucherNominal; | |
if (voucher == 0) { | |
discount.voucher; | |
} | |
teks | |
..write('Voucher'.padRight(MAX - 13)) | |
..writeln((voucher).toCurrency().padLeft(13)); | |
} | |
} | |
// Print promotions | |
sales.promotions.forEach((promo) { | |
teks | |
..write(promo.name?.padRight(MAX - 13)) | |
..writeln(promo.promotionValue.toCurrency().padLeft(13)); | |
}); | |
// Print taxes | |
sales.taxes?.where((tax) => tax.total > 0).forEach((tax) { | |
teks | |
..write(tax.name?.padRight(MAX - 13)) | |
..writeln(tax.total.toCurrency().padLeft(13)); | |
}); | |
teks.writeln(); | |
// Print grand total | |
teks.writeln('Grand Total : '.padRight(MAX - 13) + | |
sales.grandTotal.toCurrency().padLeft(13)); | |
int totalPay = 0; | |
sales.payments.forEach((payment) { | |
totalPay += payment.pay < 0 && payment.method.toLowerCase() == "piutang" | |
? -payment.pay | |
: payment.pay; | |
teks.writeln("${payment.method} : ".padRight(MAX - 13) + | |
payment.pay.toCurrency().padLeft(13)); | |
}); | |
if (!isBill) { | |
teks.writeln("${lng.change} : ".padRight(MAX - 13) + | |
(totalPay - sales.grandTotal).toCurrency().padLeft(13)); | |
} | |
// Print discount & voucher info | |
if (sales.discount != null) { | |
var discount = sales.discount!; | |
if (discount.discount > 0) { | |
teks.writeln("Ket. Disc: ${discount.discountInfo?.trim()}"); | |
} | |
if (discount.voucher > 0) { | |
teks.writeln("Ket. Voucher: ${discount.voucherInfo?.trim()}"); | |
} | |
} | |
// Print bill header | |
if (isBill) { | |
teks | |
..writeln("NOTA TAGIHAN".center(MAX, '=')) | |
..writeln("NOTA TAGIHAN".center(MAX, '=')); | |
} | |
// Print reprint header | |
if (isReprint) { | |
teks | |
..writeln("REPRINT".center(MAX, '=')) | |
..writeln("REPRINT".center(MAX, '=')) | |
..writeln(); | |
} | |
// Print refund footer | |
if (sales.status == "Refund") { | |
teks.writeln("#REFUND #REFUND #REFUND".center(MAX, '=')); | |
} | |
// Print receipt notes | |
String footer = """ | |
powered by www.uniq.id | |
""" | |
.trim() | |
.center(MAX); | |
return "$header\n$teks\n$footer"; | |
} catch (e) { | |
print('Err: ${e}'); | |
} | |
return '--error--'; | |
} | |
class ReceiptText { | |
String receiptNumber; | |
String customer; | |
String date; | |
String cashier; | |
String payment; | |
String table; | |
String change; | |
ReceiptText({ | |
this.receiptNumber = 'No. Nota', | |
this.customer = 'Pelanggan', | |
this.date = 'Tanggal', | |
this.cashier = 'Kasir', | |
this.payment = 'Pembayaran', | |
this.table = 'Meja', | |
this.change = 'Kembalian', | |
}); | |
} | |
ReceiptText receiptLanguage(String language) { | |
var receiptText = ReceiptText(); | |
if (language == "en") { | |
receiptText.receiptNumber = "Receipt Number"; | |
receiptText.customer = "Customer"; | |
receiptText.date = "Date"; | |
receiptText.cashier = "Cashier"; | |
receiptText.payment = "Payment"; | |
receiptText.table = "Table"; | |
receiptText.change = "Change"; | |
} | |
return receiptText; | |
} | |
int getMaxCharacter(int? paperSize) { | |
switch (paperSize) { | |
case 75: | |
return 40; | |
case 80: | |
return 48; | |
default: | |
return 32; | |
} | |
} | |
class Constant { | |
static const String TYPE_NOMINAL = "nominal"; | |
static const String TYPE_PERCENT = "percentage"; | |
} | |
extension IntExtension on int? { | |
String toCurrency({String prefix = ''}) { | |
var num = NumberFormat.decimalPattern(); | |
try { | |
return prefix + num.format(this ?? 0); | |
} catch (e) { | |
return '${prefix}0'; | |
} | |
} | |
String dateTimeFormat([String? format]) { | |
final DateFormat sdf = DateFormat(format ?? 'dd-MM-yyyy HH:mm'); | |
return sdf.format(DateTime.fromMillisecondsSinceEpoch(this ?? 0)); | |
} | |
} | |
extension StringExtensions on String { | |
String loop(int w) { | |
var newVal = ''; | |
for (var i = 0; i < w; i++) { | |
newVal += this; | |
} | |
return newVal; | |
} | |
String width(int width, {int max = 32, int before = 0}) { | |
try { | |
var cursor = 0; | |
var wordSplit = StringBuffer(); | |
do { | |
var suffix = ''; | |
var start = cursor; | |
var end = (start + width) < length ? start + width : length; | |
var cutWord = substring(start, end); | |
var lastSpace = cutWord.contains(' ') && end != length | |
? cutWord.lastIndexOf(' ') | |
: cutWord.length; | |
if (lastSpace < (width ~/ 2) && (width - 1) < cutWord.length) { | |
lastSpace = width - 1; | |
cursor--; | |
suffix = '-'; | |
} | |
cursor += lastSpace + 1; | |
suffix += cursor < length | |
? ' '.padRight(max - (before + lastSpace)) + '\n' | |
: ' '.padRight(width - (before + lastSpace)); | |
wordSplit.write(cutWord.substring(0, lastSpace) + suffix); | |
} while (cursor < length); | |
return wordSplit.toString(); | |
} catch (e) { | |
// Handle error | |
} | |
// Fallback logic | |
return padRight(width); | |
} | |
String widthRight(int w) { | |
return padLeft(w); | |
} | |
String center(int width, [String flanks = ' ']) { | |
if (length == width) { | |
return this; | |
} else if (length < width) { | |
var center = width ~/ 2; | |
var flank = center - (length ~/ 2); | |
var newValue = StringBuffer(); | |
for (var i = 0; i < flank; i++) { | |
newValue.write(flanks); | |
} | |
newValue.write(this); | |
for (var i = newValue.length; i < width; i++) { | |
newValue.write(flanks); | |
} | |
return newValue.toString(); | |
} else { | |
// Handle case where length > width | |
} | |
return ''; | |
} | |
} | |
SalesEntity dummySales() { | |
return SalesEntity( | |
displayNota: '1001', | |
customer: 'John Doe', | |
timeCreated: DateTime.now().millisecondsSinceEpoch, | |
payment: 'Cash', | |
table: 'Table 5', | |
payments: [ | |
Payment( | |
method: 'Cash', | |
pay: 50, | |
bank: Bank(name: 'Bank A'), | |
), | |
], | |
orderList: [ | |
Order( | |
isItemVoid: false, | |
product: Product(productId: 1, name: 'Ayam Goreng'), | |
qty: 2, | |
subTotal: 20000, | |
extra: [], | |
promotion: Promotion( | |
promotionValue: 5, | |
pomotionTypeFkid: '15', | |
), | |
discount: Discount( | |
discount: 5, | |
voucher: 0, | |
discountNominal: 2500, | |
voucherNominal: 0, | |
discountInfo: 'Flash Deals', | |
discountType: Constant.TYPE_NOMINAL), | |
), | |
Order( | |
isItemVoid: false, | |
product: Product(productId: 2, name: 'Es Teh'), | |
qty: 1, | |
subTotal: 2000, | |
extra: [], | |
promotion: null, | |
discount: Discount( | |
discount: 0, | |
voucher: 0, | |
discountNominal: 0, | |
voucherNominal: 0, | |
discountType: Constant.TYPE_NOMINAL), | |
), | |
], | |
promotions: [], | |
discount: Discount( | |
discount: 5, | |
voucher: 0, | |
discountNominal: 1000, | |
voucherNominal: 0, | |
discountInfo: 'Diskon Karyawan', | |
discountType: Constant.TYPE_PERCENT), | |
taxes: [ | |
Tax(name: 'Tax Pb1', total: 5), | |
Tax(name: 'Service', total: 10), | |
], | |
grandTotal: 50, | |
status: 'succcess', | |
); | |
} | |
Outlet dummyOutlet() { | |
return Outlet( | |
name: 'Outlet ABC', | |
receiptAddress: '123 Main Street', | |
receiptPhone: '123-456-7890', | |
receiptNote: 'Thank you for your visit!', | |
receiptSocialmedia: '@OutletABC', | |
); | |
} | |
// SalesEntity model | |
class SalesEntity { | |
String displayNota; | |
String customer; | |
int timeCreated; | |
String payment; | |
String table; | |
List<Payment> payments; | |
List<Order> orderList; | |
List<Promotion> promotions; | |
Discount? discount; | |
List<Tax>? taxes; | |
int grandTotal; | |
String status; | |
SalesTagEntity? salesTag; | |
SalesEntity({ | |
required this.displayNota, | |
required this.customer, | |
required this.timeCreated, | |
required this.payment, | |
required this.table, | |
required this.payments, | |
required this.orderList, | |
required this.promotions, | |
this.discount, | |
this.taxes, | |
required this.grandTotal, | |
required this.status, | |
this.salesTag, | |
}); | |
} | |
class SalesTagEntity { | |
final int salesTagId; | |
final int adminFkid; | |
final String dataStatus; | |
final int dateCreated; | |
final int dateModified; | |
final String name; | |
SalesTagEntity({ | |
required this.salesTagId, | |
required this.adminFkid, | |
required this.dataStatus, | |
required this.dateCreated, | |
required this.dateModified, | |
required this.name, | |
}); | |
} | |
// Outlet model | |
class Outlet { | |
String name; | |
String? receiptAddress; | |
String? receiptPhone; | |
String? receiptNote; | |
String? receiptSocialmedia; | |
Outlet({ | |
required this.name, | |
this.receiptAddress, | |
this.receiptPhone, | |
this.receiptNote, | |
this.receiptSocialmedia, | |
}); | |
} | |
// Employee model | |
class Employee { | |
String name; | |
Employee({ | |
required this.name, | |
}); | |
} | |
// Payment model | |
class Payment { | |
String method; | |
int pay; | |
Bank bank; | |
Payment({ | |
required this.method, | |
required this.pay, | |
required this.bank, | |
}); | |
} | |
// Order model | |
class Order { | |
String? note; | |
bool isItemVoid; | |
Product product; | |
int qty; | |
int subTotal; | |
List<Order> extra; | |
Promotion? promotion; | |
Discount discount; | |
Order({ | |
this.note, | |
required this.isItemVoid, | |
required this.product, | |
required this.qty, | |
required this.subTotal, | |
required this.extra, | |
this.promotion, | |
required this.discount, | |
}); | |
} | |
// Product model | |
class Product { | |
int productId; | |
String? name; | |
Product({ | |
required this.productId, | |
this.name, | |
}); | |
} | |
// Promotion model | |
class Promotion { | |
String? name; | |
String? displayVoucher; | |
int promotionValue; | |
String pomotionTypeFkid; | |
Promotion({ | |
this.name, | |
this.displayVoucher, | |
required this.promotionValue, | |
required this.pomotionTypeFkid, | |
}); | |
} | |
// Discount model | |
class Discount { | |
int discount; | |
int voucher; | |
int discountNominal; | |
int voucherNominal; | |
String discountType; | |
String? discountInfo; | |
String? voucherInfo; | |
Discount({ | |
required this.discount, | |
required this.voucher, | |
required this.discountNominal, | |
required this.voucherNominal, | |
required this.discountType, | |
this.discountInfo, | |
this.voucherInfo, | |
}); | |
} | |
// Tax model | |
class Tax { | |
String? name; | |
int total; | |
Tax({ | |
this.name, | |
required this.total, | |
}); | |
} | |
// Bank model | |
class Bank { | |
String name; | |
Bank({ | |
required this.name, | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment