apskel-pos-flutter/lib/core/utils/revenue_invoice.dart

227 lines
6.8 KiB
Dart
Raw Normal View History

2025-07-30 22:38:44 +07:00
import 'dart:io';
import 'dart:developer';
import 'package:enaklo_pos/core/extensions/date_time_ext.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:enaklo_pos/data/models/response/summary_response_model.dart';
import 'package:flutter/services.dart';
import 'package:enaklo_pos/core/utils/helper_pdf_service.dart';
import 'package:pdf/widgets.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:flutter/foundation.dart';
class RevenueInvoice {
static late Font ttf;
static Future<File> generate(
SummaryModel summaryModel,
String searchDateFormatted,
) async {
try {
log("Starting PDF generation for summary report");
log("Summary model: ${summaryModel.toMap()}");
log("Search date formatted: $searchDateFormatted");
2025-08-01 18:27:40 +07:00
2025-07-30 22:38:44 +07:00
final pdf = Document();
log("PDF document created");
2025-08-01 18:27:40 +07:00
2025-07-30 22:38:44 +07:00
// Load logo image
log("Loading logo image...");
2025-08-01 18:27:40 +07:00
final ByteData dataImage =
await rootBundle.load('assets/images/logo.png');
2025-07-30 22:38:44 +07:00
final Uint8List bytes = dataImage.buffer.asUint8List();
final image = pw.MemoryImage(bytes);
log("Logo image loaded successfully, size: ${bytes.length} bytes");
log("Adding page to PDF...");
pdf.addPage(
MultiPage(
build: (context) => [
buildHeader(summaryModel, image, searchDateFormatted),
SizedBox(height: 1 * PdfPageFormat.cm),
buildTotal(summaryModel),
],
footer: (context) => buildFooter(summaryModel),
),
);
log("PDF page added successfully");
log("Saving PDF document...");
return HelperPdfService.saveDocument(
name:
2025-08-01 18:27:40 +07:00
'Apskel POS | Summary Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf',
2025-07-30 22:38:44 +07:00
pdf: pdf,
);
} catch (e) {
log("Error generating PDF: $e");
log("Error stack trace: ${StackTrace.current}");
return Future.error("Failed to generate PDF: $e");
}
}
static Widget buildHeader(
SummaryModel invoice,
MemoryImage image,
String searchDateFormatted,
) =>
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 1 * PdfPageFormat.cm),
2025-08-01 18:27:40 +07:00
Text('Apskel POS | Summary Sales Report',
2025-07-30 22:38:44 +07:00
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
)),
SizedBox(height: 0.2 * PdfPageFormat.cm),
Text(
"Data: $searchDateFormatted",
),
Text(
'Created At: ${DateTime.now().toFormattedDate3()}',
),
],
),
Image(
image,
width: 80.0,
height: 80.0,
fit: BoxFit.fill,
),
]);
static Widget buildTotal(SummaryModel summaryModel) {
log("Building total section with summary model: ${summaryModel.toMap()}");
2025-08-01 18:27:40 +07:00
2025-07-30 22:38:44 +07:00
// Helper function to safely parse string to int
int safeParseInt(String? value) {
if (value == null || value.isEmpty) return 0;
try {
return int.parse(value.replaceAll('.00', ''));
} catch (e) {
log("Error parsing value '$value' to int: $e");
return 0;
}
}
2025-08-01 18:27:40 +07:00
2025-07-30 22:38:44 +07:00
return Container(
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildText(
title: 'Revenue',
value: safeParseInt(summaryModel.totalRevenue).currencyFormatRp,
unite: true,
),
Divider(),
buildText(
title: 'Sub Total',
titleStyle: TextStyle(fontWeight: FontWeight.normal),
value: safeParseInt(summaryModel.totalSubtotal).currencyFormatRp,
unite: true,
),
buildText(
title: 'Discount',
titleStyle: TextStyle(fontWeight: FontWeight.normal),
2025-08-01 18:27:40 +07:00
value:
"- ${safeParseInt(summaryModel.totalDiscount).currencyFormatRp}",
2025-07-30 22:38:44 +07:00
unite: true,
textStyle: TextStyle(
color: PdfColor.fromHex('#FF0000'),
fontWeight: FontWeight.bold,
),
),
buildText(
title: 'Tax',
titleStyle: TextStyle(fontWeight: FontWeight.normal),
value: "- ${safeParseInt(summaryModel.totalTax).currencyFormatRp}",
textStyle: TextStyle(
color: PdfColor.fromHex('#FF0000'),
fontWeight: FontWeight.bold,
),
unite: true,
),
buildText(
title: 'Service Charge',
titleStyle: TextStyle(
fontWeight: FontWeight.normal,
),
2025-08-01 18:27:40 +07:00
value:
safeParseInt(summaryModel.totalServiceCharge).currencyFormatRp,
2025-07-30 22:38:44 +07:00
unite: true,
),
Divider(),
buildText(
title: 'Total ',
titleStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
value: (summaryModel.total ?? 0).currencyFormatRp,
unite: true,
),
SizedBox(height: 2 * PdfPageFormat.mm),
Container(height: 1, color: PdfColors.grey400),
SizedBox(height: 0.5 * PdfPageFormat.mm),
Container(height: 1, color: PdfColors.grey400),
],
),
);
}
static Widget buildFooter(SummaryModel summaryModel) => Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Divider(),
SizedBox(height: 2 * PdfPageFormat.mm),
buildSimpleText(
title: 'Address',
value:
'Jalan Melati No. 12, Mranggen, Demak, Central Java, 89568'),
SizedBox(height: 1 * PdfPageFormat.mm),
],
);
static buildSimpleText({
required String title,
required String value,
}) {
final style = TextStyle(fontWeight: FontWeight.bold);
return Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: pw.CrossAxisAlignment.end,
children: [
Text(title, style: style),
SizedBox(width: 2 * PdfPageFormat.mm),
Text(value),
],
);
}
static buildText({
required String title,
required String value,
double width = double.infinity,
TextStyle? titleStyle,
TextStyle? textStyle,
bool unite = false,
}) {
final style = titleStyle ?? TextStyle(fontWeight: FontWeight.bold);
final style2 = textStyle ?? TextStyle(fontWeight: FontWeight.bold);
return Container(
width: width,
child: Row(
children: [
Expanded(child: Text(title, style: style)),
Text(value, style: style2),
],
),
);
}
}