142 lines
No EOL
5.1 KiB
Dart
142 lines
No EOL
5.1 KiB
Dart
// Version: 1.0 - 見積書用 PDF テンプレート
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter_pdfgenerator/flutter_pdfgenerator.dart';
|
||
import 'package:pdf/pdf.dart';
|
||
import 'package:pdf/widgets.dart' as pw;
|
||
import '../models/customer.dart';
|
||
import '../models/product.dart';
|
||
import '../models/estimate.dart';
|
||
|
||
/// 見積書用 PDF テンプレート生成クラス
|
||
class EstimateTemplate {
|
||
/// 見積書の PDF を生成
|
||
static Future<pw.Document> generateEstimatePdf(Estimate estimate) async {
|
||
final doc = pw.Document();
|
||
|
||
doc.addPage(
|
||
pw.MultiPage(
|
||
pageFormat: PdfPageSize.a5, // A5 サイズ
|
||
build: (pw.Context context) => [
|
||
_buildHeader(context, estimate),
|
||
...estimate.items.map((item) => _buildItemLine(item, context)),
|
||
_buildSummary(context, estimate),
|
||
_buildFooter(context, estimate),
|
||
],
|
||
),
|
||
);
|
||
|
||
return doc;
|
||
}
|
||
|
||
/// ヘッダーエリア
|
||
static pw.Widget _buildHeader(pw.Context context, Estimate estimate) {
|
||
return pw.Container(
|
||
padding: const EdgeInsets.all(20),
|
||
decoration: pw.BoxDecoration(color: PdfColors.white),
|
||
child: pw.Row(
|
||
children: [
|
||
pw.Expanded(
|
||
child: pw.Column(
|
||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||
children: [
|
||
pw.Text('母艦 お局様', style: pw.TextStyle(fontSize: 16, fontWeight: PdfFontWeight.bold)),
|
||
pw.Text('会社名:〇〇株式会社'),
|
||
pw.Text('住所:東京都港区_test 1-1-1'),
|
||
pw.Text('TEL:03-1234-5678 / FAX:03-1234-5679'),
|
||
pw.Text('📧 mail@example.com'),
|
||
],
|
||
),
|
||
),
|
||
pw.Container(
|
||
padding: const EdgeInsets.all(10),
|
||
decoration: pw.BoxDecoration(color: PdfColors.blueAccent.withOpacity(0.1)),
|
||
child: pw.Row(
|
||
children: [
|
||
pw.Text('見積書', style: pw.TextStyle(fontSize: 14, fontWeight: PdfFontWeight.bold)),
|
||
const Spacer(),
|
||
pw.Text('No. ${estimate.estimateNumber}', style: pw.TextStyle(fontSize: 12)),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 商品明細行
|
||
static pw.Widget _buildItemLine(EstimateItem item, pw.Context context) {
|
||
final isEven = context.pageNumber % 2 == 0;
|
||
|
||
return pw.Container(
|
||
padding: const EdgeInsets.symmetric(vertical: 2),
|
||
decoration: pw.BoxDecoration(
|
||
color: isEven ? PdfColors.grey.shade50 : PdfColors.white,
|
||
border: Border(top: pw.BorderSide(color: PdfColors.grey.shade300)),
|
||
),
|
||
child: pw.Row(
|
||
children: [
|
||
pw.Text('#${context.pageNumber.toString().padLeft(2, '0')}', style: pw.TextStyle(fontSize: 10)),
|
||
pw SizedBox(width: 5),
|
||
pw.Expanded(
|
||
child: pw.Text(item.productName, style: pw.TextStyle(fontSize: 10, overflow: pw.Overflow.ellipsis)),
|
||
),
|
||
pw Container(width: 20),
|
||
pw Text('¥${item.unitPrice.toStringAsFixed(0)}', style: pw.TextStyle(fontSize: 10)),
|
||
pw Text(item.quantity.toString(), style: pw.TextStyle(fontSize: 10)),
|
||
pw Text('¥${item.subtotal.toStringAsFixed(0)}', style: pw.TextStyle(fontSize: 10)),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 合計金額エリア
|
||
static pw.Widget _buildSummary(pw.Context context, Estimate estimate) {
|
||
return pw.Container(
|
||
padding: const EdgeInsets.all(20),
|
||
decoration: pw.BoxDecoration(color: PdfColors.blue.shade50),
|
||
child: pw.Column(
|
||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||
children: [
|
||
pw.Text('合計', style: pw.TextStyle(fontSize: 14, fontWeight: PdfFontWeight.bold)),
|
||
const SizedBox(height: 8),
|
||
pw.Text('¥${estimate.totalAmount.toStringAsFixed(0)}', style: pw.TextStyle(fontSize: 16, fontWeight: PdfFontWeight.bold, color: PdfColors.orange.shade500)),
|
||
const Spacer(),
|
||
pw.Padding(
|
||
padding: const EdgeInsets.only(top: 20),
|
||
child: pw.Text('備考:納期・決済条件などの注意事項', style: pw.TextStyle(fontSize: 10, fontStyle: PdfFontStyle.italic)),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// フッターエリア
|
||
static pw.Widget _buildFooter(pw.Context context, Estimate estimate) {
|
||
return pw.Container(
|
||
padding: const EdgeInsets.all(20),
|
||
decoration: pw.BoxDecoration(color: PdfColors.white),
|
||
child: pw.Row(
|
||
children: [
|
||
pw.Expanded(
|
||
child: pw.Column(
|
||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||
children: [
|
||
pw.Text('発行日:${estimate.createdAt.toLocal()}'),
|
||
if (estimate.expiryDate != null) pw.Text('有効期限:${estimate.expiryDate!.toLocal()}'),
|
||
],
|
||
),
|
||
),
|
||
pw const Spacer(),
|
||
pw Container(
|
||
width: 120,
|
||
height: 80,
|
||
decoration: pw.BoxDecoration(
|
||
color: PdfColors.grey.shade200,
|
||
child: pw.Center(child: pw.Text('QR コードエリア', style: pw.TextStyle(fontSize: 8))),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
} |