Fixed sales invoice issues in Ecommerce
This commit is contained in:
@@ -12,6 +12,8 @@ import 'package:pdf/widgets.dart' as pw;
|
||||
import 'package:printing/printing.dart';
|
||||
import '../../inventory/providers/products_provider.dart';
|
||||
import '../../inventory/providers/product_categories_provider.dart';
|
||||
import '../../inventory/providers/uoms_provider.dart';
|
||||
import '../../inventory/domain/product.dart';
|
||||
import '../domain/invoice.dart';
|
||||
import '../providers/invoices_provider.dart';
|
||||
import '../providers/customers_provider.dart';
|
||||
@@ -21,6 +23,46 @@ import '../../../core/network/dio_client.dart';
|
||||
import 'widgets/receive_payment_sheet.dart';
|
||||
import 'invoice_builder_screen.dart';
|
||||
|
||||
bool _isCommodityProduct({
|
||||
ProductCategory? category,
|
||||
Product? product,
|
||||
InvoiceItem? invoiceItem,
|
||||
String? commodityCode,
|
||||
String? huid,
|
||||
}) {
|
||||
final h = huid ?? invoiceItem?.huid;
|
||||
if (h != null && h.trim().isNotEmpty) return true;
|
||||
|
||||
final code = commodityCode ??
|
||||
category?.commodityCode ??
|
||||
invoiceItem?.commodityCode;
|
||||
if (code != null && code.trim().isNotEmpty && code.trim().toUpperCase() != 'NONE') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
String _resolveProductUnit({
|
||||
required Product? product,
|
||||
required ProductCategory? category,
|
||||
required List<UnitOfMeasure> uoms,
|
||||
bool isCommodity = false,
|
||||
}) {
|
||||
if (isCommodity) {
|
||||
return (category != null && category.baseUnit.isNotEmpty) ? category.baseUnit : 'g';
|
||||
}
|
||||
if (product?.uomId != null) {
|
||||
final uom = uoms.where((u) => u.id == product!.uomId).firstOrNull;
|
||||
if (uom != null && uom.abbreviation != null && uom.abbreviation!.isNotEmpty) return uom.abbreviation!;
|
||||
if (uom != null && uom.name.isNotEmpty) return uom.name;
|
||||
}
|
||||
if (category != null && category.baseUnit.isNotEmpty) {
|
||||
return category.baseUnit;
|
||||
}
|
||||
return 'pcs';
|
||||
}
|
||||
|
||||
String _resolveImageUrl(String path) {
|
||||
if (path.startsWith('http://') || path.startsWith('https://')) {
|
||||
return path;
|
||||
@@ -185,10 +227,45 @@ class _InvoiceDetailsScreenState extends ConsumerState<InvoiceDetailsScreen> {
|
||||
const Text('INVOICE NO', style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold, color: Colors.grey)),
|
||||
const SizedBox(height: 2),
|
||||
Text(latestInvoice.invoiceNumber, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: Colors.blue)),
|
||||
if (latestInvoice.salesChannel != null && latestInvoice.salesChannel!.isNotEmpty) ...[
|
||||
const SizedBox(height: 6),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: Colors.blue.withValues(alpha: 0.25)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(LucideIcons.shoppingBag, size: 10, color: Colors.blue),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
latestInvoice.salesChannel!,
|
||||
style: const TextStyle(fontSize: 10, fontWeight: FontWeight.bold, color: Colors.blue),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
if (latestInvoice.marketplaceOrderId != null && latestInvoice.marketplaceOrderId!.isNotEmpty) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Ref: ${latestInvoice.marketplaceOrderId}',
|
||||
style: TextStyle(fontSize: 10, fontWeight: FontWeight.w600, color: Colors.grey.shade700),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 10),
|
||||
const Text('INVOICE DATE', style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold, color: Colors.grey)),
|
||||
const SizedBox(height: 2),
|
||||
Text(formatDate.format(latestInvoice.issueDate), style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600)),
|
||||
if (latestInvoice.placeOfSupply != null && latestInvoice.placeOfSupply!.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
const Text('PLACE OF SUPPLY', style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold, color: Colors.grey)),
|
||||
const SizedBox(height: 2),
|
||||
Text(latestInvoice.placeOfSupply!, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600)),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -233,19 +310,33 @@ class _InvoiceDetailsScreenState extends ConsumerState<InvoiceDetailsScreen> {
|
||||
// Item Cards
|
||||
...(latestInvoice.items).map((item) {
|
||||
final product = products.where((p) => p.id == item.productId).firstOrNull;
|
||||
final category = categories.where((c) => c.id == product?.categoryId).firstOrNull;
|
||||
final weight = item.weight ?? item.quantity;
|
||||
final metalAmount = weight * item.unitPrice;
|
||||
final category = categories.where((c) => c.name == item.categoryName || c.id == product?.categoryId).firstOrNull;
|
||||
final uoms = ref.watch(uomsProvider).value ?? [];
|
||||
|
||||
final isItemCommodity = _isCommodityProduct(
|
||||
category: category,
|
||||
product: product,
|
||||
invoiceItem: item,
|
||||
);
|
||||
|
||||
final qtyOrWeight = item.weight != null && item.weight! > 0 ? item.weight! : (item.quantity > 0 ? item.quantity : 1.0);
|
||||
final baseAmount = qtyOrWeight * item.unitPrice;
|
||||
final unit = _resolveProductUnit(product: product, category: category, uoms: uoms, isCommodity: isItemCommodity);
|
||||
|
||||
double makingChargeAmt = 0.0;
|
||||
if (item.makingChargesType == 'PERCENTAGE') {
|
||||
makingChargeAmt = metalAmount * (item.makingCharge / 100.0);
|
||||
} else if (item.makingChargesType == 'PER_PIECE') {
|
||||
makingChargeAmt = item.makingCharge;
|
||||
} else {
|
||||
makingChargeAmt = weight * item.makingCharge;
|
||||
if (isItemCommodity) {
|
||||
if (item.makingChargesType == 'PERCENTAGE') {
|
||||
makingChargeAmt = baseAmount * (item.makingCharge / 100.0);
|
||||
} else if (item.makingChargesType == 'PER_PIECE') {
|
||||
makingChargeAmt = item.makingCharge;
|
||||
} else {
|
||||
makingChargeAmt = qtyOrWeight * item.makingCharge;
|
||||
}
|
||||
}
|
||||
|
||||
final taxableAmount = baseAmount + makingChargeAmt + item.otherCharges - item.discount;
|
||||
final taxAmount = (taxableAmount * item.taxRate) / 100.0;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -278,7 +369,7 @@ class _InvoiceDetailsScreenState extends ConsumerState<InvoiceDetailsScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item.productName ?? product?.name ?? item.description ?? 'Jewellery Item',
|
||||
item.productName ?? product?.name ?? item.description ?? 'Item',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
@@ -290,10 +381,12 @@ class _InvoiceDetailsScreenState extends ConsumerState<InvoiceDetailsScreen> {
|
||||
_buildDetailBadge(item.categoryName ?? category!.name, const Color(0xFFD4AF37)),
|
||||
if (item.sku != null || product?.sku != null)
|
||||
_buildDetailBadge('SKU: ${item.sku ?? product!.sku}', Colors.blue),
|
||||
if (item.huid != null)
|
||||
if (isItemCommodity && item.huid != null && item.huid!.isNotEmpty)
|
||||
_buildDetailBadge('HUID: ${item.huid}', Colors.purple),
|
||||
if (item.hsnCode != null)
|
||||
if (item.hsnCode != null && item.hsnCode!.isNotEmpty)
|
||||
_buildDetailBadge('HSN: ${item.hsnCode}', Colors.grey),
|
||||
if (!isItemCommodity)
|
||||
_buildDetailBadge('Unit: $unit', Colors.teal),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -306,27 +399,52 @@ class _InvoiceDetailsScreenState extends ConsumerState<InvoiceDetailsScreen> {
|
||||
],
|
||||
),
|
||||
const Divider(height: 18),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Metal (${weight.toStringAsFixed(3)}g @ ₹${item.unitPrice.toStringAsFixed(2)}/g):',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
|
||||
if (isItemCommodity) ...[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Metal (${qtyOrWeight.toStringAsFixed(3)}$unit @ ₹${item.unitPrice.toStringAsFixed(2)}/$unit):',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
|
||||
),
|
||||
Text(formatCurrency.format(baseAmount), style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 12)),
|
||||
],
|
||||
),
|
||||
if (makingChargeAmt > 0) ...[
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Making Charges (${item.makingChargesType == 'PERCENTAGE' ? '${item.makingCharge}%' : (item.makingChargesType == 'PER_PIECE' ? '₹${item.makingCharge}/pc' : '₹${item.makingCharge}/g')}):',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
|
||||
),
|
||||
Text(formatCurrency.format(makingChargeAmt), style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 12, color: Colors.indigo)),
|
||||
],
|
||||
),
|
||||
Text(formatCurrency.format(metalAmount), style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 12)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Making Charges (${item.makingChargesType == 'PERCENTAGE' ? '${item.makingCharge}%' : (item.makingChargesType == 'PER_PIECE' ? '₹${item.makingCharge}/pc' : '₹${item.makingCharge}/g')}):',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
|
||||
),
|
||||
Text(formatCurrency.format(makingChargeAmt), style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 12, color: Colors.indigo)),
|
||||
],
|
||||
),
|
||||
] else ...[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Rate: ₹${item.unitPrice.toStringAsFixed(2)} / $unit • Qty: ${qtyOrWeight.toStringAsFixed(qtyOrWeight.truncateToDouble() == qtyOrWeight ? 0 : 2)} $unit',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade600, fontWeight: FontWeight.w500),
|
||||
),
|
||||
Text(formatCurrency.format(baseAmount), style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 12)),
|
||||
],
|
||||
),
|
||||
],
|
||||
if (item.discount > 0) ...[
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('Discount:', style: TextStyle(fontSize: 11, color: Colors.red)),
|
||||
Text('- ${formatCurrency.format(item.discount)}', style: const TextStyle(fontSize: 11, fontWeight: FontWeight.bold, color: Colors.red)),
|
||||
],
|
||||
),
|
||||
],
|
||||
if (item.taxRate > 0) ...[
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
@@ -334,7 +452,7 @@ class _InvoiceDetailsScreenState extends ConsumerState<InvoiceDetailsScreen> {
|
||||
children: [
|
||||
Text('GST (${item.taxRate.toStringAsFixed(1)}%):', style: TextStyle(fontSize: 11, color: Colors.grey.shade500)),
|
||||
Text(
|
||||
'+ ${formatCurrency.format(((metalAmount + makingChargeAmt) * (item.taxRate / 100.0)))}',
|
||||
'+ ${formatCurrency.format(taxAmount)}',
|
||||
style: TextStyle(fontSize: 11, color: Colors.grey.shade600),
|
||||
),
|
||||
],
|
||||
@@ -556,6 +674,7 @@ class _InvoiceDetailsScreenState extends ConsumerState<InvoiceDetailsScreen> {
|
||||
final business = ref.read(businessProfileProvider).value;
|
||||
final products = ref.read(productsProvider).value ?? [];
|
||||
final categories = ref.read(productCategoriesProvider).value ?? [];
|
||||
final uoms = ref.read(uomsProvider).value ?? [];
|
||||
|
||||
final formatCurrency = NumberFormat.currency(symbol: 'Rs. ', decimalDigits: 2);
|
||||
final formatDate = DateFormat('dd MMM yyyy');
|
||||
@@ -776,6 +895,19 @@ class _InvoiceDetailsScreenState extends ConsumerState<InvoiceDetailsScreen> {
|
||||
final groupTotal = group.fold(0.0, (sum, i) => sum + i.total);
|
||||
final gstRate = group.first.taxRate > 0 ? group.first.taxRate : (product?.gstRate ?? (category?.defaultGst ?? 0.0));
|
||||
|
||||
final isGroupCommodity = group.any((item) => _isCommodityProduct(
|
||||
category: category,
|
||||
product: product,
|
||||
invoiceItem: item,
|
||||
));
|
||||
|
||||
final groupUnit = _resolveProductUnit(
|
||||
product: product,
|
||||
category: category,
|
||||
uoms: uoms,
|
||||
isCommodity: isGroupCommodity,
|
||||
);
|
||||
|
||||
return pw.Container(
|
||||
margin: const pw.EdgeInsets.only(bottom: 14),
|
||||
decoration: pw.BoxDecoration(
|
||||
@@ -839,22 +971,40 @@ class _InvoiceDetailsScreenState extends ConsumerState<InvoiceDetailsScreen> {
|
||||
style: const pw.TextStyle(fontSize: 7.5, color: PdfColors.grey800),
|
||||
),
|
||||
),
|
||||
pw.Container(
|
||||
padding: const pw.EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: pw.BoxDecoration(
|
||||
color: PdfColors.amber50,
|
||||
border: pw.Border.all(color: PdfColors.amber600, width: 0.5),
|
||||
borderRadius: const pw.BorderRadius.all(pw.Radius.circular(3)),
|
||||
),
|
||||
child: pw.Text(
|
||||
'Purity: $purityStr',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 7.5,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: PdfColors.brown800,
|
||||
if (isGroupCommodity)
|
||||
pw.Container(
|
||||
padding: const pw.EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: pw.BoxDecoration(
|
||||
color: PdfColors.amber50,
|
||||
border: pw.Border.all(color: PdfColors.amber600, width: 0.5),
|
||||
borderRadius: const pw.BorderRadius.all(pw.Radius.circular(3)),
|
||||
),
|
||||
child: pw.Text(
|
||||
'Purity: $purityStr',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 7.5,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: PdfColors.brown800,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isGroupCommodity)
|
||||
pw.Container(
|
||||
padding: const pw.EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: pw.BoxDecoration(
|
||||
color: PdfColors.teal50,
|
||||
border: pw.Border.all(color: PdfColors.teal400, width: 0.5),
|
||||
borderRadius: const pw.BorderRadius.all(pw.Radius.circular(3)),
|
||||
),
|
||||
child: pw.Text(
|
||||
'Unit: $groupUnit',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 7.5,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: PdfColors.teal900,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (hsnStr != '-')
|
||||
pw.Container(
|
||||
padding: const pw.EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
@@ -900,49 +1050,71 @@ class _InvoiceDetailsScreenState extends ConsumerState<InvoiceDetailsScreen> {
|
||||
4: pw.Alignment.centerRight,
|
||||
5: pw.Alignment.centerRight,
|
||||
},
|
||||
headers: [
|
||||
'Item / HUID',
|
||||
'Weight / Qty',
|
||||
'Rate',
|
||||
'Making Chg',
|
||||
'Other Chg',
|
||||
'Total Amt',
|
||||
],
|
||||
data: group.map((item) {
|
||||
String particular = '';
|
||||
if (item.huid != null && item.huid!.isNotEmpty) {
|
||||
particular = 'HUID: ${item.huid}';
|
||||
} else if (item.sku != null && item.sku!.isNotEmpty) {
|
||||
particular = 'SKU: ${item.sku}';
|
||||
} else {
|
||||
particular = 'Standard Item';
|
||||
}
|
||||
headers: isGroupCommodity
|
||||
? [
|
||||
'Item / HUID',
|
||||
'Weight / Qty',
|
||||
'Rate',
|
||||
'Making Chg',
|
||||
'Other Chg',
|
||||
'Total Amt',
|
||||
]
|
||||
: [
|
||||
'Item / SKU',
|
||||
'Qty',
|
||||
'Unit Price',
|
||||
'Discount',
|
||||
'GST %',
|
||||
'Total Amt',
|
||||
],
|
||||
data: isGroupCommodity
|
||||
? group.map((item) {
|
||||
String particular = '';
|
||||
if (item.huid != null && item.huid!.isNotEmpty) {
|
||||
particular = 'HUID: ${item.huid}';
|
||||
} else if (item.sku != null && item.sku!.isNotEmpty) {
|
||||
particular = 'SKU: ${item.sku}';
|
||||
} else {
|
||||
particular = 'Standard Item';
|
||||
}
|
||||
|
||||
final weight = item.weight ?? item.quantity;
|
||||
final weightQty = item.weight != null && item.weight! > 0
|
||||
? '${item.weight!.toStringAsFixed(item.weight!.truncateToDouble() == item.weight ? 0 : 3)} g'
|
||||
: '${item.quantity.toStringAsFixed(item.quantity.truncateToDouble() == item.quantity ? 0 : 0)} pcs';
|
||||
final weight = item.weight ?? item.quantity;
|
||||
final weightQty = item.weight != null && item.weight! > 0
|
||||
? '${item.weight!.toStringAsFixed(item.weight!.truncateToDouble() == item.weight ? 0 : 3)} $groupUnit'
|
||||
: '${item.quantity.toStringAsFixed(item.quantity.truncateToDouble() == item.quantity ? 0 : 0)} pcs';
|
||||
|
||||
final rateUnit = item.weight != null && item.weight! > 0 ? '/ g' : '/ pc';
|
||||
final rateUnit = item.weight != null && item.weight! > 0 ? '/ $groupUnit' : '/ pc';
|
||||
|
||||
double makingAmt = 0.0;
|
||||
if (item.makingChargesType == 'PERCENTAGE') {
|
||||
makingAmt = (weight * item.unitPrice) * (item.makingCharge / 100.0);
|
||||
} else if (item.makingChargesType == 'PER_PIECE') {
|
||||
makingAmt = item.makingCharge;
|
||||
} else {
|
||||
makingAmt = weight * item.makingCharge;
|
||||
}
|
||||
double makingAmt = 0.0;
|
||||
if (item.makingChargesType == 'PERCENTAGE') {
|
||||
makingAmt = (weight * item.unitPrice) * (item.makingCharge / 100.0);
|
||||
} else if (item.makingChargesType == 'PER_PIECE') {
|
||||
makingAmt = item.makingCharge;
|
||||
} else {
|
||||
makingAmt = weight * item.makingCharge;
|
||||
}
|
||||
|
||||
return [
|
||||
particular,
|
||||
weightQty,
|
||||
'${formatCurrency.format(item.unitPrice)} $rateUnit',
|
||||
makingAmt > 0 ? formatCurrency.format(makingAmt) : '-',
|
||||
item.otherCharges > 0 ? formatCurrency.format(item.otherCharges) : '-',
|
||||
formatCurrency.format(item.total),
|
||||
];
|
||||
}).toList(),
|
||||
return [
|
||||
particular,
|
||||
weightQty,
|
||||
'${formatCurrency.format(item.unitPrice)} $rateUnit',
|
||||
makingAmt > 0 ? formatCurrency.format(makingAmt) : '-',
|
||||
item.otherCharges > 0 ? formatCurrency.format(item.otherCharges) : '-',
|
||||
formatCurrency.format(item.total),
|
||||
];
|
||||
}).toList()
|
||||
: group.map((item) {
|
||||
final qty = item.weight != null && item.weight! > 0 ? item.weight! : (item.quantity > 0 ? item.quantity : 1.0);
|
||||
final qtyStr = '${qty.toStringAsFixed(qty.truncateToDouble() == qty ? 0 : 2)} $groupUnit';
|
||||
return [
|
||||
item.sku ?? skuStr ?? 'Item',
|
||||
qtyStr,
|
||||
formatCurrency.format(item.unitPrice),
|
||||
item.discount > 0 ? '-${formatCurrency.format(item.discount)}' : '-',
|
||||
'${item.taxRate.toStringAsFixed(1)}%',
|
||||
formatCurrency.format(item.total),
|
||||
];
|
||||
}).toList(),
|
||||
),
|
||||
|
||||
// Group Subtotal & GST Calculation Box
|
||||
@@ -959,7 +1131,9 @@ class _InvoiceDetailsScreenState extends ConsumerState<InvoiceDetailsScreen> {
|
||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
pw.Text(
|
||||
'Metal Subtotal: ${formatCurrency.format(groupSubtotal)} | Making: ${formatCurrency.format(groupMaking)}',
|
||||
isGroupCommodity
|
||||
? 'Metal Subtotal: ${formatCurrency.format(groupSubtotal)} | Making: ${formatCurrency.format(groupMaking)}'
|
||||
: 'Items Subtotal: ${formatCurrency.format(groupSubtotal)}',
|
||||
style: const pw.TextStyle(fontSize: 8, color: PdfColors.grey700),
|
||||
),
|
||||
pw.Text(
|
||||
|
||||
Reference in New Issue
Block a user