2026-02-07 15:57:09 +07:00
|
|
|
import 'package:esc_pos_utils_plus/esc_pos_utils_plus.dart';
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
|
|
|
|
import 'package:image/image.dart' as img;
|
|
|
|
|
import 'package:intl/intl.dart';
|
|
|
|
|
import 'package:mobile_pos/Const/lalnguage_data.dart';
|
|
|
|
|
import 'package:mobile_pos/service/thermal_print/src/templates/_sale_invoice_template.dart';
|
|
|
|
|
import 'package:mobile_pos/service/thermal_print/src/templates/templates.dart';
|
|
|
|
|
import 'package:nb_utils/nb_utils.dart';
|
|
|
|
|
import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart';
|
|
|
|
|
|
|
|
|
|
import '../Const/api_config.dart';
|
2026-02-09 23:56:18 +07:00
|
|
|
import '../Screens/Products/Model/product_model.dart';
|
2026-02-07 15:57:09 +07:00
|
|
|
import '../Screens/Products/add product/modle/create_product_model.dart';
|
|
|
|
|
import '../constant.dart';
|
|
|
|
|
import '../model/sale_transaction_model.dart';
|
|
|
|
|
import 'model/print_transaction_model.dart';
|
|
|
|
|
import 'network_image.dart';
|
|
|
|
|
import 'package:mobile_pos/generated/l10n.dart' as lang;
|
|
|
|
|
|
|
|
|
|
class SalesThermalPrinterInvoice {
|
|
|
|
|
///________Sales____________________
|
|
|
|
|
|
|
|
|
|
Future<void> printSalesTicket(
|
|
|
|
|
{required PrintSalesTransactionModel printTransactionModel,
|
|
|
|
|
required List<SalesDetails>? productList,
|
2026-02-09 23:56:18 +07:00
|
|
|
List<Product>? products,
|
2026-02-07 15:57:09 +07:00
|
|
|
required BuildContext context}) async {
|
|
|
|
|
bool? isConnected = await PrintBluetoothThermal.connectionStatus;
|
|
|
|
|
bool defould = (printTransactionModel.personalInformationModel.data?.invoiceLanguage == 'english' ||
|
|
|
|
|
printTransactionModel.personalInformationModel.data?.invoiceLanguage == null)
|
|
|
|
|
? true
|
|
|
|
|
: false;
|
|
|
|
|
if (isConnected == true) {
|
|
|
|
|
List<int> bytes = [];
|
|
|
|
|
final is80mm = printTransactionModel.personalInformationModel.data?.invoiceSize == '3_inch_80mm' &&
|
|
|
|
|
printTransactionModel.personalInformationModel.data?.invoiceSize != null;
|
|
|
|
|
|
|
|
|
|
if (defould) {
|
|
|
|
|
bytes = is80mm
|
2026-02-09 23:56:18 +07:00
|
|
|
? await getSalesTicket80mm(
|
|
|
|
|
printTransactionModel: printTransactionModel, productList: productList, products: products)
|
|
|
|
|
: await getSalesTicket58mm(
|
|
|
|
|
printTransactionModel: printTransactionModel, productList: productList, products: products);
|
2026-02-07 15:57:09 +07:00
|
|
|
} else {
|
|
|
|
|
final bool isRTL = rtlLang.contains(await getLanguageName());
|
|
|
|
|
|
|
|
|
|
SaleThermalInvoiceTemplate template = SaleThermalInvoiceTemplate(
|
|
|
|
|
context: context,
|
|
|
|
|
business: printTransactionModel.personalInformationModel,
|
|
|
|
|
is58mm: !is80mm,
|
|
|
|
|
isRTL: isRTL,
|
|
|
|
|
saleInvoice: printTransactionModel.transitionModel!);
|
|
|
|
|
bytes = await template.template;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (printTransactionModel.transitionModel?.salesDetails?.isNotEmpty ?? false) {
|
|
|
|
|
await PrintBluetoothThermal.writeBytes(bytes);
|
|
|
|
|
EasyLoading.showSuccess('Successfully Printed');
|
|
|
|
|
} else {
|
|
|
|
|
toast('No Product Found');
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
EasyLoading.showError('Unable to connect with printer');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<List<int>> getSalesTicket58mm(
|
2026-02-09 23:56:18 +07:00
|
|
|
{required PrintSalesTransactionModel printTransactionModel,
|
|
|
|
|
required List<SalesDetails>? productList,
|
|
|
|
|
List<Product>? products}) async {
|
2026-02-07 15:57:09 +07:00
|
|
|
List<DateTime> returnedDates = [];
|
|
|
|
|
String productName({required num detailsId}) {
|
|
|
|
|
final details = productList?[productList.indexWhere((element) => element.id == detailsId)];
|
|
|
|
|
return "${details?.product?.productName}${details?.product?.productType == ProductType.variant.name ? ' [${details?.stock?.batchNo ?? ""}]' : ''}";
|
|
|
|
|
return productList!.where((element) => element.id == detailsId).first.product?.productName ?? '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
num getProductQuantity({required num detailsId}) {
|
|
|
|
|
num totalQuantity = productList!.where((element) => element.id == detailsId).first.quantities ?? 0;
|
|
|
|
|
if (printTransactionModel.transitionModel?.salesReturns?.isNotEmpty ?? false) {
|
|
|
|
|
for (var returns in printTransactionModel.transitionModel!.salesReturns!) {
|
|
|
|
|
if (returns.salesReturnDetails?.isNotEmpty ?? false) {
|
|
|
|
|
for (var details in returns.salesReturnDetails!) {
|
|
|
|
|
if (details.saleDetailId == detailsId) {
|
|
|
|
|
totalQuantity += details.returnQty ?? 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return totalQuantity;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
num getTotalForOldInvoice() {
|
|
|
|
|
num total = 0;
|
|
|
|
|
for (var element in productList!) {
|
|
|
|
|
total += ((element.price ?? 0) * getProductQuantity(detailsId: element.id ?? 0)) -
|
|
|
|
|
((element.discount ?? 0) * getProductQuantity(detailsId: element.id ?? 0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return total;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
num productPrice({required num detailsId}) {
|
|
|
|
|
return productList!.where((element) => element.id == detailsId).first.price ?? 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String getTotalReturndAmount() {
|
|
|
|
|
num totalReturn = 0;
|
|
|
|
|
if (printTransactionModel.transitionModel?.salesReturns?.isNotEmpty ?? false) {
|
|
|
|
|
for (var returns in printTransactionModel.transitionModel!.salesReturns!) {
|
|
|
|
|
if (returns.salesReturnDetails?.isNotEmpty ?? false) {
|
|
|
|
|
for (var details in returns.salesReturnDetails!) {
|
|
|
|
|
totalReturn += details.returnAmount ?? 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return totalReturn.toStringAsFixed(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
num getReturndDiscountAmount() {
|
|
|
|
|
num totalReturnDiscount = 0;
|
|
|
|
|
if (printTransactionModel.transitionModel?.salesReturns?.isNotEmpty ?? false) {
|
|
|
|
|
for (var returns in printTransactionModel.transitionModel!.salesReturns!) {
|
|
|
|
|
if (returns.salesReturnDetails?.isNotEmpty ?? false) {
|
|
|
|
|
for (var details in returns.salesReturnDetails!) {
|
|
|
|
|
totalReturnDiscount += ((productPrice(detailsId: details.saleDetailId ?? 0) * (details.returnQty ?? 0)) -
|
|
|
|
|
((details.returnAmount ?? 0)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return totalReturnDiscount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//qr code
|
|
|
|
|
final _qrlogo = await getNetworkImage(
|
|
|
|
|
"${APIConfig.domain}${printTransactionModel.personalInformationModel.data?.invoiceScannerLogo}");
|
|
|
|
|
|
|
|
|
|
//--------total per item discount-------------------------
|
|
|
|
|
num getTotalItemDiscount() {
|
|
|
|
|
num totalDiscount = 0;
|
|
|
|
|
for (var element in printTransactionModel.transitionModel!.salesDetails!) {
|
|
|
|
|
totalDiscount += (element.discount ?? 0) * getProductQuantity(detailsId: element.id ?? 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return totalDiscount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final transactions = printTransactionModel.transitionModel!.transactions ?? [];
|
|
|
|
|
|
|
|
|
|
List<String> paymentLabels = [];
|
|
|
|
|
|
|
|
|
|
for (var item in transactions) {
|
|
|
|
|
String label;
|
|
|
|
|
|
|
|
|
|
switch (item.transactionType) {
|
|
|
|
|
case 'cash_payment':
|
|
|
|
|
label = 'Cash';
|
|
|
|
|
break;
|
|
|
|
|
case 'cheque_payment':
|
|
|
|
|
label = 'Cheque';
|
|
|
|
|
break;
|
|
|
|
|
case 'wallet_payment':
|
|
|
|
|
label = 'Wallet';
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
label = item.paymentType?.name ?? 'n/a';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
paymentLabels.add(label);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final paidViaText = "Paid Via : ${paymentLabels.join(', ')}";
|
|
|
|
|
|
|
|
|
|
List<int> bytes = [];
|
|
|
|
|
final _logo = await getNetworkImage(
|
|
|
|
|
"${APIConfig.domain}${printTransactionModel.personalInformationModel.data?.thermalInvoiceLogo}");
|
|
|
|
|
CapabilityProfile profile = await CapabilityProfile.load();
|
|
|
|
|
|
|
|
|
|
final generator = Generator(PaperSize.mm58, profile);
|
|
|
|
|
|
|
|
|
|
///____________Image__________________________________
|
|
|
|
|
if (_logo != null && printTransactionModel.personalInformationModel.data?.showThermalInvoiceLogo == 1) {
|
|
|
|
|
final img.Image resized = img.copyResize(
|
|
|
|
|
_logo,
|
|
|
|
|
width: 184,
|
|
|
|
|
);
|
|
|
|
|
final img.Image grayscale = img.grayscale(resized);
|
|
|
|
|
bytes += generator.imageRaster(grayscale, imageFn: PosImageFn.bitImageRaster);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///____________Header_____________________________________
|
|
|
|
|
if (printTransactionModel.personalInformationModel.data?.meta?.showCompanyName == 1) {
|
|
|
|
|
bytes += generator.text(printTransactionModel.personalInformationModel.data?.companyName ?? '',
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.center,
|
|
|
|
|
height: PosTextSize.size2,
|
|
|
|
|
width: PosTextSize.size2,
|
|
|
|
|
),
|
|
|
|
|
linesAfter: 1);
|
|
|
|
|
}
|
|
|
|
|
if (_logo != null) bytes += generator.text(' ');
|
|
|
|
|
if (printTransactionModel.transitionModel?.branch?.name != null) {
|
|
|
|
|
bytes += generator.text('Branch: ${printTransactionModel.transitionModel?.branch?.name ?? ''}',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.center));
|
|
|
|
|
}
|
|
|
|
|
bytes += generator.text(
|
|
|
|
|
'Seller :${printTransactionModel.transitionModel?.user?.role == "shop-owner" ? 'Admin' : printTransactionModel.transitionModel!.user?.name}',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.center));
|
|
|
|
|
if (printTransactionModel.transitionModel?.branch?.address != null ||
|
|
|
|
|
printTransactionModel.personalInformationModel.data?.address != null) {
|
|
|
|
|
bytes += generator.text(
|
|
|
|
|
printTransactionModel.transitionModel?.branch?.address ??
|
|
|
|
|
printTransactionModel.personalInformationModel.data?.address ??
|
|
|
|
|
'',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.center));
|
|
|
|
|
}
|
|
|
|
|
if (printTransactionModel.personalInformationModel.data?.meta?.showVat == 1) {
|
|
|
|
|
if (printTransactionModel.personalInformationModel.data?.vatNo != null &&
|
|
|
|
|
printTransactionModel.personalInformationModel.data?.meta?.showVat == 1) {
|
|
|
|
|
bytes += generator.text(
|
|
|
|
|
"${printTransactionModel.personalInformationModel.data?.vatName ?? 'VAT No :'}${printTransactionModel.personalInformationModel.data?.vatNo ?? ''}",
|
|
|
|
|
styles: const PosStyles(align: PosAlign.center));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (printTransactionModel.personalInformationModel.data?.meta?.showPhoneNumber == 1) {
|
|
|
|
|
if (printTransactionModel.transitionModel?.branch?.phone != null ||
|
|
|
|
|
printTransactionModel.personalInformationModel.data?.phoneNumber != null) {
|
|
|
|
|
bytes += generator.text(
|
|
|
|
|
'Tel: ${printTransactionModel.transitionModel?.branch?.phone ?? printTransactionModel.personalInformationModel.data?.phoneNumber ?? ''}',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.center));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bytes += generator.emptyLines(1);
|
|
|
|
|
bytes += generator.text('INVOICE',
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
underline: true,
|
|
|
|
|
align: PosAlign.center,
|
|
|
|
|
height: PosTextSize.size2,
|
|
|
|
|
width: PosTextSize.size2,
|
|
|
|
|
),
|
|
|
|
|
linesAfter: 1);
|
|
|
|
|
|
|
|
|
|
bytes += generator.text('Name: ${printTransactionModel.transitionModel?.party?.name ?? 'Guest'}',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.left));
|
|
|
|
|
bytes += generator.text('mobile: ${printTransactionModel.transitionModel?.party?.phone ?? 'Not Provided'}',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.left));
|
|
|
|
|
// bytes += generator.text('Sales By: ${printTransactionModel.transitionModel?.user?.name ?? 'Not Provided'}', styles: const PosStyles(align: PosAlign.left));
|
|
|
|
|
bytes += generator.text('Invoice: ${printTransactionModel.transitionModel?.invoiceNumber ?? 'Not Provided'}',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.left));
|
|
|
|
|
if (printTransactionModel.transitionModel?.saleDate != null) {
|
|
|
|
|
DateTime saleDate = DateTime.parse(printTransactionModel.transitionModel!.saleDate!);
|
|
|
|
|
String formattedDate = DateFormat('M/d/yyyy h:mm a').format(saleDate);
|
|
|
|
|
|
|
|
|
|
bytes += generator.text(
|
|
|
|
|
'Date: $formattedDate',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.left),
|
|
|
|
|
linesAfter: 1,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bytes += generator.row([
|
2026-02-09 23:56:18 +07:00
|
|
|
PosColumn(text: 'Item', width: 4, styles: const PosStyles(align: PosAlign.left, bold: true)),
|
|
|
|
|
PosColumn(text: 'Qt', width: 1, styles: const PosStyles(align: PosAlign.center, bold: true)),
|
|
|
|
|
PosColumn(text: 'Price', width: 3, styles: const PosStyles(align: PosAlign.center, bold: true)),
|
|
|
|
|
PosColumn(text: '%', width: 1, styles: const PosStyles(align: PosAlign.center, bold: true)),
|
|
|
|
|
PosColumn(text: 'Amount', width: 3, styles: const PosStyles(align: PosAlign.right, bold: true)),
|
2026-02-07 15:57:09 +07:00
|
|
|
]);
|
|
|
|
|
bytes += generator.hr();
|
|
|
|
|
List.generate(productList?.length ?? 1, (index) {
|
|
|
|
|
final warrantyInfo = printTransactionModel.transitionModel?.salesDetails?[index].warrantyInfo;
|
|
|
|
|
|
|
|
|
|
final warranty = (warrantyInfo?.warrantyDuration != null && warrantyInfo?.warrantyUnit != null)
|
|
|
|
|
? "Warranty : ${warrantyInfo?.warrantyDuration} ${warrantyInfo?.warrantyUnit}"
|
|
|
|
|
: "";
|
|
|
|
|
|
|
|
|
|
final guarantee = (warrantyInfo?.guaranteeDuration != null && warrantyInfo?.guaranteeUnit != null)
|
|
|
|
|
? "Guarantee : ${warrantyInfo?.guaranteeDuration} ${warrantyInfo?.guaranteeUnit}"
|
|
|
|
|
: "";
|
|
|
|
|
|
|
|
|
|
final name = "${productList?[index].product?.productName ?? ''}"
|
|
|
|
|
"${productList?[index].product?.productType == ProductType.variant.name ? ' [${productList?[index].stock?.batchNo ?? ''}]' : ''}";
|
|
|
|
|
|
2026-02-09 23:56:18 +07:00
|
|
|
final double qty = getProductQuantity(detailsId: productList?[index].id ?? 0).toDouble();
|
|
|
|
|
final double price = num.tryParse(productList?[index].price.toString() ?? '0')?.toDouble() ?? 0;
|
|
|
|
|
final double discount = num.tryParse(productList?[index].discount.toString() ?? '0')?.toDouble() ?? 0;
|
|
|
|
|
final product = products?.firstWhere((element) => element.id == productList?[index].productId,
|
|
|
|
|
orElse: () => Product());
|
|
|
|
|
final double vatPercent = num.tryParse(product?.vat?.rate.toString() ?? '0')?.toDouble() ?? 0;
|
|
|
|
|
final double amountExVat = (price * qty) - (discount * qty);
|
|
|
|
|
final double vatAmount = (amountExVat * vatPercent) / 100;
|
|
|
|
|
|
2026-02-07 15:57:09 +07:00
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: name,
|
|
|
|
|
width: 4,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
PosColumn(
|
2026-02-09 23:56:18 +07:00
|
|
|
text: formatPointNumber(qty),
|
2026-02-08 14:58:52 +07:00
|
|
|
width: 1,
|
2026-02-07 15:57:09 +07:00
|
|
|
styles: const PosStyles(align: PosAlign.center),
|
|
|
|
|
),
|
|
|
|
|
PosColumn(
|
2026-02-09 23:56:18 +07:00
|
|
|
text: formatPointNumber(price),
|
|
|
|
|
width: 3,
|
2026-02-07 15:57:09 +07:00
|
|
|
styles: const PosStyles(align: PosAlign.center),
|
|
|
|
|
),
|
|
|
|
|
PosColumn(
|
2026-02-09 23:56:18 +07:00
|
|
|
text: "${vatPercent % 1 == 0 ? vatPercent.toInt() : vatPercent}",
|
|
|
|
|
width: 1,
|
|
|
|
|
styles: const PosStyles(align: PosAlign.center),
|
|
|
|
|
),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: formatPointNumber(amountExVat),
|
|
|
|
|
width: 3,
|
2026-02-07 15:57:09 +07:00
|
|
|
styles: const PosStyles(align: PosAlign.right),
|
|
|
|
|
),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (warranty.isNotEmpty) {
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: '$warranty ',
|
|
|
|
|
width: 4,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
PosColumn(text: "", width: 2),
|
|
|
|
|
PosColumn(text: "", width: 3),
|
|
|
|
|
PosColumn(text: "", width: 3),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (guarantee.isNotEmpty) {
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: '$guarantee ',
|
|
|
|
|
width: 4,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
PosColumn(text: "", width: 2),
|
|
|
|
|
PosColumn(text: "", width: 3),
|
|
|
|
|
PosColumn(text: "", width: 3),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
return bytes;
|
|
|
|
|
});
|
|
|
|
|
bytes += generator.hr();
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Sub-total',
|
|
|
|
|
width: 8,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: formatPointNumber(getTotalForOldInvoice(), addComma: true),
|
|
|
|
|
width: 4,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Discount',
|
|
|
|
|
width: 8,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: formatPointNumber((printTransactionModel.transitionModel?.discountAmount ?? 0) +
|
|
|
|
|
getReturndDiscountAmount() +
|
|
|
|
|
getTotalItemDiscount()),
|
|
|
|
|
width: 4,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: printTransactionModel.transitionModel?.vat?.name ?? 'VAT',
|
|
|
|
|
width: 8,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: '${printTransactionModel.transitionModel?.vatAmount ?? 0}',
|
|
|
|
|
width: 4,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Shipping Charge',
|
|
|
|
|
width: 8,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: '${printTransactionModel.transitionModel?.shippingCharge ?? 0}',
|
|
|
|
|
width: 4,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (printTransactionModel.transitionModel?.roundingAmount != 0) {
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Total',
|
|
|
|
|
width: 8,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: (formatPointNumber(printTransactionModel.transitionModel?.actualTotalAmount ?? 0)),
|
|
|
|
|
width: 4,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Rounding',
|
|
|
|
|
width: 8,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text:
|
|
|
|
|
("${!(printTransactionModel.transitionModel?.roundingAmount?.isNegative ?? true) ? '+' : ''}${formatPointNumber(printTransactionModel.transitionModel?.roundingAmount ?? 0)}"),
|
|
|
|
|
width: 4,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///_____Return_table_______________________________
|
|
|
|
|
if (printTransactionModel.transitionModel?.salesReturns?.isNotEmpty ?? false) {
|
|
|
|
|
List.generate(printTransactionModel.transitionModel?.salesReturns?.length ?? 0, (i) {
|
|
|
|
|
bytes += generator.hr();
|
|
|
|
|
if (!returnedDates.any((element) => element.isAtSameMomentAs(DateTime.tryParse(
|
|
|
|
|
printTransactionModel.transitionModel?.salesReturns?[i].returnDate?.substring(0, 10) ?? '') ??
|
|
|
|
|
DateTime.now()))) {
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text:
|
|
|
|
|
'Return-${DateFormat.yMd().format(DateTime.parse(printTransactionModel.transitionModel?.salesReturns?[i].returnDate ?? DateTime.now().toString()))}',
|
|
|
|
|
width: 7,
|
|
|
|
|
styles: const PosStyles(align: PosAlign.left, bold: true)),
|
|
|
|
|
PosColumn(text: 'Qty', width: 2, styles: const PosStyles(align: PosAlign.center, bold: true)),
|
|
|
|
|
PosColumn(text: 'Total', width: 3, styles: const PosStyles(align: PosAlign.right, bold: true)),
|
|
|
|
|
]);
|
|
|
|
|
bytes += generator.hr();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List.generate(printTransactionModel.transitionModel?.salesReturns?[i].salesReturnDetails?.length ?? 0, (index) {
|
|
|
|
|
returnedDates.add(DateTime.tryParse(
|
|
|
|
|
printTransactionModel.transitionModel?.salesReturns?[i].returnDate?.substring(0, 10) ?? '') ??
|
|
|
|
|
DateTime.now());
|
|
|
|
|
final product = printTransactionModel.transitionModel?.salesReturns?[i].salesReturnDetails?[index];
|
|
|
|
|
return bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: productName(detailsId: product?.saleDetailId ?? 0),
|
|
|
|
|
width: 7,
|
|
|
|
|
styles: const PosStyles(align: PosAlign.left)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: product?.returnQty.toString() ?? 'Not Defined',
|
|
|
|
|
width: 2,
|
|
|
|
|
styles: const PosStyles(align: PosAlign.center)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: "${(product?.returnAmount ?? 0)}", width: 3, styles: const PosStyles(align: PosAlign.right)),
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
//
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
bytes += generator.hr();
|
|
|
|
|
|
|
|
|
|
///_____Total Returned Amount_______________________________
|
|
|
|
|
if (printTransactionModel.transitionModel?.salesReturns?.isNotEmpty ?? false) {
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Returned Amount',
|
|
|
|
|
width: 8,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: getTotalReturndAmount(),
|
|
|
|
|
width: 4,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(text: 'Total Payable', width: 8, styles: const PosStyles(align: PosAlign.left, bold: true)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: printTransactionModel.transitionModel?.totalAmount.toString() ?? '',
|
|
|
|
|
width: 4,
|
|
|
|
|
styles: const PosStyles(align: PosAlign.right, bold: true)),
|
|
|
|
|
]);
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Paid Amount',
|
|
|
|
|
width: 8,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: formatPointNumber(((printTransactionModel.transitionModel?.totalAmount ?? 0) -
|
|
|
|
|
(printTransactionModel.transitionModel?.dueAmount ?? 0)) +
|
|
|
|
|
(printTransactionModel.transitionModel?.changeAmount ?? 0)),
|
|
|
|
|
width: 4,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
if ((printTransactionModel.transitionModel?.dueAmount ?? 0) > 0) {
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Due Amount',
|
|
|
|
|
width: 8,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: formatPointNumber(printTransactionModel.transitionModel?.dueAmount ?? 0),
|
|
|
|
|
width: 4,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
if ((printTransactionModel.transitionModel?.changeAmount ?? 0) > 0) {
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Change Amount',
|
|
|
|
|
width: 8,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: formatPointNumber(printTransactionModel.transitionModel?.changeAmount ?? 0),
|
|
|
|
|
width: 4,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
bytes += generator.hr();
|
|
|
|
|
|
|
|
|
|
bytes += generator.text(
|
|
|
|
|
paidViaText,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
),
|
|
|
|
|
linesAfter: 1,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (printTransactionModel.personalInformationModel.data?.gratitudeMessage != null &&
|
|
|
|
|
printTransactionModel.personalInformationModel.data?.showGratitudeMsg == 1) {
|
|
|
|
|
bytes += generator.text(printTransactionModel.personalInformationModel.data?.gratitudeMessage ?? '',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.center, bold: true));
|
|
|
|
|
}
|
|
|
|
|
bytes += generator.text(printTransactionModel.transitionModel!.saleDate ?? '',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.center), linesAfter: 1);
|
|
|
|
|
|
|
|
|
|
if ((printTransactionModel.personalInformationModel.data?.invoiceNoteLevel != null ||
|
|
|
|
|
printTransactionModel.personalInformationModel.data?.invoiceNote != null) &&
|
|
|
|
|
printTransactionModel.personalInformationModel.data?.showNote == 1) {
|
|
|
|
|
bytes += generator.text(
|
|
|
|
|
'${printTransactionModel.personalInformationModel.data?.invoiceNoteLevel ?? ''}: ${printTransactionModel.personalInformationModel.data?.invoiceNote ?? ''}',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.left, bold: false),
|
|
|
|
|
linesAfter: 1,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (printTransactionModel.personalInformationModel.data?.showInvoiceScannerLogo == 1) {
|
|
|
|
|
if (_qrlogo != null) {
|
|
|
|
|
final img.Image resized = img.copyResize(
|
|
|
|
|
_qrlogo,
|
|
|
|
|
width: 120,
|
|
|
|
|
height: 120,
|
|
|
|
|
);
|
|
|
|
|
final img.Image grayscale = img.grayscale(resized);
|
|
|
|
|
bytes += generator.imageRaster(grayscale, imageFn: PosImageFn.bitImageRaster);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (printTransactionModel.personalInformationModel.data?.developByLevel != null ||
|
|
|
|
|
printTransactionModel.personalInformationModel.data?.developBy != null) {
|
|
|
|
|
bytes += generator.text(
|
|
|
|
|
'${printTransactionModel.personalInformationModel.data?.developByLevel ?? ''}: ${printTransactionModel.personalInformationModel.data?.developBy ?? ''}',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.center),
|
|
|
|
|
linesAfter: 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bytes += generator.cut();
|
|
|
|
|
return bytes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<List<int>> getSalesTicket80mm(
|
2026-02-09 23:56:18 +07:00
|
|
|
{required PrintSalesTransactionModel printTransactionModel,
|
|
|
|
|
required List<SalesDetails>? productList,
|
|
|
|
|
List<Product>? products}) async {
|
2026-02-07 15:57:09 +07:00
|
|
|
List<DateTime> returnedDates = [];
|
|
|
|
|
String productName({required num detailsId}) {
|
|
|
|
|
final details = productList?[productList.indexWhere((element) => element.id == detailsId)];
|
|
|
|
|
return "${details?.product?.productName}${details?.product?.productType == ProductType.variant.name ? ' [${details?.stock?.batchNo ?? ""}]' : ''}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
num getProductQuantity({required num detailsId}) {
|
|
|
|
|
num totalQuantity = productList!.where((element) => element.id == detailsId).first.quantities ?? 0;
|
|
|
|
|
if (printTransactionModel.transitionModel?.salesReturns?.isNotEmpty ?? false) {
|
|
|
|
|
for (var returns in printTransactionModel.transitionModel!.salesReturns!) {
|
|
|
|
|
if (returns.salesReturnDetails?.isNotEmpty ?? false) {
|
|
|
|
|
for (var details in returns.salesReturnDetails!) {
|
|
|
|
|
if (details.saleDetailId == detailsId) {
|
|
|
|
|
totalQuantity += details.returnQty ?? 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return totalQuantity;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--------total per item discount-------------------------
|
|
|
|
|
num getTotalItemDiscount() {
|
|
|
|
|
num totalDiscount = 0;
|
|
|
|
|
for (var element in printTransactionModel.transitionModel!.salesDetails!) {
|
|
|
|
|
totalDiscount += (element.discount ?? 0) * getProductQuantity(detailsId: element.id ?? 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return totalDiscount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final transactions = printTransactionModel.transitionModel!.transactions ?? [];
|
|
|
|
|
|
|
|
|
|
List<String> paymentLabels = [];
|
|
|
|
|
|
|
|
|
|
for (var item in transactions) {
|
|
|
|
|
String label;
|
|
|
|
|
|
|
|
|
|
switch (item.transactionType) {
|
|
|
|
|
case 'cash_payment':
|
|
|
|
|
label = 'Cash';
|
|
|
|
|
break;
|
|
|
|
|
case 'cheque_payment':
|
|
|
|
|
label = 'Cheque';
|
|
|
|
|
break;
|
|
|
|
|
case 'wallet_payment':
|
|
|
|
|
label = 'Wallet';
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
label = item.paymentType?.name ?? 'n/a';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
paymentLabels.add(label);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final paidViaText = "Paid Via : ${paymentLabels.join(', ')}";
|
|
|
|
|
|
|
|
|
|
num getTotalForOldInvoice() {
|
|
|
|
|
num total = 0;
|
|
|
|
|
for (var element in productList!) {
|
|
|
|
|
total += ((element.price ?? 0) * getProductQuantity(detailsId: element.id ?? 0) -
|
|
|
|
|
((element.discount ?? 0) * getProductQuantity(detailsId: element.id ?? 0)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return total;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
num productPrice({required num detailsId}) {
|
|
|
|
|
return productList!.where((element) => element.id == detailsId).first.price ?? 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
num getTotalReturndAmount() {
|
|
|
|
|
num totalReturn = 0;
|
|
|
|
|
if (printTransactionModel.transitionModel?.salesReturns?.isNotEmpty ?? false) {
|
|
|
|
|
for (var returns in printTransactionModel.transitionModel!.salesReturns!) {
|
|
|
|
|
if (returns.salesReturnDetails?.isNotEmpty ?? false) {
|
|
|
|
|
for (var details in returns.salesReturnDetails!) {
|
|
|
|
|
totalReturn += details.returnAmount ?? 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return totalReturn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
num getReturndDiscountAmount() {
|
|
|
|
|
num totalReturnDiscount = 0;
|
|
|
|
|
if (printTransactionModel.transitionModel?.salesReturns?.isNotEmpty ?? false) {
|
|
|
|
|
for (var returns in printTransactionModel.transitionModel!.salesReturns!) {
|
|
|
|
|
if (returns.salesReturnDetails?.isNotEmpty ?? false) {
|
|
|
|
|
for (var details in returns.salesReturnDetails!) {
|
|
|
|
|
totalReturnDiscount += ((productPrice(detailsId: details.saleDetailId ?? 0) * (details.returnQty ?? 0)) -
|
|
|
|
|
((details.returnAmount ?? 0)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return totalReturnDiscount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<int> bytes = [];
|
|
|
|
|
final _logo = await getNetworkImage(
|
|
|
|
|
"${APIConfig.domain}${printTransactionModel.personalInformationModel.data?.thermalInvoiceLogo}");
|
|
|
|
|
|
|
|
|
|
//qr code
|
|
|
|
|
final _qrlogo = await getNetworkImage(
|
|
|
|
|
"${APIConfig.domain}${printTransactionModel.personalInformationModel.data?.invoiceScannerLogo}");
|
|
|
|
|
|
|
|
|
|
CapabilityProfile profile = await CapabilityProfile.load();
|
|
|
|
|
|
|
|
|
|
final generator = Generator(PaperSize.mm80, profile);
|
|
|
|
|
|
|
|
|
|
///____________Image__________________________________
|
|
|
|
|
if (printTransactionModel.personalInformationModel.data?.showThermalInvoiceLogo == 1) {
|
|
|
|
|
if (_logo != null) {
|
|
|
|
|
final img.Image resized = img.copyResize(
|
|
|
|
|
_logo,
|
|
|
|
|
width: 184,
|
|
|
|
|
);
|
|
|
|
|
final img.Image grayscale = img.grayscale(resized);
|
|
|
|
|
bytes += generator.imageRaster(grayscale, imageFn: PosImageFn.bitImageRaster);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///____________Header_____________________________________
|
|
|
|
|
if (printTransactionModel.personalInformationModel.data?.meta?.showCompanyName == 1) {
|
|
|
|
|
bytes += generator.text(printTransactionModel.personalInformationModel.data?.companyName ?? '',
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.center,
|
|
|
|
|
height: PosTextSize.size2,
|
|
|
|
|
width: PosTextSize.size2,
|
|
|
|
|
),
|
|
|
|
|
linesAfter: 1);
|
|
|
|
|
}
|
|
|
|
|
if (printTransactionModel.transitionModel?.branch?.name != null) {
|
|
|
|
|
bytes += generator.text('Branch: ${printTransactionModel.transitionModel?.branch?.name ?? ''}',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.center));
|
|
|
|
|
}
|
|
|
|
|
if (printTransactionModel.personalInformationModel.data?.meta?.showAddress == 1) {
|
|
|
|
|
if (printTransactionModel.transitionModel?.branch?.address != null ||
|
|
|
|
|
printTransactionModel.personalInformationModel.data?.address != null) {
|
|
|
|
|
bytes += generator.text(
|
|
|
|
|
'Address: ${printTransactionModel.transitionModel?.branch?.address ?? printTransactionModel.personalInformationModel.data?.address ?? ''}',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.center));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (printTransactionModel.personalInformationModel.data?.meta?.showPhoneNumber == 1) {
|
|
|
|
|
if (printTransactionModel.transitionModel?.branch?.phone != null ||
|
|
|
|
|
printTransactionModel.personalInformationModel.data?.phoneNumber != null) {
|
|
|
|
|
bytes += generator.text(
|
|
|
|
|
'Mobile: ${printTransactionModel.transitionModel?.branch?.phone ?? printTransactionModel.personalInformationModel.data?.phoneNumber ?? ''}',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.center));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (printTransactionModel.personalInformationModel.data?.meta?.showVat == 1) {
|
|
|
|
|
if (printTransactionModel.personalInformationModel.data?.vatNo != null &&
|
|
|
|
|
printTransactionModel.personalInformationModel.data?.meta?.showVat == 1) {
|
|
|
|
|
bytes += generator.text(
|
|
|
|
|
"${printTransactionModel.personalInformationModel.data?.vatName ?? 'VAT No'}: ${printTransactionModel.personalInformationModel.data?.vatNo}",
|
|
|
|
|
styles: const PosStyles(align: PosAlign.center));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bytes += generator.emptyLines(1);
|
|
|
|
|
bytes += generator.text('INVOICE',
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
bold: true,
|
|
|
|
|
underline: true,
|
|
|
|
|
align: PosAlign.center,
|
|
|
|
|
height: PosTextSize.size2,
|
|
|
|
|
width: PosTextSize.size2,
|
|
|
|
|
),
|
|
|
|
|
linesAfter: 1);
|
|
|
|
|
|
|
|
|
|
///__________Customer_and_time_section_______________________
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Invoice: ${printTransactionModel.transitionModel?.invoiceNumber ?? 'Not Provided'}',
|
|
|
|
|
width: 6,
|
|
|
|
|
styles: const PosStyles(align: PosAlign.left)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text:
|
|
|
|
|
'Date: ${DateFormat.yMd().format(DateTime.parse(printTransactionModel.transitionModel?.saleDate ?? DateTime.now().toString()))}',
|
|
|
|
|
width: 6,
|
|
|
|
|
styles: const PosStyles(align: PosAlign.right)),
|
|
|
|
|
]);
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Name: ${printTransactionModel.transitionModel?.party?.name ?? ''}',
|
|
|
|
|
width: 6,
|
|
|
|
|
styles: const PosStyles(align: PosAlign.left)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text:
|
|
|
|
|
'Time: ${DateFormat.jm().format(DateTime.parse(printTransactionModel.transitionModel?.saleDate ?? DateTime.now().toString()))}',
|
|
|
|
|
width: 6,
|
|
|
|
|
styles: const PosStyles(align: PosAlign.right)),
|
|
|
|
|
]);
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Mobile: ${printTransactionModel.transitionModel?.party?.phone ?? ''}',
|
|
|
|
|
width: 6,
|
|
|
|
|
styles: const PosStyles(align: PosAlign.left)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text:
|
|
|
|
|
'Sales By: ${printTransactionModel.transitionModel?.user?.role == "shop-owner" ? 'Admin' : printTransactionModel.transitionModel!.user?.name}',
|
|
|
|
|
width: 6,
|
|
|
|
|
styles: const PosStyles(align: PosAlign.right)),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
bytes += generator.emptyLines(1);
|
|
|
|
|
|
|
|
|
|
///____________Products_Section_________________________________
|
|
|
|
|
bytes += generator.row([
|
2026-02-09 23:56:18 +07:00
|
|
|
PosColumn(text: 'Item', width: 1, styles: const PosStyles(align: PosAlign.left, bold: true)),
|
|
|
|
|
PosColumn(text: 'Qt', width: 1, styles: const PosStyles(align: PosAlign.center, bold: true)),
|
|
|
|
|
PosColumn(text: 'Price', width: 2, styles: const PosStyles(align: PosAlign.center, bold: true)),
|
|
|
|
|
PosColumn(text: '%', width: 2, styles: const PosStyles(align: PosAlign.center, bold: true)),
|
|
|
|
|
PosColumn(text: 'Amount(Ex Vat)', width: 3, styles: const PosStyles(align: PosAlign.right, bold: true)),
|
2026-02-07 15:57:09 +07:00
|
|
|
]);
|
|
|
|
|
bytes += generator.hr();
|
|
|
|
|
List.generate(productList?.length ?? 1, (index) {
|
|
|
|
|
final warrantyInfo = printTransactionModel.transitionModel?.salesDetails?[index].warrantyInfo;
|
|
|
|
|
|
|
|
|
|
final warranty = (warrantyInfo?.warrantyDuration != null && warrantyInfo?.warrantyUnit != null)
|
|
|
|
|
? "Warranty : ${warrantyInfo?.warrantyDuration} ${warrantyInfo?.warrantyUnit}"
|
|
|
|
|
: "";
|
|
|
|
|
|
|
|
|
|
final guarantee = (warrantyInfo?.guaranteeDuration != null && warrantyInfo?.guaranteeUnit != null)
|
|
|
|
|
? "Guarantee : ${warrantyInfo?.guaranteeDuration} ${warrantyInfo?.guaranteeUnit}"
|
|
|
|
|
: "";
|
|
|
|
|
|
|
|
|
|
final name = "${productList?[index].product?.productName ?? ''}"
|
|
|
|
|
"${productList?[index].product?.productType == ProductType.variant.name ? ' [${productList?[index].stock?.batchNo ?? ''}]' : ''}";
|
2026-02-09 23:56:18 +07:00
|
|
|
|
|
|
|
|
final double qty = getProductQuantity(detailsId: productList?[index].id ?? 0).toDouble();
|
|
|
|
|
final double price = num.tryParse(productList?[index].price.toString() ?? '0')?.toDouble() ?? 0;
|
|
|
|
|
final double discount = num.tryParse(productList?[index].discount.toString() ?? '0')?.toDouble() ?? 0;
|
|
|
|
|
final product = products?.firstWhere((element) => element.id == productList?[index].productId,
|
|
|
|
|
orElse: () => Product());
|
|
|
|
|
final double vatPercent = num.tryParse(product?.vat?.rate.toString() ?? '0')?.toDouble() ?? 0;
|
|
|
|
|
final double amountExVat = (price * qty) - (discount * qty);
|
|
|
|
|
final double vatAmount = (amountExVat * vatPercent) / 100;
|
|
|
|
|
|
2026-02-07 15:57:09 +07:00
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text:
|
|
|
|
|
"${productList?[index].product?.productName ?? ''}${productList?[index].product?.productType == ProductType.variant.name ? ' [${productList?[index].stock?.batchNo ?? ''}]' : ''}",
|
2026-02-09 23:56:18 +07:00
|
|
|
width: 1,
|
2026-02-07 15:57:09 +07:00
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
2026-02-09 23:56:18 +07:00
|
|
|
text: formatPointNumber(qty, addComma: true),
|
2026-02-08 14:58:52 +07:00
|
|
|
width: 1,
|
2026-02-07 15:57:09 +07:00
|
|
|
styles: const PosStyles(align: PosAlign.center)),
|
|
|
|
|
PosColumn(
|
2026-02-09 23:56:18 +07:00
|
|
|
text: formatPointNumber(price, addComma: true),
|
|
|
|
|
width: 2,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.center,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: "${vatPercent % 1 == 0 ? vatPercent.toInt() : vatPercent}",
|
2026-02-07 15:57:09 +07:00
|
|
|
width: 2,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.center,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
2026-02-09 23:56:18 +07:00
|
|
|
text: formatPointNumber(amountExVat, addComma: true),
|
2026-02-08 14:58:52 +07:00
|
|
|
width: 3,
|
2026-02-07 15:57:09 +07:00
|
|
|
styles: const PosStyles(align: PosAlign.right)),
|
|
|
|
|
]);
|
|
|
|
|
if (warranty.isNotEmpty) {
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: '$warranty ',
|
|
|
|
|
width: 4,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
fontType: PosFontType.fontB,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
PosColumn(text: "", width: 2),
|
|
|
|
|
PosColumn(text: "", width: 3),
|
|
|
|
|
PosColumn(text: "", width: 3),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (guarantee.isNotEmpty) {
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: '$guarantee ',
|
|
|
|
|
width: 4,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
fontType: PosFontType.fontB,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
PosColumn(text: "", width: 2),
|
|
|
|
|
PosColumn(text: "", width: 3),
|
|
|
|
|
PosColumn(text: "", width: 3),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
return bytes;
|
|
|
|
|
});
|
|
|
|
|
bytes += generator.hr();
|
|
|
|
|
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Sub-total:',
|
|
|
|
|
width: 9,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: formatPointNumber(getTotalForOldInvoice(), addComma: true),
|
|
|
|
|
width: 3,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Discount:',
|
|
|
|
|
width: 9,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: formatPointNumber(
|
|
|
|
|
(printTransactionModel.transitionModel?.discountAmount ?? 0) +
|
|
|
|
|
getReturndDiscountAmount() +
|
|
|
|
|
getTotalItemDiscount(),
|
|
|
|
|
addComma: true),
|
|
|
|
|
width: 3,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: '${printTransactionModel.transitionModel?.vat?.name ?? 'VAT'}:',
|
|
|
|
|
width: 9,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: formatPointNumber(printTransactionModel.transitionModel?.vatAmount ?? 0, addComma: true),
|
|
|
|
|
width: 3,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Shipping Charge:',
|
|
|
|
|
width: 9,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: formatPointNumber(printTransactionModel.transitionModel?.shippingCharge ?? 0, addComma: true),
|
|
|
|
|
width: 3,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (printTransactionModel.transitionModel?.roundingAmount != 0) {
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Total:',
|
|
|
|
|
width: 9,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: (formatPointNumber(printTransactionModel.transitionModel?.actualTotalAmount ?? 0, addComma: true)),
|
|
|
|
|
width: 3,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Rounding:',
|
|
|
|
|
width: 9,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text:
|
|
|
|
|
("${!(printTransactionModel.transitionModel?.roundingAmount?.isNegative ?? true) ? '+' : ''}${formatPointNumber(printTransactionModel.transitionModel?.roundingAmount ?? 0, addComma: true)}"),
|
|
|
|
|
width: 3,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///_____Return_table_______________________________
|
|
|
|
|
if (printTransactionModel.transitionModel?.salesReturns?.isNotEmpty ?? false) {
|
|
|
|
|
List.generate(printTransactionModel.transitionModel?.salesReturns?.length ?? 0, (i) {
|
|
|
|
|
bytes += generator.hr();
|
|
|
|
|
if (!returnedDates.any((element) => element.isAtSameMomentAs(DateTime.tryParse(
|
|
|
|
|
printTransactionModel.transitionModel?.salesReturns?[i].returnDate?.substring(0, 10) ?? '') ??
|
|
|
|
|
DateTime.now()))) {
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(text: 'SL', width: 1, styles: const PosStyles(align: PosAlign.left, bold: true)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text:
|
|
|
|
|
'Return-${DateFormat.yMd().format(DateTime.parse(printTransactionModel.transitionModel?.salesReturns?[i].returnDate ?? DateTime.now().toString()))}',
|
|
|
|
|
width: 6,
|
|
|
|
|
styles: const PosStyles(align: PosAlign.left, bold: true)),
|
|
|
|
|
PosColumn(text: 'Qty', width: 2, styles: const PosStyles(align: PosAlign.center, bold: true)),
|
|
|
|
|
PosColumn(text: 'Total', width: 3, styles: const PosStyles(align: PosAlign.right, bold: true)),
|
|
|
|
|
]);
|
|
|
|
|
bytes += generator.hr();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List.generate(printTransactionModel.transitionModel?.salesReturns?[i].salesReturnDetails?.length ?? 0, (index) {
|
|
|
|
|
returnedDates.add(DateTime.tryParse(
|
|
|
|
|
printTransactionModel.transitionModel?.salesReturns?[i].returnDate?.substring(0, 10) ?? '') ??
|
|
|
|
|
DateTime.now());
|
|
|
|
|
final product = printTransactionModel.transitionModel?.salesReturns?[i].salesReturnDetails?[index];
|
|
|
|
|
return bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: '${index + 1}',
|
|
|
|
|
width: 1,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: productName(detailsId: product?.saleDetailId ?? 0),
|
|
|
|
|
width: 6,
|
|
|
|
|
styles: const PosStyles(align: PosAlign.left)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: product?.returnQty.toString() ?? 'Not Defined',
|
|
|
|
|
width: 2,
|
|
|
|
|
styles: const PosStyles(align: PosAlign.center)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: formatPointNumber(product?.returnAmount ?? 0, addComma: true),
|
|
|
|
|
width: 3,
|
|
|
|
|
styles: const PosStyles(align: PosAlign.right)),
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
//
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///_____Total Returned Amount_______________________________
|
|
|
|
|
if (printTransactionModel.transitionModel?.salesReturns?.isNotEmpty ?? false) {
|
|
|
|
|
bytes += generator.hr();
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Returned Amount:',
|
|
|
|
|
width: 9,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: formatPointNumber(getTotalReturndAmount(), addComma: true),
|
|
|
|
|
width: 3,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
} else {
|
|
|
|
|
bytes += generator.text(' ----------------------------');
|
|
|
|
|
}
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(text: 'Total Payable:', width: 9, styles: const PosStyles(align: PosAlign.right, bold: true)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: formatPointNumber(printTransactionModel.transitionModel?.totalAmount ?? 0, addComma: true),
|
|
|
|
|
width: 3,
|
|
|
|
|
styles: const PosStyles(align: PosAlign.right, bold: true)),
|
|
|
|
|
]);
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Paid Amount:',
|
|
|
|
|
width: 9,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: formatPointNumber(
|
|
|
|
|
((printTransactionModel.transitionModel?.totalAmount ?? 0) -
|
|
|
|
|
(printTransactionModel.transitionModel?.dueAmount ?? 0)) +
|
|
|
|
|
(printTransactionModel.transitionModel?.changeAmount ?? 0),
|
|
|
|
|
addComma: true),
|
|
|
|
|
width: 3,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
if ((printTransactionModel.transitionModel?.dueAmount ?? 0) > 0) {
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Due Amount:',
|
|
|
|
|
width: 9,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: formatPointNumber(printTransactionModel.transitionModel?.dueAmount ?? 0, addComma: true),
|
|
|
|
|
width: 3,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
if ((printTransactionModel.transitionModel?.changeAmount ?? 0) > 0) {
|
|
|
|
|
bytes += generator.row([
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: 'Change Amount:',
|
|
|
|
|
width: 9,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
PosColumn(
|
|
|
|
|
text: formatPointNumber(printTransactionModel.transitionModel?.changeAmount ?? 0, addComma: true),
|
|
|
|
|
width: 3,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.right,
|
|
|
|
|
)),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
bytes += generator.hr();
|
|
|
|
|
|
|
|
|
|
bytes += generator.text(
|
|
|
|
|
paidViaText,
|
|
|
|
|
styles: const PosStyles(
|
|
|
|
|
align: PosAlign.left,
|
|
|
|
|
),
|
|
|
|
|
linesAfter: 1,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (printTransactionModel.personalInformationModel.data?.gratitudeMessage != null &&
|
|
|
|
|
printTransactionModel.personalInformationModel.data?.showGratitudeMsg == 1) {
|
|
|
|
|
bytes += generator.text(printTransactionModel.personalInformationModel.data?.gratitudeMessage ?? '',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.center, bold: true));
|
|
|
|
|
}
|
|
|
|
|
bytes += generator.text(printTransactionModel.transitionModel!.saleDate ?? '',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.center), linesAfter: 1);
|
|
|
|
|
if ((printTransactionModel.personalInformationModel.data?.invoiceNoteLevel != null ||
|
|
|
|
|
printTransactionModel.personalInformationModel.data?.invoiceNote != null) &&
|
|
|
|
|
printTransactionModel.personalInformationModel.data?.showNote == 1) {
|
|
|
|
|
bytes += generator.text(
|
|
|
|
|
'${printTransactionModel.personalInformationModel.data?.invoiceNoteLevel ?? ''}: ${printTransactionModel.personalInformationModel.data?.invoiceNote ?? ''}',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.left, bold: false),
|
|
|
|
|
linesAfter: 1,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
///____________qr logo__________________________________
|
|
|
|
|
if (printTransactionModel.personalInformationModel.data?.showInvoiceScannerLogo == 1) {
|
|
|
|
|
if (_qrlogo != null) {
|
|
|
|
|
final img.Image resized = img.copyResize(
|
|
|
|
|
_qrlogo,
|
|
|
|
|
width: 120,
|
|
|
|
|
height: 120,
|
|
|
|
|
);
|
|
|
|
|
final img.Image grayscale = img.grayscale(resized);
|
|
|
|
|
bytes += generator.imageRaster(grayscale, imageFn: PosImageFn.bitImageRaster);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (printTransactionModel.personalInformationModel.data?.developByLevel != null ||
|
|
|
|
|
printTransactionModel.personalInformationModel.data?.developBy != null) {
|
|
|
|
|
bytes += generator.text(
|
|
|
|
|
'${printTransactionModel.personalInformationModel.data?.developByLevel ?? ''}: ${printTransactionModel.personalInformationModel.data?.developBy ?? ''}',
|
|
|
|
|
styles: const PosStyles(align: PosAlign.center),
|
|
|
|
|
linesAfter: 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bytes += generator.cut();
|
|
|
|
|
return bytes;
|
|
|
|
|
}
|
|
|
|
|
}
|