267 lines
9.2 KiB
Python
267 lines
9.2 KiB
Python
"""
|
|
伝票データモデル
|
|
Flutter参考プロジェクトの構造をFletに適用
|
|
"""
|
|
|
|
from enum import Enum
|
|
from datetime import datetime
|
|
from typing import List, Optional, Dict, Any
|
|
import json
|
|
|
|
class DocumentType(Enum):
|
|
"""帳票の種類を定義"""
|
|
ESTIMATE = "見積書"
|
|
DELIVERY = "納品書"
|
|
INVOICE = "請求書"
|
|
RECEIPT = "領収書"
|
|
SALES = "売上伝票"
|
|
|
|
class InvoiceItem:
|
|
"""伝票の各明細行を表すモデル"""
|
|
|
|
def __init__(self, description: str, quantity: int, unit_price: int, is_discount: bool = False):
|
|
self.description = description
|
|
self.quantity = quantity
|
|
self.unit_price = unit_price
|
|
self.is_discount = is_discount # 値引き項目かどうかを示すフラグ
|
|
|
|
@property
|
|
def subtotal(self) -> int:
|
|
"""小計 (数量 * 単価)"""
|
|
return self.quantity * self.unit_price * (-1 if self.is_discount else 1)
|
|
|
|
def copy_with(self, **kwargs) -> 'InvoiceItem':
|
|
"""編集用のコピーメソッド"""
|
|
return InvoiceItem(
|
|
description=kwargs.get('description', self.description),
|
|
quantity=kwargs.get('quantity', self.quantity),
|
|
unit_price=kwargs.get('unit_price', self.unit_price),
|
|
is_discount=kwargs.get('is_discount', self.is_discount)
|
|
)
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""JSON変換"""
|
|
return {
|
|
'description': self.description,
|
|
'quantity': self.quantity,
|
|
'unit_price': self.unit_price,
|
|
'is_discount': self.is_discount
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any]) -> 'InvoiceItem':
|
|
"""JSONから復元"""
|
|
return cls(
|
|
description=data['description'],
|
|
quantity=data['quantity'],
|
|
unit_price=data['unit_price'],
|
|
is_discount=data.get('is_discount', False)
|
|
)
|
|
|
|
class Customer:
|
|
"""顧客情報モデル"""
|
|
|
|
def __init__(self, id: int, name: str, formal_name: str, address: str = "", phone: str = ""):
|
|
self.id = id
|
|
self.name = name
|
|
self.formal_name = formal_name
|
|
self.address = address
|
|
self.phone = phone
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""JSON変換"""
|
|
return {
|
|
'id': self.id,
|
|
'name': self.name,
|
|
'formal_name': self.formal_name,
|
|
'address': self.address,
|
|
'phone': self.phone
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any]) -> 'Customer':
|
|
"""JSONから復元"""
|
|
return cls(
|
|
id=data['id'],
|
|
name=data['name'],
|
|
formal_name=data['formal_name'],
|
|
address=data.get('address', ''),
|
|
phone=data.get('phone', '')
|
|
)
|
|
|
|
class Invoice:
|
|
"""帳票全体を管理するモデル (見積・納品・請求・領収に対応)"""
|
|
|
|
def __init__(self,
|
|
customer: Customer,
|
|
date: datetime,
|
|
items: List[InvoiceItem],
|
|
invoice_number: Optional[str] = None,
|
|
notes: Optional[str] = None,
|
|
is_shared: bool = False,
|
|
document_type: DocumentType = DocumentType.INVOICE,
|
|
file_path: Optional[str] = None,
|
|
uuid: Optional[str] = None):
|
|
import uuid as uuid_module
|
|
self.uuid = uuid or str(uuid_module.uuid4())
|
|
self.customer = customer
|
|
self.date = date
|
|
self.items = items
|
|
self.invoice_number = invoice_number or self._generate_invoice_number()
|
|
self.notes = notes
|
|
self.is_shared = is_shared
|
|
self.document_type = document_type
|
|
self.file_path = file_path
|
|
|
|
def _generate_invoice_number(self) -> str:
|
|
"""請求書番号を生成"""
|
|
return self.date.strftime('%Y%m%d-%H%M')
|
|
|
|
@property
|
|
def client_name(self) -> str:
|
|
"""互換性のためのゲッター"""
|
|
return self.customer.formal_name
|
|
|
|
@property
|
|
def subtotal(self) -> int:
|
|
"""税抜合計金額"""
|
|
return sum(item.subtotal for item in self.items)
|
|
|
|
@property
|
|
def tax(self) -> int:
|
|
"""消費税 (10%固定として計算、端数切り捨て)"""
|
|
return int(self.subtotal * 0.1)
|
|
|
|
@property
|
|
def total_amount(self) -> int:
|
|
"""税込合計金額"""
|
|
return self.subtotal + self.tax
|
|
|
|
def copy_with(self, **kwargs) -> 'Invoice':
|
|
"""状態更新のためのコピーメソッド"""
|
|
return Invoice(
|
|
customer=kwargs.get('customer', self.customer),
|
|
date=kwargs.get('date', self.date),
|
|
items=kwargs.get('items', self.items),
|
|
invoice_number=kwargs.get('invoice_number', self.invoice_number),
|
|
notes=kwargs.get('notes', self.notes),
|
|
is_shared=kwargs.get('is_shared', self.is_shared),
|
|
document_type=kwargs.get('document_type', self.document_type),
|
|
file_path=kwargs.get('file_path', self.file_path)
|
|
)
|
|
|
|
def to_csv(self) -> str:
|
|
"""CSV形式への変換"""
|
|
lines = [
|
|
f"Type,{self.document_type.value}",
|
|
f"Customer,{self.customer.formal_name}",
|
|
f"Number,{self.invoice_number}",
|
|
f"Date,{self.date.strftime('%Y/%m/%d')}",
|
|
f"Shared,{'Yes' if self.is_shared else 'No'}",
|
|
"",
|
|
"Description,Quantity,UnitPrice,Subtotal,IsDiscount"
|
|
]
|
|
|
|
for item in self.items:
|
|
lines.append(f"{item.description},{item.quantity},{item.unit_price},{item.subtotal},{'Yes' if item.is_discount else 'No'}")
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""JSON変換 (データベース保存用)"""
|
|
return {
|
|
'uuid': self.uuid,
|
|
'customer': self.customer.to_dict(),
|
|
'date': self.date.isoformat(),
|
|
'items': [item.to_dict() for item in self.items],
|
|
'file_path': self.file_path,
|
|
'invoice_number': self.invoice_number,
|
|
'notes': self.notes,
|
|
'is_shared': self.is_shared,
|
|
'document_type': self.document_type.value
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any]) -> 'Invoice':
|
|
"""JSONから復元 (データベース読み込み用)"""
|
|
customer = Customer.from_dict(data['customer'])
|
|
date = datetime.fromisoformat(data['date'])
|
|
items = [InvoiceItem.from_dict(item_data) for item_data in data['items']]
|
|
|
|
# DocumentTypeの文字列からEnumに変換
|
|
doc_type_str = data.get('document_type', '請求書')
|
|
doc_type = next((dt for dt in DocumentType if dt.value == doc_type_str), DocumentType.INVOICE)
|
|
|
|
return cls(
|
|
customer=customer,
|
|
date=date,
|
|
items=items,
|
|
invoice_number=data['invoice_number'],
|
|
notes=data.get('notes'),
|
|
is_shared=data.get('is_shared', False),
|
|
document_type=doc_type,
|
|
file_path=data.get('file_path'),
|
|
uuid=data.get('uuid')
|
|
)
|
|
|
|
# サンプルデータ生成関数
|
|
def create_sample_invoices() -> List[Invoice]:
|
|
"""サンプル伝票データを生成"""
|
|
customers = [
|
|
Customer(1, "田中商事", "田中商事株式会社", "東京都千代田区丸の内1-1-1", "03-1234-5678"),
|
|
Customer(2, "鈴木商店", "鈴木商店", "東京都港区芝1-1-1", "03-2345-6789"),
|
|
Customer(3, "佐藤工業", "佐藤工業株式会社", "東京都品川区東品川1-1-1", "03-3456-7890"),
|
|
Customer(4, "高橋建設", "高橋建設株式会社", "東京都新宿区西新宿1-1-1", "03-4567-8901"),
|
|
Customer(5, "伊藤電機", "伊藤電機株式会社", "東京都渋谷区渋谷1-1-1", "03-5678-9012")
|
|
]
|
|
|
|
sample_invoices = [
|
|
Invoice(
|
|
customer=customers[0],
|
|
date=datetime(2024, 1, 15),
|
|
items=[
|
|
InvoiceItem("A商品セット", 1, 150000),
|
|
InvoiceItem("設置費用", 1, 25000)
|
|
],
|
|
document_type=DocumentType.SALES,
|
|
notes="A商品セット販売"
|
|
),
|
|
Invoice(
|
|
customer=customers[1],
|
|
date=datetime(2024, 1, 14),
|
|
items=[
|
|
InvoiceItem("B部品", 10, 8500)
|
|
],
|
|
document_type=DocumentType.ESTIMATE,
|
|
notes="B部品見積"
|
|
),
|
|
Invoice(
|
|
customer=customers[2],
|
|
date=datetime(2024, 1, 13),
|
|
items=[
|
|
InvoiceItem("C機器", 1, 120000)
|
|
],
|
|
document_type=DocumentType.DELIVERY,
|
|
notes="C機器納品"
|
|
),
|
|
Invoice(
|
|
customer=customers[3],
|
|
date=datetime(2024, 1, 12),
|
|
items=[
|
|
InvoiceItem("D工事費用", 1, 200000)
|
|
],
|
|
document_type=DocumentType.INVOICE,
|
|
notes="D工事請求"
|
|
),
|
|
Invoice(
|
|
customer=customers[4],
|
|
date=datetime(2024, 1, 11),
|
|
items=[
|
|
InvoiceItem("E製品", 1, 75000)
|
|
],
|
|
document_type=DocumentType.RECEIPT,
|
|
notes="E製品領収"
|
|
)
|
|
]
|
|
|
|
return sample_invoices
|