h-1.flet.3/app_simple_working.py

377 lines
14 KiB
Python

import flet as ft
import sqlite3
import signal
import sys
import logging
import random
from datetime import datetime
from typing import List, Dict, Optional
class ErrorHandler:
"""グローバルエラーハンドラ"""
@staticmethod
def handle_error(error: Exception, context: str = ""):
"""エラーを一元処理"""
error_msg = f"{context}: {str(error)}"
logging.error(error_msg)
print(f"{error_msg}")
try:
if hasattr(ErrorHandler, 'current_page'):
ErrorHandler.show_snackbar(ErrorHandler.current_page, error_msg, ft.Colors.RED)
except:
pass
@staticmethod
def show_snackbar(page, message: str, color: ft.Colors = ft.Colors.RED):
"""SnackBarを表示"""
try:
page.snack_bar = ft.SnackBar(
content=ft.Text(message),
bgcolor=color
)
page.snack_bar.open = True
page.update()
except:
pass
class DummyDataGenerator:
"""テスト用ダミーデータ生成"""
@staticmethod
def generate_customers(count: int = 100) -> List[Dict]:
"""ダミー顧客データ生成"""
first_names = ["田中", "佐藤", "鈴木", "高橋", "伊藤", "渡辺", "山本", "中村", "小林", "加藤"]
last_names = ["太郎", "次郎", "三郎", "花子", "美子", "健一", "恵子", "大輔", "由美", "翔太"]
customers = []
for i in range(count):
name = f"{random.choice(first_names)} {random.choice(last_names)}"
customers.append({
'id': i + 1,
'name': name,
'phone': f"090-{random.randint(1000, 9999)}-{random.randint(1000, 9999)}",
'email': f"customer{i+1}@example.com",
'address': f"東京都{random.choice(['渋谷区', '新宿区', '港区', '千代田区'])}{random.randint(1, 50)}-{random.randint(1, 10)}"
})
return customers
@staticmethod
def generate_products(count: int = 50) -> List[Dict]:
"""ダミー商品データ生成"""
categories = ["電子機器", "衣料品", "食品", "書籍", "家具"]
products = []
for i in range(count):
category = random.choice(categories)
products.append({
'id': i + 1,
'name': f"{category}{i+1}",
'category': category,
'price': random.randint(100, 50000),
'stock': random.randint(0, 100)
})
return products
@staticmethod
def generate_sales(count: int = 200) -> List[Dict]:
"""ダミー売上データ生成"""
customers = DummyDataGenerator.generate_customers(20)
products = DummyDataGenerator.generate_products(30)
sales = []
for i in range(count):
customer = random.choice(customers)
product = random.choice(products)
quantity = random.randint(1, 10)
sales.append({
'id': i + 1,
'customer_id': customer['id'],
'customer_name': customer['name'],
'product_id': product['id'],
'product_name': product['name'],
'quantity': quantity,
'unit_price': product['price'],
'total_price': quantity * product['price'],
'date': datetime.now().strftime("%Y-%m-%d"),
'created_at': datetime.now().isoformat()
})
return sales
def main(page: ft.Page):
"""メイン関数"""
try:
# ログ設定
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('app.log'),
logging.StreamHandler()
]
)
# シグナルハンドラ設定
def signal_handler(signum, frame):
print(f"\nシグナル {signum} を受信しました")
print("✅ 正常終了処理完了")
logging.info("アプリケーション正常終了")
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# データベース初期化
def init_db():
try:
conn = sqlite3.connect('sales.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS customers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
phone TEXT,
email TEXT,
address TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
category TEXT,
price REAL NOT NULL,
stock INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS sales (
id INTEGER PRIMARY KEY AUTOINCREMENT,
customer_name TEXT NOT NULL,
product_name TEXT NOT NULL,
quantity INTEGER NOT NULL,
unit_price REAL NOT NULL,
total_price REAL NOT NULL,
date TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
conn.commit()
conn.close()
logging.info("データベース初期化完了")
except Exception as e:
logging.error(f"データベース初期化エラー: {e}")
print(f"❌ データベース初期化エラー: {e}")
# ダミーデータ生成
def generate_dummy_data():
try:
conn = sqlite3.connect('sales.db')
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM customers")
if cursor.fetchone()[0] == 0:
print("📊 ダミーデータを生成中...")
# 顧客データ
customers = DummyDataGenerator.generate_customers(50)
for customer in customers:
cursor.execute('''
INSERT INTO customers (name, phone, email, address)
VALUES (?, ?, ?, ?)
''', (customer['name'], customer['phone'], customer['email'], customer['address']))
# 商品データ
products = DummyDataGenerator.generate_products(30)
for product in products:
cursor.execute('''
INSERT INTO products (name, category, price, stock)
VALUES (?, ?, ?, ?)
''', (product['name'], product['category'], product['price'], product['stock']))
# 売上データ
sales = DummyDataGenerator.generate_sales(100)
for sale in sales:
cursor.execute('''
INSERT INTO sales (customer_name, product_name, quantity, unit_price, total_price, date)
VALUES (?, ?, ?, ?, ?, ?)
''', (sale['customer_name'], sale['product_name'], sale['quantity'],
sale['unit_price'], sale['total_price'], sale['date']))
conn.commit()
print("✅ ダミーデータ生成完了")
conn.close()
except Exception as e:
logging.error(f"ダミーデータ生成エラー: {e}")
print(f"❌ ダミーデータ生成エラー: {e}")
# ウィンドウ設定
page.title = "販売アシスト1号 (シンプル版)"
page.window_width = 800
page.window_height = 600
page.theme_mode = ft.ThemeMode.LIGHT
# ウィンドウクローズイベント
page.on_window_close = lambda _: signal_handler(0, None)
# データベース初期化とダミーデータ生成
init_db()
generate_dummy_data()
# 現在のページインデックス
current_page_index = [0]
# UI要素
title = ft.Text("販売アシスト1号", size=24, weight=ft.FontWeight.BOLD)
# フォーム要素
customer_tf = ft.TextField(label="顧客名", width=200, autofocus=True)
product_tf = ft.TextField(label="商品名", width=200)
amount_tf = ft.TextField(label="金額", width=150, keyboard_type=ft.KeyboardType.NUMBER)
# ボタン
add_btn = ft.Button("追加", bgcolor=ft.Colors.BLUE, color=ft.Colors.WHITE)
# 売上一覧
sales_list = ft.Column([], scroll=ft.ScrollMode.AUTO, height=300)
# 操作説明
instructions = ft.Text(
"操作方法: TABでフォーカス移動、SPACE/ENTERで追加、1-4でページ遷移",
size=12,
color=ft.Colors.GREY_600
)
def load_sales():
"""売上データ読み込み"""
try:
conn = sqlite3.connect('sales.db')
cursor = conn.cursor()
cursor.execute('''
SELECT customer_name, product_name, total_price, date
FROM sales
ORDER BY created_at DESC
LIMIT 20
''')
sales = cursor.fetchall()
conn.close()
# リストを更新
sales_list.controls.clear()
for sale in sales:
customer, product, amount, date = sale
sales_list.controls.append(
ft.Text(f"{date}: {customer} - {product}: ¥{amount:,.0f}")
)
page.update()
except Exception as e:
logging.error(f"売上データ読み込みエラー: {e}")
def add_sale(e):
"""売上データ追加"""
if customer_tf.value and product_tf.value and amount_tf.value:
try:
# 保存前に値を取得
customer_val = customer_tf.value
product_val = product_tf.value
amount_val = float(amount_tf.value)
# データベースに保存
conn = sqlite3.connect('sales.db')
cursor = conn.cursor()
cursor.execute('''
INSERT INTO sales (customer_name, product_name, quantity, unit_price, total_price, date)
VALUES (?, ?, ?, ?, ?, ?)
''', (customer_val, product_val, 1, amount_val, amount_val, datetime.now().strftime("%Y-%m-%d")))
conn.commit()
conn.close()
# フィールドをクリア
customer_tf.value = ""
product_tf.value = ""
amount_tf.value = ""
# リスト更新
load_sales()
# 成功メッセージ
page.snack_bar = ft.SnackBar(
content=ft.Text("保存しました"),
bgcolor=ft.Colors.GREEN
)
page.snack_bar.open = True
page.update()
logging.info(f"売上データ追加: {customer_val} {product_val} {amount_val}")
except Exception as ex:
logging.error(f"売上データ保存エラー: {ex}")
page.snack_bar = ft.SnackBar(
content=ft.Text("エラーが発生しました"),
bgcolor=ft.Colors.RED
)
page.snack_bar.open = True
page.update()
# キーボードイベントハンドラ
def on_keyboard(e: ft.KeyboardEvent):
# SPACEまたはENTERで追加
if e.key == " " or e.key == "Enter":
add_sale(None)
# 数字キーでページ遷移(今回はシンプルに)
elif e.key == "1":
print("ダッシュボードに遷移します")
elif e.key == "2":
print("売上管理に遷移します")
elif e.key == "3":
print("顧客管理に遷移します")
elif e.key == "4":
print("商品管理に遷移します")
# ESCで終了
elif e.key == "Escape":
signal_handler(0, None)
# キーボードイベント設定
page.on_keyboard_event = on_keyboard
# 初期データ読み込み
load_sales()
# ページ構築
page.add(
title,
ft.Divider(),
ft.Text("売上登録", size=18, weight=ft.FontWeight.BOLD),
ft.Row([customer_tf, product_tf, amount_tf, add_btn]),
ft.Divider(),
ft.Text("売上一覧", size=18),
instructions,
ft.Divider(),
sales_list
)
logging.info("アプリケーション起動完了")
print("🚀 シンプル版販売アシスト1号起動完了")
except Exception as e:
logging.error(f"アプリケーション起動エラー: {e}")
print(f"❌ アプリケーション起動エラー: {e}")
if __name__ == "__main__":
ft.run(main)