import 'package:meta/meta.dart'; import '../models/invoice_models.dart'; enum SalesEntryStatus { draft, confirmed, settled } @immutable class SalesInvoiceImportData { const SalesInvoiceImportData({ required this.invoiceId, required this.documentType, required this.issueDate, required this.taxRate, required this.totalAmount, required this.items, required this.isLocked, required this.chainStatus, required this.contentHash, this.customerId, this.customerFormalName, this.subject, }); final String invoiceId; final DocumentType documentType; final DateTime issueDate; final double taxRate; final int totalAmount; final List items; final bool isLocked; final String chainStatus; final String contentHash; final String? customerId; final String? customerFormalName; final String? subject; } extension SalesEntryStatusDisplay on SalesEntryStatus { String get displayName { switch (this) { case SalesEntryStatus.draft: return '下書き'; case SalesEntryStatus.confirmed: return '確定'; case SalesEntryStatus.settled: return '入金済み'; } } } class SalesReceiptAllocationInput { const SalesReceiptAllocationInput({required this.salesEntryId, required this.amount}); final String salesEntryId; final int amount; } @immutable class SalesLineItem { const SalesLineItem({ required this.id, required this.salesEntryId, required this.description, required this.quantity, required this.unitPrice, required this.lineTotal, this.productId, this.taxRate = 0, this.sourceInvoiceId, this.sourceInvoiceItemId, this.costAmount = 0, this.costIsProvisional = false, }); final String id; final String salesEntryId; final String description; final int quantity; final int unitPrice; final int lineTotal; final String? productId; final double taxRate; final String? sourceInvoiceId; final String? sourceInvoiceItemId; final int costAmount; final bool costIsProvisional; Map toMap() => { 'id': id, 'sales_entry_id': salesEntryId, 'product_id': productId, 'description': description, 'quantity': quantity, 'unit_price': unitPrice, 'tax_rate': taxRate, 'line_total': lineTotal, 'cost_amount': costAmount, 'cost_is_provisional': costIsProvisional ? 1 : 0, 'source_invoice_id': sourceInvoiceId, 'source_invoice_item_id': sourceInvoiceItemId, }; factory SalesLineItem.fromMap(Map map) => SalesLineItem( id: map['id'] as String, salesEntryId: map['sales_entry_id'] as String, productId: map['product_id'] as String?, description: map['description'] as String, quantity: map['quantity'] as int? ?? 0, unitPrice: map['unit_price'] as int? ?? 0, taxRate: (map['tax_rate'] as num?)?.toDouble() ?? 0, lineTotal: map['line_total'] as int? ?? 0, costAmount: map['cost_amount'] as int? ?? 0, costIsProvisional: (map['cost_is_provisional'] as int? ?? 0) == 1, sourceInvoiceId: map['source_invoice_id'] as String?, sourceInvoiceItemId: map['source_invoice_item_id'] as String?, ); SalesLineItem copyWith({ String? id, String? salesEntryId, String? description, int? quantity, int? unitPrice, int? lineTotal, String? productId, double? taxRate, String? sourceInvoiceId, String? sourceInvoiceItemId, int? costAmount, bool? costIsProvisional, }) { return SalesLineItem( id: id ?? this.id, salesEntryId: salesEntryId ?? this.salesEntryId, description: description ?? this.description, quantity: quantity ?? this.quantity, unitPrice: unitPrice ?? this.unitPrice, lineTotal: lineTotal ?? this.lineTotal, productId: productId ?? this.productId, taxRate: taxRate ?? this.taxRate, sourceInvoiceId: sourceInvoiceId ?? this.sourceInvoiceId, sourceInvoiceItemId: sourceInvoiceItemId ?? this.sourceInvoiceItemId, costAmount: costAmount ?? this.costAmount, costIsProvisional: costIsProvisional ?? this.costIsProvisional, ); } } @immutable class SalesEntry { const SalesEntry({ required this.id, required this.issueDate, required this.status, required this.createdAt, required this.updatedAt, this.customerId, this.customerNameSnapshot, this.subject, this.amountTaxExcl = 0, this.taxAmount = 0, this.amountTaxIncl = 0, this.notes, this.items = const [], }); final String id; final String? customerId; final String? customerNameSnapshot; final String? subject; final DateTime issueDate; final SalesEntryStatus status; final int amountTaxExcl; final int taxAmount; final int amountTaxIncl; final String? notes; final DateTime createdAt; final DateTime updatedAt; final List items; SalesEntry copyWith({ String? id, String? customerId, String? customerNameSnapshot, String? subject, DateTime? issueDate, SalesEntryStatus? status, int? amountTaxExcl, int? taxAmount, int? amountTaxIncl, String? notes, DateTime? createdAt, DateTime? updatedAt, List? items, }) { return SalesEntry( id: id ?? this.id, customerId: customerId ?? this.customerId, customerNameSnapshot: customerNameSnapshot ?? this.customerNameSnapshot, subject: subject ?? this.subject, issueDate: issueDate ?? this.issueDate, status: status ?? this.status, amountTaxExcl: amountTaxExcl ?? this.amountTaxExcl, taxAmount: taxAmount ?? this.taxAmount, amountTaxIncl: amountTaxIncl ?? this.amountTaxIncl, notes: notes ?? this.notes, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, items: items ?? this.items, ); } Map toMap() => { 'id': id, 'customer_id': customerId, 'customer_name_snapshot': customerNameSnapshot, 'subject': subject, 'issue_date': issueDate.toIso8601String(), 'status': status.name, 'amount_tax_excl': amountTaxExcl, 'tax_amount': taxAmount, 'amount_tax_incl': amountTaxIncl, 'notes': notes, 'created_at': createdAt.toIso8601String(), 'updated_at': updatedAt.toIso8601String(), }; factory SalesEntry.fromMap(Map map, {List items = const []}) => SalesEntry( id: map['id'] as String, customerId: map['customer_id'] as String?, customerNameSnapshot: map['customer_name_snapshot'] as String?, subject: map['subject'] as String?, issueDate: DateTime.parse(map['issue_date'] as String), status: SalesEntryStatus.values.firstWhere( (s) => s.name == map['status'], orElse: () => SalesEntryStatus.draft, ), amountTaxExcl: map['amount_tax_excl'] as int? ?? 0, taxAmount: map['tax_amount'] as int? ?? 0, amountTaxIncl: map['amount_tax_incl'] as int? ?? 0, notes: map['notes'] as String?, createdAt: DateTime.parse(map['created_at'] as String), updatedAt: DateTime.parse(map['updated_at'] as String), items: items, ); SalesEntry recalcTotals() { final subtotal = items.fold(0, (sum, item) => sum + item.lineTotal); final tax = items.fold(0, (sum, item) => sum + item.lineTotal * item.taxRate).round(); return copyWith( amountTaxExcl: subtotal, taxAmount: tax, amountTaxIncl: subtotal + tax, ); } } @immutable class SalesReceipt { const SalesReceipt({ required this.id, required this.paymentDate, required this.amount, required this.createdAt, required this.updatedAt, this.customerId, this.method, this.notes, }); final String id; final String? customerId; final DateTime paymentDate; final String? method; final int amount; final String? notes; final DateTime createdAt; final DateTime updatedAt; Map toMap() => { 'id': id, 'customer_id': customerId, 'payment_date': paymentDate.toIso8601String(), 'method': method, 'amount': amount, 'notes': notes, 'created_at': createdAt.toIso8601String(), 'updated_at': updatedAt.toIso8601String(), }; factory SalesReceipt.fromMap(Map map) => SalesReceipt( id: map['id'] as String, customerId: map['customer_id'] as String?, paymentDate: DateTime.parse(map['payment_date'] as String), method: map['method'] as String?, amount: map['amount'] as int? ?? 0, notes: map['notes'] as String?, createdAt: DateTime.parse(map['created_at'] as String), updatedAt: DateTime.parse(map['updated_at'] as String), ); SalesReceipt copyWith({ String? id, String? customerId, DateTime? paymentDate, String? method, int? amount, String? notes, DateTime? createdAt, DateTime? updatedAt, }) { return SalesReceipt( id: id ?? this.id, customerId: customerId ?? this.customerId, paymentDate: paymentDate ?? this.paymentDate, method: method ?? this.method, amount: amount ?? this.amount, notes: notes ?? this.notes, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, ); } } @immutable class SalesReceiptLink { const SalesReceiptLink({required this.receiptId, required this.salesEntryId, required this.allocatedAmount}); final String receiptId; final String salesEntryId; final int allocatedAmount; Map toMap() => { 'receipt_id': receiptId, 'sales_entry_id': salesEntryId, 'allocated_amount': allocatedAmount, }; factory SalesReceiptLink.fromMap(Map map) => SalesReceiptLink( receiptId: map['receipt_id'] as String, salesEntryId: map['sales_entry_id'] as String, allocatedAmount: map['allocated_amount'] as int? ?? 0, ); } @immutable class SalesEntrySource { const SalesEntrySource({ required this.id, required this.salesEntryId, required this.invoiceId, required this.importedAt, this.invoiceHashSnapshot, }); final String id; final String salesEntryId; final String invoiceId; final DateTime importedAt; final String? invoiceHashSnapshot; Map toMap() => { 'id': id, 'sales_entry_id': salesEntryId, 'invoice_id': invoiceId, 'imported_at': importedAt.toIso8601String(), 'invoice_hash_snapshot': invoiceHashSnapshot, }; factory SalesEntrySource.fromMap(Map map) => SalesEntrySource( id: map['id'] as String, salesEntryId: map['sales_entry_id'] as String, invoiceId: map['invoice_id'] as String, importedAt: DateTime.parse(map['imported_at'] as String), invoiceHashSnapshot: map['invoice_hash_snapshot'] as String?, ); } @immutable class SalesImportCandidate { const SalesImportCandidate({ required this.invoiceId, required this.invoiceNumber, required this.documentType, required this.invoiceDate, required this.customerName, required this.totalAmount, required this.isLocked, required this.chainStatus, required this.contentHash, this.subject, }); final String invoiceId; final String invoiceNumber; final DocumentType documentType; final DateTime invoiceDate; final String customerName; final int totalAmount; final bool isLocked; final String chainStatus; final String contentHash; final String? subject; String get documentTypeName => documentType.displayName; factory SalesImportCandidate.fromMap(Map row) { final docTypeName = row['document_type'] as String? ?? DocumentType.invoice.name; final documentType = DocumentType.values.firstWhere( (type) => type.name == docTypeName, orElse: () => DocumentType.invoice, ); return SalesImportCandidate( invoiceId: row['id'] as String, invoiceNumber: row['invoice_number'] as String, documentType: documentType, invoiceDate: DateTime.parse(row['date'] as String), customerName: row['customer_name'] as String? ?? '-', totalAmount: (row['total_amount'] as num?)?.toInt() ?? 0, isLocked: (row['is_locked'] as int?) == 1, chainStatus: row['chain_status'] as String? ?? 'pending', contentHash: row['content_hash'] as String? ?? '', subject: row['subject'] as String?, ); } }