// 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 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; } }