877 lines
32 KiB
Python
877 lines
32 KiB
Python
import flet as ft
|
||
import sqlite3
|
||
import datetime
|
||
from typing import List, Dict, Optional
|
||
from data_export import DataExporter
|
||
from compliance import ComplianceManager
|
||
|
||
class SalesAssistant:
|
||
def __init__(self, page: ft.Page):
|
||
self.page = page
|
||
self.page.title = "販売アシスト1号"
|
||
self.page.theme_mode = ft.ThemeMode.LIGHT
|
||
self.page.vertical_alignment = ft.MainAxisAlignment.CENTER
|
||
self.page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
|
||
|
||
# Initialize database
|
||
self.init_database()
|
||
|
||
# Initialize data exporter and compliance manager
|
||
self.exporter = DataExporter()
|
||
self.compliance = ComplianceManager()
|
||
|
||
# Navigation
|
||
self.navigation_rail = ft.NavigationRail(
|
||
selected_index=0,
|
||
label_type=ft.NavigationRailLabelType.ALL,
|
||
min_width=100,
|
||
min_extended_width=200,
|
||
destinations=[
|
||
ft.NavigationRailDestination(
|
||
label="ダッシュボード"
|
||
),
|
||
ft.NavigationRailDestination(
|
||
label="顧客管理"
|
||
),
|
||
ft.NavigationRailDestination(
|
||
label="商品管理"
|
||
),
|
||
ft.NavigationRailDestination(
|
||
label="売上管理"
|
||
),
|
||
ft.NavigationRailDestination(
|
||
label="データ出力"
|
||
),
|
||
ft.NavigationRailDestination(
|
||
label="コンプライアンス"
|
||
),
|
||
],
|
||
on_change=self.navigation_changed
|
||
)
|
||
|
||
# Content area
|
||
self.content_area = ft.Container(expand=True)
|
||
|
||
# Main layout
|
||
self.main_layout = ft.Row(
|
||
[
|
||
self.navigation_rail,
|
||
ft.VerticalDivider(width=1),
|
||
self.content_area
|
||
],
|
||
expand=True
|
||
)
|
||
|
||
# Show dashboard initially
|
||
self.show_dashboard()
|
||
|
||
# Add main layout to page
|
||
self.page.add(self.main_layout)
|
||
|
||
def init_database(self):
|
||
"""Initialize SQLite database"""
|
||
conn = sqlite3.connect('sales.db')
|
||
cursor = conn.cursor()
|
||
|
||
# Create customers table
|
||
cursor.execute('''
|
||
CREATE TABLE IF NOT EXISTS customers (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
name TEXT NOT NULL,
|
||
company TEXT,
|
||
phone TEXT,
|
||
email TEXT,
|
||
address TEXT,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
)
|
||
''')
|
||
|
||
# Create products table
|
||
cursor.execute('''
|
||
CREATE TABLE IF NOT EXISTS products (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
name TEXT NOT NULL,
|
||
code TEXT UNIQUE,
|
||
price REAL NOT NULL,
|
||
stock INTEGER DEFAULT 0,
|
||
description TEXT,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
)
|
||
''')
|
||
|
||
# Create sales table
|
||
cursor.execute('''
|
||
CREATE TABLE IF NOT EXISTS sales (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
customer_id INTEGER,
|
||
product_id INTEGER,
|
||
quantity INTEGER NOT NULL,
|
||
unit_price REAL NOT NULL,
|
||
total_price REAL NOT NULL,
|
||
sale_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
FOREIGN KEY (customer_id) REFERENCES customers (id),
|
||
FOREIGN KEY (product_id) REFERENCES products (id)
|
||
)
|
||
''')
|
||
|
||
conn.commit()
|
||
conn.close()
|
||
|
||
def navigation_changed(self, e):
|
||
"""Handle navigation rail changes"""
|
||
if e.control.selected_index == 0:
|
||
self.show_dashboard()
|
||
elif e.control.selected_index == 1:
|
||
self.show_customers()
|
||
elif e.control.selected_index == 2:
|
||
self.show_products()
|
||
elif e.control.selected_index == 3:
|
||
self.show_sales()
|
||
elif e.control.selected_index == 4:
|
||
self.show_data_export()
|
||
elif e.control.selected_index == 5:
|
||
self.show_compliance()
|
||
|
||
def show_dashboard(self):
|
||
"""Show dashboard view"""
|
||
# Get statistics
|
||
conn = sqlite3.connect('sales.db')
|
||
cursor = conn.cursor()
|
||
|
||
# Total customers
|
||
cursor.execute("SELECT COUNT(*) FROM customers")
|
||
total_customers = cursor.fetchone()[0]
|
||
|
||
# Total products
|
||
cursor.execute("SELECT COUNT(*) FROM products")
|
||
total_products = cursor.fetchone()[0]
|
||
|
||
# Total sales
|
||
cursor.execute("SELECT COUNT(*) FROM sales")
|
||
total_sales = cursor.fetchone()[0]
|
||
|
||
# Total revenue
|
||
cursor.execute("SELECT SUM(total_price) FROM sales")
|
||
total_revenue = cursor.fetchone()[0] or 0
|
||
|
||
conn.close()
|
||
|
||
dashboard_content = ft.Column([
|
||
ft.Text("ダッシュボード", size=24, weight=ft.FontWeight.BOLD),
|
||
ft.Divider(),
|
||
ft.Row([
|
||
ft.Card(
|
||
content=ft.Container(
|
||
content=ft.Column([
|
||
ft.Text("顧客数", size=16, color=ft.Colors.BLUE),
|
||
ft.Text(str(total_customers), size=32, weight=ft.FontWeight.BOLD)
|
||
]),
|
||
padding=20,
|
||
width=150
|
||
)
|
||
),
|
||
ft.Card(
|
||
content=ft.Container(
|
||
content=ft.Column([
|
||
ft.Text("商品数", size=16, color=ft.Colors.GREEN),
|
||
ft.Text(str(total_products), size=32, weight=ft.FontWeight.BOLD)
|
||
]),
|
||
padding=20,
|
||
width=150
|
||
)
|
||
),
|
||
ft.Card(
|
||
content=ft.Container(
|
||
content=ft.Column([
|
||
ft.Text("売上件数", size=16, color=ft.Colors.ORANGE),
|
||
ft.Text(str(total_sales), size=32, weight=ft.FontWeight.BOLD)
|
||
]),
|
||
padding=20,
|
||
width=150
|
||
)
|
||
),
|
||
ft.Card(
|
||
content=ft.Container(
|
||
content=ft.Column([
|
||
ft.Text("総売上", size=16, color=ft.Colors.PURPLE),
|
||
ft.Text(f"¥{total_revenue:,.0f}", size=32, weight=ft.FontWeight.BOLD)
|
||
]),
|
||
padding=20,
|
||
width=150
|
||
)
|
||
)
|
||
], wrap=True)
|
||
], scroll=ft.ScrollMode.AUTO)
|
||
|
||
self.content_area.content = ft.Container(dashboard_content, padding=20)
|
||
self.page.update()
|
||
|
||
def show_customers(self):
|
||
"""Show customers management view"""
|
||
customers = self.get_customers()
|
||
|
||
# Customer list
|
||
customer_list = ft.ListView(
|
||
expand=True,
|
||
spacing=10,
|
||
padding=10
|
||
)
|
||
|
||
for customer in customers:
|
||
customer_list.controls.append(
|
||
ft.Card(
|
||
content=ft.Container(
|
||
content=ft.ListTile(
|
||
title=ft.Text(customer['name']),
|
||
subtitle=ft.Text(customer['company'] or ''),
|
||
trailing=ft.Row([
|
||
ft.IconButton(
|
||
icon=ft.icons.EDIT,
|
||
on_click=lambda e, c=customer: self.edit_customer(c)
|
||
),
|
||
ft.IconButton(
|
||
icon=ft.icons.DELETE,
|
||
on_click=lambda e, c=customer: self.delete_customer(c)
|
||
)
|
||
])
|
||
),
|
||
padding=10
|
||
)
|
||
)
|
||
)
|
||
|
||
# Add customer button
|
||
add_button = ft.ElevatedButton(
|
||
"顧客を追加",
|
||
icon=ft.icons.ADD,
|
||
on_click=self.add_customer_dialog
|
||
)
|
||
|
||
customers_content = ft.Column([
|
||
ft.Row([
|
||
ft.Text("顧客管理", size=24, weight=ft.FontWeight.BOLD),
|
||
add_button
|
||
], alignment=ft.MainAxisAlignment.SPACE_BETWEEN),
|
||
ft.Divider(),
|
||
customer_list
|
||
], scroll=ft.ScrollMode.AUTO)
|
||
|
||
self.content_area.content = ft.Container(customers_content, padding=20)
|
||
self.page.update()
|
||
|
||
def show_products(self):
|
||
"""Show products management view"""
|
||
products = self.get_products()
|
||
|
||
# Product list
|
||
product_list = ft.ListView(
|
||
expand=True,
|
||
spacing=10,
|
||
padding=10
|
||
)
|
||
|
||
for product in products:
|
||
product_list.controls.append(
|
||
ft.Card(
|
||
content=ft.Container(
|
||
content=ft.ListTile(
|
||
title=ft.Text(product['name']),
|
||
subtitle=ft.Text(f"¥{product['price']:,.0f} | 在庫: {product['stock']}"),
|
||
trailing=ft.Row([
|
||
ft.IconButton(
|
||
icon=ft.icons.EDIT,
|
||
on_click=lambda e, p=product: self.edit_product(p)
|
||
),
|
||
ft.IconButton(
|
||
icon=ft.icons.DELETE,
|
||
on_click=lambda e, p=product: self.delete_product(p)
|
||
)
|
||
])
|
||
),
|
||
padding=10
|
||
)
|
||
)
|
||
)
|
||
|
||
# Add product button
|
||
add_button = ft.ElevatedButton(
|
||
"商品を追加",
|
||
icon=ft.icons.ADD,
|
||
on_click=self.add_product_dialog
|
||
)
|
||
|
||
products_content = ft.Column([
|
||
ft.Row([
|
||
ft.Text("商品管理", size=24, weight=ft.FontWeight.BOLD),
|
||
add_button
|
||
], alignment=ft.MainAxisAlignment.SPACE_BETWEEN),
|
||
ft.Divider(),
|
||
product_list
|
||
], scroll=ft.ScrollMode.AUTO)
|
||
|
||
self.content_area.content = ft.Container(products_content, padding=20)
|
||
self.page.update()
|
||
|
||
def show_sales(self):
|
||
"""Show sales management view"""
|
||
sales = self.get_sales()
|
||
|
||
# Sales list
|
||
sales_list = ft.ListView(
|
||
expand=True,
|
||
spacing=10,
|
||
padding=10
|
||
)
|
||
|
||
for sale in sales:
|
||
sales_list.controls.append(
|
||
ft.Card(
|
||
content=ft.Container(
|
||
content=ft.ListTile(
|
||
title=ft.Text(f"{sale['customer_name']} - {sale['product_name']}"),
|
||
subtitle=ft.Text(f"{sale['quantity']}個 × ¥{sale['unit_price']:,.0f} = ¥{sale['total_price']:,.0f}"),
|
||
trailing=ft.Text(sale['sale_date'].split()[0])
|
||
),
|
||
padding=10
|
||
)
|
||
)
|
||
)
|
||
|
||
# Add sale button
|
||
add_button = ft.ElevatedButton(
|
||
"売上を追加",
|
||
icon=ft.icons.ADD,
|
||
on_click=self.add_sale_dialog
|
||
)
|
||
|
||
sales_content = ft.Column([
|
||
ft.Row([
|
||
ft.Text("売上管理", size=24, weight=ft.FontWeight.BOLD),
|
||
add_button
|
||
], alignment=ft.MainAxisAlignment.SPACE_BETWEEN),
|
||
ft.Divider(),
|
||
sales_list
|
||
], scroll=ft.ScrollMode.AUTO)
|
||
|
||
self.content_area.content = ft.Container(sales_content, padding=20)
|
||
self.page.update()
|
||
|
||
def get_customers(self) -> List[Dict]:
|
||
"""Get all customers from database"""
|
||
conn = sqlite3.connect('sales.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute("SELECT * FROM customers ORDER BY created_at DESC")
|
||
customers = []
|
||
for row in cursor.fetchall():
|
||
customers.append({
|
||
'id': row[0],
|
||
'name': row[1],
|
||
'company': row[2],
|
||
'phone': row[3],
|
||
'email': row[4],
|
||
'address': row[5],
|
||
'created_at': row[6]
|
||
})
|
||
conn.close()
|
||
return customers
|
||
|
||
def get_products(self) -> List[Dict]:
|
||
"""Get all products from database"""
|
||
conn = sqlite3.connect('sales.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute("SELECT * FROM products ORDER BY created_at DESC")
|
||
products = []
|
||
for row in cursor.fetchall():
|
||
products.append({
|
||
'id': row[0],
|
||
'name': row[1],
|
||
'code': row[2],
|
||
'price': row[3],
|
||
'stock': row[4],
|
||
'description': row[5],
|
||
'created_at': row[6]
|
||
})
|
||
conn.close()
|
||
return products
|
||
|
||
def get_sales(self) -> List[Dict]:
|
||
"""Get all sales with customer and product names"""
|
||
conn = sqlite3.connect('sales.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute('''
|
||
SELECT s.*, c.name as customer_name, p.name as product_name
|
||
FROM sales s
|
||
LEFT JOIN customers c ON s.customer_id = c.id
|
||
LEFT JOIN products p ON s.product_id = p.id
|
||
ORDER BY s.sale_date DESC
|
||
''')
|
||
sales = []
|
||
for row in cursor.fetchall():
|
||
sales.append({
|
||
'id': row[0],
|
||
'customer_id': row[1],
|
||
'product_id': row[2],
|
||
'quantity': row[3],
|
||
'unit_price': row[4],
|
||
'total_price': row[5],
|
||
'sale_date': row[6],
|
||
'customer_name': row[7] or '不明',
|
||
'product_name': row[8] or '不明'
|
||
})
|
||
conn.close()
|
||
return sales
|
||
|
||
def add_customer_dialog(self, e):
|
||
"""Show add customer dialog"""
|
||
name_field = ft.TextField(label="氏名", autofocus=True)
|
||
company_field = ft.TextField(label="会社名")
|
||
phone_field = ft.TextField(label="電話番号")
|
||
email_field = ft.TextField(label="メールアドレス")
|
||
address_field = ft.TextField(label="住所")
|
||
|
||
def save_customer(e):
|
||
conn = sqlite3.connect('sales.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute('''
|
||
INSERT INTO customers (name, company, phone, email, address)
|
||
VALUES (?, ?, ?, ?, ?)
|
||
''', (name_field.value, company_field.value, phone_field.value,
|
||
email_field.value, address_field.value))
|
||
conn.commit()
|
||
conn.close()
|
||
dialog.open = False
|
||
self.show_customers()
|
||
self.page.update()
|
||
|
||
dialog = ft.AlertDialog(
|
||
title=ft.Text("顧客を追加"),
|
||
content=ft.Column([
|
||
name_field,
|
||
company_field,
|
||
phone_field,
|
||
email_field,
|
||
address_field
|
||
], tight=True),
|
||
actions=[
|
||
ft.TextButton("キャンセル", on_click=lambda e: self.close_dialog(dialog)),
|
||
ft.TextButton("保存", on_click=save_customer)
|
||
],
|
||
actions_alignment=ft.MainAxisAlignment.END
|
||
)
|
||
|
||
self.page.dialog = dialog
|
||
dialog.open = True
|
||
self.page.update()
|
||
|
||
def add_product_dialog(self, e):
|
||
"""Show add product dialog"""
|
||
name_field = ft.TextField(label="商品名", autofocus=True)
|
||
code_field = ft.TextField(label="商品コード")
|
||
price_field = ft.TextField(label="価格", keyboard_type=ft.KeyboardType.NUMBER)
|
||
stock_field = ft.TextField(label="在庫数", keyboard_type=ft.KeyboardType.NUMBER)
|
||
description_field = ft.TextField(label="説明", multiline=True)
|
||
|
||
def save_product(e):
|
||
conn = sqlite3.connect('sales.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute('''
|
||
INSERT INTO products (name, code, price, stock, description)
|
||
VALUES (?, ?, ?, ?, ?)
|
||
''', (name_field.value, code_field.value, float(price_field.value or 0),
|
||
int(stock_field.value or 0), description_field.value))
|
||
conn.commit()
|
||
conn.close()
|
||
dialog.open = False
|
||
self.show_products()
|
||
self.page.update()
|
||
|
||
dialog = ft.AlertDialog(
|
||
title=ft.Text("商品を追加"),
|
||
content=ft.Column([
|
||
name_field,
|
||
code_field,
|
||
price_field,
|
||
stock_field,
|
||
description_field
|
||
], tight=True),
|
||
actions=[
|
||
ft.TextButton("キャンセル", on_click=lambda e: self.close_dialog(dialog)),
|
||
ft.TextButton("保存", on_click=save_product)
|
||
],
|
||
actions_alignment=ft.MainAxisAlignment.END
|
||
)
|
||
|
||
self.page.dialog = dialog
|
||
dialog.open = True
|
||
self.page.update()
|
||
|
||
def add_sale_dialog(self, e):
|
||
"""Show add sale dialog"""
|
||
customers = self.get_customers()
|
||
products = self.get_products()
|
||
|
||
customer_dropdown = ft.Dropdown(
|
||
label="顧客",
|
||
options=[ft.dropdown.Option(c['name'], key=str(c['id'])) for c in customers]
|
||
)
|
||
product_dropdown = ft.Dropdown(
|
||
label="商品",
|
||
options=[ft.dropdown.Option(p['name'], key=str(p['id'])) for p in products],
|
||
on_change=lambda e: self.update_price_display(e, product_dropdown, price_field, quantity_field, total_field)
|
||
)
|
||
quantity_field = ft.TextField(
|
||
label="数量",
|
||
value="1",
|
||
keyboard_type=ft.KeyboardType.NUMBER,
|
||
on_change=lambda e: self.calculate_total(price_field, quantity_field, total_field)
|
||
)
|
||
price_field = ft.TextField(
|
||
label="単価",
|
||
keyboard_type=ft.KeyboardType.NUMBER,
|
||
on_change=lambda e: self.calculate_total(price_field, quantity_field, total_field)
|
||
)
|
||
total_field = ft.TextField(label="合計", read_only=True)
|
||
|
||
def save_sale(e):
|
||
conn = sqlite3.connect('sales.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute('''
|
||
INSERT INTO sales (customer_id, product_id, quantity, unit_price, total_price)
|
||
VALUES (?, ?, ?, ?, ?)
|
||
''', (int(customer_dropdown.value), int(product_dropdown.value),
|
||
int(quantity_field.value), float(price_field.value),
|
||
float(total_field.value)))
|
||
conn.commit()
|
||
conn.close()
|
||
dialog.open = False
|
||
self.show_sales()
|
||
self.page.update()
|
||
|
||
dialog = ft.AlertDialog(
|
||
title=ft.Text("売上を追加"),
|
||
content=ft.Column([
|
||
customer_dropdown,
|
||
product_dropdown,
|
||
quantity_field,
|
||
price_field,
|
||
total_field
|
||
], tight=True),
|
||
actions=[
|
||
ft.TextButton("キャンセル", on_click=lambda e: self.close_dialog(dialog)),
|
||
ft.TextButton("保存", on_click=save_sale)
|
||
],
|
||
actions_alignment=ft.MainAxisAlignment.END
|
||
)
|
||
|
||
self.page.dialog = dialog
|
||
dialog.open = True
|
||
self.page.update()
|
||
|
||
def update_price_display(self, e, product_dropdown, price_field, quantity_field, total_field):
|
||
"""Update price when product is selected"""
|
||
if product_dropdown.value:
|
||
conn = sqlite3.connect('sales.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute("SELECT price FROM products WHERE id = ?", (int(product_dropdown.value),))
|
||
result = cursor.fetchone()
|
||
conn.close()
|
||
if result:
|
||
price_field.value = str(result[0])
|
||
self.calculate_total(price_field, quantity_field, total_field)
|
||
self.page.update()
|
||
|
||
def calculate_total(self, price_field, quantity_field, total_field):
|
||
"""Calculate total price"""
|
||
try:
|
||
price = float(price_field.value or 0)
|
||
quantity = int(quantity_field.value or 1)
|
||
total_field.value = str(price * quantity)
|
||
except ValueError:
|
||
total_field.value = "0"
|
||
self.page.update()
|
||
|
||
def close_dialog(self, dialog):
|
||
"""Close dialog"""
|
||
dialog.open = False
|
||
self.page.update()
|
||
|
||
def edit_customer(self, customer):
|
||
"""Edit customer (placeholder)"""
|
||
self.page.snack_bar = ft.SnackBar(content=ft.Text("顧客編集機能は準備中です"))
|
||
self.page.snack_bar.open = True
|
||
self.page.update()
|
||
|
||
def delete_customer(self, customer):
|
||
"""Delete customer"""
|
||
def confirm_delete(e):
|
||
conn = sqlite3.connect('sales.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute("DELETE FROM customers WHERE id = ?", (customer['id'],))
|
||
conn.commit()
|
||
conn.close()
|
||
dialog.open = False
|
||
self.show_customers()
|
||
self.page.update()
|
||
|
||
dialog = ft.AlertDialog(
|
||
title=ft.Text("確認"),
|
||
content=ft.Text(f"顧客「{customer['name']}」を削除してもよろしいですか?"),
|
||
actions=[
|
||
ft.TextButton("キャンセル", on_click=lambda e: self.close_dialog(dialog)),
|
||
ft.TextButton("削除", on_click=confirm_delete)
|
||
],
|
||
actions_alignment=ft.MainAxisAlignment.END
|
||
)
|
||
|
||
self.page.dialog = dialog
|
||
dialog.open = True
|
||
self.page.update()
|
||
|
||
def edit_product(self, product):
|
||
"""Edit product (placeholder)"""
|
||
self.page.snack_bar = ft.SnackBar(content=ft.Text("商品編集機能は準備中です"))
|
||
self.page.snack_bar.open = True
|
||
self.page.update()
|
||
|
||
def delete_product(self, product):
|
||
"""Delete product"""
|
||
def confirm_delete(e):
|
||
conn = sqlite3.connect('sales.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute("DELETE FROM products WHERE id = ?", (product['id'],))
|
||
conn.commit()
|
||
conn.close()
|
||
dialog.open = False
|
||
self.show_products()
|
||
self.page.update()
|
||
|
||
dialog = ft.AlertDialog(
|
||
title=ft.Text("確認"),
|
||
content=ft.Text(f"商品「{product['name']}」を削除してもよろしいですか?"),
|
||
actions=[
|
||
ft.TextButton("キャンセル", on_click=lambda e: self.close_dialog(dialog)),
|
||
ft.TextButton("削除", on_click=confirm_delete)
|
||
],
|
||
actions_alignment=ft.MainAxisAlignment.END
|
||
)
|
||
|
||
self.page.dialog = dialog
|
||
dialog.open = True
|
||
self.page.update()
|
||
|
||
def show_data_export(self):
|
||
"""データ出力画面を表示"""
|
||
export_content = ft.Column([
|
||
ft.Text("データ出力", size=24, weight=ft.FontWeight.BOLD),
|
||
ft.Divider(),
|
||
ft.Card(
|
||
content=ft.Container(
|
||
content=ft.Column([
|
||
ft.Text("JSON形式で出力", size=16, weight=ft.FontWeight.BOLD),
|
||
ft.Text("全データをJSON形式でエクスポートします"),
|
||
ft.ElevatedButton(
|
||
"JSON出力",
|
||
icon=ft.icons.DOWNLOAD,
|
||
on_click=self.export_json
|
||
)
|
||
]),
|
||
padding=20
|
||
)
|
||
),
|
||
ft.Card(
|
||
content=ft.Container(
|
||
content=ft.Column([
|
||
ft.Text("CSV形式で出力", size=16, weight=ft.FontWeight.BOLD),
|
||
ft.Text("各テーブルをCSV形式でエクスポートします"),
|
||
ft.ElevatedButton(
|
||
"CSV出力",
|
||
icon=ft.icons.DOWNLOAD,
|
||
on_click=self.export_csv
|
||
)
|
||
]),
|
||
padding=20
|
||
)
|
||
)
|
||
], scroll=ft.ScrollMode.AUTO)
|
||
|
||
self.content_area.content = ft.Container(export_content, padding=20)
|
||
self.page.update()
|
||
|
||
def show_compliance(self):
|
||
"""コンプライアンス画面を表示"""
|
||
compliance_content = ft.Column([
|
||
ft.Text("電子帳簿保存法対応", size=24, weight=ft.FontWeight.BOLD),
|
||
ft.Divider(),
|
||
ft.Card(
|
||
content=ft.Container(
|
||
content=ft.Column([
|
||
ft.Text("データ整合性チェック", size=16, weight=ft.FontWeight.BOLD),
|
||
ft.Text("データの整合性を検証し、チェックサムを生成します"),
|
||
ft.ElevatedButton(
|
||
"整合性チェック",
|
||
icon=ft.icons.CHECK_CIRCLE,
|
||
on_click=self.check_integrity
|
||
)
|
||
]),
|
||
padding=20
|
||
)
|
||
),
|
||
ft.Card(
|
||
content=ft.Container(
|
||
content=ft.Column([
|
||
ft.Text("古いデータのアーカイブ", size=16, weight=ft.FontWeight.BOLD),
|
||
ft.Text("7年以上前のデータをアーカイブします(10年保存)"),
|
||
ft.ElevatedButton(
|
||
"データアーカイブ",
|
||
icon=ft.icons.ARCHIVE,
|
||
on_click=self.archive_data
|
||
)
|
||
]),
|
||
padding=20
|
||
)
|
||
),
|
||
ft.Card(
|
||
content=ft.Container(
|
||
content=ft.Column([
|
||
ft.Text("コンプライアンスレポート", size=16, weight=ft.FontWeight.BOLD),
|
||
ft.Text("電子帳簿保存法対応レポートを生成します"),
|
||
ft.ElevatedButton(
|
||
"レポート生成",
|
||
icon=ft.icons.ASSIGNMENT,
|
||
on_click=self.generate_compliance_report
|
||
)
|
||
]),
|
||
padding=20
|
||
)
|
||
)
|
||
], scroll=ft.ScrollMode.AUTO)
|
||
|
||
self.content_area.content = ft.Container(compliance_content, padding=20)
|
||
self.page.update()
|
||
|
||
def export_json(self, e):
|
||
"""JSON形式でデータをエクスポート"""
|
||
try:
|
||
file_path = self.exporter.export_to_json()
|
||
self.page.snack_bar = ft.SnackBar(
|
||
content=ft.Text(f"JSON出力完了: {file_path}"),
|
||
bgcolor=ft.Colors.GREEN
|
||
)
|
||
except Exception as ex:
|
||
self.page.snack_bar = ft.SnackBar(
|
||
content=ft.Text(f"JSON出力エラー: {str(ex)}"),
|
||
bgcolor=ft.Colors.RED
|
||
)
|
||
|
||
self.page.snack_bar.open = True
|
||
self.page.update()
|
||
|
||
def export_csv(self, e):
|
||
"""CSV形式でデータをエクスポート"""
|
||
try:
|
||
files = self.exporter.export_to_csv()
|
||
file_list = "\n".join([f"{k}: {v}" for k, v in files.items()])
|
||
self.page.snack_bar = ft.SnackBar(
|
||
content=ft.Text(f"CSV出力完了:\n{file_list}"),
|
||
bgcolor=ft.Colors.GREEN
|
||
)
|
||
except Exception as ex:
|
||
self.page.snack_bar = ft.SnackBar(
|
||
content=ft.Text(f"CSV出力エラー: {str(ex)}"),
|
||
bgcolor=ft.Colors.RED
|
||
)
|
||
|
||
self.page.snack_bar.open = True
|
||
self.page.update()
|
||
|
||
def check_integrity(self, e):
|
||
"""データ整合性チェック"""
|
||
try:
|
||
results = []
|
||
for table in ["customers", "products", "sales"]:
|
||
result = self.compliance.verify_data_integrity(table)
|
||
results.append(f"{table}: {result['status']} ({result['record_count']}件)")
|
||
|
||
message = "整合性チェック完了:\n" + "\n".join(results)
|
||
self.page.snack_bar = ft.SnackBar(
|
||
content=ft.Text(message),
|
||
bgcolor=ft.Colors.GREEN
|
||
)
|
||
except Exception as ex:
|
||
self.page.snack_bar = ft.SnackBar(
|
||
content=ft.Text(f"整合性チェックエラー: {str(ex)}"),
|
||
bgcolor=ft.Colors.RED
|
||
)
|
||
|
||
self.page.snack_bar.open = True
|
||
self.page.update()
|
||
|
||
def archive_data(self, e):
|
||
"""古いデータをアーカイブ"""
|
||
try:
|
||
result = self.compliance.archive_old_data()
|
||
if result["status"] == "SUCCESS":
|
||
message = f"アーカイブ完了: {result['archived_count']}件"
|
||
bgcolor = ft.Colors.GREEN
|
||
else:
|
||
message = f"アーカイブエラー: {result['error']}"
|
||
bgcolor = ft.colors.RED
|
||
|
||
self.page.snack_bar = ft.SnackBar(
|
||
content=ft.Text(message),
|
||
bgcolor=bgcolor
|
||
)
|
||
except Exception as ex:
|
||
self.page.snack_bar = ft.SnackBar(
|
||
content=ft.Text(f"アーカイブエラー: {str(ex)}"),
|
||
bgcolor=ft.Colors.RED
|
||
)
|
||
|
||
self.page.snack_bar.open = True
|
||
self.page.update()
|
||
|
||
def generate_compliance_report(self, e):
|
||
"""コンプライアンスレポートを生成"""
|
||
try:
|
||
report = self.compliance.generate_compliance_report()
|
||
|
||
# レポート内容を表示
|
||
dialog = ft.AlertDialog(
|
||
title=ft.Text("コンプライアンスレポート"),
|
||
content=ft.Container(
|
||
content=ft.Column([
|
||
ft.Text(f"生成日時: {report['generated_date']}"),
|
||
ft.Text(f"データベース: {report['database_path']}"),
|
||
ft.Divider(),
|
||
ft.Text("テーブル状況:", weight=ft.FontWeight.BOLD),
|
||
*[ft.Text(f" {table}: {info['record_count']}件")
|
||
for table, info in report['tables'].items()],
|
||
ft.Divider(),
|
||
ft.Text("監査ログ:", weight=ft.FontWeight.BOLD),
|
||
*[ft.Text(f" {op}: {count}件")
|
||
for op, count in report['audit_summary'].items()]
|
||
], scroll=ft.ScrollMode.AUTO),
|
||
height=400
|
||
),
|
||
actions=[
|
||
ft.TextButton("閉じる", on_click=lambda e: self.close_dialog(dialog))
|
||
]
|
||
)
|
||
|
||
self.page.dialog = dialog
|
||
dialog.open = True
|
||
self.page.update()
|
||
|
||
except Exception as ex:
|
||
self.page.snack_bar = ft.SnackBar(
|
||
content=ft.Text(f"レポート生成エラー: {str(ex)}"),
|
||
bgcolor=ft.Colors.RED
|
||
)
|
||
self.page.snack_bar.open = True
|
||
self.page.update()
|
||
|
||
def main(page: ft.Page):
|
||
app = SalesAssistant(page)
|
||
|
||
if __name__ == "__main__":
|
||
ft.app(target=main)
|