h-1.flutter.0/lib/models/invoice_models.dart

196 lines
6.1 KiB
Dart

import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:intl/intl.dart';
import 'customer_model.dart';
class InvoiceItem {
final String? id;
final String? productId; // 追加
String description;
int quantity;
int unitPrice;
InvoiceItem({
this.id,
this.productId, // 追加
required this.description,
required this.quantity,
required this.unitPrice,
});
int get subtotal => quantity * unitPrice;
Map<String, dynamic> toMap(String invoiceId) {
return {
'id': id ?? DateTime.now().microsecondsSinceEpoch.toString(),
'invoice_id': invoiceId,
'product_id': productId, // 追加
'description': description,
'quantity': quantity,
'unit_price': unitPrice,
};
}
factory InvoiceItem.fromMap(Map<String, dynamic> map) {
return InvoiceItem(
id: map['id'],
productId: map['product_id'], // 追加
description: map['description'],
quantity: map['quantity'],
unitPrice: map['unit_price'],
);
}
}
enum DocumentType {
estimation, // 見積
delivery, // 納品
invoice, // 請求
receipt, // 領収
}
class Invoice {
final String id;
final Customer customer;
final DateTime date;
final List<InvoiceItem> items;
final String? notes;
final String? filePath;
final double taxRate;
final DocumentType documentType; // 追加
final String? customerFormalNameSnapshot;
final String? odooId;
final bool isSynced;
final DateTime updatedAt;
final double? latitude; // 追加
final double? longitude; // 追加
final String terminalId; // 追加: 端末識別子
final bool isDraft; // 追加: 下書きフラグ
Invoice({
String? id,
required this.customer,
required this.date,
required this.items,
this.notes,
this.filePath,
this.taxRate = 0.10,
this.documentType = DocumentType.invoice, // デフォルト請求書
this.customerFormalNameSnapshot,
this.odooId,
this.isSynced = false,
DateTime? updatedAt,
this.latitude, // 追加
this.longitude, // 追加
String? terminalId, // 追加
this.isDraft = false, // 追加: デフォルトは通常
}) : id = id ?? DateTime.now().millisecondsSinceEpoch.toString(),
terminalId = terminalId ?? "T1", // デフォルト端末ID
updatedAt = updatedAt ?? DateTime.now();
/// 伝票内容から決定論的なハッシュを生成する (SHA256の一部)
String get contentHash {
final input = "$id|$terminalId|${date.toIso8601String()}|${customer.id}|$totalAmount|${items.map((e) => "${e.description}${e.quantity}${e.unitPrice}").join()}";
final bytes = utf8.encode(input);
return sha256.convert(bytes).toString().substring(0, 8).toUpperCase();
}
String get documentTypeName {
switch (documentType) {
case DocumentType.estimation: return "見積書";
case DocumentType.delivery: return "納品書";
case DocumentType.invoice: return "請求書";
case DocumentType.receipt: return "領収書";
}
}
String get invoiceNumberPrefix {
switch (documentType) {
case DocumentType.estimation: return "EST";
case DocumentType.delivery: return "DEL";
case DocumentType.invoice: return "INV";
case DocumentType.receipt: return "REC";
}
}
String get invoiceNumber => "$invoiceNumberPrefix-$terminalId-${DateFormat('yyyyMMdd').format(date)}-${id.substring(id.length > 4 ? id.length - 4 : 0)}";
// 表示用の宛名(スナップショットがあれば優先)
String get customerNameForDisplay => customerFormalNameSnapshot ?? customer.formalName;
int get subtotal => items.fold(0, (sum, item) => sum + item.subtotal);
int get tax => (subtotal * taxRate).floor();
int get totalAmount => subtotal + tax;
Map<String, dynamic> toMap() {
return {
'id': id,
'customer_id': customer.id,
'date': date.toIso8601String(),
'notes': notes,
'file_path': filePath,
'total_amount': totalAmount,
'tax_rate': taxRate,
'document_type': documentType.name, // 追加
'customer_formal_name': customerFormalNameSnapshot ?? customer.formalName,
'odoo_id': odooId,
'is_synced': isSynced ? 1 : 0,
'updated_at': updatedAt.toIso8601String(),
'latitude': latitude, // 追加
'longitude': longitude, // 追加
'terminal_id': terminalId, // 追加
'content_hash': contentHash, // 追加
'is_draft': isDraft ? 1 : 0, // 追加
};
}
Invoice copyWith({
String? id,
Customer? customer,
DateTime? date,
List<InvoiceItem>? items,
String? notes,
String? filePath,
double? taxRate,
DocumentType? documentType,
String? customerFormalNameSnapshot,
String? odooId,
bool? isSynced,
DateTime? updatedAt,
double? latitude,
double? longitude,
String? terminalId,
bool? isDraft,
}) {
return Invoice(
id: id ?? this.id,
customer: customer ?? this.customer,
date: date ?? this.date,
items: items ?? List.from(this.items),
notes: notes ?? this.notes,
filePath: filePath ?? this.filePath,
taxRate: taxRate ?? this.taxRate,
documentType: documentType ?? this.documentType,
customerFormalNameSnapshot: customerFormalNameSnapshot ?? this.customerFormalNameSnapshot,
odooId: odooId ?? this.odooId,
isSynced: isSynced ?? this.isSynced,
updatedAt: updatedAt ?? this.updatedAt,
latitude: latitude ?? this.latitude,
longitude: longitude ?? this.longitude,
terminalId: terminalId ?? this.terminalId,
isDraft: isDraft ?? this.isDraft,
);
}
String toCsv() {
final buffer = StringBuffer();
buffer.writeln('伝票種別,伝票番号,日付,取引先,合計金額,緯度,経度');
buffer.writeln('$documentTypeName,$invoiceNumber,${DateFormat('yyyy/MM/dd').format(date)},$customerNameForDisplay,$totalAmount,${latitude ?? ""},${longitude ?? ""}');
buffer.writeln('');
buffer.writeln('品名,数量,単価,小計');
for (var item in items) {
buffer.writeln('${item.description},${item.quantity},${item.unitPrice},${item.subtotal}');
}
return buffer.toString();
}
}