340 lines
12 KiB
Python
340 lines
12 KiB
Python
"""
|
|
マスタ編集コンポーネント
|
|
再利用可能な顧客・商品・伝票マスタ編集機能
|
|
"""
|
|
|
|
import flet as ft
|
|
import sqlite3
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import List, Dict, Optional, Callable
|
|
|
|
class MasterEditor:
|
|
"""マスタ編集コンポーネントのベースクラス"""
|
|
|
|
def __init__(
|
|
self,
|
|
page: ft.Page,
|
|
title: str = "マスタ編集",
|
|
table_name: str = "",
|
|
fields: List[Dict] = None,
|
|
data: List[Dict] = None,
|
|
on_save: Optional[Callable] = None,
|
|
on_delete: Optional[Callable] = None
|
|
):
|
|
self.page = page
|
|
self.title_text = title
|
|
self.table_name = table_name
|
|
self.fields = fields or []
|
|
self.data = data or []
|
|
self.on_save = on_save
|
|
self.on_delete = on_delete
|
|
|
|
# UI部品
|
|
self.form_fields = ft.Column([], spacing=10)
|
|
self.data_list = ft.Column([], scroll=ft.ScrollMode.AUTO, height=200)
|
|
|
|
# ボタン群
|
|
self.save_btn = ft.Button("保存", on_click=self.save_data, bgcolor=ft.Colors.GREEN, color=ft.Colors.WHITE)
|
|
self.add_btn = ft.Button("追加", on_click=self.add_new, bgcolor=ft.Colors.BLUE, color=ft.Colors.WHITE)
|
|
self.delete_btn = ft.Button("削除", on_click=self.delete_selected, bgcolor=ft.Colors.RED, color=ft.Colors.WHITE)
|
|
|
|
self._build_form()
|
|
self._load_data()
|
|
|
|
def _build_form(self):
|
|
"""入力フォームを構築"""
|
|
self.form_fields.controls.clear()
|
|
|
|
for field in self.fields:
|
|
field_control = ft.TextField(
|
|
label=field['label'],
|
|
value=field.get('value', ''),
|
|
width=field.get('width', 200),
|
|
keyboard_type=field.get('keyboard_type', ft.KeyboardType.TEXT)
|
|
)
|
|
self.form_fields.controls.append(field_control)
|
|
|
|
self.page.update()
|
|
|
|
def _load_data(self):
|
|
"""データを読み込む"""
|
|
try:
|
|
conn = sqlite3.connect('sales.db')
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute(f'''
|
|
SELECT * FROM {self.table_name}
|
|
ORDER BY id
|
|
''')
|
|
|
|
self.data = cursor.fetchall()
|
|
conn.close()
|
|
|
|
# データリストを更新
|
|
self.data_list.controls.clear()
|
|
for item in self.data:
|
|
item_id = item[0]
|
|
item_data = dict(zip([col[0] for col in cursor.description], item))
|
|
|
|
# データ行
|
|
row_controls = []
|
|
for field in self.fields:
|
|
field_name = field['name']
|
|
value = item_data.get(field_name, '')
|
|
|
|
row_controls.append(
|
|
ft.Text(f"{field['label']}: {value}", size=12)
|
|
)
|
|
|
|
# 操作ボタン
|
|
row_controls.extend([
|
|
ft.Button(
|
|
"編集",
|
|
on_click=lambda _, did=item_id: self.edit_item(item_id),
|
|
bgcolor=ft.Colors.ORANGE,
|
|
color=ft.Colors.WHITE,
|
|
width=60
|
|
),
|
|
ft.Button(
|
|
"削除",
|
|
on_click=lambda _, did=item_id: self.delete_item(item_id),
|
|
bgcolor=ft.Colors.RED,
|
|
color=ft.Colors.WHITE,
|
|
width=60
|
|
)
|
|
])
|
|
|
|
# データカード
|
|
data_card = ft.Card(
|
|
content=ft.Container(
|
|
content=ft.Column(row_controls),
|
|
padding=10
|
|
),
|
|
margin=ft.margin.only(bottom=5)
|
|
)
|
|
|
|
self.data_list.controls.append(data_card)
|
|
|
|
self.page.update()
|
|
|
|
except Exception as e:
|
|
logging.error(f"{self.table_name}データ読込エラー: {e}")
|
|
|
|
def save_data(self, e):
|
|
"""データを保存"""
|
|
try:
|
|
# フォームデータを収集
|
|
form_data = {}
|
|
for i, field in enumerate(self.fields):
|
|
field_control = self.form_fields.controls[i]
|
|
form_data[field['name']] = field_control.value
|
|
|
|
# データベースに保存
|
|
conn = sqlite3.connect('sales.db')
|
|
cursor = conn.cursor()
|
|
|
|
if self.data and len(self.data) > 0:
|
|
# 更新
|
|
cursor.execute(f'''
|
|
UPDATE {self.table_name}
|
|
SET {', '.join([f"{key} = ?" for key in form_data.keys()])}
|
|
WHERE id = ?
|
|
''', tuple(form_data.values()) + (self.data[0][0],))
|
|
else:
|
|
# 新規追加
|
|
columns = ', '.join([field['name'] for field in self.fields])
|
|
placeholders = ', '.join(['?' for _ in self.fields])
|
|
|
|
cursor.execute(f'''
|
|
INSERT INTO {self.table_name} ({columns})
|
|
VALUES ({placeholders})
|
|
''', tuple(form_data.values()))
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
# コールバック実行
|
|
if self.on_save:
|
|
self.on_save(form_data)
|
|
|
|
# データ再読み込み
|
|
self._load_data()
|
|
|
|
logging.info(f"{self.table_name}データ保存完了")
|
|
|
|
except Exception as e:
|
|
logging.error(f"{self.table_name}データ保存エラー: {e}")
|
|
|
|
def add_new(self, e):
|
|
"""新規データを追加"""
|
|
# 空のデータを作成
|
|
empty_data = {field['name']: '' for field in self.fields}
|
|
|
|
# データベースに保存
|
|
conn = sqlite3.connect('sales.db')
|
|
cursor = conn.cursor()
|
|
|
|
columns = ', '.join([field['name'] for field in self.fields])
|
|
placeholders = ', '.join(['?' for _ in self.fields])
|
|
|
|
cursor.execute(f'''
|
|
INSERT INTO {self.table_name} ({columns})
|
|
VALUES ({placeholders})
|
|
''', tuple(empty_data.values()))
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
# データ再読み込み
|
|
self._load_data()
|
|
|
|
# コールバック実行
|
|
if self.on_save:
|
|
self.on_save(empty_data)
|
|
|
|
logging.info(f"{self.table_name}新規追加完了")
|
|
|
|
def edit_item(self, item_id):
|
|
"""データを編集"""
|
|
try:
|
|
conn = sqlite3.connect('sales.db')
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute(f'''
|
|
SELECT * FROM {self.table_name}
|
|
WHERE id = ?
|
|
''', (item_id,))
|
|
|
|
result = cursor.fetchone()
|
|
conn.close()
|
|
|
|
if result:
|
|
# フォームにデータを設定
|
|
for i, field in enumerate(self.fields):
|
|
field_control = self.form_fields.controls[i]
|
|
field_control.value = result[i+1] if i+1 < len(result) else ''
|
|
|
|
self.page.update()
|
|
logging.info(f"{self.table_name}編集モード: ID={item_id}")
|
|
|
|
except Exception as e:
|
|
logging.error(f"{self.table_name}編集エラー: {e}")
|
|
|
|
def delete_item(self, item_id):
|
|
"""データを削除"""
|
|
try:
|
|
conn = sqlite3.connect('sales.db')
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute(f'''
|
|
DELETE FROM {self.table_name}
|
|
WHERE id = ?
|
|
''', (item_id,))
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
# データ再読み込み
|
|
self._load_data()
|
|
|
|
# コールバック実行
|
|
if self.on_delete:
|
|
self.on_delete(item_id)
|
|
|
|
logging.info(f"{self.table_name}削除完了: ID={item_id}")
|
|
|
|
except Exception as e:
|
|
logging.error(f"{self.table_name}削除エラー: {e}")
|
|
|
|
def build(self):
|
|
"""UIを構築して返す"""
|
|
return ft.Column([
|
|
ft.Text(self.title_text, size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900),
|
|
ft.Divider(),
|
|
ft.Text("入力フォーム", size=18, weight=ft.FontWeight.BOLD),
|
|
self.form_fields,
|
|
ft.Row([self.add_btn, self.save_btn, self.delete_btn], spacing=10),
|
|
ft.Divider(),
|
|
ft.Text(f"{self.table_name}一覧", size=18, weight=ft.FontWeight.BOLD),
|
|
self.data_list
|
|
], expand=True, spacing=15)
|
|
|
|
class CustomerMasterEditor(MasterEditor):
|
|
"""顧客マスタ編集"""
|
|
|
|
def __init__(self, page: ft.Page, on_save: Optional[Callable] = None, on_delete: Optional[Callable] = None):
|
|
super().__init__(
|
|
page=page,
|
|
title="顧客マスタ",
|
|
table_name="customers",
|
|
fields=[
|
|
{'name': 'name', 'label': '顧客名', 'width': 200},
|
|
{'name': 'phone', 'label': '電話番号', 'width': 200},
|
|
{'name': 'email', 'label': 'メールアドレス', 'width': 250},
|
|
{'name': 'address', 'label': '住所', 'width': 300}
|
|
],
|
|
on_save=on_save,
|
|
on_delete=on_delete
|
|
)
|
|
|
|
class ProductMasterEditor(MasterEditor):
|
|
"""商品マスタ編集"""
|
|
|
|
def __init__(self, page: ft.Page, on_save: Optional[Callable] = None, on_delete: Optional[Callable] = None):
|
|
super().__init__(
|
|
page=page,
|
|
title="商品マスタ",
|
|
table_name="products",
|
|
fields=[
|
|
{'name': 'name', 'label': '商品名', 'width': 200},
|
|
{'name': 'category', 'label': 'カテゴリ', 'width': 150},
|
|
{'name': 'price', 'label': '価格', 'width': 100, 'keyboard_type': ft.KeyboardType.NUMBER},
|
|
{'name': 'stock', 'label': '在庫数', 'width': 100, 'keyboard_type': ft.KeyboardType.NUMBER}
|
|
],
|
|
on_save=on_save,
|
|
on_delete=on_delete
|
|
)
|
|
|
|
class SalesSlipMasterEditor(MasterEditor):
|
|
"""伝票マスタ編集"""
|
|
|
|
def __init__(self, page: ft.Page, on_save: Optional[Callable] = None, on_delete: Optional[Callable] = None):
|
|
super().__init__(
|
|
page=page,
|
|
title="伝票マスタ",
|
|
table_name="sales_slips",
|
|
fields=[
|
|
{'name': 'title', 'label': '伝票タイトル', 'width': 300},
|
|
{'name': 'customer_name', 'label': '顧客名', 'width': 200},
|
|
{'name': 'items', 'label': '明細', 'width': 400, 'keyboard_type': ft.KeyboardType.MULTILINE},
|
|
{'name': 'total_amount', 'label': '合計金額', 'width': 150, 'keyboard_type': ft.KeyboardType.NUMBER}
|
|
],
|
|
on_save=on_save,
|
|
on_delete=on_delete
|
|
)
|
|
|
|
# 使用例
|
|
def create_customer_master(page: ft.Page) -> CustomerMasterEditor:
|
|
"""顧客マスタ編集画面を作成"""
|
|
return CustomerMasterEditor(
|
|
page=page,
|
|
on_save=lambda data: print(f"顧客保存: {data['name']}"),
|
|
on_delete=lambda item_id: print(f"顧客削除: {item_id}")
|
|
)
|
|
|
|
def create_product_master(page: ft.Page) -> ProductMasterEditor:
|
|
"""商品マスタ編集画面を作成"""
|
|
return ProductMasterEditor(
|
|
page=page,
|
|
on_save=lambda data: print(f"商品保存: {data['name']}"),
|
|
on_delete=lambda item_id: print(f"商品削除: {item_id}")
|
|
)
|
|
|
|
def create_sales_slip_master(page: ft.Page) -> SalesSlipMasterEditor:
|
|
"""伝票マスタ編集画面を作成"""
|
|
return SalesSlipMasterEditor(
|
|
page=page,
|
|
on_save=lambda data: print(f"伝票保存: {data['title']}"),
|
|
on_delete=lambda item_id: print(f"伝票削除: {item_id}")
|
|
)
|