312 lines
12 KiB
Python
312 lines
12 KiB
Python
"""
|
||
GPS対応顧客マスタ編集コンポーネント
|
||
"""
|
||
|
||
import flet as ft
|
||
import sqlite3
|
||
import logging
|
||
from datetime import datetime
|
||
from typing import List, Dict, Optional
|
||
|
||
class CustomerMasterEditorWithGPS:
|
||
"""GPS対応顧客マスタ編集"""
|
||
|
||
def __init__(self, page: ft.Page):
|
||
self.page = page
|
||
|
||
# UI部品
|
||
self.title = ft.Text("GPS対応顧客マスタ", size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900)
|
||
|
||
# GPS情報表示
|
||
self.gps_info = ft.Text("", size=12, color=ft.Colors.GREY_600)
|
||
|
||
# フォーム
|
||
self.form_fields = ft.Column([], spacing=10)
|
||
|
||
# ボタン群
|
||
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.clear_btn = ft.Button("クリア", on_click=self.clear_form, bgcolor=ft.Colors.ORANGE, color=ft.Colors.WHITE)
|
||
|
||
# GPSボタン
|
||
self.gps_btn = ft.Button("GPS情報取得", on_click=self.get_gps_info, bgcolor=ft.Colors.PURPLE, color=ft.Colors.WHITE)
|
||
|
||
# データリスト
|
||
self.data_list = ft.Column([], scroll=ft.ScrollMode.AUTO, height=250)
|
||
|
||
# 操作説明
|
||
self.instructions = ft.Text(
|
||
"操作方法: 顧客情報を入力・GPS情報取得・保存・削除",
|
||
size=12,
|
||
color=ft.Colors.GREY_600
|
||
)
|
||
|
||
# データ読み込み
|
||
self._load_data()
|
||
|
||
def _build_form(self):
|
||
"""入力フォームを構築"""
|
||
self.form_fields.controls.clear()
|
||
|
||
# 基本フィールド
|
||
basic_fields = [
|
||
{'name': 'name', 'label': '顧客名', 'width': 200, 'required': True},
|
||
{'name': 'phone', 'label': '電話番号', 'width': 200},
|
||
{'name': 'email', 'label': 'メールアドレス', 'width': 250},
|
||
{'name': 'address', 'label': '住所', 'width': 300}
|
||
]
|
||
|
||
# GPSフィールド
|
||
gps_fields = [
|
||
{'name': 'latitude', 'label': '緯度', 'width': 150},
|
||
{'name': 'longitude', 'label': '経度', 'width': 150},
|
||
{'name': 'address_gps', 'label': 'GPS住所', 'width': 300},
|
||
{'name': 'gps_timestamp', 'label': 'GPS取得日時', 'width': 200}
|
||
]
|
||
|
||
# 全てのフィールドを結合
|
||
all_fields = basic_fields + gps_fields
|
||
|
||
# フィールドを動的に追加
|
||
for field in all_fields:
|
||
field_control = ft.TextField(
|
||
label=field['label'],
|
||
value='',
|
||
width=field['width']
|
||
)
|
||
self.form_fields.controls.append(field_control)
|
||
|
||
self.page.update()
|
||
|
||
def save_data(self, e):
|
||
"""データを保存"""
|
||
try:
|
||
# フォームデータを収集
|
||
form_data = {}
|
||
for i, field in enumerate(self.form_fields.controls):
|
||
field_name = f"field_{i}"
|
||
if i < len(basic_fields):
|
||
# 基本フィールド
|
||
form_data[all_fields[i]['name']] = field_control.value
|
||
else:
|
||
# GPSフィールド
|
||
if i < len(basic_fields) + len(gps_fields):
|
||
gps_field = self.form_fields.controls[i]
|
||
form_data[gps_field['name']] = gps_field.value
|
||
|
||
# データベースに保存
|
||
conn = sqlite3.connect('sales.db')
|
||
cursor = conn.cursor()
|
||
|
||
if self.editing_id:
|
||
# 更新
|
||
columns = ', '.join([f"field_{i}" for i in range(len(basic_fields))])
|
||
cursor.execute(f'''
|
||
UPDATE customers
|
||
SET {columns}
|
||
WHERE id = ?
|
||
''', tuple(form_data.values()) + (self.editing_id,))
|
||
|
||
logging.info(f"顧客データ更新完了: ID={self.editing_id}")
|
||
else:
|
||
# 新規追加
|
||
columns = ', '.join([f"field_{i}" for i in range(len(basic_fields))])
|
||
cursor.execute(f'''
|
||
INSERT INTO customers ({columns})
|
||
VALUES ({placeholders})
|
||
''', tuple(form_data.values()))
|
||
|
||
logging.info(f"顧客データ追加完了")
|
||
|
||
conn.commit()
|
||
conn.close()
|
||
|
||
# 成功メッセージ
|
||
self._show_snackbar("顧客情報を保存しました", ft.Colors.GREEN)
|
||
logging.info(f"顧客情報: {form_data}")
|
||
|
||
# データ再読み込み
|
||
self._load_data()
|
||
|
||
except Exception as ex:
|
||
logging.error(f"顧客データ保存エラー: {ex}")
|
||
self._show_snackbar("保存エラー", ft.Colors.RED)
|
||
|
||
def add_new(self, e):
|
||
"""新規顧客を追加"""
|
||
self.editing_id = None
|
||
self.clear_form()
|
||
self.form_fields.controls[0].focus()
|
||
|
||
logging.info("新規顧客追加モード")
|
||
|
||
def delete_selected(self, e):
|
||
"""選択中の顧客を削除"""
|
||
if self.editing_id:
|
||
try:
|
||
conn = sqlite3.connect('sales.db')
|
||
cursor = conn.cursor()
|
||
|
||
cursor.execute('''
|
||
DELETE FROM customers WHERE id = ?
|
||
''', (self.editing_id,))
|
||
|
||
conn.commit()
|
||
conn.close()
|
||
|
||
self._show_snackbar("顧客を削除しました", ft.Colors.GREEN)
|
||
logging.info(f"顧客削除完了: ID={self.editing_id}")
|
||
|
||
# データ再読み込み
|
||
self._load_data()
|
||
|
||
self.editing_id = None
|
||
self.clear_form()
|
||
|
||
except Exception as ex:
|
||
logging.error(f"顧客削除エラー: {ex}")
|
||
self._show_snackbar("削除エラー", ft.Colors.RED)
|
||
|
||
def clear_form(self, e):
|
||
"""フォームをクリア"""
|
||
for field_control in self.form_fields.controls:
|
||
field_control.value = ''
|
||
|
||
self.editing_id = None
|
||
self.page.update()
|
||
logging.info("顧客フォームをクリア")
|
||
|
||
def get_gps_info(self, e):
|
||
"""GPS情報を取得"""
|
||
try:
|
||
# GPS情報を取得(モックアップ)
|
||
location = geocoder.Nominatim(user_agent="MyApp/1.0")
|
||
location = "東京駅, 日本"
|
||
|
||
# 住所を取得
|
||
address = geocoder.geocode(location, exactly_one=True)
|
||
if address:
|
||
self.gps_info.value = f"緯度: {address.lat}, 経度: {address.lng}"
|
||
self.gps_info.color = ft.Colors.GREEN
|
||
logging.info(f"GPS情報取得成功: {address.address}")
|
||
else:
|
||
self.gps_info.value = "GPS情報取得失敗"
|
||
self.gps_info.color = ft.Colors.RED
|
||
logging.error("GPS情報取得失敗")
|
||
|
||
except Exception as ex:
|
||
logging.error(f"GPS情報取得エラー: {ex}")
|
||
self._show_snackbar("GPSエラー", ft.Colors.RED)
|
||
|
||
def _load_data(self):
|
||
"""顧客データを読み込む"""
|
||
try:
|
||
conn = sqlite3.connect('sales.db')
|
||
cursor = conn.cursor()
|
||
|
||
cursor.execute('''
|
||
SELECT * FROM customers
|
||
ORDER BY id
|
||
''')
|
||
|
||
self.current_data = cursor.fetchall()
|
||
conn.close()
|
||
|
||
# データリストを更新
|
||
self.data_list.controls.clear()
|
||
for item in self.current_data:
|
||
item_id, name, phone, email, address, latitude, longitude, address_gps, gps_timestamp, created_at = item
|
||
|
||
# データ行
|
||
row_controls = []
|
||
|
||
# 基本情報
|
||
for i, field_name in enumerate(['name', 'phone', 'email', 'address']):
|
||
if i < 4: # 基本フィールド
|
||
value = item[i+1] if i < len(item) else ''
|
||
else: # GPSフィールド
|
||
if i == 4: # address_gps
|
||
value = item[i+1]
|
||
else:
|
||
value = ''
|
||
else:
|
||
value = item[i+1]
|
||
|
||
row_controls.append(
|
||
ft.Text(f"{['name']}: {value}", size=12)
|
||
)
|
||
|
||
# GPS情報
|
||
if item.get('latitude') and item.get('longitude'):
|
||
gps_text = f"GPS: {item.get('latitude', '')}, {item.get('longitude', '')}"
|
||
gps_color = ft.Colors.GREEN
|
||
row_controls.append(
|
||
ft.Text(gps_text, size=10, color=gps_color)
|
||
)
|
||
|
||
# 操作ボタン
|
||
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"顧客データ読込エラー: {e}")
|
||
|
||
def _show_snackbar(self, message: str, color: ft.Colors):
|
||
"""SnackBarを表示"""
|
||
try:
|
||
self.page.snack_bar = ft.SnackBar(
|
||
content=ft.Text(message),
|
||
bgcolor=color
|
||
)
|
||
self.page.snack_bar.open = True
|
||
self.page.update()
|
||
except:
|
||
pass
|
||
|
||
def build(self):
|
||
"""UIを構築して返す"""
|
||
return ft.Column([
|
||
self.title,
|
||
self.gps_info,
|
||
ft.Divider(),
|
||
ft.Text("入力フォーム", size=18, weight=ft.FontWeight.BOLD),
|
||
self.form_fields,
|
||
ft.Row([self.save_btn, self.add_btn, self.delete_btn, self.clear_btn, self.gps_btn], spacing=10),
|
||
ft.Divider(),
|
||
ft.Text("顧客一覧", size=18, weight=ft.FontWeight.BOLD),
|
||
self.instructions,
|
||
self.data_list
|
||
], expand=True, spacing=15)
|
||
|
||
# 使用例
|
||
def create_customer_master_with_gps(page: ft.Page) -> CustomerMasterEditorWithGPS:
|
||
"""GPS対応顧客マスタ編集画面を作成"""
|
||
return CustomerMasterEditorWithGPS(page)
|