import 'dart:convert'; import 'dart:io'; import 'package:path_provider/path_provider.dart'; import '../models/invoice_models.dart'; /// 請求書のオリジナルデータを管理するリポジトリ(簡易DB) /// PDFファイルとデータの整合性を保つための機能を提供します class InvoiceRepository { static const String _dbFileName = 'invoices_db.json'; /// データベースファイルのパスを取得 Future _getDbFile() async { final directory = await getApplicationDocumentsDirectory(); return File('${directory.path}/$_dbFileName'); } /// 全ての請求書データを読み込む Future> getAllInvoices() async { try { final file = await _getDbFile(); if (!await file.exists()) return []; final String content = await file.readAsString(); final List jsonList = json.decode(content); return jsonList.map((json) => Invoice.fromJson(json)).toList() ..sort((a, b) => b.date.compareTo(a.date)); // 新しい順にソート } catch (e) { print('DB Loading Error: $e'); return []; } } /// 請求書データを保存・更新する Future saveInvoice(Invoice invoice) async { final List all = await getAllInvoices(); // 同じ請求番号があれば差し替え、なければ追加 final index = all.indexWhere((i) => i.invoiceNumber == invoice.invoiceNumber); if (index != -1) { final oldInvoice = all[index]; final oldPath = oldInvoice.filePath; // 古いファイルが存在し、かつ新しいパスと異なる場合 if (oldPath != null && oldPath != invoice.filePath) { // 【重要】共有済みのファイルは、証跡として残すために自動削除から除外する if (!oldInvoice.isShared) { await _deletePhysicalFile(oldPath); } else { print('Skipping deletion of shared file: $oldPath'); } } all[index] = invoice; } else { all.add(invoice); } final file = await _getDbFile(); await file.writeAsString(json.encode(all.map((i) => i.toJson()).toList())); } /// 請求書データを削除する Future deleteInvoice(Invoice invoice) async { final List all = await getAllInvoices(); all.removeWhere((i) => i.invoiceNumber == invoice.invoiceNumber); // 物理ファイルも削除 if (invoice.filePath != null) { await _deletePhysicalFile(invoice.filePath!); } final file = await _getDbFile(); await file.writeAsString(json.encode(all.map((i) => i.toJson()).toList())); } /// 実際のPDFファイルをストレージから削除する Future _deletePhysicalFile(String path) async { try { final file = File(path); if (await file.exists()) { await file.delete(); print('Physical file deleted: $path'); } } catch (e) { print('File Deletion Error: $path, $e'); } } /// DBに登録されていない「浮いたPDFファイル」をスキャンして掃除する /// ※共有済みフラグが立っているDBエントリーのパスは、削除対象から除外されます。 Future cleanupOrphanedPdfs() async { final List all = await getAllInvoices(); // DBに登録されている全ての有効なパス(共有済みも含む)をセットにする final Set registeredPaths = all .where((i) => i.filePath != null) .map((i) => i.filePath!) .toSet(); final directory = await getExternalStorageDirectory(); if (directory == null) return 0; int deletedCount = 0; final List files = directory.listSync(); for (var entity in files) { if (entity is File && entity.path.endsWith('.pdf')) { // DBのどの請求データ(最新も共有済みも)にも紐付いていないファイルだけを削除 if (!registeredPaths.contains(entity.path)) { await entity.delete(); deletedCount++; } } } return deletedCount; } }