import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import 'package:intl/intl.dart'; import 'package:screenshot/screenshot.dart'; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import '../../inventory/providers/products_provider.dart'; import '../../inventory/providers/product_categories_provider.dart'; import '../domain/invoice.dart'; import '../providers/invoices_provider.dart'; import '../providers/customers_provider.dart'; import '../../business/providers/business_provider.dart'; import '../../transactions/presentation/widgets/attachment_gallery_screen.dart'; import '../../../core/network/dio_client.dart'; import 'widgets/receive_payment_sheet.dart'; import 'invoice_builder_screen.dart'; String _resolveImageUrl(String path) { if (path.startsWith('http://') || path.startsWith('https://')) { return path; } final base = DioClient().dio.options.baseUrl; final cleanPath = path.startsWith('/') ? path.substring(1) : path; if (cleanPath.startsWith('api/kifi-v2/upload/view') || cleanPath.startsWith('upload/view')) { return '$base/${cleanPath.replaceFirst('api/kifi-v2/', '')}'; } return '$base/upload/view?path=${Uri.encodeComponent(cleanPath)}'; } class InvoiceDetailsScreen extends ConsumerStatefulWidget { final Invoice invoice; const InvoiceDetailsScreen({super.key, required this.invoice}); @override ConsumerState createState() => _InvoiceDetailsScreenState(); } class _InvoiceDetailsScreenState extends ConsumerState { final ScreenshotController _screenshotController = ScreenshotController(); void _showReceivePaymentSheet(BuildContext context, WidgetRef ref, Invoice latestInvoice) async { final result = await showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => ReceivePaymentSheet(invoice: latestInvoice), ); if (result == true) { ref.read(invoicesProvider.notifier).refresh(); } } void _editInvoice(Invoice invoice) { Navigator.push( context, MaterialPageRoute( builder: (_) => InvoiceBuilderScreen(existingInvoice: invoice), ), ); } @override Widget build(BuildContext context) { final invoicesState = ref.watch(invoicesProvider); final latestInvoice = invoicesState.value?.firstWhere( (i) => i.id == widget.invoice.id, orElse: () => widget.invoice, ) ?? widget.invoice; final customersState = ref.watch(customersProvider); final customer = customersState.value?.where((c) => c.id == latestInvoice.customerId).firstOrNull; final businessState = ref.watch(businessProfileProvider); final business = businessState.value; final products = ref.watch(productsProvider).value ?? []; final categories = ref.watch(productCategoriesProvider).value ?? []; final isDark = Theme.of(context).brightness == Brightness.dark; final formatCurrency = NumberFormat.currency(symbol: '₹'); final formatDate = DateFormat('dd MMM yyyy'); double remaining = latestInvoice.totalAmount - latestInvoice.amountPaid; return Scaffold( backgroundColor: isDark ? const Color(0xFF0F172A) : Colors.grey[50], appBar: AppBar( title: Text( 'Invoice #${latestInvoice.invoiceNumber}', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), centerTitle: true, actions: [ IconButton( icon: const Icon(LucideIcons.edit3, size: 20), tooltip: 'Edit Invoice', onPressed: () => _editInvoice(latestInvoice), ), IconButton( icon: const Icon(LucideIcons.share2, size: 20), tooltip: 'Share Invoice', onPressed: () => _showShareOptions(context, latestInvoice.invoiceNumber), ), ], ), body: SingleChildScrollView( child: Screenshot( controller: _screenshotController, child: Container( color: isDark ? const Color(0xFF0F172A) : Colors.grey[50], padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Top Header Card: Business info Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isDark ? const Color(0xFF1E293B) : Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: isDark ? Colors.white12 : Colors.grey.shade200), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( business?.businessName ?? 'KIFI JEWELLERS', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), if (business?.address != null) Text(business!.address!, style: TextStyle(fontSize: 12, color: Colors.grey.shade600)), if (business?.gstin != null) Text('GSTIN: ${business!.gstin}', style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Colors.blue)), ], ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: _getStatusColor(latestInvoice.status).withValues(alpha: 0.15), borderRadius: BorderRadius.circular(8), ), child: Text( latestInvoice.status, style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: _getStatusColor(latestInvoice.status), ), ), ), ], ), ), const SizedBox(height: 12), // Invoice metadata & Customer details Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Invoice metadata Expanded( flex: 2, child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: isDark ? const Color(0xFF1E293B) : Colors.white, borderRadius: BorderRadius.circular(14), border: Border.all(color: isDark ? Colors.white12 : Colors.grey.shade200), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ 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)), 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)), ], ), ), ), const SizedBox(width: 12), // Customer Details Expanded( flex: 3, child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: isDark ? const Color(0xFF1E293B) : Colors.white, borderRadius: BorderRadius.circular(14), border: Border.all(color: isDark ? Colors.white12 : Colors.grey.shade200), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('BILL TO', style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold, color: Colors.grey)), const SizedBox(height: 4), if (customer != null) ...[ Text(customer.name, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), if (customer.phone != null) Text('Ph: ${customer.phone}', style: TextStyle(fontSize: 12, color: Colors.grey.shade600)), if (customer.gstin != null) Text('GSTIN: ${customer.gstin}', style: const TextStyle(fontSize: 11, fontWeight: FontWeight.bold, color: Colors.blue)), ] else ...[ const Text('Walk-in Customer', style: TextStyle(fontSize: 13, fontStyle: FontStyle.italic, color: Colors.grey)), ], ], ), ), ), ], ), const SizedBox(height: 20), // Itemized Details Header const Text('Itemized Particulars', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 10), // 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; 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; } return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isDark ? const Color(0xFF1E293B) : Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: isDark ? Colors.white12 : Colors.grey.shade200), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (item.photoUrl != null && item.photoUrl!.isNotEmpty) Container( width: 44, height: 44, margin: const EdgeInsets.only(right: 12), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), image: DecorationImage( image: NetworkImage(_resolveImageUrl(item.photoUrl!)), fit: BoxFit.cover, ), ), ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item.productName ?? product?.name ?? item.description ?? 'Jewellery Item', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15), ), const SizedBox(height: 4), Wrap( spacing: 6, runSpacing: 4, children: [ if (item.categoryName != null || category?.name != null) _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) _buildDetailBadge('HUID: ${item.huid}', Colors.purple), if (item.hsnCode != null) _buildDetailBadge('HSN: ${item.hsnCode}', Colors.grey), ], ), ], ), ), Text( formatCurrency.format(item.total), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.green), ), ], ), 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), ), 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)), ], ), if (item.taxRate > 0) ...[ const SizedBox(height: 4), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('GST (${item.taxRate.toStringAsFixed(1)}%):', style: TextStyle(fontSize: 11, color: Colors.grey.shade500)), Text( '+ ${formatCurrency.format(((metalAmount + makingChargeAmt) * (item.taxRate / 100.0)))}', style: TextStyle(fontSize: 11, color: Colors.grey.shade600), ), ], ), ], ], ), ); }), const SizedBox(height: 12), // Grand Totals Summary Container( padding: const EdgeInsets.all(18), decoration: BoxDecoration( color: isDark ? const Color(0xFF1E293B) : Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: isDark ? Colors.white12 : Colors.grey.shade200), ), child: Column( children: [ _buildSummaryRow('Subtotal', formatCurrency.format(latestInvoice.subtotal)), if (latestInvoice.cgstTotal > 0 && latestInvoice.sgstTotal > 0) ...[ _buildSummaryRow('CGST', formatCurrency.format(latestInvoice.cgstTotal)), _buildSummaryRow('SGST', formatCurrency.format(latestInvoice.sgstTotal)), ] else if (latestInvoice.igstTotal > 0) ...[ _buildSummaryRow('IGST', formatCurrency.format(latestInvoice.igstTotal)), ] else if (latestInvoice.taxTotal > 0) ...[ _buildSummaryRow('Tax', formatCurrency.format(latestInvoice.taxTotal)), ], if (latestInvoice.discountTotal > 0) _buildSummaryRow('Discount', '-${formatCurrency.format(latestInvoice.discountTotal)}', color: Colors.green), const Divider(height: 20), _buildSummaryRow('Grand Total', formatCurrency.format(latestInvoice.totalAmount), isBold: true, fontSize: 18), const SizedBox(height: 10), Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( color: isDark ? Colors.black26 : Colors.grey.shade100, borderRadius: BorderRadius.circular(10), ), child: Column( children: [ _buildSummaryRow('Amount Received', formatCurrency.format(latestInvoice.amountPaid), color: Colors.green, isBold: true), const SizedBox(height: 6), _buildSummaryRow('Balance Due', formatCurrency.format(remaining), color: remaining > 0 ? Colors.red : Colors.green, isBold: true, fontSize: 15), ], ), ), ], ), ), const SizedBox(height: 20), // Attached Invoice Bill if available if (latestInvoice.invoiceUrl != null && latestInvoice.invoiceUrl!.isNotEmpty) ...[ const Text('Attached Document', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 8), ListTile( tileColor: isDark ? const Color(0xFF1E293B) : Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), leading: const Icon(LucideIcons.image, color: Colors.blue), title: const Text('View Attached Sales Slip'), subtitle: const Text('Tap to open photo gallery', style: TextStyle(fontSize: 12, color: Colors.blue)), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (_) => AttachmentGalleryScreen( images: [NetworkImage(_resolveImageUrl(latestInvoice.invoiceUrl!))], initialIndex: 0, ), ), ); }, ), const SizedBox(height: 20), ], // Bottom Action: Receive Payment if balance due if (remaining > 0) ElevatedButton.icon( onPressed: () => _showReceivePaymentSheet(context, ref, latestInvoice), icon: const Icon(LucideIcons.plusCircle, size: 18), label: const Text('Record Payment', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), backgroundColor: Colors.green, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ), const SizedBox(height: 30), ], ), ), ), ), ); } Widget _buildDetailBadge(String text, Color color) { return Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: color.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(4), border: Border.all(color: color.withValues(alpha: 0.3), width: 0.5), ), child: Text( text, style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold, color: color), ), ); } Widget _buildSummaryRow(String label, String value, {bool isBold = false, double fontSize = 14, Color? color}) { return Padding( padding: const EdgeInsets.symmetric(vertical: 3), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, style: TextStyle(fontSize: fontSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal)), Text(value, style: TextStyle(fontSize: fontSize, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color)), ], ), ); } Color _getStatusColor(String status) { switch (status) { case 'PAID': return Colors.green; case 'PARTIAL': return Colors.blue; case 'FINALIZED': return Colors.orange; case 'DRAFT': default: return Colors.grey; } } void _showShareOptions(BuildContext context, String invoiceNumber) { showModalBottomSheet( context: context, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20))), builder: (ctx) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Padding( padding: EdgeInsets.all(16), child: Text('Share Invoice', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), ), ListTile( leading: const Icon(LucideIcons.image, color: Colors.blue), title: const Text('Share as Image'), subtitle: const Text('High-resolution receipt preview'), onTap: () { Navigator.pop(ctx); _shareAsImage(invoiceNumber); }, ), ListTile( leading: const Icon(LucideIcons.fileText, color: Colors.red), title: const Text('Share as PDF (Tax Invoice)'), subtitle: const Text('Official jewellery tax invoice format'), onTap: () { Navigator.pop(ctx); _shareAsPdf(invoiceNumber); }, ), const SizedBox(height: 16), ], ), ), ); } Future _shareAsImage(String invoiceNumber) async { try { final Uint8List? image = await _screenshotController.capture(pixelRatio: 3.0); if (image == null) return; final directory = await getTemporaryDirectory(); final imagePath = await File('${directory.path}/Invoice_$invoiceNumber.png').create(); await imagePath.writeAsBytes(image); await Share.shareXFiles([XFile(imagePath.path)], text: 'Invoice $invoiceNumber'); } catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error sharing image: $e'))); } } Future _shareAsPdf(String invoiceNumber) async { try { final invoicesState = ref.read(invoicesProvider); final latestInvoice = invoicesState.value?.firstWhere( (i) => i.id == widget.invoice.id, orElse: () => widget.invoice, ) ?? widget.invoice; final customers = ref.read(customersProvider).value ?? []; final customer = customers.where((c) => c.id == latestInvoice.customerId).firstOrNull; final business = ref.read(businessProfileProvider).value; final formatCurrency = NumberFormat.currency(symbol: '₹ ', decimalDigits: 2); final formatDate = DateFormat('dd MMM yyyy'); final pdf = pw.Document(); pdf.addPage( pw.MultiPage( pageFormat: PdfPageFormat.a4, margin: const pw.EdgeInsets.all(28), build: (pw.Context context) { return [ // Header with Business Details & TAX INVOICE title pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, crossAxisAlignment: pw.CrossAxisAlignment.start, children: [ pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.start, children: [ pw.Text( business?.businessName ?? 'KIFI JEWELLERS', style: pw.TextStyle(fontSize: 22, fontWeight: pw.FontWeight.bold, color: PdfColors.blue900), ), pw.SizedBox(height: 4), if (business?.address != null) pw.Text(business!.address!, style: const pw.TextStyle(fontSize: 10)), if (business?.contactNumber != null) pw.Text('Phone: ${business!.contactNumber}', style: const pw.TextStyle(fontSize: 10)), if (business?.gstin != null) pw.Text('GSTIN: ${business!.gstin}', style: pw.TextStyle(fontSize: 10, fontWeight: pw.FontWeight.bold, color: PdfColors.blue800)), ], ), pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.end, children: [ pw.Text( 'TAX INVOICE', style: pw.TextStyle(fontSize: 24, fontWeight: pw.FontWeight.bold, color: PdfColors.blue900), ), pw.SizedBox(height: 4), pw.Text('Invoice #: ${latestInvoice.invoiceNumber}', style: pw.TextStyle(fontSize: 12, fontWeight: pw.FontWeight.bold)), pw.Text('Date: ${formatDate.format(latestInvoice.issueDate)}', style: const pw.TextStyle(fontSize: 10)), if (latestInvoice.dueDate != null) pw.Text('Due Date: ${formatDate.format(latestInvoice.dueDate!)}', style: const pw.TextStyle(fontSize: 10)), ], ), ], ), pw.SizedBox(height: 20), // Bill To Box pw.Container( padding: const pw.EdgeInsets.all(10), decoration: pw.BoxDecoration( border: pw.Border.all(color: PdfColors.grey400, width: 0.5), borderRadius: const pw.BorderRadius.all(pw.Radius.circular(6)), ), child: pw.Row( crossAxisAlignment: pw.CrossAxisAlignment.start, children: [ pw.Expanded( child: pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.start, children: [ pw.Text('BILL TO (CUSTOMER):', style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold, color: PdfColors.grey700)), pw.SizedBox(height: 2), pw.Text(customer?.name ?? 'Walk-in Customer', style: pw.TextStyle(fontSize: 12, fontWeight: pw.FontWeight.bold)), if (customer?.phone != null) pw.Text('Phone: ${customer!.phone}', style: const pw.TextStyle(fontSize: 9)), if (customer?.address != null) pw.Text('Address: ${customer!.address}', style: const pw.TextStyle(fontSize: 9)), if (customer?.gstin != null) pw.Text('GSTIN: ${customer!.gstin}', style: pw.TextStyle(fontSize: 9, fontWeight: pw.FontWeight.bold)), ], ), ), ], ), ), pw.SizedBox(height: 16), // Table of Items with HUID, SKU, Making Charges, etc. pw.TableHelper.fromTextArray( context: context, border: const pw.TableBorder( bottom: pw.BorderSide(color: PdfColors.grey300, width: 0.5), horizontalInside: pw.BorderSide(color: PdfColors.grey300, width: 0.5), ), headerStyle: pw.TextStyle(fontWeight: pw.FontWeight.bold, color: PdfColors.white, fontSize: 9), headerDecoration: const pw.BoxDecoration(color: PdfColors.blue900), cellStyle: const pw.TextStyle(fontSize: 8.5), cellAlignments: { 0: pw.Alignment.centerLeft, 1: pw.Alignment.centerLeft, 2: pw.Alignment.centerRight, 3: pw.Alignment.centerRight, 4: pw.Alignment.centerRight, 5: pw.Alignment.centerRight, 6: pw.Alignment.centerRight, }, headers: ['Item / Particulars', 'HSN / HUID', 'Weight', 'Rate', 'Making Chg', 'GST', 'Total'], data: latestInvoice.items.map((item) { final weight = item.weight ?? item.quantity; final metalAmt = weight * item.unitPrice; double makingAmt = 0.0; if (item.makingChargesType == 'PERCENTAGE') { makingAmt = metalAmt * (item.makingCharge / 100.0); } else if (item.makingChargesType == 'PER_PIECE') { makingAmt = item.makingCharge; } else { makingAmt = weight * item.makingCharge; } String particulars = item.productName ?? item.description ?? 'Item'; if (item.sku != null && item.sku!.isNotEmpty) particulars += '\nSKU: ${item.sku}'; if (item.categoryName != null) particulars += ' (${item.categoryName})'; String hsnHuid = 'HSN: ${item.hsnCode ?? '-'}'; if (item.huid != null && item.huid!.isNotEmpty) hsnHuid += '\nHUID: ${item.huid}'; return [ particulars, hsnHuid, '${weight.toStringAsFixed(3)} g', formatCurrency.format(item.unitPrice), formatCurrency.format(makingAmt), '${item.taxRate.toStringAsFixed(1)}%', formatCurrency.format(item.total), ]; }).toList(), ), pw.SizedBox(height: 16), // Summary Section pw.Row( mainAxisAlignment: pw.MainAxisAlignment.end, children: [ pw.Container( width: 240, child: pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.stretch, children: [ pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text('Metal Subtotal:', style: const pw.TextStyle(fontSize: 10)), pw.Text(formatCurrency.format(latestInvoice.subtotal), style: const pw.TextStyle(fontSize: 10)), ], ), if (latestInvoice.cgstTotal > 0 && latestInvoice.sgstTotal > 0) ...[ pw.SizedBox(height: 2), pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text('CGST:', style: const pw.TextStyle(fontSize: 9)), pw.Text(formatCurrency.format(latestInvoice.cgstTotal), style: const pw.TextStyle(fontSize: 9)), ], ), pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text('SGST:', style: const pw.TextStyle(fontSize: 9)), pw.Text(formatCurrency.format(latestInvoice.sgstTotal), style: const pw.TextStyle(fontSize: 9)), ], ), ] else if (latestInvoice.igstTotal > 0) ...[ pw.SizedBox(height: 2), pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text('IGST:', style: const pw.TextStyle(fontSize: 9)), pw.Text(formatCurrency.format(latestInvoice.igstTotal), style: const pw.TextStyle(fontSize: 9)), ], ), ], if (latestInvoice.discountTotal > 0) ...[ pw.SizedBox(height: 2), pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text('Discount:', style: const pw.TextStyle(fontSize: 9, color: PdfColors.green700)), pw.Text('-${formatCurrency.format(latestInvoice.discountTotal)}', style: const pw.TextStyle(fontSize: 9, color: PdfColors.green700)), ], ), ], pw.Divider(color: PdfColors.grey400, height: 10), pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text('Grand Total:', style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 13)), pw.Text(formatCurrency.format(latestInvoice.totalAmount), style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 13)), ], ), pw.SizedBox(height: 4), pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text('Amount Received:', style: const pw.TextStyle(fontSize: 10, color: PdfColors.green800)), pw.Text(formatCurrency.format(latestInvoice.amountPaid), style: const pw.TextStyle(fontSize: 10, color: PdfColors.green800)), ], ), pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text('Balance Due:', style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 11, color: PdfColors.red800)), pw.Text(formatCurrency.format(latestInvoice.totalAmount - latestInvoice.amountPaid), style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 11, color: PdfColors.red800)), ], ), ], ), ), ], ), pw.SizedBox(height: 30), // Terms & Signatory pw.Divider(color: PdfColors.grey300), pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, crossAxisAlignment: pw.CrossAxisAlignment.end, children: [ pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.start, children: [ pw.Text('Terms & Conditions:', style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 8)), pw.Text('1. Goods once sold are subject to standard hallmarking certification.', style: const pw.TextStyle(fontSize: 7, color: PdfColors.grey700)), pw.Text('2. Weight & purity tested under industry standard electronic balance.', style: const pw.TextStyle(fontSize: 7, color: PdfColors.grey700)), ], ), pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.center, children: [ pw.Text('For ${business?.businessName ?? "KIFI JEWELLERS"}', style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 9)), pw.SizedBox(height: 24), pw.Text('Authorized Signatory', style: const pw.TextStyle(fontSize: 8, color: PdfColors.grey700)), ], ), ], ), ]; }, ), ); final directory = await getTemporaryDirectory(); final pdfPath = await File('${directory.path}/TaxInvoice_$invoiceNumber.pdf').create(); await pdfPath.writeAsBytes(await pdf.save()); await Share.shareXFiles([XFile(pdfPath.path)], text: 'Tax Invoice $invoiceNumber'); } catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error sharing PDF: $e'))); } } }