180 lines
5.2 KiB
Dart
180 lines
5.2 KiB
Dart
// lib/models/invoice_models.dart
|
|
import 'package:intl/intl.dart';
|
|
import 'customer_model.dart';
|
|
|
|
/// 帳票の種類を定義
|
|
enum DocumentType {
|
|
estimate('見積書'),
|
|
delivery('納品書'),
|
|
invoice('請求書'),
|
|
receipt('領収書');
|
|
|
|
final String label;
|
|
const DocumentType(this.label);
|
|
}
|
|
|
|
/// 請求書の各明細行を表すモデル
|
|
class InvoiceItem {
|
|
String description;
|
|
int quantity;
|
|
int unitPrice;
|
|
bool isDiscount; // 値引き項目かどうかを示すフラグ
|
|
|
|
InvoiceItem({
|
|
required this.description,
|
|
required this.quantity,
|
|
required this.unitPrice,
|
|
this.isDiscount = false, // デフォルトはfalse (値引きではない)
|
|
});
|
|
|
|
// 小計 (数量 * 単価)
|
|
int get subtotal => quantity * unitPrice * (isDiscount ? -1 : 1);
|
|
|
|
// 編集用のコピーメソッド
|
|
InvoiceItem copyWith({
|
|
String? description,
|
|
int? quantity,
|
|
int? unitPrice,
|
|
bool? isDiscount,
|
|
}) {
|
|
return InvoiceItem(
|
|
description: description ?? this.description,
|
|
quantity: quantity ?? this.quantity,
|
|
unitPrice: unitPrice ?? this.unitPrice,
|
|
isDiscount: isDiscount ?? this.isDiscount,
|
|
);
|
|
}
|
|
|
|
// JSON変換
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
'description': description,
|
|
'quantity': quantity,
|
|
'unit_price': unitPrice,
|
|
'is_discount': isDiscount,
|
|
};
|
|
}
|
|
|
|
// JSONから復元
|
|
factory InvoiceItem.fromJson(Map<String, dynamic> json) {
|
|
return InvoiceItem(
|
|
description: json['description'] as String,
|
|
quantity: json['quantity'] as int,
|
|
unitPrice: json['unit_price'] as int,
|
|
isDiscount: json['is_discount'] ?? false,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 帳票全体を管理するモデル (見積・納品・請求・領収に対応)
|
|
class Invoice {
|
|
Customer customer; // 顧客情報
|
|
DateTime date;
|
|
List<InvoiceItem> items;
|
|
String? filePath; // 保存されたPDFのパス
|
|
String invoiceNumber; // 請求書番号
|
|
String? notes; // 備考
|
|
bool isShared; // 外部共有(送信)済みフラグ。送信済みファイルは自動削除から保護する。
|
|
DocumentType type; // 帳票の種類
|
|
|
|
Invoice({
|
|
required this.customer,
|
|
required this.date,
|
|
required this.items,
|
|
this.filePath,
|
|
String? invoiceNumber,
|
|
this.notes,
|
|
this.isShared = false,
|
|
this.type = DocumentType.invoice,
|
|
}) : invoiceNumber = invoiceNumber ?? DateFormat('yyyyMMdd-HHmm').format(date);
|
|
|
|
// 互換性のためのゲッター
|
|
String get clientName => customer.formalName;
|
|
|
|
// 税抜合計金額
|
|
int get subtotal {
|
|
return items.fold(0, (sum, item) => sum + item.subtotal);
|
|
}
|
|
|
|
// 消費税 (10%固定として計算、端数切り捨て)
|
|
int get tax {
|
|
return (subtotal * 0.1).floor();
|
|
}
|
|
|
|
// 税込合計金額
|
|
int get totalAmount {
|
|
return subtotal + tax;
|
|
}
|
|
|
|
// 状態更新のためのコピーメソッド
|
|
Invoice copyWith({
|
|
Customer? customer,
|
|
DateTime? date,
|
|
List<InvoiceItem>? items,
|
|
String? filePath,
|
|
String? invoiceNumber,
|
|
String? notes,
|
|
bool? isShared,
|
|
DocumentType? type,
|
|
}) {
|
|
return Invoice(
|
|
customer: customer ?? this.customer,
|
|
date: date ?? this.date,
|
|
items: items ?? this.items,
|
|
filePath: filePath ?? this.filePath,
|
|
invoiceNumber: invoiceNumber ?? this.invoiceNumber,
|
|
notes: notes ?? this.notes,
|
|
isShared: isShared ?? this.isShared,
|
|
type: type ?? this.type,
|
|
);
|
|
}
|
|
|
|
// CSV形式への変換
|
|
String toCsv() {
|
|
StringBuffer sb = StringBuffer();
|
|
sb.writeln("Type,${type.label}");
|
|
sb.writeln("Customer,${customer.formalName}");
|
|
sb.writeln("Number,$invoiceNumber");
|
|
sb.writeln("Date,${DateFormat('yyyy/MM/dd').format(date)}");
|
|
sb.writeln("Shared,${isShared ? 'Yes' : 'No'}");
|
|
sb.writeln("");
|
|
sb.writeln("Description,Quantity,UnitPrice,Subtotal,IsDiscount"); // isDiscountを追加
|
|
for (var item in items) {
|
|
sb.writeln("${item.description},${item.quantity},${item.unitPrice},${item.subtotal},${item.isDiscount ? 'Yes' : 'No'}");
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
// JSON変換 (データベース保存用)
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
'customer': customer.toJson(),
|
|
'date': date.toIso8601String(),
|
|
'items': items.map((item) => item.toJson()).toList(),
|
|
'file_path': filePath,
|
|
'invoice_number': invoiceNumber,
|
|
'notes': notes,
|
|
'is_shared': isShared,
|
|
'type': type.name, // Enumの名前で保存
|
|
};
|
|
}
|
|
|
|
// JSONから復元 (データベース読み込み用)
|
|
factory Invoice.fromJson(Map<String, dynamic> json) {
|
|
return Invoice(
|
|
customer: Customer.fromJson(json['customer'] as Map<String, dynamic>),
|
|
date: DateTime.parse(json['date'] as String),
|
|
items: (json['items'] as List)
|
|
.map((i) => InvoiceItem.fromJson(i as Map<String, dynamic>))
|
|
.toList(),
|
|
filePath: json['file_path'] as String?,
|
|
invoiceNumber: json['invoice_number'] as String,
|
|
notes: (json['notes'] == 'null') ? null : json['notes'] as String?, // 'null'文字列の可能性も考慮
|
|
isShared: json['is_shared'] ?? false,
|
|
type: DocumentType.values.firstWhere(
|
|
(e) => e.name == (json['type'] ?? 'invoice'),
|
|
orElse: () => DocumentType.invoice,
|
|
),
|
|
);
|
|
}
|
|
}
|