h-1.flutter.0/lib/models/sales_entry_models.dart
2026-03-05 09:33:38 +09:00

476 lines
14 KiB
Dart

import 'package:meta/meta.dart';
import '../models/invoice_models.dart';
const _unset = Object();
enum SalesEntryStatus { draft, confirmed, settled }
enum SettlementMethod { cash, bankTransfer, card, accountsReceivable, other }
extension SettlementMethodX on SettlementMethod {
String get displayName {
switch (this) {
case SettlementMethod.cash:
return '現金';
case SettlementMethod.bankTransfer:
return '銀行振込';
case SettlementMethod.card:
return 'カード';
case SettlementMethod.accountsReceivable:
return '売掛';
case SettlementMethod.other:
return 'その他';
}
}
}
SettlementMethod? settlementMethodFromName(String? value) {
if (value == null) return null;
for (final method in SettlementMethod.values) {
if (method.name == value) {
return method;
}
}
return null;
}
@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<InvoiceItem> 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<String, dynamic> 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<String, dynamic> 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.settlementMethod,
this.settlementCardCompany,
this.settlementDueDate,
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 SettlementMethod? settlementMethod;
final String? settlementCardCompany;
final DateTime? settlementDueDate;
final DateTime createdAt;
final DateTime updatedAt;
final List<SalesLineItem> 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<SalesLineItem>? items,
Object? settlementMethod = _unset,
Object? settlementCardCompany = _unset,
Object? settlementDueDate = _unset,
}) {
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,
settlementMethod: settlementMethod == _unset ? this.settlementMethod : settlementMethod as SettlementMethod?,
settlementCardCompany: settlementCardCompany == _unset ? this.settlementCardCompany : settlementCardCompany as String?,
settlementDueDate: settlementDueDate == _unset ? this.settlementDueDate : settlementDueDate as DateTime?,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
items: items ?? this.items,
);
}
Map<String, dynamic> 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,
'settlement_method': settlementMethod?.name,
'settlement_card_company': settlementCardCompany,
'settlement_due_date': settlementDueDate?.toIso8601String(),
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
};
factory SalesEntry.fromMap(Map<String, dynamic> map, {List<SalesLineItem> 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?,
settlementMethod: settlementMethodFromName(map['settlement_method'] as String?),
settlementCardCompany: map['settlement_card_company'] as String?,
settlementDueDate: map['settlement_due_date'] != null && (map['settlement_due_date'] as String).isNotEmpty
? DateTime.parse(map['settlement_due_date'] as String)
: null,
createdAt: DateTime.parse(map['created_at'] as String),
updatedAt: DateTime.parse(map['updated_at'] as String),
items: items,
);
SalesEntry recalcTotals() {
final subtotal = items.fold<int>(0, (sum, item) => sum + item.lineTotal);
final tax = items.fold<double>(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<String, dynamic> 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<String, dynamic> 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<String, dynamic> toMap() => {
'receipt_id': receiptId,
'sales_entry_id': salesEntryId,
'allocated_amount': allocatedAmount,
};
factory SalesReceiptLink.fromMap(Map<String, dynamic> 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<String, dynamic> toMap() => {
'id': id,
'sales_entry_id': salesEntryId,
'invoice_id': invoiceId,
'imported_at': importedAt.toIso8601String(),
'invoice_hash_snapshot': invoiceHashSnapshot,
};
factory SalesEntrySource.fromMap(Map<String, dynamic> 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<String, Object?> 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?,
);
}
}