inv/gemi_invoice/lib/services/invoice_repository.dart
2026-02-01 12:12:35 +09:00

117 lines
4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<File> _getDbFile() async {
final directory = await getApplicationDocumentsDirectory();
return File('${directory.path}/$_dbFileName');
}
/// 全ての請求書データを読み込む
Future<List<Invoice>> getAllInvoices() async {
try {
final file = await _getDbFile();
if (!await file.exists()) return [];
final String content = await file.readAsString();
final List<dynamic> 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<void> saveInvoice(Invoice invoice) async {
final List<Invoice> 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<void> deleteInvoice(Invoice invoice) async {
final List<Invoice> 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<void> _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<int> cleanupOrphanedPdfs() async {
final List<Invoice> all = await getAllInvoices();
// DBに登録されている全ての有効なパス共有済みも含むをセットにする
final Set<String> 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<FileSystemEntity> 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;
}
}