""" マスタ編集コンポーネント 再利用可能な顧客・商品・伝票マスタ編集機能 """ 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}") )