913 lines
38 KiB
Dart
913 lines
38 KiB
Dart
import 'dart:io';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:enaklo_pos/core/extensions/string_ext.dart';
|
|
import 'package:enaklo_pos/core/utils/helper_pdf_service.dart';
|
|
import 'package:enaklo_pos/data/models/response/category_analytic_response_model.dart';
|
|
import 'package:enaklo_pos/data/models/response/payment_method_analytic_response_model.dart';
|
|
import 'package:enaklo_pos/data/models/response/product_analytic_response_model.dart';
|
|
import 'package:enaklo_pos/data/models/response/profit_loss_response_model.dart';
|
|
import 'package:enaklo_pos/presentation/home/models/outlet_model.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:pdf/pdf.dart';
|
|
import 'package:pdf/widgets.dart' as pw;
|
|
|
|
class TransactionReport {
|
|
static final primaryColor = PdfColor.fromHex("36175e");
|
|
|
|
static Future<File> previewPdf({
|
|
required Outlet outlet,
|
|
required String searchDateFormatted,
|
|
required CategoryAnalyticData? categoryAnalyticData,
|
|
required ProfitLossData? profitLossData,
|
|
required PaymentMethodAnalyticData? paymentMethodAnalyticData,
|
|
required ProductAnalyticData? productAnalyticData,
|
|
}) async {
|
|
final pdf = pw.Document();
|
|
final ByteData dataImage = await rootBundle.load('assets/logo/logo.png');
|
|
final Uint8List bytes = dataImage.buffer.asUint8List();
|
|
|
|
final profitLossProductSummary = {
|
|
'totalRevenue': profitLossData?.productData
|
|
.fold<num>(0, (sum, item) => sum + (item.revenue)) ??
|
|
0,
|
|
'totalCost': profitLossData?.productData
|
|
.fold<num>(0, (sum, item) => sum + (item.cost)) ??
|
|
0,
|
|
'totalGrossProfit': profitLossData?.productData
|
|
.fold<num>(0, (sum, item) => sum + (item.grossProfit)) ??
|
|
0,
|
|
'totalQuantity': profitLossData?.productData
|
|
.fold<num>(0, (sum, item) => sum + (item.quantitySold)) ??
|
|
0,
|
|
};
|
|
|
|
final categorySummary = {
|
|
'totalRevenue': categoryAnalyticData?.data
|
|
.fold<num>(0, (sum, item) => sum + (item.totalRevenue)) ??
|
|
0,
|
|
'orderCount': categoryAnalyticData?.data
|
|
.fold<num>(0, (sum, item) => sum + (item.orderCount)) ??
|
|
0,
|
|
'productCount': categoryAnalyticData?.data
|
|
.fold<num>(0, (sum, item) => sum + (item.productCount)) ??
|
|
0,
|
|
'totalQuantity': categoryAnalyticData?.data
|
|
.fold<num>(0, (sum, item) => sum + (item.totalQuantity)) ??
|
|
0,
|
|
};
|
|
|
|
final productItemSummary = {
|
|
'totalRevenue': productAnalyticData?.data
|
|
.fold<num>(0, (sum, item) => sum + (item.revenue)) ??
|
|
0,
|
|
'orderCount': productAnalyticData?.data
|
|
.fold<num>(0, (sum, item) => sum + (item.orderCount)) ??
|
|
0,
|
|
'totalQuantitySold': productAnalyticData?.data
|
|
.fold<num>(0, (sum, item) => sum + (item.quantitySold)) ??
|
|
0,
|
|
};
|
|
|
|
// Membuat objek Image dari gambar
|
|
final image = pw.MemoryImage(bytes);
|
|
pdf.addPage(
|
|
pw.MultiPage(
|
|
pageFormat: PdfPageFormat.a4,
|
|
margin: pw.EdgeInsets.zero,
|
|
build: (pw.Context context) {
|
|
return [
|
|
pw.Container(
|
|
padding: pw.EdgeInsets.all(20),
|
|
child: pw.Row(
|
|
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
children: [
|
|
// Bagian kiri - Logo dan Info Perusahaan
|
|
pw.Row(
|
|
crossAxisAlignment: pw.CrossAxisAlignment.center,
|
|
children: [
|
|
// Icon/Logo placeholder (bisa diganti dengan gambar logo)
|
|
pw.Container(
|
|
width: 40,
|
|
height: 40,
|
|
child: pw.Image(image),
|
|
),
|
|
pw.SizedBox(width: 15),
|
|
pw.Column(
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
children: [
|
|
pw.Text(
|
|
'Apskel',
|
|
style: pw.TextStyle(
|
|
fontSize: 28,
|
|
fontWeight: pw.FontWeight.bold,
|
|
color: primaryColor,
|
|
),
|
|
),
|
|
pw.SizedBox(height: 4),
|
|
pw.Text(
|
|
outlet.name ?? "",
|
|
style: pw.TextStyle(
|
|
fontSize: 16,
|
|
color: PdfColors.grey700,
|
|
),
|
|
),
|
|
pw.SizedBox(height: 2),
|
|
pw.Text(
|
|
outlet.address ?? "",
|
|
style: pw.TextStyle(
|
|
fontSize: 12,
|
|
color: PdfColors.grey600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
// Bagian kanan - Info Laporan
|
|
pw.Column(
|
|
crossAxisAlignment: pw.CrossAxisAlignment.end,
|
|
children: [
|
|
pw.Text(
|
|
'Laporan Transaksi',
|
|
style: pw.TextStyle(
|
|
fontSize: 24,
|
|
fontWeight: pw.FontWeight.bold,
|
|
color: PdfColors.grey800,
|
|
),
|
|
),
|
|
pw.SizedBox(height: 8),
|
|
pw.Text(
|
|
searchDateFormatted,
|
|
style: pw.TextStyle(
|
|
fontSize: 14,
|
|
color: PdfColors.grey600,
|
|
),
|
|
),
|
|
pw.SizedBox(height: 4),
|
|
pw.Text(
|
|
'Laporan',
|
|
style: pw.TextStyle(
|
|
fontSize: 12,
|
|
color: PdfColors.grey500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
pw.Container(
|
|
width: double.infinity,
|
|
height: 3,
|
|
color: primaryColor,
|
|
),
|
|
|
|
// Summary
|
|
pw.Container(
|
|
padding: pw.EdgeInsets.all(20),
|
|
child: pw.Column(
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
children: [
|
|
_buildSectionWidget('1. Ringkasan'),
|
|
pw.SizedBox(height: 30),
|
|
pw.Row(
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
children: [
|
|
pw.Expanded(
|
|
flex: 1,
|
|
child: pw.Column(
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
children: [
|
|
_buildSummaryItem(
|
|
'Total Penjualan (termasuk rasik)',
|
|
(profitLossData?.summary.totalRevenue ?? 0)
|
|
.toString()
|
|
.currencyFormatRpV2,
|
|
),
|
|
_buildSummaryItem(
|
|
'Total Total Terjual',
|
|
(profitLossData?.summary.totalOrders ?? 0)
|
|
.toString(),
|
|
),
|
|
_buildSummaryItem(
|
|
'HPP',
|
|
'${(profitLossData?.summary.totalCost ?? 0).toString().currencyFormatRpV2} | ${(((profitLossData?.summary.totalCost ?? 0) / (profitLossData?.summary.totalRevenue ?? 1)) * 100).round()}%',
|
|
),
|
|
_buildSummaryItem(
|
|
'Laba Kotor',
|
|
'${(profitLossData?.summary.grossProfit ?? 0).toString().currencyFormatRpV2} | ${(profitLossData?.summary.grossProfitMargin ?? 0).round()}%',
|
|
valueStyle: pw.TextStyle(
|
|
color: PdfColors.green800,
|
|
fontWeight: pw.FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
labelStyle: pw.TextStyle(
|
|
color: PdfColors.green800,
|
|
fontWeight: pw.FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
pw.SizedBox(width: 20),
|
|
pw.Expanded(
|
|
flex: 1,
|
|
child: pw.Column(
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
children: [
|
|
_buildSummaryItem(
|
|
'Biaya Lain lain',
|
|
'${(profitLossData?.summary.totalTax ?? 0).toString().currencyFormatRpV2} | ${(((profitLossData?.summary.totalTax ?? 0) / (profitLossData?.summary.totalRevenue ?? 1)) * 100).round()}%',
|
|
),
|
|
_buildSummaryItem(
|
|
'Laba/Rugi',
|
|
'${(profitLossData?.summary.netProfit ?? 0).toString().currencyFormatRpV2} | ${(profitLossData?.summary.netProfitMargin ?? 0).round()}%',
|
|
valueStyle: pw.TextStyle(
|
|
color: PdfColors.blue800,
|
|
fontWeight: pw.FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
labelStyle: pw.TextStyle(
|
|
color: PdfColors.blue800,
|
|
fontWeight: pw.FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
pw.SizedBox(height: 16),
|
|
pw.Text(
|
|
"Laba Rugi Perproduk",
|
|
style: pw.TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: pw.FontWeight.bold,
|
|
color: primaryColor,
|
|
),
|
|
),
|
|
pw.SizedBox(height: 20),
|
|
pw.Column(
|
|
children: [
|
|
pw.Container(
|
|
decoration: pw.BoxDecoration(
|
|
color: primaryColor, // Purple color
|
|
borderRadius: pw.BorderRadius.only(
|
|
topLeft: pw.Radius.circular(8),
|
|
topRight: pw.Radius.circular(8),
|
|
),
|
|
),
|
|
child: pw.Table(
|
|
columnWidths: const {
|
|
0: pw.FlexColumnWidth(2.5), // Produk
|
|
1: pw.FlexColumnWidth(1), // Qty
|
|
2: pw.FlexColumnWidth(2.5), // Pendapatan
|
|
3: pw.FlexColumnWidth(2), // HPP
|
|
4: pw.FlexColumnWidth(2), // Laba Kotor
|
|
5: pw.FlexColumnWidth(2), // Margin (%)
|
|
},
|
|
children: [
|
|
pw.TableRow(
|
|
children: [
|
|
_buildHeaderCell('Produk'),
|
|
_buildHeaderCell('Qty'),
|
|
_buildHeaderCell('Pendapatan'),
|
|
_buildHeaderCell('HPP'),
|
|
_buildHeaderCell('Laba Kotor'),
|
|
_buildHeaderCell('Margin (%)'),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
pw.Container(
|
|
decoration: pw.BoxDecoration(
|
|
color: PdfColors.white,
|
|
),
|
|
child: pw.Table(
|
|
columnWidths: {
|
|
0: pw.FlexColumnWidth(2.5), // Produk
|
|
1: pw.FlexColumnWidth(1), // Qty
|
|
2: pw.FlexColumnWidth(2.5), // Pendapatan
|
|
3: pw.FlexColumnWidth(2), // HPP
|
|
4: pw.FlexColumnWidth(2), // Laba Kotor
|
|
5: pw.FlexColumnWidth(2), // Margin (%)
|
|
},
|
|
children: profitLossData?.productData
|
|
.map(
|
|
(profitLoss) => _buildPerProductDataRow(
|
|
product: profitLoss.productName,
|
|
qty: profitLoss.quantitySold.toString(),
|
|
pendapatan: profitLoss.revenue
|
|
.toString()
|
|
.currencyFormatRpV2,
|
|
hpp: profitLoss.cost
|
|
.toString()
|
|
.currencyFormatRpV2,
|
|
labaKotor: profitLoss.grossProfit
|
|
.toString()
|
|
.currencyFormatRpV2,
|
|
margin:
|
|
'${profitLoss.grossProfitMargin.round()}%',
|
|
isEven: profitLossData.productData
|
|
.indexOf(profitLoss) %
|
|
2 ==
|
|
0,
|
|
),
|
|
)
|
|
.toList() ??
|
|
[],
|
|
),
|
|
),
|
|
pw.Container(
|
|
decoration: pw.BoxDecoration(
|
|
color: primaryColor, // Purple color
|
|
borderRadius: pw.BorderRadius.only(
|
|
bottomLeft: pw.Radius.circular(8),
|
|
bottomRight: pw.Radius.circular(8),
|
|
),
|
|
),
|
|
child: pw.Table(
|
|
columnWidths: const {
|
|
0: pw.FlexColumnWidth(2.5), // Produk
|
|
1: pw.FlexColumnWidth(1), // Qty
|
|
2: pw.FlexColumnWidth(2.5), // Pendapatan
|
|
3: pw.FlexColumnWidth(2), // HPP
|
|
4: pw.FlexColumnWidth(2), // Laba Kotor
|
|
5: pw.FlexColumnWidth(2), // Margin (%)
|
|
},
|
|
children: [
|
|
pw.TableRow(
|
|
children: [
|
|
_buildTotalCell('TOTAL'),
|
|
_buildTotalCell(
|
|
profitLossProductSummary['totalQuantity']
|
|
.toString()),
|
|
_buildTotalCell(
|
|
profitLossProductSummary['totalRevenue']
|
|
.toString()
|
|
.currencyFormatRpV2),
|
|
_buildTotalCell(
|
|
profitLossProductSummary['totalCost']
|
|
.toString()
|
|
.currencyFormatRpV2),
|
|
_buildTotalCell(
|
|
profitLossProductSummary['totalGrossProfit']
|
|
.toString()
|
|
.currencyFormatRpV2),
|
|
_buildTotalCell(''),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Summary Payment Method
|
|
pw.Container(
|
|
padding: pw.EdgeInsets.all(20),
|
|
child: pw.Column(
|
|
children: [
|
|
pw.Column(
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
children: [
|
|
_buildSectionWidget('2. Ringkasan Metode Pembayaran'),
|
|
pw.SizedBox(height: 30),
|
|
pw.Container(
|
|
decoration: pw.BoxDecoration(
|
|
color: primaryColor, // Purple color
|
|
borderRadius: pw.BorderRadius.only(
|
|
topLeft: pw.Radius.circular(8),
|
|
topRight: pw.Radius.circular(8),
|
|
),
|
|
),
|
|
child: pw.Table(
|
|
columnWidths: const {
|
|
0: pw.FlexColumnWidth(2.5), // Nama
|
|
1: pw.FlexColumnWidth(1), // Tipe
|
|
2: pw.FlexColumnWidth(2.5), // Jumlah Order
|
|
3: pw.FlexColumnWidth(2), // Total Amount
|
|
4: pw.FlexColumnWidth(2), // Presentase
|
|
},
|
|
children: [
|
|
pw.TableRow(
|
|
children: [
|
|
_buildHeaderCell('Nama'),
|
|
_buildHeaderCell('Tipe'),
|
|
_buildHeaderCell('Jumlah Order'),
|
|
_buildHeaderCell('Total Amount'),
|
|
_buildHeaderCell('Presentase'),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
pw.Container(
|
|
decoration: pw.BoxDecoration(
|
|
color: PdfColors.white,
|
|
),
|
|
child: pw.Table(
|
|
columnWidths: {
|
|
0: pw.FlexColumnWidth(2.5), // Nama
|
|
1: pw.FlexColumnWidth(1), // Tipe
|
|
2: pw.FlexColumnWidth(2.5), // Jumlah Order
|
|
3: pw.FlexColumnWidth(2), // Total Amount
|
|
4: pw.FlexColumnWidth(2), // Presentase
|
|
},
|
|
children: paymentMethodAnalyticData?.data
|
|
.map(
|
|
(payment) => _buildPaymentMethodDataRow(
|
|
name: payment.paymentMethodName,
|
|
tipe: payment.paymentMethodType
|
|
.toTitleCase(),
|
|
jumlahOrder:
|
|
payment.orderCount.toString(),
|
|
totalAmount: payment.totalAmount
|
|
.toString()
|
|
.currencyFormatRpV2,
|
|
presentase:
|
|
'${payment.percentage.round()}%',
|
|
isEven: paymentMethodAnalyticData.data
|
|
.indexOf(payment) %
|
|
2 ==
|
|
0,
|
|
),
|
|
)
|
|
.toList() ??
|
|
[],
|
|
),
|
|
),
|
|
pw.Container(
|
|
decoration: pw.BoxDecoration(
|
|
color: primaryColor, // Purple color
|
|
borderRadius: pw.BorderRadius.only(
|
|
bottomLeft: pw.Radius.circular(8),
|
|
bottomRight: pw.Radius.circular(8),
|
|
),
|
|
),
|
|
child: pw.Table(
|
|
columnWidths: const {
|
|
0: pw.FlexColumnWidth(2.5), // Produk
|
|
1: pw.FlexColumnWidth(1), // Qty
|
|
2: pw.FlexColumnWidth(2.5), // Pendapatan
|
|
3: pw.FlexColumnWidth(2), // HPP
|
|
4: pw.FlexColumnWidth(2), // Laba Kotor
|
|
5: pw.FlexColumnWidth(2), // Margin (%)
|
|
},
|
|
children: [
|
|
pw.TableRow(
|
|
children: [
|
|
_buildTotalCell('TOTAL'),
|
|
_buildTotalCell(''),
|
|
_buildTotalCell((paymentMethodAnalyticData
|
|
?.summary.totalOrders ??
|
|
0)
|
|
.toString()),
|
|
_buildTotalCell((paymentMethodAnalyticData
|
|
?.summary.totalAmount ??
|
|
0)
|
|
.toString()
|
|
.currencyFormatRpV2),
|
|
_buildTotalCell(''),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Summary Category
|
|
pw.Container(
|
|
padding: pw.EdgeInsets.all(20),
|
|
child: pw.Column(
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
children: [
|
|
_buildSectionWidget('3. Ringkasan Kategori'),
|
|
pw.SizedBox(height: 30),
|
|
pw.Column(
|
|
children: [
|
|
pw.Container(
|
|
decoration: pw.BoxDecoration(
|
|
color: primaryColor, // Purple color
|
|
borderRadius: pw.BorderRadius.only(
|
|
topLeft: pw.Radius.circular(8),
|
|
topRight: pw.Radius.circular(8),
|
|
),
|
|
),
|
|
child: pw.Table(
|
|
columnWidths: const {
|
|
0: pw.FlexColumnWidth(2.5), // Nama
|
|
1: pw.FlexColumnWidth(2), // Total Product
|
|
2: pw.FlexColumnWidth(1), // qty
|
|
3: pw.FlexColumnWidth(2), // Jumlah Order
|
|
4: pw.FlexColumnWidth(2.5), // Presentase
|
|
},
|
|
children: [
|
|
pw.TableRow(
|
|
children: [
|
|
_buildHeaderCell('Nama'),
|
|
_buildHeaderCell('Total Produk'),
|
|
_buildHeaderCell('Qty'),
|
|
_buildHeaderCell('Jumlah Order'),
|
|
_buildHeaderCell('Pendapatan'),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
pw.Container(
|
|
decoration: pw.BoxDecoration(
|
|
color: PdfColors.white,
|
|
),
|
|
child: pw.Table(
|
|
columnWidths: {
|
|
0: pw.FlexColumnWidth(2.5), // Nama
|
|
1: pw.FlexColumnWidth(2), // Total Product
|
|
2: pw.FlexColumnWidth(1), // qty
|
|
3: pw.FlexColumnWidth(2), // Jumlah Order
|
|
4: pw.FlexColumnWidth(2.5), // Presentase
|
|
},
|
|
children: categoryAnalyticData?.data
|
|
.map((category) => _buildCategoryDataRow(
|
|
name: category.categoryName,
|
|
totalProduct:
|
|
category.productCount.toString(),
|
|
qty: category.totalQuantity.toString(),
|
|
jumlahOrder:
|
|
category.orderCount.toString(),
|
|
pendapatan: category.totalRevenue
|
|
.toString()
|
|
.currencyFormatRpV2,
|
|
isEven: categoryAnalyticData.data
|
|
.indexOf(category) %
|
|
2 ==
|
|
0,
|
|
))
|
|
.toList() ??
|
|
[],
|
|
),
|
|
),
|
|
pw.Container(
|
|
decoration: pw.BoxDecoration(
|
|
color: primaryColor, // Purple color
|
|
borderRadius: pw.BorderRadius.only(
|
|
bottomLeft: pw.Radius.circular(8),
|
|
bottomRight: pw.Radius.circular(8),
|
|
),
|
|
),
|
|
child: pw.Table(
|
|
columnWidths: const {
|
|
0: pw.FlexColumnWidth(2.5), // Nama
|
|
1: pw.FlexColumnWidth(2), // Total Product
|
|
2: pw.FlexColumnWidth(1), // qty
|
|
3: pw.FlexColumnWidth(2), // Jumlah Order
|
|
4: pw.FlexColumnWidth(2.5), // Presentase
|
|
},
|
|
children: [
|
|
pw.TableRow(
|
|
children: [
|
|
_buildTotalCell('TOTAL'),
|
|
_buildTotalCell(
|
|
categorySummary['productCount'].toString()),
|
|
_buildTotalCell(categorySummary['totalQuantity']
|
|
.toString()),
|
|
_buildTotalCell(
|
|
categorySummary['orderCount'].toString()),
|
|
_buildTotalCell(categorySummary['totalRevenue']
|
|
.toString()
|
|
.currencyFormatRpV2),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Summary Item
|
|
pw.Container(
|
|
padding: pw.EdgeInsets.all(20),
|
|
child: pw.Column(
|
|
children: [
|
|
pw.Column(
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
children: [
|
|
_buildSectionWidget('4. Ringkasan Item'),
|
|
pw.SizedBox(height: 30),
|
|
pw.Container(
|
|
decoration: pw.BoxDecoration(
|
|
color: primaryColor, // Purple color
|
|
borderRadius: pw.BorderRadius.only(
|
|
topLeft: pw.Radius.circular(8),
|
|
topRight: pw.Radius.circular(8),
|
|
),
|
|
),
|
|
child: pw.Table(
|
|
columnWidths: const {
|
|
0: pw.FlexColumnWidth(2.5), // Produk
|
|
1: pw.FlexColumnWidth(2), // Kategori
|
|
2: pw.FlexColumnWidth(1), // qty
|
|
3: pw.FlexColumnWidth(2), // Order
|
|
4: pw.FlexColumnWidth(2), // Pendapatan
|
|
5: pw.FlexColumnWidth(2), // Average
|
|
},
|
|
children: [
|
|
pw.TableRow(
|
|
children: [
|
|
_buildHeaderCell('Produk'),
|
|
_buildHeaderCell('Kategori'),
|
|
_buildHeaderCell('Qty'),
|
|
_buildHeaderCell('Order'),
|
|
_buildHeaderCell('Pendapatan'),
|
|
_buildHeaderCell('Rata Rata'),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
pw.Container(
|
|
decoration: pw.BoxDecoration(
|
|
color: PdfColors.white,
|
|
),
|
|
child: pw.Table(
|
|
columnWidths: {
|
|
0: pw.FlexColumnWidth(2.5), // Produk
|
|
1: pw.FlexColumnWidth(2), // Kategori
|
|
2: pw.FlexColumnWidth(1), // qty
|
|
3: pw.FlexColumnWidth(2), // Order
|
|
4: pw.FlexColumnWidth(2), // Pendapatan
|
|
5: pw.FlexColumnWidth(2), // Average
|
|
},
|
|
children: productAnalyticData?.data
|
|
.map((item) => _buildItemDataRow(
|
|
product: item.productName,
|
|
category: item.categoryName,
|
|
qty: item.quantitySold.toString(),
|
|
order: item.orderCount.toString(),
|
|
pendapatan: item.revenue
|
|
.toString()
|
|
.currencyFormatRpV2,
|
|
average: item.averagePrice
|
|
.round()
|
|
.toString()
|
|
.currencyFormatRpV2,
|
|
isEven: productAnalyticData.data
|
|
.indexOf(item) %
|
|
2 ==
|
|
0,
|
|
))
|
|
.toList() ??
|
|
[],
|
|
),
|
|
),
|
|
pw.Container(
|
|
decoration: pw.BoxDecoration(
|
|
color: primaryColor, // Purple color
|
|
borderRadius: pw.BorderRadius.only(
|
|
bottomLeft: pw.Radius.circular(8),
|
|
bottomRight: pw.Radius.circular(8),
|
|
),
|
|
),
|
|
child: pw.Table(
|
|
columnWidths: const {
|
|
0: pw.FlexColumnWidth(2.5), // Produk
|
|
1: pw.FlexColumnWidth(2), // Kategori
|
|
2: pw.FlexColumnWidth(1), // qty
|
|
3: pw.FlexColumnWidth(2), // Order
|
|
4: pw.FlexColumnWidth(2), // Pendapatan
|
|
5: pw.FlexColumnWidth(2), // Average
|
|
},
|
|
children: [
|
|
pw.TableRow(
|
|
children: [
|
|
_buildTotalCell('TOTAL'),
|
|
_buildTotalCell(''),
|
|
_buildTotalCell(
|
|
productItemSummary['totalQuantitySold']
|
|
.toString()),
|
|
_buildTotalCell(productItemSummary['orderCount']
|
|
.toString()),
|
|
_buildTotalCell(
|
|
productItemSummary['totalRevenue']
|
|
.toString()
|
|
.currencyFormatRpV2),
|
|
_buildTotalCell(''),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
];
|
|
},
|
|
),
|
|
);
|
|
|
|
return HelperPdfService.saveDocument(
|
|
name: 'Laporan Transaksi | $searchDateFormatted.pdf', pdf: pdf);
|
|
}
|
|
|
|
static pw.Widget _buildSectionWidget(String title) {
|
|
return pw.Text(
|
|
title,
|
|
style: pw.TextStyle(
|
|
fontSize: 20,
|
|
fontWeight: pw.FontWeight.bold,
|
|
color: primaryColor,
|
|
),
|
|
);
|
|
}
|
|
|
|
static pw.Widget _buildSummaryItem(
|
|
String label,
|
|
String value, {
|
|
pw.TextStyle? valueStyle,
|
|
pw.TextStyle? labelStyle,
|
|
}) {
|
|
return pw.Container(
|
|
padding: pw.EdgeInsets.only(bottom: 8),
|
|
margin: pw.EdgeInsets.only(bottom: 16),
|
|
decoration: pw.BoxDecoration(
|
|
border: pw.Border(
|
|
bottom: pw.BorderSide(
|
|
color: PdfColors.grey300,
|
|
),
|
|
),
|
|
),
|
|
child: pw.Row(
|
|
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
pw.Text(label, style: labelStyle),
|
|
pw.Text(
|
|
value,
|
|
style: valueStyle ??
|
|
pw.TextStyle(
|
|
fontWeight: pw.FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
static pw.Widget _buildHeaderCell(String text) {
|
|
return pw.Container(
|
|
padding: pw.EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
|
child: pw.Text(
|
|
text,
|
|
style: pw.TextStyle(
|
|
color: PdfColors.white,
|
|
fontWeight: pw.FontWeight.bold,
|
|
fontSize: 12,
|
|
),
|
|
textAlign: pw.TextAlign.center,
|
|
),
|
|
);
|
|
}
|
|
|
|
static pw.Widget _buildDataCell(String text,
|
|
{pw.Alignment alignment = pw.Alignment.center, PdfColor? textColor}) {
|
|
return pw.Container(
|
|
padding: pw.EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
|
alignment: alignment,
|
|
child: pw.Text(
|
|
text,
|
|
style: pw.TextStyle(
|
|
fontSize: 12,
|
|
color: textColor ?? PdfColors.black,
|
|
fontWeight: pw.FontWeight.normal,
|
|
),
|
|
textAlign: alignment == pw.Alignment.centerLeft
|
|
? pw.TextAlign.left
|
|
: pw.TextAlign.center,
|
|
),
|
|
);
|
|
}
|
|
|
|
static pw.Widget _buildTotalCell(String text) {
|
|
return pw.Container(
|
|
padding: pw.EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
|
child: pw.Text(
|
|
text,
|
|
style: pw.TextStyle(
|
|
color: PdfColors.white,
|
|
fontWeight: pw.FontWeight.bold,
|
|
fontSize: 12,
|
|
),
|
|
textAlign: pw.TextAlign.center,
|
|
),
|
|
);
|
|
}
|
|
|
|
static pw.TableRow _buildPerProductDataRow({
|
|
required String product,
|
|
required String qty,
|
|
required String pendapatan,
|
|
required String hpp,
|
|
required String labaKotor,
|
|
required String margin,
|
|
required bool isEven,
|
|
}) {
|
|
return pw.TableRow(
|
|
decoration: pw.BoxDecoration(
|
|
color: isEven ? PdfColors.grey50 : PdfColors.white,
|
|
),
|
|
children: [
|
|
_buildDataCell(product, alignment: pw.Alignment.centerLeft),
|
|
_buildDataCell(qty),
|
|
_buildDataCell(pendapatan),
|
|
_buildDataCell(hpp, textColor: PdfColors.red600),
|
|
_buildDataCell(labaKotor, textColor: PdfColors.green600),
|
|
_buildDataCell(margin),
|
|
],
|
|
);
|
|
}
|
|
|
|
static pw.TableRow _buildPaymentMethodDataRow({
|
|
required String name,
|
|
required String tipe,
|
|
required String jumlahOrder,
|
|
required String totalAmount,
|
|
required String presentase,
|
|
required bool isEven,
|
|
}) {
|
|
return pw.TableRow(
|
|
decoration: pw.BoxDecoration(
|
|
color: isEven ? PdfColors.grey50 : PdfColors.white,
|
|
),
|
|
children: [
|
|
_buildDataCell(name, alignment: pw.Alignment.centerLeft),
|
|
_buildDataCell(tipe),
|
|
_buildDataCell(jumlahOrder),
|
|
_buildDataCell(totalAmount),
|
|
_buildDataCell(presentase),
|
|
],
|
|
);
|
|
}
|
|
|
|
static pw.TableRow _buildCategoryDataRow({
|
|
required String name,
|
|
required String totalProduct,
|
|
required String qty,
|
|
required String jumlahOrder,
|
|
required String pendapatan,
|
|
required bool isEven,
|
|
}) {
|
|
return pw.TableRow(
|
|
decoration: pw.BoxDecoration(
|
|
color: isEven ? PdfColors.grey50 : PdfColors.white,
|
|
),
|
|
children: [
|
|
_buildDataCell(name, alignment: pw.Alignment.centerLeft),
|
|
_buildDataCell(totalProduct),
|
|
_buildDataCell(qty),
|
|
_buildDataCell(jumlahOrder),
|
|
_buildDataCell(pendapatan),
|
|
],
|
|
);
|
|
}
|
|
|
|
static pw.TableRow _buildItemDataRow({
|
|
required String product,
|
|
required String category,
|
|
required String qty,
|
|
required String order,
|
|
required String pendapatan,
|
|
required String average,
|
|
required bool isEven,
|
|
}) {
|
|
return pw.TableRow(
|
|
decoration: pw.BoxDecoration(
|
|
color: isEven ? PdfColors.grey50 : PdfColors.white,
|
|
),
|
|
children: [
|
|
_buildDataCell(product, alignment: pw.Alignment.centerLeft),
|
|
_buildDataCell(category, alignment: pw.Alignment.centerLeft),
|
|
_buildDataCell(qty),
|
|
_buildDataCell(order),
|
|
_buildDataCell(pendapatan),
|
|
_buildDataCell(average),
|
|
],
|
|
);
|
|
}
|
|
}
|