import 'package:uuid/uuid.dart'; import '../models/sales_entry_models.dart'; import 'sales_entry_repository.dart'; import 'sales_receipt_repository.dart'; class SalesReceiptService { SalesReceiptService({ SalesReceiptRepository? receiptRepository, SalesEntryRepository? entryRepository, }) : _receiptRepository = receiptRepository ?? SalesReceiptRepository(), _entryRepository = entryRepository ?? SalesEntryRepository(); final SalesReceiptRepository _receiptRepository; final SalesEntryRepository _entryRepository; final Uuid _uuid = const Uuid(); Future> fetchReceipts({DateTime? startDate, DateTime? endDate}) { return _receiptRepository.fetchReceipts(startDate: startDate, endDate: endDate); } Future> fetchAllocatedTotals(Iterable entryIds) { return _receiptRepository.fetchAllocatedTotals(entryIds); } Future> fetchLinks(String receiptId) { return _receiptRepository.fetchLinks(receiptId); } Future findById(String id) { return _receiptRepository.findById(id); } Future deleteReceipt(String id) { return _receiptRepository.deleteReceipt(id); } Future createReceipt({ String? customerId, required DateTime paymentDate, required int amount, String? method, String? notes, List allocations = const [], }) async { if (amount <= 0) { throw ArgumentError('amount must be greater than 0'); } final receipt = SalesReceipt( id: _uuid.v4(), customerId: customerId, paymentDate: paymentDate, method: method, amount: amount, notes: notes, createdAt: DateTime.now(), updatedAt: DateTime.now(), ); return _saveReceipt(receipt: receipt, allocations: allocations); } Future updateReceipt({ required SalesReceipt receipt, List allocations = const [], }) { final updated = receipt.copyWith(updatedAt: DateTime.now()); return _saveReceipt(receipt: updated, allocations: allocations); } Future _saveReceipt({ required SalesReceipt receipt, required List allocations, }) async { final entries = await _loadEntries(allocations.map((a) => a.salesEntryId)); final allocatedTotals = await _receiptRepository.fetchAllocatedTotals(entries.keys); final links = []; for (final allocation in allocations) { final entry = entries[allocation.salesEntryId]; if (entry == null) { throw StateError('売上伝票が見つかりません: ${allocation.salesEntryId}'); } final currentAllocated = allocatedTotals[entry.id] ?? 0; final outstanding = entry.amountTaxIncl - currentAllocated; if (allocation.amount > outstanding) { throw StateError('割当額が未収残を超えています: ${entry.id}'); } links.add( SalesReceiptLink( receiptId: receipt.id, salesEntryId: entry.id, allocatedAmount: allocation.amount, ), ); allocatedTotals[entry.id] = currentAllocated + allocation.amount; } final totalAllocated = links.fold(0, (sum, link) => sum + link.allocatedAmount); if (totalAllocated > receipt.amount) { throw StateError('割当総額が入金額を超えています'); } await _receiptRepository.upsertReceipt(receipt, links); await _updateEntryStatuses(entries.values, allocatedTotals); return receipt; } Future _updateEntryStatuses(Iterable entries, Map allocatedTotals) async { for (final entry in entries) { final allocated = allocatedTotals[entry.id] ?? 0; SalesEntryStatus newStatus; if (allocated >= entry.amountTaxIncl) { newStatus = SalesEntryStatus.settled; } else if (allocated > 0) { newStatus = SalesEntryStatus.confirmed; } else { newStatus = entry.status; } if (newStatus != entry.status) { await _entryRepository.upsertEntry(entry.copyWith(status: newStatus, updatedAt: DateTime.now())); } } } Future> _loadEntries(Iterable entryIds) async { final map = {}; for (final id in entryIds) { final entry = await _entryRepository.findById(id); if (entry != null) { map[id] = entry; } } return map; } }