311 lines
10 KiB
Python
311 lines
10 KiB
Python
import flet as ft
|
|
import sqlite3
|
|
import datetime
|
|
from typing import List, Dict, Optional
|
|
|
|
async def main(page: ft.Page):
|
|
# データベース初期化
|
|
init_database()
|
|
|
|
page.title = "販売アシスト1号"
|
|
page.theme_mode = ft.ThemeMode.LIGHT
|
|
page.window_width = 450
|
|
page.window_height = 800
|
|
|
|
async def refresh_all():
|
|
await list_view.refresh()
|
|
|
|
# Viewの生成
|
|
input_view = get_input_view(page, refresh_all)
|
|
list_view = get_list_view(page, input_view)
|
|
|
|
# ナビゲーション制御
|
|
async def nav_change(e):
|
|
idx = e.control.selected_index
|
|
input_view.visible = (idx == 0)
|
|
list_view.visible = (idx == 1)
|
|
|
|
if list_view.visible:
|
|
await list_view.refresh()
|
|
|
|
page.update()
|
|
|
|
page.navigation_bar = ft.NavigationBar(
|
|
selected_index=1, # 最初はリスト
|
|
destinations=[
|
|
ft.NavigationBarDestination(icon=ft.Icons.EDIT_NOTE, label="売上起票"),
|
|
ft.NavigationBarDestination(icon=ft.Icons.LIST_ALT, label="一覧"),
|
|
],
|
|
on_change=nav_change
|
|
)
|
|
|
|
# 画面への追加
|
|
page.add(ft.Container(
|
|
content=ft.Column([input_view, list_view], expand=True),
|
|
padding=10, expand=True
|
|
))
|
|
|
|
# 初期表示設定
|
|
input_view.visible = False
|
|
list_view.visible = True
|
|
await list_view.refresh()
|
|
page.update()
|
|
|
|
def init_database():
|
|
"""Initialize SQLite database"""
|
|
conn = sqlite3.connect('sales.db')
|
|
cursor = conn.cursor()
|
|
|
|
# Create sales table
|
|
cursor.execute('''
|
|
CREATE TABLE IF NOT EXISTS sales (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
date TEXT NOT NULL,
|
|
customer TEXT NOT NULL,
|
|
product TEXT NOT NULL,
|
|
amount REAL NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
''')
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
class InputView(ft.Column):
|
|
def __init__(self, page: ft.Page, on_success):
|
|
super().__init__(expand=True, spacing=15, visible=False)
|
|
self.my_page = page
|
|
self.on_success = on_success
|
|
self.edit_id = None
|
|
|
|
# UI部品の定義
|
|
self.title = ft.Text("売上伝票の起票", size=24, weight="bold", color=ft.Colors.BLUE_900)
|
|
|
|
self.date_tf = ft.TextField(
|
|
label="伝票日付",
|
|
value=datetime.datetime.now().strftime("%Y-%m-%d"),
|
|
width=200,
|
|
border_radius=10
|
|
)
|
|
|
|
self.customer_tf = ft.TextField(label="顧客名", border_radius=10)
|
|
self.product_tf = ft.TextField(label="項目/商品名", border_radius=10)
|
|
|
|
self.amount_tf = ft.TextField(
|
|
label="金額 (税込)",
|
|
width=200,
|
|
keyboard_type=ft.KeyboardType.NUMBER,
|
|
prefix_icon=ft.Icons.ATTACH_MONEY,
|
|
border_radius=10
|
|
)
|
|
|
|
self.save_btn = ft.ElevatedButton(
|
|
"保存",
|
|
icon=ft.Icons.SAVE,
|
|
on_click=self.save_data,
|
|
bgcolor=ft.Colors.BLUE_900,
|
|
color=ft.Colors.WHITE
|
|
)
|
|
|
|
self.controls = [
|
|
self.title,
|
|
ft.Row([self.date_tf, self.customer_tf], spacing=10),
|
|
self.product_tf,
|
|
self.amount_tf,
|
|
self.save_btn
|
|
]
|
|
|
|
async def save_data(self, e):
|
|
try:
|
|
if not all([self.customer_tf.value, self.product_tf.value, self.amount_tf.value]):
|
|
self.my_page.snack_bar = ft.SnackBar(
|
|
content=ft.Text("すべての項目を入力してください"),
|
|
bgcolor=ft.Colors.RED_500
|
|
)
|
|
self.my_page.snack_bar.open = True
|
|
self.my_page.update()
|
|
return
|
|
|
|
amount = float(self.amount_tf.value)
|
|
|
|
conn = sqlite3.connect('sales.db')
|
|
cursor = conn.cursor()
|
|
|
|
if self.edit_id:
|
|
cursor.execute('''
|
|
UPDATE sales
|
|
SET date=?, customer=?, product=?, amount=?
|
|
WHERE id=?
|
|
''', (self.date_tf.value, self.customer_tf.value, self.product_tf.value, amount, self.edit_id))
|
|
else:
|
|
cursor.execute('''
|
|
INSERT INTO sales (date, customer, product, amount)
|
|
VALUES (?, ?, ?, ?)
|
|
''', (self.date_tf.value, self.customer_tf.value, self.product_tf.value, amount))
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
# フォームをクリア
|
|
self.clear_form()
|
|
|
|
# 成功メッセージ
|
|
self.my_page.snack_bar = ft.SnackBar(
|
|
content=ft.Text("保存しました"),
|
|
bgcolor=ft.Colors.GREEN_500
|
|
)
|
|
self.my_page.snack_bar.open = True
|
|
self.my_page.update()
|
|
|
|
# 一覧を更新
|
|
await self.on_success()
|
|
|
|
except ValueError:
|
|
self.my_page.snack_bar = ft.SnackBar(
|
|
content=ft.Text("金額は数値で入力してください"),
|
|
bgcolor=ft.Colors.RED_500
|
|
)
|
|
self.my_page.snack_bar.open = True
|
|
self.my_page.update()
|
|
except Exception as ex:
|
|
self.my_page.snack_bar = ft.SnackBar(
|
|
content=ft.Text(f"エラー: {str(ex)}"),
|
|
bgcolor=ft.Colors.RED_500
|
|
)
|
|
self.my_page.snack_bar.open = True
|
|
self.my_page.update()
|
|
|
|
def clear_form(self):
|
|
self.customer_tf.value = ""
|
|
self.product_tf.value = ""
|
|
self.amount_tf.value = ""
|
|
self.date_tf.value = datetime.datetime.now().strftime("%Y-%m-%d")
|
|
self.edit_id = None
|
|
self.update()
|
|
|
|
class ListView(ft.Column):
|
|
def __init__(self, page: ft.Page, input_view):
|
|
super().__init__(expand=True, visible=True)
|
|
self.my_page = page
|
|
self.input_view = input_view
|
|
self.data_list = ft.ListView(expand=True, spacing=5)
|
|
|
|
self.title = ft.Text("売上一覧", size=20, weight="bold")
|
|
self.controls = [self.title, self.data_list]
|
|
|
|
async def refresh(self):
|
|
try:
|
|
conn = sqlite3.connect('sales.db')
|
|
cursor = conn.cursor()
|
|
cursor.execute('''
|
|
SELECT id, date, customer, product, amount
|
|
FROM sales
|
|
ORDER BY date DESC, created_at DESC
|
|
LIMIT 50
|
|
''')
|
|
|
|
self.data_list.controls.clear()
|
|
|
|
for row in cursor.fetchall():
|
|
sale_id, date, customer, product, amount = row
|
|
|
|
card = ft.Card(
|
|
content=ft.Container(
|
|
content=ft.ListTile(
|
|
title=ft.Text(f"{customer} - {product}"),
|
|
subtitle=ft.Text(f"{date} | ¥{amount:,.0f}"),
|
|
trailing=ft.Row([
|
|
ft.IconButton(
|
|
icon=ft.Icons.EDIT,
|
|
on_click=lambda e, id=sale_id: self.edit_data(id)
|
|
),
|
|
ft.IconButton(
|
|
icon=ft.Icons.DELETE,
|
|
on_click=lambda e, id=sale_id: self.delete_data(id)
|
|
)
|
|
])
|
|
),
|
|
padding=10
|
|
)
|
|
)
|
|
self.data_list.controls.append(card)
|
|
|
|
conn.close()
|
|
self.update()
|
|
|
|
except Exception as ex:
|
|
self.my_page.snack_bar = ft.SnackBar(
|
|
content=ft.Text(f"データ読み込みエラー: {str(ex)}"),
|
|
bgcolor=ft.Colors.RED_500
|
|
)
|
|
self.my_page.snack_bar.open = True
|
|
self.my_page.update()
|
|
|
|
def edit_data(self, sale_id):
|
|
try:
|
|
conn = sqlite3.connect('sales.db')
|
|
cursor = conn.cursor()
|
|
cursor.execute('''
|
|
SELECT date, customer, product, amount
|
|
FROM sales
|
|
WHERE id=?
|
|
''', (sale_id,))
|
|
|
|
row = cursor.fetchone()
|
|
conn.close()
|
|
|
|
if row:
|
|
date, customer, product, amount = row
|
|
self.input_view.date_tf.value = date
|
|
self.input_view.customer_tf.value = customer
|
|
self.input_view.product_tf.value = product
|
|
self.input_view.amount_tf.value = str(amount)
|
|
self.input_view.edit_id = sale_id
|
|
|
|
# 入力画面を表示
|
|
self.input_view.visible = True
|
|
self.visible = False
|
|
self.my_page.update()
|
|
|
|
except Exception as ex:
|
|
self.my_page.snack_bar = ft.SnackBar(
|
|
content=ft.Text(f"データ読み込みエラー: {str(ex)}"),
|
|
bgcolor=ft.Colors.RED_500
|
|
)
|
|
self.my_page.snack_bar.open = True
|
|
self.my_page.update()
|
|
|
|
def delete_data(self, sale_id):
|
|
try:
|
|
conn = sqlite3.connect('sales.db')
|
|
cursor = conn.cursor()
|
|
cursor.execute("DELETE FROM sales WHERE id=?", (sale_id,))
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
self.my_page.snack_bar = ft.SnackBar(
|
|
content=ft.Text("削除しました"),
|
|
bgcolor=ft.Colors.GREEN_500
|
|
)
|
|
self.my_page.snack_bar.open = True
|
|
self.my_page.update()
|
|
|
|
# 一覧を更新
|
|
self.refresh()
|
|
|
|
except Exception as ex:
|
|
self.my_page.snack_bar = ft.SnackBar(
|
|
content=ft.Text(f"削除エラー: {str(ex)}"),
|
|
bgcolor=ft.Colors.RED_500
|
|
)
|
|
self.my_page.snack_bar.open = True
|
|
self.my_page.update()
|
|
|
|
def get_input_view(page: ft.Page, on_success):
|
|
return InputView(page, on_success)
|
|
|
|
def get_list_view(page: ft.Page, input_view):
|
|
return ListView(page, input_view)
|
|
|
|
if __name__ == "__main__":
|
|
ft.app(target=main)
|