h-1.flutter.0/lib/services/sales_receipt_service.dart
2026-03-04 14:55:40 +09:00

135 lines
4.4 KiB
Dart

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<List<SalesReceipt>> fetchReceipts({DateTime? startDate, DateTime? endDate}) {
return _receiptRepository.fetchReceipts(startDate: startDate, endDate: endDate);
}
Future<Map<String, int>> fetchAllocatedTotals(Iterable<String> entryIds) {
return _receiptRepository.fetchAllocatedTotals(entryIds);
}
Future<List<SalesReceiptLink>> fetchLinks(String receiptId) {
return _receiptRepository.fetchLinks(receiptId);
}
Future<SalesReceipt?> findById(String id) {
return _receiptRepository.findById(id);
}
Future<void> deleteReceipt(String id) {
return _receiptRepository.deleteReceipt(id);
}
Future<SalesReceipt> createReceipt({
String? customerId,
required DateTime paymentDate,
required int amount,
String? method,
String? notes,
List<SalesReceiptAllocationInput> 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<SalesReceipt> updateReceipt({
required SalesReceipt receipt,
List<SalesReceiptAllocationInput> allocations = const [],
}) {
final updated = receipt.copyWith(updatedAt: DateTime.now());
return _saveReceipt(receipt: updated, allocations: allocations);
}
Future<SalesReceipt> _saveReceipt({
required SalesReceipt receipt,
required List<SalesReceiptAllocationInput> allocations,
}) async {
final entries = await _loadEntries(allocations.map((a) => a.salesEntryId));
final allocatedTotals = await _receiptRepository.fetchAllocatedTotals(entries.keys);
final links = <SalesReceiptLink>[];
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<int>(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<void> _updateEntryStatuses(Iterable<SalesEntry> entries, Map<String, int> 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<Map<String, SalesEntry>> _loadEntries(Iterable<String> entryIds) async {
final map = <String, SalesEntry>{};
for (final id in entryIds) {
final entry = await _entryRepository.findById(id);
if (entry != null) {
map[id] = entry;
}
}
return map;
}
}