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'; import '../Screens/Products/Model/product_model.dart'; 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 printSalesTicket( {required PrintSalesTransactionModel printTransactionModel, required List? productList, List? products, 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 bytes = []; final is80mm = printTransactionModel.personalInformationModel.data?.invoiceSize == '3_inch_80mm' && printTransactionModel.personalInformationModel.data?.invoiceSize != null; if (defould) { bytes = is80mm ? await getSalesTicket80mm( printTransactionModel: printTransactionModel, productList: productList, products: products) : await getSalesTicket58mm( printTransactionModel: printTransactionModel, productList: productList, products: products); } 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> getSalesTicket58mm( {required PrintSalesTransactionModel printTransactionModel, required List? productList, List? products}) async { List 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 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 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([ 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)), ]); 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 ?? ''}]' : ''}"; 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; bytes += generator.row([ PosColumn( text: name, width: 4, styles: const PosStyles( align: PosAlign.left, ), ), PosColumn( text: formatPointNumber(qty), width: 1, styles: const PosStyles(align: PosAlign.center), ), PosColumn( text: formatPointNumber(price), width: 3, styles: const PosStyles(align: PosAlign.center), ), PosColumn( text: "${vatPercent % 1 == 0 ? vatPercent.toInt() : vatPercent}", width: 1, styles: const PosStyles(align: PosAlign.center), ), PosColumn( text: formatPointNumber(amountExVat), width: 3, 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> getSalesTicket80mm( {required PrintSalesTransactionModel printTransactionModel, required List? productList, List? products}) async { List 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 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 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([ 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)), ]); 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 ?? ''}]' : ''}"; 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; bytes += generator.row([ PosColumn( text: "${productList?[index].product?.productName ?? ''}${productList?[index].product?.productType == ProductType.variant.name ? ' [${productList?[index].stock?.batchNo ?? ''}]' : ''}", width: 1, styles: const PosStyles( align: PosAlign.left, )), PosColumn( text: formatPointNumber(qty, addComma: true), width: 1, styles: const PosStyles(align: PosAlign.center)), PosColumn( text: formatPointNumber(price, addComma: true), width: 2, styles: const PosStyles( align: PosAlign.center, )), PosColumn( text: "${vatPercent % 1 == 0 ? vatPercent.toInt() : vatPercent}", width: 2, styles: const PosStyles( align: PosAlign.center, )), PosColumn( text: formatPointNumber(amountExVat, addComma: true), width: 3, 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; } }