135 lines
4.4 KiB
Dart
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;
|
|
}
|
|
}
|