99 lines
No EOL
3.7 KiB
Dart
99 lines
No EOL
3.7 KiB
Dart
// lib/services/pdf_generator.dart
|
|
// Version: 1.0.1
|
|
// Updated: 2026-02-08
|
|
// Description: 日本語対応PDF生成および命名規則に基づいたファイル保存
|
|
|
|
import 'dart:io';
|
|
import 'dart:convert';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:pdf/pdf.dart';
|
|
import 'package:pdf/widgets.dart' as pw;
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:crypto/crypto.dart';
|
|
import '../models/invoice_models.dart';
|
|
|
|
class PdfGenerator {
|
|
/// 請求書PDFを生成し、一意のファイル名で保存する
|
|
static Future<File> generateInvoicePdf(Invoice invoice) async {
|
|
final pdf = pw.Document();
|
|
|
|
// 1. フォントの読み込み
|
|
final fontData = await rootBundle.load("assets/fonts/ipaexg.ttf");
|
|
final ttf = pw.Font.ttf(fontData);
|
|
|
|
// 2. PDFコンテンツの構築
|
|
pdf.addPage(
|
|
pw.MultiPage(
|
|
theme: pw.ThemeData.withFont(base: ttf),
|
|
build: (pw.Context context) {
|
|
return [
|
|
pw.Header(
|
|
level: 0,
|
|
child: pw.Text("請求書", style: pw.TextStyle(fontSize: 24)),
|
|
),
|
|
pw.SizedBox(height: 20),
|
|
pw.Text("請求先: ${invoice.customer.formalName} 御中"),
|
|
pw.Text("請求番号: ${invoice.invoiceNumber}"),
|
|
pw.Text("日付: ${DateFormat('yyyy/MM/dd').format(invoice.date)}"),
|
|
pw.Divider(),
|
|
pw.Table.fromTextArray(
|
|
headers: ['内容', '数量', '単価', '金額'],
|
|
data: invoice.items.map((item) {
|
|
return [
|
|
item.description,
|
|
item.quantity.toString(),
|
|
"¥${item.unitPrice}",
|
|
"¥${item.subtotal}",
|
|
];
|
|
}).toList(),
|
|
),
|
|
pw.SizedBox(height: 20),
|
|
pw.Container(
|
|
alignment: pw.Alignment.centerRight,
|
|
child: pw.Column(
|
|
crossAxisAlignment: pw.CrossAxisAlignment.end,
|
|
children: [
|
|
pw.Text("小計: ¥${invoice.subtotal}"),
|
|
pw.Text("消費税: ¥${invoice.tax}"),
|
|
pw.Text("合計金額: ¥${invoice.totalAmount}",
|
|
style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
|
|
],
|
|
),
|
|
),
|
|
if (invoice.notes != null) ...[
|
|
pw.SizedBox(height: 20),
|
|
pw.Text("備考:"),
|
|
pw.Text(invoice.notes!),
|
|
],
|
|
];
|
|
},
|
|
),
|
|
);
|
|
|
|
// 3. 命名規則に基づくファイル名の生成
|
|
// 規則: [日付]_[依頼種別]_[顧客名]_[タイトル]_[金額]_[SHA256ハッシュ].pdf
|
|
final dateStr = DateFormat('yyyyMMdd').format(invoice.date);
|
|
|
|
// モデルにない項目は安全な代替値を使用
|
|
const typeStr = "請求書"; // 固定またはInvoiceにプロパティ追加検討
|
|
final customerStr = invoice.customer.formalName.replaceAll(RegExp(r'[\\/:*?"<>|]'), '');
|
|
final titleStr = (invoice.notes != null && invoice.notes!.length > 10)
|
|
? invoice.notes!.substring(0, 10)
|
|
: (invoice.items.isNotEmpty ? invoice.items.first.description : "名称未設定");
|
|
final amountStr = invoice.totalAmount.toString();
|
|
|
|
// SHA256ハッシュの生成
|
|
final rawData = "$dateStr$customerStr$amountStr${invoice.invoiceNumber}";
|
|
final hash = sha256.convert(utf8.encode(rawData)).toString().substring(0, 8);
|
|
|
|
final fileName = "${dateStr}_${typeStr}_${customerStr}_${titleStr}_${amountStr}_$hash.pdf";
|
|
|
|
// 4. 保存
|
|
final directory = await getApplicationDocumentsDirectory();
|
|
final file = File("${directory.path}/$fileName");
|
|
await file.writeAsBytes(await pdf.save());
|
|
|
|
return file;
|
|
}
|
|
} |