476 lines
14 KiB
Dart
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?,
|
|
);
|
|
}
|
|
}
|