feat(app): implement Product Scan review flow, dynamic batch expiry picker, custom PDFs, PO relationships, and aligned card layouts
This commit is contained in:
1 parent
9ff4a4a922
commit
577be04308
69 files changed
+5645
-1657
No files matched your search
@@ -10,6 +10,7 @@ class PdfService {
|
||||
|
||||
// Load PFM logo asset
|
||||
final logoImage = await flutterImageProvider(const AssetImage('assets/logo.png'));
|
||||
final isProductScan = document.orderUntuk == 'PRODUCT SCAN';
|
||||
|
||||
// Add page
|
||||
pdf.addPage(
|
||||
@@ -23,7 +24,10 @@ class PdfService {
|
||||
pw.Row(
|
||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
pw.Text('TANDA TERIMA PENGIRIMAN', style: pw.TextStyle(fontSize: 24, fontWeight: pw.FontWeight.bold)),
|
||||
pw.Text(
|
||||
isProductScan ? 'BUKTI VERIFIKASI SCAN PRODUK' : 'TANDA TERIMA PENGIRIMAN',
|
||||
style: pw.TextStyle(fontSize: 22, fontWeight: pw.FontWeight.bold)
|
||||
),
|
||||
pw.SizedBox(
|
||||
width: 60,
|
||||
height: 60,
|
||||
@@ -42,18 +46,27 @@ class PdfService {
|
||||
),
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildTextRow('Tanggal', document.tanggal),
|
||||
_buildTextRow('No PO', document.noPo),
|
||||
_buildTextRow('No SO', document.noSo),
|
||||
_buildTextRow('No DO', document.noDo),
|
||||
]
|
||||
children: isProductScan
|
||||
? [
|
||||
_buildTextRow('Tanggal Scan', document.tanggal),
|
||||
_buildTextRow('Toko / Cabang', document.kepadaYth),
|
||||
_buildTextRow('Jenis Verifikasi', 'Pemindaian Produk & Kadaluwarsa'),
|
||||
]
|
||||
: [
|
||||
_buildTextRow('Tanggal', document.tanggal),
|
||||
_buildTextRow('No PO', document.noPo),
|
||||
_buildTextRow('No SO', document.noSo),
|
||||
_buildTextRow('No DO', document.noDo),
|
||||
]
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 20),
|
||||
|
||||
// Shipment Data
|
||||
pw.Text('Detail Pengiriman', style: pw.TextStyle(fontSize: 16, fontWeight: pw.FontWeight.bold)),
|
||||
// Detail Scan / Pengiriman Section
|
||||
pw.Text(
|
||||
isProductScan ? 'Informasi Verifikasi' : 'Detail Pengiriman',
|
||||
style: pw.TextStyle(fontSize: 16, fontWeight: pw.FontWeight.bold)
|
||||
),
|
||||
pw.SizedBox(height: 8),
|
||||
pw.Container(
|
||||
padding: const pw.EdgeInsets.all(10),
|
||||
@@ -63,72 +76,131 @@ class PdfService {
|
||||
),
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildTextRow('Kepada Yth', document.kepadaYth),
|
||||
_buildTextRow('Order Untuk', document.orderUntuk),
|
||||
_buildTextRow('Alamat', document.alamat),
|
||||
_buildTextRow('Plat Truk', document.platTruk),
|
||||
_buildTextRow('Nama Driver', document.namaDriver),
|
||||
]
|
||||
children: isProductScan
|
||||
? [
|
||||
_buildTextRow('Nama Produk', document.items.isNotEmpty ? document.items.first.namaBarang : '-'),
|
||||
_buildTextRow('Kode SKU', document.items.isNotEmpty ? document.items.first.nomorSku : '-'),
|
||||
_buildTextRow('Nomor Batch', document.noDo),
|
||||
_buildTextRow('Tanggal Expired', document.alamat.replaceAll("Product Scan: Expired ", "")),
|
||||
]
|
||||
: [
|
||||
_buildTextRow('Kepada Yth', document.kepadaYth),
|
||||
_buildTextRow('Order Untuk', document.orderUntuk),
|
||||
_buildTextRow('Alamat', document.alamat),
|
||||
_buildTextRow('Plat Truk', document.platTruk),
|
||||
_buildTextRow('Nama Driver', document.namaDriver),
|
||||
]
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 20),
|
||||
|
||||
// Items Table
|
||||
pw.Text('Barang Dikirim', style: pw.TextStyle(fontSize: 16, fontWeight: pw.FontWeight.bold)),
|
||||
pw.SizedBox(height: 8),
|
||||
pw.Table.fromTextArray(
|
||||
context: context,
|
||||
border: pw.TableBorder.all(color: PdfColors.grey),
|
||||
headerDecoration: const pw.BoxDecoration(color: PdfColors.grey300),
|
||||
headerHeight: 25,
|
||||
cellHeight: 25,
|
||||
columnWidths: {
|
||||
0: const pw.FixedColumnWidth(80), // SKU
|
||||
1: const pw.FlexColumnWidth(2), // Nama Barang
|
||||
2: const pw.FixedColumnWidth(70), // Banyak
|
||||
3: const pw.FixedColumnWidth(70), // Jumlah
|
||||
},
|
||||
cellAlignments: {
|
||||
0: pw.Alignment.centerLeft,
|
||||
1: pw.Alignment.centerLeft,
|
||||
2: pw.Alignment.centerRight,
|
||||
3: pw.Alignment.centerRight,
|
||||
},
|
||||
headers: ['Nomor SKU', 'Nama Barang', 'Banyak', 'Jumlah'],
|
||||
data: document.items.map((item) {
|
||||
return [
|
||||
item.nomorSku,
|
||||
item.namaBarang,
|
||||
item.banyak,
|
||||
item.jumlah,
|
||||
];
|
||||
}).toList(),
|
||||
// Items Table / Scanned Product Details
|
||||
pw.Text(
|
||||
isProductScan ? 'Detail Item Scanned' : 'Barang Dikirim',
|
||||
style: pw.TextStyle(fontSize: 16, fontWeight: pw.FontWeight.bold)
|
||||
),
|
||||
pw.SizedBox(height: 8),
|
||||
|
||||
isProductScan
|
||||
? pw.Table.fromTextArray(
|
||||
context: context,
|
||||
border: pw.TableBorder.all(color: PdfColors.grey),
|
||||
headerDecoration: const pw.BoxDecoration(color: PdfColors.grey300),
|
||||
headerHeight: 25,
|
||||
cellHeight: 25,
|
||||
columnWidths: {
|
||||
0: const pw.FixedColumnWidth(100), // SKU
|
||||
1: const pw.FlexColumnWidth(2), // Nama Barang
|
||||
2: const pw.FixedColumnWidth(100), // Batch
|
||||
3: const pw.FixedColumnWidth(100), // Expired
|
||||
},
|
||||
cellAlignments: {
|
||||
0: pw.Alignment.centerLeft,
|
||||
1: pw.Alignment.centerLeft,
|
||||
2: pw.Alignment.center,
|
||||
3: pw.Alignment.center,
|
||||
},
|
||||
headers: ['Nomor SKU', 'Nama Barang', 'Nomor Batch', 'Expired Date'],
|
||||
data: [
|
||||
[
|
||||
document.items.isNotEmpty ? document.items.first.nomorSku : '-',
|
||||
document.items.isNotEmpty ? document.items.first.namaBarang : '-',
|
||||
document.noDo,
|
||||
document.alamat.replaceAll("Product Scan: Expired ", ""),
|
||||
]
|
||||
],
|
||||
)
|
||||
: pw.Table.fromTextArray(
|
||||
context: context,
|
||||
border: pw.TableBorder.all(color: PdfColors.grey),
|
||||
headerDecoration: const pw.BoxDecoration(color: PdfColors.grey300),
|
||||
headerHeight: 25,
|
||||
cellHeight: 25,
|
||||
columnWidths: {
|
||||
0: const pw.FixedColumnWidth(80), // SKU
|
||||
1: const pw.FlexColumnWidth(2), // Nama Barang
|
||||
2: const pw.FixedColumnWidth(70), // Banyak
|
||||
3: const pw.FixedColumnWidth(70), // Jumlah
|
||||
},
|
||||
cellAlignments: {
|
||||
0: pw.Alignment.centerLeft,
|
||||
1: pw.Alignment.centerLeft,
|
||||
2: pw.Alignment.centerRight,
|
||||
3: pw.Alignment.centerRight,
|
||||
},
|
||||
headers: ['Nomor SKU', 'Nama Barang', 'Banyak', 'Jumlah'],
|
||||
data: document.items.map((item) {
|
||||
return [
|
||||
item.nomorSku,
|
||||
item.namaBarang,
|
||||
item.banyak,
|
||||
item.jumlah,
|
||||
];
|
||||
}).toList(),
|
||||
),
|
||||
|
||||
pw.Spacer(),
|
||||
|
||||
// Footer & Signatures
|
||||
pw.Row(
|
||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
pw.Column(
|
||||
children: [
|
||||
pw.SizedBox(height: 40),
|
||||
pw.Text(document.namaDriver.isNotEmpty ? document.namaDriver : '_______________________', style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
|
||||
pw.SizedBox(height: 4),
|
||||
pw.Text('Pengirim (Driver)'),
|
||||
children: isProductScan
|
||||
? [
|
||||
pw.Column(
|
||||
children: [
|
||||
pw.SizedBox(height: 40),
|
||||
pw.Text('_______________________', style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
|
||||
pw.SizedBox(height: 4),
|
||||
pw.Text('Petugas / Staff Toko'),
|
||||
]
|
||||
),
|
||||
pw.Column(
|
||||
children: [
|
||||
pw.SizedBox(height: 40),
|
||||
pw.Text('_______________________', style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
|
||||
pw.SizedBox(height: 4),
|
||||
pw.Text('Penanggung Jawab Toko'),
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
pw.Column(
|
||||
children: [
|
||||
pw.SizedBox(height: 40),
|
||||
pw.Text(document.namaPenerima.isNotEmpty ? document.namaPenerima : '_______________________', style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
|
||||
pw.SizedBox(height: 4),
|
||||
pw.Text('Penerima (Penjaga Toko)'),
|
||||
: [
|
||||
pw.Column(
|
||||
children: [
|
||||
pw.SizedBox(height: 40),
|
||||
pw.Text(document.namaDriver.isNotEmpty ? document.namaDriver : '_______________________', style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
|
||||
pw.SizedBox(height: 4),
|
||||
pw.Text('Pengirim (Driver)'),
|
||||
]
|
||||
),
|
||||
pw.Column(
|
||||
children: [
|
||||
pw.SizedBox(height: 40),
|
||||
pw.Text(document.namaPenerima.isNotEmpty ? document.namaPenerima : '_______________________', style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
|
||||
pw.SizedBox(height: 4),
|
||||
pw.Text('Penerima (Penjaga Toko)'),
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
],
|
||||
);
|
||||
@@ -140,7 +212,9 @@ class PdfService {
|
||||
await Printing.layoutPdf(
|
||||
onLayout: (PdfPageFormat format) async => pdf.save(),
|
||||
format: PdfPageFormat.a4,
|
||||
name: 'Document_${document.noDo.replaceAll('/', '_')}',
|
||||
name: isProductScan
|
||||
? 'ProductScan_${document.items.isNotEmpty ? document.items.first.nomorSku : "unknown"}'
|
||||
: 'Document_${document.noDo.replaceAll('/', '_')}',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user