From 85263ebbf60233212344e9609464bd902c5126dc Mon Sep 17 00:00:00 2001 From: joe Date: Thu, 19 Feb 2026 22:48:41 +0900 Subject: [PATCH] FLET_FORCE_PIXEL_RATIO=1.0 GDK_SCALE=1 flet run app_compiz_shortcuts.py --- app.log | 1783 +++++++++++++++++++++ app_compiz_fixed.py | 807 ++++++++++ app_compiz_fixed_v2.py | 837 ++++++++++ app_compiz_shortcuts.py | 388 +++++ app_framework_demo.py | 114 ++ app_hierarchical_product_master.py | 133 ++ app_master_management.py | 156 ++ app_robust.py | 681 ++++++++ app_simple_working.py | 377 +++++ app_slip_framework_demo.py | 95 ++ app_text_editor.py | 427 +++++ app_theme_master.py | 521 ++++++ app_universal_master.py | 122 ++ components/customer_master_with_gps.py | 312 ++++ components/hierarchical_product_master.py | 771 +++++++++ components/master_editor.py | 340 ++++ components/slip_entry_framework.py | 482 ++++++ components/text_editor.py | 386 +++++ components/universal_master_editor.py | 348 ++++ debug_test.py | 9 + main.py | 38 +- main_simple.py | 311 ++++ minimal.py | 177 ++ run_4k.py | 47 + run_linux.py | 42 + 25 files changed, 9679 insertions(+), 25 deletions(-) create mode 100644 app.log create mode 100644 app_compiz_fixed.py create mode 100644 app_compiz_fixed_v2.py create mode 100644 app_compiz_shortcuts.py create mode 100644 app_framework_demo.py create mode 100644 app_hierarchical_product_master.py create mode 100644 app_master_management.py create mode 100644 app_robust.py create mode 100644 app_simple_working.py create mode 100644 app_slip_framework_demo.py create mode 100644 app_text_editor.py create mode 100644 app_theme_master.py create mode 100644 app_universal_master.py create mode 100644 components/customer_master_with_gps.py create mode 100644 components/hierarchical_product_master.py create mode 100644 components/master_editor.py create mode 100644 components/slip_entry_framework.py create mode 100644 components/text_editor.py create mode 100644 components/universal_master_editor.py create mode 100644 debug_test.py create mode 100644 main_simple.py create mode 100644 minimal.py create mode 100755 run_4k.py create mode 100755 run_linux.py diff --git a/app.log b/app.log new file mode 100644 index 0000000..1ac78ff --- /dev/null +++ b/app.log @@ -0,0 +1,1783 @@ +2026-02-19 17:28:59,111 - INFO - アプリケーション起動 +2026-02-19 17:28:59,113 - INFO - データベース初期化完了 +2026-02-19 17:28:59,113 - ERROR - Unhandled error in main() handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/app.py", line 271, in on_session_created + main(session.page) + File "/home/user/dev/h-1.flet.3/minimal.py", line 102, in main + update_list() + ^^^^^^^^^^^ +UnboundLocalError: cannot access local variable 'update_list' where it is not associated with a value +2026-02-19 17:30:37,742 - INFO - アプリケーション終了処理開始 +2026-02-19 17:30:37,742 - INFO - アプリケーション正常終了 +2026-02-19 17:30:37,746 - INFO - Session was garbage collected: SmUklbjtRAI1nphK +2026-02-19 17:30:41,622 - INFO - アプリケーション起動 +2026-02-19 17:30:41,625 - INFO - データベース初期化完了 +2026-02-19 17:30:41,627 - ERROR - Unhandled error in main() handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/app.py", line 271, in on_session_created + main(session.page) + File "/home/user/dev/h-1.flet.3/minimal.py", line 105, in main + add_btn = ft.Button("追加", on_click=add_sale_clicked) + ^^^^^^^^^^^^^^^^ +UnboundLocalError: cannot access local variable 'add_sale_clicked' where it is not associated with a value +2026-02-19 17:30:58,107 - INFO - Session was garbage collected: QZUFd21BGUIkJxfi +2026-02-19 17:30:58,126 - INFO - アプリケーション終了処理開始 +2026-02-19 17:30:58,126 - INFO - アプリケーション正常終了 +2026-02-19 17:31:11,609 - INFO - アプリケーション起動 +2026-02-19 17:31:11,610 - INFO - データベース初期化完了 +2026-02-19 17:31:11,611 - ERROR - Unhandled error in main() handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/app.py", line 271, in on_session_created + main(session.page) + File "/home/user/dev/h-1.flet.3/minimal.py", line 105, in main + add_btn = ft.Button("追加", on_click=add_sale_clicked) + ^^^^^^^^^^^^^^^^ +UnboundLocalError: cannot access local variable 'add_sale_clicked' where it is not associated with a value +2026-02-19 17:31:28,580 - INFO - Session was garbage collected: PWaPWZ3Im9gX0udX +2026-02-19 17:31:28,591 - INFO - アプリケーション終了処理開始 +2026-02-19 17:31:28,591 - INFO - アプリケーション正常終了 +2026-02-19 17:31:48,306 - INFO - アプリケーション起動 +2026-02-19 17:31:48,306 - INFO - データベース初期化完了 +2026-02-19 17:31:48,307 - ERROR - Unhandled error in main() handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/app.py", line 271, in on_session_created + main(session.page) + File "/home/user/dev/h-1.flet.3/minimal.py", line 105, in main + add_btn = ft.Button("追加", on_click=add_sale_clicked) + ^^^^^^^^^^^^^^^^ +UnboundLocalError: cannot access local variable 'add_sale_clicked' where it is not associated with a value +2026-02-19 17:31:52,995 - INFO - Session was garbage collected: 5YCxmVpVc2QujkzJ +2026-02-19 17:31:53,002 - INFO - アプリケーション終了処理開始 +2026-02-19 17:31:53,003 - INFO - アプリケーション正常終了 +2026-02-19 17:33:10,977 - INFO - アプリケーション起動 +2026-02-19 17:33:10,978 - INFO - データベース初期化完了 +2026-02-19 17:33:10,978 - ERROR - リスト更新エラー: no such column: customer +2026-02-19 17:33:10,979 - INFO - UI初期化完了 +2026-02-19 17:33:27,861 - ERROR - 保存エラー: table sales has no column named customer +2026-02-19 17:33:39,091 - ERROR - 保存エラー: table sales has no column named customer +2026-02-19 17:33:44,369 - INFO - Session was garbage collected: tpofJ1WKLOrjyN8g +2026-02-19 17:33:44,370 - INFO - アプリケーション終了処理開始 +2026-02-19 17:33:44,371 - INFO - アプリケーション正常終了 +2026-02-19 17:35:03,010 - INFO - アプリケーション起動 +2026-02-19 17:35:03,023 - INFO - データベース初期化完了 +2026-02-19 17:35:03,025 - INFO - UI初期化完了 +2026-02-19 17:35:09,697 - INFO - 売上データ追加: +2026-02-19 17:35:16,946 - INFO - 売上データ追加: +2026-02-19 17:35:24,336 - INFO - Session was garbage collected: rgY1SGL7e1FaFzNb +2026-02-19 17:35:24,344 - INFO - アプリケーション終了処理開始 +2026-02-19 17:35:24,344 - INFO - アプリケーション正常終了 +2026-02-19 17:38:25,649 - INFO - アプリケーション起動 +2026-02-19 17:38:25,650 - INFO - データベース初期化完了 +2026-02-19 17:38:25,651 - INFO - UI初期化完了 +2026-02-19 17:38:39,906 - INFO - 売上データ追加: c cc 3 +2026-02-19 17:38:48,777 - INFO - 売上データ追加: d dd 4 +2026-02-19 17:38:59,404 - INFO - Session was garbage collected: NFYWZWucd8jD4CaW +2026-02-19 17:38:59,413 - INFO - アプリケーション終了処理開始 +2026-02-19 17:38:59,414 - INFO - アプリケーション正常終了 +2026-02-19 18:51:55,313 - INFO - データベース初期化完了 +2026-02-19 18:51:55,315 - ERROR - ダミーデータ生成エラー: 3 values for 4 columns +2026-02-19 18:51:55,330 - ERROR - ページ遷移エラー (dashboard): DashboardPage.__init__() missing 1 required positional argument: 'page_manager' +2026-02-19 18:51:55,334 - INFO - アプリケーション起動完了 +2026-02-19 18:51:58,184 - ERROR - ページ遷移エラー (dashboard): DashboardPage.__init__() missing 1 required positional argument: 'page_manager' +2026-02-19 18:51:58,852 - ERROR - ページ遷移エラー (sales): SalesPage.__init__() missing 1 required positional argument: 'page_manager' +2026-02-19 18:51:59,801 - ERROR - ページ遷移エラー (customers): CustomerPage.__init__() missing 1 required positional argument: 'page_manager' +2026-02-19 18:52:01,149 - ERROR - ページ遷移エラー (products): ProductPage.__init__() missing 1 required positional argument: 'page_manager' +2026-02-19 18:52:01,845 - ERROR - ページ遷移エラー (customers): CustomerPage.__init__() missing 1 required positional argument: 'page_manager' +2026-02-19 18:52:05,998 - ERROR - ページ遷移エラー (sales): SalesPage.__init__() missing 1 required positional argument: 'page_manager' +2026-02-19 18:52:07,184 - ERROR - ページ遷移エラー (customers): CustomerPage.__init__() missing 1 required positional argument: 'page_manager' +2026-02-19 18:52:08,051 - ERROR - ページ遷移エラー (products): ProductPage.__init__() missing 1 required positional argument: 'page_manager' +2026-02-19 18:52:09,448 - ERROR - ページ遷移エラー (dashboard): DashboardPage.__init__() missing 1 required positional argument: 'page_manager' +2026-02-19 18:52:15,497 - INFO - Session was garbage collected: nIcscR86bZWkbc5D +2026-02-19 18:52:15,503 - INFO - アプリケーション終了処理開始 +2026-02-19 18:52:15,503 - INFO - アプリケーション正常終了 +2026-02-19 19:18:39,066 - INFO - データベース初期化完了 +2026-02-19 19:18:39,068 - ERROR - ダミーデータ生成エラー: table sales has no column named customer_id +2026-02-19 19:18:39,082 - ERROR - ページ表示エラー (dashboard): can not serialize 'DashboardPage' object +2026-02-19 19:18:39,084 - INFO - アプリケーション起動完了 +2026-02-19 19:18:47,318 - ERROR - ページ表示エラー (dashboard): can not serialize 'DashboardPage' object +2026-02-19 19:18:48,296 - ERROR - ページ表示エラー (sales): can not serialize 'SalesPage' object +2026-02-19 19:18:49,183 - ERROR - ページ表示エラー (customers): can not serialize 'CustomerPage' object +2026-02-19 19:18:50,343 - ERROR - ページ表示エラー (products): can not serialize 'ProductPage' object +2026-02-19 19:18:56,495 - INFO - Session was garbage collected: uExM11AVkcTg7aKg +2026-02-19 19:18:56,516 - INFO - アプリケーション終了処理開始 +2026-02-19 19:18:56,517 - INFO - アプリケーション正常終了 +2026-02-19 20:26:44,046 - INFO - データベース初期化完了 +2026-02-19 20:26:44,096 - INFO - ページ遷移成功: dashboard +2026-02-19 20:26:44,097 - INFO - アプリケーション起動完了 +2026-02-19 20:26:51,139 - INFO - ページ遷移成功: dashboard +2026-02-19 20:26:52,151 - INFO - ページ遷移成功: sales +2026-02-19 20:26:57,696 - INFO - ページ遷移成功: dashboard +2026-02-19 20:27:00,799 - INFO - ページ遷移成功: customers +2026-02-19 20:27:11,560 - INFO - ページ遷移成功: products +2026-02-19 20:34:54,303 - INFO - ページ遷移成功: dashboard +2026-02-19 20:34:59,052 - INFO - ページ遷移成功: sales +2026-02-19 20:34:59,858 - INFO - ページ遷移成功: customers +2026-02-19 20:35:01,439 - INFO - ページ遷移成功: customers +2026-02-19 20:35:02,038 - INFO - ページ遷移成功: sales +2026-02-19 20:35:23,650 - INFO - ページ遷移成功: dashboard +2026-02-19 20:35:27,004 - INFO - ページ遷移成功: products +2026-02-19 20:35:32,512 - INFO - アプリケーション終了処理開始 +2026-02-19 20:35:32,513 - INFO - アプリケーション正常終了 +2026-02-19 20:35:32,515 - INFO - Session was garbage collected: GK6X2SmohDjzuOyU +2026-02-19 20:35:32,578 - ERROR - Task exception was never retrieved +future: exception=SystemExit(0)> +Traceback (most recent call last): + File "/home/user/dev/h-1.flet.3/app_robust.py", line 681, in + ft.run(main) + File "/home/user/.venv/lib/python3.12/site-packages/flet/app.py", line 96, in run + return asyncio.run( + ^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run + return runner.run(main) + ^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run + return self._loop.run_until_complete(task) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/base_events.py", line 674, in run_until_complete + self.run_forever() + File "/usr/lib/python3.12/asyncio/base_events.py", line 641, in run_forever + self._run_once() + File "/usr/lib/python3.12/asyncio/base_events.py", line 1987, in _run_once + handle._run() + File "/usr/lib/python3.12/asyncio/events.py", line 88, in _run + self._context.run(self._callback, *self._args) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 346, in _trigger_event + await session.after_event(session.index.get(self._i)) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 263, in after_event + await self.__auto_update(control) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 275, in __auto_update + control.update() + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 452, in update + self.__update(self) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 461, in __update + self.session.patch_control(c) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 125, in patch_control + patch, added_controls, removed_controls = self.__get_update_control_patch( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 301, in __get_update_control_patch + patch, added_controls, removed_controls = ObjectPatch.from_diff( + ^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 245, in from_diff + builder._compare_values(parent, path or [], None, src, dst, frozen=frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1049, in _compare_values + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 617, in _compare_lists + logger.debug(f"\n_compare_lists: {path} {src} {dst}") + ^^^^^ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/home/user/dev/h-1.flet.3/app_robust.py", line 435, in _signal_handler + sys.exit(0) +SystemExit: 0 +2026-02-19 20:37:14,153 - INFO - データベース初期化完了 +2026-02-19 20:37:14,174 - INFO - ページ遷移成功: dashboard +2026-02-19 20:37:14,174 - INFO - アプリケーション起動完了 +2026-02-19 20:37:19,134 - INFO - ページ遷移成功: sales +2026-02-19 20:37:34,924 - INFO - ページ遷移成功: customers +2026-02-19 20:37:38,094 - INFO - ページ遷移成功: products +2026-02-19 20:37:39,541 - INFO - ページ遷移成功: products +2026-02-19 20:37:42,010 - INFO - ページ遷移成功: customers +2026-02-19 20:37:44,915 - INFO - Session was garbage collected: Bv112eTpvuoudI7Q +2026-02-19 20:37:44,928 - INFO - アプリケーション終了処理開始 +2026-02-19 20:37:44,929 - INFO - アプリケーション正常終了 +2026-02-19 20:37:54,626 - INFO - データベース初期化完了 +2026-02-19 20:37:54,648 - INFO - ページ遷移成功: dashboard +2026-02-19 20:37:54,648 - INFO - アプリケーション起動完了 +2026-02-19 20:38:04,815 - INFO - Session was garbage collected: BeYemr2acqSluBNH +2026-02-19 20:38:04,817 - INFO - アプリケーション終了処理開始 +2026-02-19 20:38:04,817 - INFO - アプリケーション正常終了 +2026-02-19 20:40:47,807 - INFO - データベース初期化完了 +2026-02-19 20:40:47,844 - INFO - ページ遷移成功: dashboard +2026-02-19 20:40:47,844 - INFO - アプリケーション起動完了 +2026-02-19 20:40:50,138 - INFO - ページ遷移成功: sales +2026-02-19 20:40:51,005 - INFO - ページ遷移成功: customers +2026-02-19 20:40:51,878 - INFO - ページ遷移成功: products +2026-02-19 20:40:52,904 - INFO - ページ遷移成功: dashboard +2026-02-19 20:41:01,613 - INFO - ページ遷移成功: sales +2026-02-19 20:42:13,292 - INFO - ページ遷移成功: dashboard +2026-02-19 20:43:51,441 - INFO - アプリケーション終了処理開始 +2026-02-19 20:43:51,441 - INFO - アプリケーション正常終了 +2026-02-19 20:43:51,447 - INFO - Session was garbage collected: X7M3te9p008sClPI +2026-02-19 20:43:51,526 - ERROR - Task exception was never retrieved +future: exception=SystemExit(0)> +Traceback (most recent call last): + File "/home/user/dev/h-1.flet.3/app_robust.py", line 681, in + ft.run(main) + File "/home/user/.venv/lib/python3.12/site-packages/flet/app.py", line 96, in run + return asyncio.run( + ^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run + return runner.run(main) + ^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run + return self._loop.run_until_complete(task) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/base_events.py", line 674, in run_until_complete + self.run_forever() + File "/usr/lib/python3.12/asyncio/base_events.py", line 641, in run_forever + self._run_once() + File "/usr/lib/python3.12/asyncio/base_events.py", line 1987, in _run_once + handle._run() + File "/usr/lib/python3.12/asyncio/events.py", line 88, in _run + self._context.run(self._callback, *self._args) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 346, in _trigger_event + await session.after_event(session.index.get(self._i)) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 263, in after_event + await self.__auto_update(control) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 275, in __auto_update + control.update() + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 452, in update + self.__update(self) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 461, in __update + self.session.patch_control(c) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 125, in patch_control + patch, added_controls, removed_controls = self.__get_update_control_patch( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 301, in __get_update_control_patch + patch, added_controls, removed_controls = ObjectPatch.from_diff( + ^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 245, in from_diff + builder._compare_values(parent, path or [], None, src, dst, frozen=frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1049, in _compare_values + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 990, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1049, in _compare_values + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 617, in _compare_lists + logger.debug(f"\n_compare_lists: {path} {src} {dst}") + ^^^^^ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/enum.py", line 1230, in __repr__ + return "<%s.%s: %s>" % (self.__class__.__name__, self._name_, v_repr(self._value_)) + ^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/dev/h-1.flet.3/app_robust.py", line 435, in _signal_handler + sys.exit(0) +SystemExit: 0 +2026-02-19 20:46:49,840 - INFO - データベース初期化完了 +2026-02-19 20:46:49,841 - ERROR - アプリケーション起動エラー: property 'page' of 'DashboardView' object has no setter +2026-02-19 20:47:01,280 - INFO - アプリケーション終了処理開始 +2026-02-19 20:47:01,280 - INFO - アプリケーション正常終了 +2026-02-19 20:47:01,282 - INFO - Session was garbage collected: d9rkXRKRKTQMW0FV +2026-02-19 20:47:05,192 - INFO - データベース初期化完了 +2026-02-19 20:47:05,193 - ERROR - アプリケーション起動エラー: property 'page' of 'DashboardView' object has no setter +2026-02-19 20:48:14,329 - INFO - アプリケーション終了処理開始 +2026-02-19 20:48:14,329 - INFO - アプリケーション正常終了 +2026-02-19 20:48:14,331 - INFO - Session was garbage collected: cd6jAdoCyrI0ygXc +2026-02-19 20:48:18,977 - INFO - データベース初期化完了 +2026-02-19 20:48:18,978 - ERROR - アプリケーション起動エラー: property 'page' of 'DashboardView' object has no setter +2026-02-19 20:48:28,402 - INFO - Session was garbage collected: FWAtOWxi5Twrv95i +2026-02-19 20:48:28,407 - INFO - アプリケーション終了処理開始 +2026-02-19 20:48:28,407 - INFO - アプリケーション正常終了 +2026-02-19 20:50:33,563 - INFO - データベース初期化完了 +2026-02-19 20:50:33,564 - ERROR - アプリケーション起動エラー: property 'page' of 'DashboardView' object has no setter +2026-02-19 20:50:39,929 - INFO - Session was garbage collected: Fl0nc6XbLfD3Tn43 +2026-02-19 20:50:39,930 - INFO - アプリケーション終了処理開始 +2026-02-19 20:50:39,930 - INFO - アプリケーション正常終了 +2026-02-19 20:53:58,179 - INFO - データベース初期化完了 +2026-02-19 20:53:58,180 - ERROR - アプリケーション起動エラー: property 'page' of 'DashboardView' object has no setter +2026-02-19 20:54:09,026 - INFO - Session was garbage collected: 0A8EiVfYNSsoMXjw +2026-02-19 20:54:09,037 - INFO - アプリケーション終了処理開始 +2026-02-19 20:54:09,037 - INFO - アプリケーション正常終了 +2026-02-19 20:56:55,377 - INFO - データベース初期化完了 +2026-02-19 20:56:55,378 - ERROR - アプリケーション起動エラー: property 'page' of 'DashboardView' object has no setter +2026-02-19 20:57:01,480 - INFO - Session was garbage collected: xIxXgnyzkoxLBqzJ +2026-02-19 20:57:01,514 - INFO - アプリケーション終了処理開始 +2026-02-19 20:57:01,516 - INFO - アプリケーション正常終了 +2026-02-19 20:57:16,234 - INFO - データベース初期化完了 +2026-02-19 20:57:16,373 - INFO - キーボードショートカット設定完了 +2026-02-19 20:57:16,374 - INFO - アプリケーション起動完了 +2026-02-19 20:57:20,085 - ERROR - Unhandled error in 'on_keyboard_event' handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 344, in _trigger_event + event_handler(e) + File "/home/user/dev/h-1.flet.3/app_compiz_fixed_v2.py", line 800, in on_keyboard + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/typing.py", line 1157, in __call__ + result = self.__origin__(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TypeError: Event.__init__() missing 1 required positional argument: 'name' +2026-02-19 20:57:22,738 - ERROR - Unhandled error in 'on_keyboard_event' handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 344, in _trigger_event + event_handler(e) + File "/home/user/dev/h-1.flet.3/app_compiz_fixed_v2.py", line 803, in on_keyboard + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/typing.py", line 1157, in __call__ + result = self.__origin__(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TypeError: Event.__init__() missing 1 required positional argument: 'name' +2026-02-19 20:57:23,284 - ERROR - Unhandled error in 'on_keyboard_event' handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 344, in _trigger_event + event_handler(e) + File "/home/user/dev/h-1.flet.3/app_compiz_fixed_v2.py", line 797, in on_keyboard + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/typing.py", line 1157, in __call__ + result = self.__origin__(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TypeError: Event.__init__() missing 1 required positional argument: 'name' +2026-02-19 20:57:23,915 - ERROR - Unhandled error in 'on_keyboard_event' handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 344, in _trigger_event + event_handler(e) + File "/home/user/dev/h-1.flet.3/app_compiz_fixed_v2.py", line 797, in on_keyboard + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/typing.py", line 1157, in __call__ + result = self.__origin__(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TypeError: Event.__init__() missing 1 required positional argument: 'name' +2026-02-19 20:57:24,250 - ERROR - Unhandled error in 'on_keyboard_event' handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 344, in _trigger_event + event_handler(e) + File "/home/user/dev/h-1.flet.3/app_compiz_fixed_v2.py", line 800, in on_keyboard + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/typing.py", line 1157, in __call__ + result = self.__origin__(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TypeError: Event.__init__() missing 1 required positional argument: 'name' +2026-02-19 20:57:24,592 - ERROR - Unhandled error in 'on_keyboard_event' handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 344, in _trigger_event + event_handler(e) + File "/home/user/dev/h-1.flet.3/app_compiz_fixed_v2.py", line 803, in on_keyboard + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/typing.py", line 1157, in __call__ + result = self.__origin__(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TypeError: Event.__init__() missing 1 required positional argument: 'name' +2026-02-19 20:57:25,000 - ERROR - Unhandled error in 'on_keyboard_event' handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 344, in _trigger_event + event_handler(e) + File "/home/user/dev/h-1.flet.3/app_compiz_fixed_v2.py", line 806, in on_keyboard + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/typing.py", line 1157, in __call__ + result = self.__origin__(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TypeError: Event.__init__() missing 1 required positional argument: 'name' +2026-02-19 20:57:25,618 - ERROR - Unhandled error in 'on_keyboard_event' handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 344, in _trigger_event + event_handler(e) + File "/home/user/dev/h-1.flet.3/app_compiz_fixed_v2.py", line 806, in on_keyboard + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/typing.py", line 1157, in __call__ + result = self.__origin__(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TypeError: Event.__init__() missing 1 required positional argument: 'name' +2026-02-19 20:57:26,267 - ERROR - Unhandled error in 'on_keyboard_event' handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 344, in _trigger_event + event_handler(e) + File "/home/user/dev/h-1.flet.3/app_compiz_fixed_v2.py", line 806, in on_keyboard + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/typing.py", line 1157, in __call__ + result = self.__origin__(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TypeError: Event.__init__() missing 1 required positional argument: 'name' +2026-02-19 20:57:26,520 - ERROR - Unhandled error in 'on_keyboard_event' handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 344, in _trigger_event + event_handler(e) + File "/home/user/dev/h-1.flet.3/app_compiz_fixed_v2.py", line 800, in on_keyboard + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/typing.py", line 1157, in __call__ + result = self.__origin__(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TypeError: Event.__init__() missing 1 required positional argument: 'name' +2026-02-19 20:57:27,377 - ERROR - Unhandled error in 'on_keyboard_event' handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 344, in _trigger_event + event_handler(e) + File "/home/user/dev/h-1.flet.3/app_compiz_fixed_v2.py", line 803, in on_keyboard + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/typing.py", line 1157, in __call__ + result = self.__origin__(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TypeError: Event.__init__() missing 1 required positional argument: 'name' +2026-02-19 20:57:32,669 - INFO - アプリケーション終了処理開始 +2026-02-19 20:57:32,669 - INFO - アプリケーション正常終了 +2026-02-19 20:57:32,671 - INFO - Session was garbage collected: jjJU5Gj5LoS7pLZm +2026-02-19 20:57:32,730 - ERROR - Task exception was never retrieved +future: exception=SystemExit(0)> +Traceback (most recent call last): + File "/home/user/dev/h-1.flet.3/app_compiz_fixed_v2.py", line 837, in + ft.run(main) + File "/home/user/.venv/lib/python3.12/site-packages/flet/app.py", line 96, in run + return asyncio.run( + ^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run + return runner.run(main) + ^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run + return self._loop.run_until_complete(task) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/base_events.py", line 674, in run_until_complete + self.run_forever() + File "/usr/lib/python3.12/asyncio/base_events.py", line 641, in run_forever + self._run_once() + File "/usr/lib/python3.12/asyncio/base_events.py", line 1987, in _run_once + handle._run() + File "/usr/lib/python3.12/asyncio/events.py", line 88, in _run + self._context.run(self._callback, *self._args) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 346, in _trigger_event + await session.after_event(session.index.get(self._i)) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 263, in after_event + await self.__auto_update(control) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 275, in __auto_update + control.update() + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 452, in update + self.__update(self) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 461, in __update + self.session.patch_control(c) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 125, in patch_control + patch, added_controls, removed_controls = self.__get_update_control_patch( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 301, in __get_update_control_patch + patch, added_controls, removed_controls = ObjectPatch.from_diff( + ^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 245, in from_diff + builder._compare_values(parent, path or [], None, src, dst, frozen=frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1049, in _compare_values + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 617, in _compare_lists + logger.debug(f"\n_compare_lists: {path} {src} {dst}") + ^^^^^ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/home/user/dev/h-1.flet.3/app_compiz_fixed_v2.py", line 584, in _signal_handler + sys.exit(0) +SystemExit: 0 +2026-02-19 21:00:01,908 - INFO - データベース初期化完了 +2026-02-19 21:00:02,045 - INFO - キーボードショートカット設定完了 +2026-02-19 21:00:02,045 - INFO - アプリケーション起動完了 +2026-02-19 21:00:04,046 - ERROR - Unhandled error in 'on_keyboard_event' handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 344, in _trigger_event + event_handler(e) + File "/home/user/dev/h-1.flet.3/app_compiz_fixed_v2.py", line 797, in on_keyboard + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/typing.py", line 1157, in __call__ + result = self.__origin__(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TypeError: Event.__init__() missing 1 required positional argument: 'name' +2026-02-19 21:00:05,148 - ERROR - Unhandled error in 'on_keyboard_event' handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 344, in _trigger_event + event_handler(e) + File "/home/user/dev/h-1.flet.3/app_compiz_fixed_v2.py", line 800, in on_keyboard + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/typing.py", line 1157, in __call__ + result = self.__origin__(*args, **kwargs) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TypeError: Event.__init__() missing 1 required positional argument: 'name' +2026-02-19 21:00:08,548 - INFO - アプリケーション終了処理開始 +2026-02-19 21:00:08,548 - INFO - アプリケーション正常終了 +2026-02-19 21:00:08,648 - INFO - Session was garbage collected: 7ogqTsPKI5MUr2zj +2026-02-19 21:03:26,329 - INFO - データベース初期化完了 +2026-02-19 21:03:26,339 - INFO - アプリケーション起動完了 +2026-02-19 21:03:50,527 - INFO - 売上データ追加: a a 1.0 +2026-02-19 21:03:59,721 - INFO - 売上データ追加: b 2 2.0 +2026-02-19 21:04:36,723 - INFO - 売上データ追加: c c 3.0 +2026-02-19 21:07:45,184 - INFO - アプリケーション正常終了 +2026-02-19 21:07:45,205 - INFO - Session was garbage collected: ciOiULQFgkRRVNPW +2026-02-19 21:08:51,188 - INFO - データベース初期化完了 +2026-02-19 21:08:51,209 - INFO - テキストエディタ起動完了 +2026-02-19 21:09:21,788 - INFO - 下書き保存: 24 文字 +2026-02-19 21:09:23,474 - INFO - 下書き保存: 24 文字 +2026-02-19 21:09:23,747 - INFO - 下書き保存: 24 文字 +2026-02-19 21:09:23,950 - INFO - 下書き保存: 24 文字 +2026-02-19 21:09:25,866 - INFO - 下書き保存: 24 文字 +2026-02-19 21:09:29,112 - INFO - 下書き保存: 24 文字 +2026-02-19 21:09:33,424 - INFO - 下書き読込完了: 6 件 +2026-02-19 21:09:43,231 - INFO - 下書き読込: ID=1 +2026-02-19 21:09:44,519 - INFO - 下書き読込: ID=1 +2026-02-19 21:09:56,376 - INFO - アプリケーション正常終了 +2026-02-19 21:09:56,380 - INFO - Session was garbage collected: E3fHQBsAbloOxWzt +2026-02-19 21:09:56,447 - ERROR - Task exception was never retrieved +future: exception=SystemExit(0)> +Traceback (most recent call last): + File "/home/user/dev/h-1.flet.3/app_text_editor.py", line 427, in + ft.run(main) + File "/home/user/.venv/lib/python3.12/site-packages/flet/app.py", line 96, in run + return asyncio.run( + ^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run + return runner.run(main) + ^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run + return self._loop.run_until_complete(task) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/base_events.py", line 674, in run_until_complete + self.run_forever() + File "/usr/lib/python3.12/asyncio/base_events.py", line 641, in run_forever + self._run_once() + File "/usr/lib/python3.12/asyncio/base_events.py", line 1987, in _run_once + handle._run() + File "/usr/lib/python3.12/asyncio/events.py", line 88, in _run + self._context.run(self._callback, *self._args) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 346, in _trigger_event + await session.after_event(session.index.get(self._i)) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 263, in after_event + await self.__auto_update(control) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 275, in __auto_update + control.update() + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 452, in update + self.__update(self) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 461, in __update + self.session.patch_control(c) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 125, in patch_control + patch, added_controls, removed_controls = self.__get_update_control_patch( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 301, in __get_update_control_patch + patch, added_controls, removed_controls = ObjectPatch.from_diff( + ^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 245, in from_diff + builder._compare_values(parent, path or [], None, src, dst, frozen=frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1049, in _compare_values + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 617, in _compare_lists + logger.debug(f"\n_compare_lists: {path} {src} {dst}") + ^^^^^ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/home/user/dev/h-1.flet.3/app_text_editor.py", line 365, in signal_handler + sys.exit(0) +SystemExit: 0 +2026-02-19 21:44:12,019 - INFO - ショートカットキー設定完了 +2026-02-19 21:44:12,019 - ERROR - アプリケーション起動エラー: module 'flet.controls.alignment' has no attribute 'center' +2026-02-19 21:44:26,288 - INFO - Session was garbage collected: 8nA043SAkEFumeFY +2026-02-19 21:44:26,291 - INFO - アプリケーション正常終了 +2026-02-19 21:45:17,755 - INFO - ショートカットキー設定完了 +2026-02-19 21:45:17,787 - INFO - Compiz対応ショートカットキーアプリ起動完了 +2026-02-19 21:45:33,664 - INFO - 機能選択: ダッシュボード +2026-02-19 21:45:34,784 - INFO - 機能選択: 売上管理 +2026-02-19 21:45:35,420 - INFO - 機能選択: 顧客管理 +2026-02-19 21:45:36,409 - INFO - 機能選択: ダッシュボード +2026-02-19 21:45:36,926 - INFO - 機能選択: 売上管理 +2026-02-19 21:45:37,572 - INFO - 売上管理機能実行 +2026-02-19 21:45:38,049 - INFO - 売上管理機能実行 +2026-02-19 21:45:39,530 - INFO - 売上管理機能実行 +2026-02-19 21:45:41,141 - INFO - 売上管理機能実行 +2026-02-19 21:45:41,187 - INFO - 売上管理機能実行 +2026-02-19 21:45:42,004 - INFO - 売上管理機能実行 +2026-02-19 21:45:42,143 - INFO - 売上管理機能実行 +2026-02-19 21:45:42,542 - INFO - 売上管理機能実行 +2026-02-19 21:45:42,612 - INFO - 売上管理機能実行 +2026-02-19 21:45:42,713 - INFO - 売上管理機能実行 +2026-02-19 21:45:42,766 - INFO - 売上管理機能実行 +2026-02-19 21:45:42,880 - INFO - 売上管理機能実行 +2026-02-19 21:45:42,936 - INFO - 売上管理機能実行 +2026-02-19 21:45:43,163 - INFO - 売上管理機能実行 +2026-02-19 21:45:43,225 - INFO - 売上管理機能実行 +2026-02-19 21:45:43,405 - INFO - 売上管理機能実行 +2026-02-19 21:45:43,477 - INFO - 売上管理機能実行 +2026-02-19 21:45:43,643 - INFO - 売上管理機能実行 +2026-02-19 21:45:43,699 - INFO - 売上管理機能実行 +2026-02-19 21:45:43,855 - INFO - 売上管理機能実行 +2026-02-19 21:45:43,909 - INFO - 売上管理機能実行 +2026-02-19 21:45:44,484 - INFO - 機能選択: 顧客管理 +2026-02-19 21:45:45,409 - INFO - 顧客管理機能実行 +2026-02-19 21:45:45,517 - INFO - 顧客管理機能実行 +2026-02-19 21:45:45,601 - INFO - 顧客管理機能実行 +2026-02-19 21:45:45,675 - INFO - 顧客管理機能実行 +2026-02-19 21:45:45,758 - INFO - 顧客管理機能実行 +2026-02-19 21:45:45,809 - INFO - 顧客管理機能実行 +2026-02-19 21:45:45,964 - INFO - 顧客管理機能実行 +2026-02-19 21:45:46,022 - INFO - 顧客管理機能実行 +2026-02-19 21:45:46,204 - INFO - 顧客管理機能実行 +2026-02-19 21:45:46,291 - INFO - 顧客管理機能実行 +2026-02-19 21:45:46,489 - INFO - 顧客管理機能実行 +2026-02-19 21:45:46,574 - INFO - 顧客管理機能実行 +2026-02-19 21:45:46,852 - INFO - 機能選択: 商品管理 +2026-02-19 21:45:47,304 - INFO - 商品管理機能実行 +2026-02-19 21:45:47,362 - INFO - 商品管理機能実行 +2026-02-19 21:45:47,510 - INFO - 商品管理機能実行 +2026-02-19 21:45:47,563 - INFO - 商品管理機能実行 +2026-02-19 21:45:47,693 - INFO - 商品管理機能実行 +2026-02-19 21:45:47,758 - INFO - 商品管理機能実行 +2026-02-19 21:45:48,333 - INFO - 商品管理機能実行 +2026-02-19 21:45:48,416 - INFO - 商品管理機能実行 +2026-02-19 21:45:48,678 - INFO - 商品管理機能実行 +2026-02-19 21:45:48,729 - INFO - 商品管理機能実行 +2026-02-19 21:45:49,402 - INFO - 機能選択: 商品管理 +2026-02-19 21:45:50,056 - INFO - 機能選択: ダッシュボード +2026-02-19 21:45:50,166 - INFO - 機能選択: ダッシュボード +2026-02-19 21:45:50,788 - INFO - ダッシュボード機能実行 +2026-02-19 21:45:50,868 - INFO - ダッシュボード機能実行 +2026-02-19 21:45:51,315 - INFO - 機能選択: 売上管理 +2026-02-19 21:45:52,062 - INFO - 売上管理機能実行 +2026-02-19 21:45:52,164 - INFO - 売上管理機能実行 +2026-02-19 21:45:52,272 - INFO - 売上管理機能実行 +2026-02-19 21:45:52,329 - INFO - 売上管理機能実行 +2026-02-19 21:45:52,473 - INFO - 売上管理機能実行 +2026-02-19 21:45:52,528 - INFO - 売上管理機能実行 +2026-02-19 21:45:52,925 - INFO - 機能選択: 売上管理 +2026-02-19 21:45:53,191 - INFO - 機能選択: 顧客管理 +2026-02-19 21:45:53,716 - INFO - 顧客管理機能実行 +2026-02-19 21:45:53,811 - INFO - 顧客管理機能実行 +2026-02-19 21:45:54,005 - INFO - 顧客管理機能実行 +2026-02-19 21:45:54,057 - INFO - 顧客管理機能実行 +2026-02-19 21:45:54,310 - INFO - 機能選択: ダッシュボード +2026-02-19 21:45:54,672 - INFO - 機能選択: 売上管理 +2026-02-19 21:45:55,189 - INFO - 売上管理機能実行 +2026-02-19 21:45:55,273 - INFO - 売上管理機能実行 +2026-02-19 21:45:55,464 - INFO - 売上管理機能実行 +2026-02-19 21:45:55,516 - INFO - 売上管理機能実行 +2026-02-19 21:45:55,759 - INFO - 売上管理機能実行 +2026-02-19 21:45:55,816 - INFO - 売上管理機能実行 +2026-02-19 21:46:00,980 - INFO - アプリケーション正常終了 +2026-02-19 21:46:00,982 - INFO - Session was garbage collected: iKxgPyddiACoX2OP +2026-02-19 21:46:01,035 - ERROR - Task exception was never retrieved +future: exception=SystemExit(0)> +Traceback (most recent call last): + File "/home/user/dev/h-1.flet.3/app_compiz_shortcuts.py", line 289, in + ft.run(main) + File "/home/user/.venv/lib/python3.12/site-packages/flet/app.py", line 96, in run + return asyncio.run( + ^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run + return runner.run(main) + ^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run + return self._loop.run_until_complete(task) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/base_events.py", line 674, in run_until_complete + self.run_forever() + File "/usr/lib/python3.12/asyncio/base_events.py", line 641, in run_forever + self._run_once() + File "/usr/lib/python3.12/asyncio/base_events.py", line 1987, in _run_once + handle._run() + File "/usr/lib/python3.12/asyncio/events.py", line 88, in _run + self._context.run(self._callback, *self._args) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 346, in _trigger_event + await session.after_event(session.index.get(self._i)) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 263, in after_event + await self.__auto_update(control) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 275, in __auto_update + control.update() + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 452, in update + self.__update(self) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 461, in __update + self.session.patch_control(c) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 125, in patch_control + patch, added_controls, removed_controls = self.__get_update_control_patch( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 301, in __get_update_control_patch + patch, added_controls, removed_controls = ObjectPatch.from_diff( + ^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 245, in from_diff + builder._compare_values(parent, path or [], None, src, dst, frozen=frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1049, in _compare_values + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 617, in _compare_lists + logger.debug(f"\n_compare_lists: {path} {src} {dst}") + ^^^^^ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/home/user/dev/h-1.flet.3/app_compiz_shortcuts.py", line 33, in signal_handler + sys.exit(0) +SystemExit: 0 +2026-02-19 21:52:01,685 - INFO - ショートカットキー設定完了 +2026-02-19 21:52:01,731 - INFO - Compiz対応ショートカットキーアプリ起動完了 +2026-02-19 21:52:06,572 - INFO - 機能選択: ダッシュボード +2026-02-19 21:52:07,147 - INFO - ダッシュボード機能実行 +2026-02-19 21:52:07,677 - INFO - 機能選択: 売上管理 +2026-02-19 21:52:08,306 - INFO - 売上管理機能実行 +2026-02-19 21:52:08,825 - INFO - 売上管理機能実行 +2026-02-19 21:52:09,517 - INFO - 売上管理機能実行 +2026-02-19 21:52:10,144 - INFO - 売上管理機能実行 +2026-02-19 21:52:10,431 - INFO - 売上管理機能実行 +2026-02-19 21:52:10,861 - INFO - 売上管理機能実行 +2026-02-19 21:52:11,854 - INFO - 売上管理機能実行 +2026-02-19 21:52:12,468 - INFO - 売上管理機能実行 +2026-02-19 21:52:14,001 - INFO - 機能選択: 顧客管理 +2026-02-19 21:52:14,685 - INFO - 顧客管理機能実行 +2026-02-19 21:52:14,949 - INFO - 顧客管理機能実行 +2026-02-19 21:52:17,214 - INFO - 顧客管理機能実行 +2026-02-19 21:52:20,060 - INFO - 顧客管理機能実行 +2026-02-19 21:52:21,343 - INFO - 機能選択: 商品管理 +2026-02-19 21:52:22,546 - INFO - 機能選択: 伝票入力 +2026-02-19 21:52:24,390 - INFO - 機能選択: テキストエディタ +2026-02-19 21:52:28,016 - INFO - テキストエディタ機能実行 +2026-02-19 21:52:28,488 - INFO - テキストエディタ機能実行 +2026-02-19 21:52:30,569 - INFO - テキストエディタ機能実行 +2026-02-19 21:53:03,916 - INFO - テキストエディタ機能実行 +2026-02-19 21:53:04,315 - INFO - テキストエディタ機能実行 +2026-02-19 21:53:04,510 - INFO - テキストエディタ機能実行 +2026-02-19 21:53:08,902 - INFO - アプリケーション正常終了 +2026-02-19 21:53:08,903 - INFO - Session was garbage collected: sVBHOTPwPdHXq26H +2026-02-19 21:53:08,959 - ERROR - Task exception was never retrieved +future: exception=SystemExit(0)> +Traceback (most recent call last): + File "/home/user/dev/h-1.flet.3/app_compiz_shortcuts.py", line 303, in + ft.run(main) + File "/home/user/.venv/lib/python3.12/site-packages/flet/app.py", line 96, in run + return asyncio.run( + ^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run + return runner.run(main) + ^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run + return self._loop.run_until_complete(task) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/base_events.py", line 674, in run_until_complete + self.run_forever() + File "/usr/lib/python3.12/asyncio/base_events.py", line 641, in run_forever + self._run_once() + File "/usr/lib/python3.12/asyncio/base_events.py", line 1987, in _run_once + handle._run() + File "/usr/lib/python3.12/asyncio/events.py", line 88, in _run + self._context.run(self._callback, *self._args) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 346, in _trigger_event + await session.after_event(session.index.get(self._i)) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 263, in after_event + await self.__auto_update(control) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 275, in __auto_update + control.update() + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 452, in update + self.__update(self) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 461, in __update + self.session.patch_control(c) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 125, in patch_control + patch, added_controls, removed_controls = self.__get_update_control_patch( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 301, in __get_update_control_patch + patch, added_controls, removed_controls = ObjectPatch.from_diff( + ^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 245, in from_diff + builder._compare_values(parent, path or [], None, src, dst, frozen=frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1049, in _compare_values + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 617, in _compare_lists + logger.debug(f"\n_compare_lists: {path} {src} {dst}") + ^^^^^ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/home/user/dev/h-1.flet.3/app_compiz_shortcuts.py", line 33, in signal_handler + sys.exit(0) +SystemExit: 0 +2026-02-19 21:53:39,359 - INFO - ショートカットキー設定完了 +2026-02-19 21:53:39,390 - INFO - Compiz対応ショートカットキーアプリ起動完了 +2026-02-19 21:53:44,038 - INFO - 機能選択: ダッシュボード +2026-02-19 21:53:44,806 - INFO - ダッシュボード機能実行 +2026-02-19 21:53:45,363 - INFO - 機能選択: 売上管理 +2026-02-19 21:53:45,687 - INFO - 売上管理機能実行 +2026-02-19 21:53:45,946 - INFO - 機能選択: 顧客管理 +2026-02-19 21:53:46,253 - INFO - 顧客管理機能実行 +2026-02-19 21:53:47,229 - INFO - 顧客管理機能実行 +2026-02-19 21:53:47,372 - INFO - 機能選択: 商品管理 +2026-02-19 21:53:47,852 - INFO - 機能選択: 伝票入力 +2026-02-19 21:53:48,147 - INFO - 伝票入力機能実行 +2026-02-19 21:53:48,216 - INFO - 伝票入力機能実行 +2026-02-19 21:53:49,161 - INFO - 伝票入力機能実行 +2026-02-19 21:53:49,349 - INFO - 機能選択: テキストエディタ +2026-02-19 21:53:50,955 - INFO - テキストエディタ機能実行 +2026-02-19 21:53:51,249 - INFO - 機能選択: GPS機能 +2026-02-19 21:53:52,821 - INFO - GPS機能実行 +2026-02-19 21:53:54,973 - INFO - 機能選択: PDF出力 +2026-02-19 21:53:55,573 - INFO - 機能選択: 設定 +2026-02-19 21:54:02,245 - INFO - 設定機能実行 +2026-02-19 21:54:04,346 - INFO - 機能選択: 終了 +2026-02-19 21:54:06,747 - ERROR - Unhandled error in 'on_click' handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 344, in _trigger_event + event_handler(e) + File "/home/user/dev/h-1.flet.3/app_compiz_shortcuts.py", line 283, in execute_current_function + signal_handler(0, None) + ^^^^^^^^^^^^^^ +NameError: name 'signal_handler' is not defined +2026-02-19 21:54:10,582 - INFO - アプリケーション正常終了 +2026-02-19 21:54:10,584 - INFO - Session was garbage collected: UGi9bsL6frxvN6hs +2026-02-19 21:54:10,625 - ERROR - Task exception was never retrieved +future: exception=SystemExit(0)> +Traceback (most recent call last): + File "/home/user/dev/h-1.flet.3/app_compiz_shortcuts.py", line 303, in + ft.run(main) + File "/home/user/.venv/lib/python3.12/site-packages/flet/app.py", line 96, in run + return asyncio.run( + ^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run + return runner.run(main) + ^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run + return self._loop.run_until_complete(task) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/base_events.py", line 674, in run_until_complete + self.run_forever() + File "/usr/lib/python3.12/asyncio/base_events.py", line 641, in run_forever + self._run_once() + File "/usr/lib/python3.12/asyncio/base_events.py", line 1987, in _run_once + handle._run() + File "/usr/lib/python3.12/asyncio/events.py", line 88, in _run + self._context.run(self._callback, *self._args) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 282, in _trigger_event + event_type = get_event_field_type(self, field_name) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/control_event.py", line 48, in get_event_field_type + if get_origin(annotation) is InitVar or str(annotation).startswith( + ^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/typing.py", line 1576, in __repr__ + def __repr__(self): + + File "/home/user/dev/h-1.flet.3/app_compiz_shortcuts.py", line 33, in signal_handler + sys.exit(0) +SystemExit: 0 +2026-02-19 22:25:51,328 - INFO - ショートカットキー設定完了 +2026-02-19 22:25:51,330 - ERROR - アプリケーション起動エラー: module 'flet.controls.alignment' has no attribute 'center' +2026-02-19 22:26:00,827 - INFO - Session was garbage collected: aNRYDWqiCY3heeht +2026-02-19 22:26:00,835 - INFO - アプリケーション正常終了 +2026-02-19 22:26:14,711 - INFO - 階層構造商品マスターアプリ起動完了 +2026-02-19 22:26:57,840 - ERROR - Unhandled error in 'on_click' handler +Traceback (most recent call last): + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 344, in _trigger_event + event_handler(e) + File "/home/user/dev/h-1.flet.3/components/hierarchical_product_master.py", line 534, in add_node + self.update_tree_view() + File "/home/user/dev/h-1.flet.3/components/hierarchical_product_master.py", line 405, in update_tree_view + self.tree_view.controls.append(self._create_tree_node(root)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/dev/h-1.flet.3/components/hierarchical_product_master.py", line 478, in _create_tree_node + children_container.controls.append(self._create_tree_node(child)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/dev/h-1.flet.3/components/hierarchical_product_master.py", line 478, in _create_tree_node + children_container.controls.append(self._create_tree_node(child)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/dev/h-1.flet.3/components/hierarchical_product_master.py", line 422, in _create_tree_node + icon = ft.Icons.PRODUCT + ^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/material/icons.py", line 72, in __getattr__ + raise AttributeError(name) from None +AttributeError: PRODUCT +2026-02-19 22:27:03,552 - INFO - アプリケーション正常終了 +2026-02-19 22:27:03,554 - INFO - Session was garbage collected: 4NqTaaNld5q0ZqaR +2026-02-19 22:27:03,603 - ERROR - Task exception was never retrieved +future: exception=SystemExit(0)> +Traceback (most recent call last): + File "/home/user/dev/h-1.flet.3/app_hierarchical_product_master.py", line 133, in + ft.run(main) + File "/home/user/.venv/lib/python3.12/site-packages/flet/app.py", line 96, in run + return asyncio.run( + ^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run + return runner.run(main) + ^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run + return self._loop.run_until_complete(task) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/base_events.py", line 674, in run_until_complete + self.run_forever() + File "/usr/lib/python3.12/asyncio/base_events.py", line 641, in run_forever + self._run_once() + File "/usr/lib/python3.12/asyncio/base_events.py", line 1987, in _run_once + handle._run() + File "/usr/lib/python3.12/asyncio/events.py", line 88, in _run + self._context.run(self._callback, *self._args) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 346, in _trigger_event + await session.after_event(session.index.get(self._i)) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 263, in after_event + await self.__auto_update(control) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 275, in __auto_update + control.update() + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 452, in update + self.__update(self) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 461, in __update + self.session.patch_control(c) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 125, in patch_control + patch, added_controls, removed_controls = self.__get_update_control_patch( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 301, in __get_update_control_patch + patch, added_controls, removed_controls = ObjectPatch.from_diff( + ^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 245, in from_diff + builder._compare_values(parent, path or [], None, src, dst, frozen=frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1049, in _compare_values + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 990, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1049, in _compare_values + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 617, in _compare_lists + logger.debug(f"\n_compare_lists: {path} {src} {dst}") + ^^^^^ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/home/user/dev/h-1.flet.3/app_hierarchical_product_master.py", line 33, in signal_handler + sys.exit(0) +SystemExit: 0 +2026-02-19 22:33:24,673 - INFO - ショートカットキー設定完了 +2026-02-19 22:33:24,813 - INFO - Compiz対応ショートカットキーアプリ起動完了 +2026-02-19 22:33:31,934 - INFO - 機能選択: ダッシュボード +2026-02-19 22:33:32,991 - INFO - 機能選択: 売上管理 +2026-02-19 22:33:34,557 - INFO - 売上管理機能実行 +2026-02-19 22:33:35,596 - INFO - 売上管理機能実行 +2026-02-19 22:33:36,058 - INFO - 売上管理機能実行 +2026-02-19 22:33:36,998 - INFO - 売上管理機能実行 +2026-02-19 22:33:37,293 - INFO - 売上管理機能実行 +2026-02-19 22:33:38,272 - INFO - データベース初期化完了 +2026-02-19 22:33:38,302 - INFO - アプリケーション起動完了 +2026-02-19 22:33:39,950 - INFO - データベース初期化完了 +2026-02-19 22:33:39,974 - INFO - アプリケーション起動完了 +2026-02-19 22:33:41,014 - INFO - データベース初期化完了 +2026-02-19 22:33:41,041 - INFO - アプリケーション起動完了 +2026-02-19 22:33:42,307 - INFO - データベース初期化完了 +2026-02-19 22:33:42,322 - INFO - アプリケーション起動完了 +2026-02-19 22:33:43,057 - INFO - データベース初期化完了 +2026-02-19 22:33:43,079 - INFO - アプリケーション起動完了 +2026-02-19 22:33:47,491 - INFO - Session was garbage collected: fkR41nmosDWoB3MR +2026-02-19 22:33:47,505 - INFO - アプリケーション正常終了 +2026-02-19 22:33:50,229 - INFO - Session was garbage collected: 2HKHl62ZkzZRZ9V9 +2026-02-19 22:33:50,265 - INFO - アプリケーション正常終了 +2026-02-19 22:33:52,938 - INFO - Session was garbage collected: ULwIJLAUvhhuxZq6 +2026-02-19 22:33:52,975 - INFO - アプリケーション正常終了 +2026-02-19 22:33:55,215 - INFO - Session was garbage collected: boUla75tPq38A1s3 +2026-02-19 22:33:55,241 - INFO - アプリケーション正常終了 +2026-02-19 22:33:57,646 - INFO - Session was garbage collected: 7IdWC502bbowno7O +2026-02-19 22:33:57,692 - INFO - アプリケーション正常終了 +2026-02-19 22:34:51,473 - INFO - 売上管理機能実行 +2026-02-19 22:34:54,297 - INFO - データベース初期化完了 +2026-02-19 22:34:54,315 - INFO - アプリケーション起動完了 +2026-02-19 22:35:02,607 - INFO - Session was garbage collected: fP1glm1SuCGVq9Uo +2026-02-19 22:35:02,640 - INFO - アプリケーション正常終了 +2026-02-19 22:35:22,502 - INFO - 機能選択: ダッシュボード +2026-02-19 22:35:24,997 - INFO - 機能選択: ダッシュボード +2026-02-19 22:35:25,444 - INFO - 機能選択: ダッシュボード +2026-02-19 22:35:25,986 - INFO - 機能選択: ダッシュボード +2026-02-19 22:35:26,297 - INFO - 機能選択: ダッシュボード +2026-02-19 22:35:26,742 - INFO - 機能選択: ダッシュボード +2026-02-19 22:35:27,183 - INFO - 機能選択: ダッシュボード +2026-02-19 22:35:28,209 - INFO - 機能選択: ダッシュボード +2026-02-19 22:35:42,056 - INFO - 機能選択: 売上管理 +2026-02-19 22:36:21,988 - INFO - 売上管理機能実行 +2026-02-19 22:36:24,583 - INFO - データベース初期化完了 +2026-02-19 22:36:24,598 - INFO - アプリケーション起動完了 +2026-02-19 22:36:51,159 - INFO - 売上データ追加: じょんうん 電話帳 500.0 +2026-02-19 22:37:20,751 - INFO - 売上データ追加: いるそん メガネ 1000.0 +2026-02-19 22:37:50,965 - INFO - アプリケーション正常終了 +2026-02-19 22:37:50,967 - INFO - Session was garbage collected: 0KqvTiv4vxt352Mh +2026-02-19 22:37:51,017 - ERROR - Task exception was never retrieved +future: exception=SystemExit(0)> +Traceback (most recent call last): + File "/home/user/dev/h-1.flet.3/app_simple_working.py", line 377, in + ft.run(main) + File "/home/user/.venv/lib/python3.12/site-packages/flet/app.py", line 96, in run + return asyncio.run( + ^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run + return runner.run(main) + ^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run + return self._loop.run_until_complete(task) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/base_events.py", line 674, in run_until_complete + self.run_forever() + File "/usr/lib/python3.12/asyncio/base_events.py", line 641, in run_forever + self._run_once() + File "/usr/lib/python3.12/asyncio/base_events.py", line 1987, in _run_once + handle._run() + File "/usr/lib/python3.12/asyncio/events.py", line 88, in _run + self._context.run(self._callback, *self._args) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 282, in _trigger_event + event_type = get_event_field_type(self, field_name) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/control_event.py", line 48, in get_event_field_type + if get_origin(annotation) is InitVar or str(annotation).startswith( + ^^^^^^^^^^^^^^^ + File "/home/user/dev/h-1.flet.3/app_simple_working.py", line 121, in signal_handler + sys.exit(0) +SystemExit: 0 +2026-02-19 22:37:54,004 - INFO - 機能選択: 売上管理 +2026-02-19 22:37:55,966 - INFO - 機能選択: 顧客管理 +2026-02-19 22:37:57,366 - INFO - 顧客管理機能実行 +2026-02-19 22:38:10,250 - INFO - 顧客管理機能実行 +2026-02-19 22:38:31,235 - INFO - 機能選択: 顧客管理 +2026-02-19 22:38:32,349 - INFO - 機能選択: 商品管理 +2026-02-19 22:38:33,540 - INFO - 商品管理機能実行 +2026-02-19 22:38:36,078 - INFO - 階層構造商品マスターアプリ起動完了 +2026-02-19 22:41:13,314 - INFO - アプリケーション正常終了 +2026-02-19 22:41:13,318 - INFO - Session was garbage collected: wwhq0VLeqTaicojg +2026-02-19 22:41:13,385 - ERROR - Task exception was never retrieved +future: exception=SystemExit(0)> +Traceback (most recent call last): + File "/home/user/dev/h-1.flet.3/app_hierarchical_product_master.py", line 133, in + ft.run(main) + File "/home/user/.venv/lib/python3.12/site-packages/flet/app.py", line 96, in run + return asyncio.run( + ^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run + return runner.run(main) + ^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run + return self._loop.run_until_complete(task) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/base_events.py", line 674, in run_until_complete + self.run_forever() + File "/usr/lib/python3.12/asyncio/base_events.py", line 641, in run_forever + self._run_once() + File "/usr/lib/python3.12/asyncio/base_events.py", line 1987, in _run_once + handle._run() + File "/usr/lib/python3.12/asyncio/events.py", line 88, in _run + self._context.run(self._callback, *self._args) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 346, in _trigger_event + await session.after_event(session.index.get(self._i)) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 263, in after_event + await self.__auto_update(control) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 275, in __auto_update + control.update() + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 452, in update + self.__update(self) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 461, in __update + self.session.patch_control(c) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 125, in patch_control + patch, added_controls, removed_controls = self.__get_update_control_patch( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 301, in __get_update_control_patch + patch, added_controls, removed_controls = ObjectPatch.from_diff( + ^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 245, in from_diff + builder._compare_values(parent, path or [], None, src, dst, frozen=frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1049, in _compare_values + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 990, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1049, in _compare_values + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 990, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1049, in _compare_values + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 990, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1049, in _compare_values + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 617, in _compare_lists + logger.debug(f"\n_compare_lists: {path} {src} {dst}") + ^^^^^ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/home/user/dev/h-1.flet.3/app_hierarchical_product_master.py", line 33, in signal_handler + sys.exit(0) +SystemExit: 0 +2026-02-19 22:41:16,162 - INFO - 機能選択: 伝票入力 +2026-02-19 22:41:17,528 - INFO - 機能選択: 商品管理 +2026-02-19 22:41:18,099 - INFO - 機能選択: 伝票入力 +2026-02-19 22:41:18,690 - INFO - 伝票入力機能実行 +2026-02-19 22:41:21,256 - INFO - 伝票データベース初期化完了 +2026-02-19 22:41:21,256 - ERROR - アプリケーション起動エラー: Dropdown.__init__() got an unexpected keyword argument 'on_change' +2026-02-19 22:41:39,938 - INFO - Session was garbage collected: CLvXyxcFGKBlowoO +2026-02-19 22:41:39,978 - INFO - アプリケーション正常終了 +2026-02-19 22:41:42,514 - INFO - 機能選択: 伝票入力 +2026-02-19 22:41:43,055 - INFO - 機能選択: テキストエディタ +2026-02-19 22:41:44,610 - INFO - テキストエディタ機能実行 +2026-02-19 22:41:47,059 - INFO - データベース初期化完了 +2026-02-19 22:41:47,112 - INFO - テキストエディタ起動完了 +2026-02-19 22:41:59,800 - INFO - 下書き読込: ID=1 +2026-02-19 22:42:22,022 - INFO - 下書き保存: 40 文字 +2026-02-19 22:42:30,674 - INFO - Session was garbage collected: W4MkmNCcqebWbShm +2026-02-19 22:42:30,805 - INFO - アプリケーション正常終了 +2026-02-19 22:42:33,102 - INFO - 機能選択: GPS機能 +2026-02-19 22:42:35,254 - INFO - GPS機能実行 +2026-02-19 22:42:37,967 - INFO - マスタデータベース初期化完了 +2026-02-19 22:42:37,983 - ERROR - テーマ読込エラー: name 'true' is not defined +2026-02-19 22:42:37,984 - ERROR - アプリケーション起動エラー: Dropdown.__init__() got an unexpected keyword argument 'on_change' +2026-02-19 22:42:43,730 - INFO - Session was garbage collected: Rlpkm8xrZTBQhLc9 +2026-02-19 22:42:43,758 - INFO - アプリケーション正常終了 +2026-02-19 22:42:46,131 - INFO - 機能選択: PDF出力 +2026-02-19 22:42:47,761 - INFO - PDF出力機能実行 +2026-02-19 22:42:50,390 - INFO - データベース初期化完了 +2026-02-19 22:42:50,396 - ERROR - アプリケーション起動エラー: Tab.__init__() got an unexpected keyword argument 'text' +2026-02-19 22:42:53,608 - INFO - Session was garbage collected: c3qlShMMSc02SnbC +2026-02-19 22:42:53,639 - INFO - アプリケーション正常終了 +2026-02-19 22:42:55,601 - INFO - 機能選択: PDF出力 +2026-02-19 22:42:55,980 - INFO - 機能選択: 設定 +2026-02-19 22:42:57,144 - INFO - 設定機能実行 +2026-02-19 22:42:59,797 - INFO - データベース初期化完了 +2026-02-19 22:42:59,848 - INFO - ページ遷移成功: dashboard +2026-02-19 22:42:59,849 - INFO - アプリケーション起動完了 +2026-02-19 22:43:05,646 - INFO - ページ遷移成功: sales +2026-02-19 22:43:06,670 - INFO - ページ遷移成功: customers +2026-02-19 22:43:07,998 - INFO - ページ遷移成功: products +2026-02-19 22:43:10,928 - INFO - ページ遷移成功: dashboard +2026-02-19 22:43:12,656 - INFO - ページ遷移成功: sales +2026-02-19 22:43:14,198 - INFO - ページ遷移成功: products +2026-02-19 22:43:42,989 - INFO - Session was garbage collected: RQ9Js3XBEB9Rq3ob +2026-02-19 22:43:43,017 - INFO - アプリケーション終了処理開始 +2026-02-19 22:43:43,019 - INFO - アプリケーション正常終了 +2026-02-19 22:43:44,622 - INFO - 機能選択: 設定 +2026-02-19 22:43:45,688 - INFO - 機能選択: 終了 +2026-02-19 22:43:47,903 - INFO - アプリケーション正常終了 +2026-02-19 22:43:47,939 - INFO - Session was garbage collected: oKfS1LMh2AMF60j5 +2026-02-19 22:43:48,018 - ERROR - Task exception was never retrieved +future: exception=SystemExit(0)> +Traceback (most recent call last): + File "/home/user/dev/h-1.flet.3/app_compiz_shortcuts.py", line 388, in + ft.run(main) + File "/home/user/.venv/lib/python3.12/site-packages/flet/app.py", line 96, in run + return asyncio.run( + ^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run + return runner.run(main) + ^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run + return self._loop.run_until_complete(task) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/base_events.py", line 674, in run_until_complete + self.run_forever() + File "/usr/lib/python3.12/asyncio/base_events.py", line 641, in run_forever + self._run_once() + File "/usr/lib/python3.12/asyncio/base_events.py", line 1987, in _run_once + handle._run() + File "/usr/lib/python3.12/asyncio/events.py", line 88, in _run + self._context.run(self._callback, *self._args) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 344, in _trigger_event + event_handler(e) + File "/home/user/dev/h-1.flet.3/app_compiz_shortcuts.py", line 349, in execute_current_function + self.signal_handler(0, None) + File "/home/user/dev/h-1.flet.3/app_compiz_shortcuts.py", line 33, in signal_handler + sys.exit(0) +SystemExit: 0 +2026-02-19 22:46:32,464 - INFO - ショートカットキー設定完了 +2026-02-19 22:46:32,497 - INFO - Compiz対応ショートカットキーアプリ起動完了 +2026-02-19 22:46:37,869 - INFO - 機能選択: ダッシュボード +2026-02-19 22:46:38,839 - INFO - 機能選択: 売上管理 +2026-02-19 22:46:39,886 - INFO - 機能選択: ダッシュボード +2026-02-19 22:46:41,618 - INFO - ダッシュボード機能実行 +2026-02-19 22:46:43,825 - INFO - データベース初期化完了 +2026-02-19 22:46:43,828 - ERROR - アプリケーション起動エラー: property 'page' of 'DashboardView' object has no setter +2026-02-19 22:46:51,845 - INFO - Session was garbage collected: RBryQ0KKo7XmewNp +2026-02-19 22:46:51,852 - INFO - アプリケーション終了処理開始 +2026-02-19 22:46:51,853 - INFO - アプリケーション正常終了 +2026-02-19 22:46:53,028 - INFO - アプリケーション正常終了 +2026-02-19 22:46:53,029 - INFO - Session was garbage collected: Kh0ORipbOXTtkALt +2026-02-19 22:46:53,089 - ERROR - Task exception was never retrieved +future: exception=SystemExit(0)> +Traceback (most recent call last): + File "/home/user/dev/h-1.flet.3/app_compiz_shortcuts.py", line 388, in + ft.run(main) + File "/home/user/.venv/lib/python3.12/site-packages/flet/app.py", line 96, in run + return asyncio.run( + ^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run + return runner.run(main) + ^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run + return self._loop.run_until_complete(task) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/base_events.py", line 674, in run_until_complete + self.run_forever() + File "/usr/lib/python3.12/asyncio/base_events.py", line 641, in run_forever + self._run_once() + File "/usr/lib/python3.12/asyncio/base_events.py", line 1987, in _run_once + handle._run() + File "/usr/lib/python3.12/asyncio/events.py", line 88, in _run + self._context.run(self._callback, *self._args) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 346, in _trigger_event + await session.after_event(session.index.get(self._i)) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 263, in after_event + await self.__auto_update(control) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 275, in __auto_update + control.update() + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 452, in update + self.__update(self) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 461, in __update + self.session.patch_control(c) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 125, in patch_control + patch, added_controls, removed_controls = self.__get_update_control_patch( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 301, in __get_update_control_patch + patch, added_controls, removed_controls = ObjectPatch.from_diff( + ^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 245, in from_diff + builder._compare_values(parent, path or [], None, src, dst, frozen=frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1049, in _compare_values + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 617, in _compare_lists + logger.debug(f"\n_compare_lists: {path} {src} {dst}") + ^^^^^ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/home/user/dev/h-1.flet.3/app_compiz_shortcuts.py", line 33, in signal_handler + sys.exit(0) +SystemExit: 0 +2026-02-19 22:47:19,968 - INFO - ショートカットキー設定完了 +2026-02-19 22:47:20,005 - INFO - Compiz対応ショートカットキーアプリ起動完了 +2026-02-19 22:47:21,616 - INFO - 機能選択: ダッシュボード +2026-02-19 22:47:22,949 - INFO - ダッシュボード機能実行 +2026-02-19 22:47:25,123 - INFO - データベース初期化完了 +2026-02-19 22:47:25,125 - ERROR - アプリケーション起動エラー: property 'page' of 'DashboardView' object has no setter +2026-02-19 22:47:28,860 - INFO - 機能選択: 売上管理 +2026-02-19 22:47:29,640 - INFO - 売上管理機能実行 +2026-02-19 22:47:31,848 - INFO - データベース初期化完了 +2026-02-19 22:47:31,867 - INFO - アプリケーション起動完了 +2026-02-19 22:47:40,564 - INFO - Session was garbage collected: RVyaPlsYQrjoO48W +2026-02-19 22:47:40,572 - INFO - アプリケーション正常終了 +2026-02-19 22:47:42,378 - INFO - 機能選択: 顧客管理 +2026-02-19 22:47:44,367 - INFO - 顧客管理機能実行 +2026-02-19 22:47:56,219 - INFO - アプリケーション正常終了 +2026-02-19 22:47:56,220 - INFO - Session was garbage collected: 4gLACyWsP8MmyuhY +2026-02-19 22:47:56,269 - ERROR - Task exception was never retrieved +future: exception=SystemExit(0)> +Traceback (most recent call last): + File "/home/user/dev/h-1.flet.3/app_compiz_shortcuts.py", line 388, in + ft.run(main) + File "/home/user/.venv/lib/python3.12/site-packages/flet/app.py", line 96, in run + return asyncio.run( + ^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run + return runner.run(main) + ^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run + return self._loop.run_until_complete(task) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/base_events.py", line 674, in run_until_complete + self.run_forever() + File "/usr/lib/python3.12/asyncio/base_events.py", line 641, in run_forever + self._run_once() + File "/usr/lib/python3.12/asyncio/base_events.py", line 1987, in _run_once + handle._run() + File "/usr/lib/python3.12/asyncio/events.py", line 88, in _run + self._context.run(self._callback, *self._args) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 346, in _trigger_event + await session.after_event(session.index.get(self._i)) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 263, in after_event + await self.__auto_update(control) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 275, in __auto_update + control.update() + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 452, in update + self.__update(self) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 461, in __update + self.session.patch_control(c) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 125, in patch_control + patch, added_controls, removed_controls = self.__get_update_control_patch( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 301, in __get_update_control_patch + patch, added_controls, removed_controls = ObjectPatch.from_diff( + ^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 245, in from_diff + builder._compare_values(parent, path or [], None, src, dst, frozen=frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1049, in _compare_values + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 617, in _compare_lists + logger.debug(f"\n_compare_lists: {path} {src} {dst}") + ^^^^^ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/home/user/dev/h-1.flet.3/app_compiz_shortcuts.py", line 33, in signal_handler + sys.exit(0) +SystemExit: 0 +2026-02-19 22:48:12,251 - INFO - ショートカットキー設定完了 +2026-02-19 22:48:12,294 - INFO - Compiz対応ショートカットキーアプリ起動完了 +2026-02-19 22:48:16,760 - INFO - アプリケーション正常終了 +2026-02-19 22:48:16,761 - INFO - Session was garbage collected: 0Xm2gTAzzvCY2qpq +2026-02-19 22:48:16,813 - ERROR - Task exception was never retrieved +future: exception=SystemExit(0)> +Traceback (most recent call last): + File "/home/user/dev/h-1.flet.3/app_compiz_shortcuts.py", line 388, in + ft.run(main) + File "/home/user/.venv/lib/python3.12/site-packages/flet/app.py", line 96, in run + return asyncio.run( + ^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run + return runner.run(main) + ^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run + return self._loop.run_until_complete(task) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.12/asyncio/base_events.py", line 674, in run_until_complete + self.run_forever() + File "/usr/lib/python3.12/asyncio/base_events.py", line 641, in run_forever + self._run_once() + File "/usr/lib/python3.12/asyncio/base_events.py", line 1987, in _run_once + handle._run() + File "/usr/lib/python3.12/asyncio/events.py", line 88, in _run + self._context.run(self._callback, *self._args) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 198, in dispatch_event + await control._trigger_event(event_name, event_data) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/base_control.py", line 346, in _trigger_event + await session.after_event(session.index.get(self._i)) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 263, in after_event + await self.__auto_update(control) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 275, in __auto_update + control.update() + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 452, in update + self.__update(self) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/page.py", line 461, in __update + self.session.patch_control(c) + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 125, in patch_control + patch, added_controls, removed_controls = self.__get_update_control_patch( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/messaging/session.py", line 301, in __get_update_control_patch + patch, added_controls, removed_controls = ObjectPatch.from_diff( + ^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 245, in from_diff + builder._compare_values(parent, path or [], None, src, dst, frozen=frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1049, in _compare_values + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 730, in _compare_lists + self._compare_dataclasses( + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 964, in _compare_dataclasses + self._compare_values(dst, path, field_name, old, new, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 1033, in _compare_values + self._compare_lists(parent, _path_join(path, key), src, dst, frozen) + File "/home/user/.venv/lib/python3.12/site-packages/flet/controls/object_patch.py", line 617, in _compare_lists + logger.debug(f"\n_compare_lists: {path} {src} {dst}") + ^^^^^ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper + result = user_function(self) + ^^^^^^^^^^^^^^^^^^^ + File "", line 3, in __repr__ + File "/usr/lib/python3.12/enum.py", line 1230, in __repr__ + return "<%s.%s: %s>" % (self.__class__.__name__, self._name_, v_repr(self._value_)) + ^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/user/dev/h-1.flet.3/app_compiz_shortcuts.py", line 33, in signal_handler + sys.exit(0) +SystemExit: 0 diff --git a/app_compiz_fixed.py b/app_compiz_fixed.py new file mode 100644 index 0000000..6eb5867 --- /dev/null +++ b/app_compiz_fixed.py @@ -0,0 +1,807 @@ +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 + +class DashboardView(ft.Column): + """ダッシュボードビュー""" + + def __init__(self, page: ft.Page): + super().__init__(expand=True, spacing=15, visible=False) + # pageプロパティを設定 + object.__setattr__(self, 'page', page) + + # UI構築 + self.title = ft.Text("ダッシュボード", size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900) + + # 統計カード + self.stats_row1 = ft.Row([], spacing=10) + self.stats_row2 = ft.Row([], spacing=10) + + # キーボードショートカット表示 + self.shortcuts = ft.Container( + content=ft.Column([ + ft.Text("キーボードショートカット", size=16, weight=ft.FontWeight.BOLD), + ft.Text("1: ダッシュボード", size=14), + ft.Text("2: 売上管理", size=14), + ft.Text("3: 顧客管理", size=14), + ft.Text("4: 商品管理", size=14), + ]), + padding=10, + bgcolor=ft.Colors.GREY_100, + border_radius=10 + ) + + self.controls = [ + self.title, + ft.Divider(), + self.stats_row1, + self.stats_row2, + ft.Divider(), + self.shortcuts + ] + + # データ読み込み + self.load_stats() + + def load_stats(self): + """統計データ読み込み""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + # 各テーブルの件数取得 + cursor.execute("SELECT COUNT(*) FROM customers") + customers = cursor.fetchone()[0] + + cursor.execute("SELECT COUNT(*) FROM products") + products = cursor.fetchone()[0] + + cursor.execute("SELECT COUNT(*), COALESCE(SUM(total_price), 0) FROM sales") + sales_result = cursor.fetchone() + sales_count = sales_result[0] + total_sales = sales_result[1] + + conn.close() + + # カードを更新 + self.stats_row1.controls = [ + self._stat_card("総顧客数", customers, ft.Colors.BLUE), + self._stat_card("総商品数", products, ft.Colors.GREEN), + ] + + self.stats_row2.controls = [ + self._stat_card("総売上件数", sales_count, ft.Colors.ORANGE), + self._stat_card("総売上高", f"¥{total_sales:,.0f}", ft.Colors.PURPLE), + ] + + self.update() + + except Exception as e: + ErrorHandler.handle_error(e, "統計データ取得エラー") + + def _stat_card(self, title: str, value: str, color: ft.Colors): + """統計カード作成""" + return ft.Card( + content=ft.Container( + content=ft.Column([ + ft.Text(title, size=16, color=color), + ft.Text(value, size=20, weight=ft.FontWeight.BOLD), + ]), + padding=15 + ), + width=200 + ) + +class SalesView(ft.Column): + """売上管理ビュー""" + + def __init__(self, page: ft.Page): + super().__init__(expand=True, spacing=15, visible=False) + # pageプロパティを設定 + object.__setattr__(self, 'page', page) + + # UI部品 + self.title = ft.Text("売上管理", size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900) + + self.customer_tf = ft.TextField(label="顧客名", width=200, autofocus=True) + self.product_tf = ft.TextField(label="商品名", width=200) + self.amount_tf = ft.TextField(label="金額", width=150, keyboard_type=ft.KeyboardType.NUMBER) + self.add_btn = ft.Button("追加", on_click=self.add_sale, bgcolor=ft.Colors.BLUE, color=ft.Colors.WHITE) + + # 売上一覧 + self.sales_list = ft.Column([], scroll=ft.ScrollMode.AUTO, height=300) + + self.controls = [ + self.title, + ft.Divider(), + ft.Row([self.customer_tf, self.product_tf, self.amount_tf, self.add_btn]), + ft.Divider(), + ft.Text("売上一覧", size=18), + ft.Text("キーボード操作: TABでフォーカス移動, SPACE/ENTERで追加", size=12, color=ft.Colors.GREY_600), + self.sales_list + ] + + # データ読み込み + self.load_sales() + + # キーボードショートカット設定 + self._setup_keyboard_shortcuts() + + def load_sales(self): + """売上データ読み込み""" + 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() + + # リストを更新 + self.sales_list.controls.clear() + for sale in sales: + customer, product, amount, date = sale + self.sales_list.controls.append( + ft.Text(f"{date}: {customer} - {product}: ¥{amount:,.0f}") + ) + + self.update() + + except Exception as e: + ErrorHandler.handle_error(e, "売上データ読み込みエラー") + + def add_sale(self, e): + """売上データ追加""" + if self.customer_tf.value and self.product_tf.value and self.amount_tf.value: + try: + # 保存前に値を取得 + customer_val = self.customer_tf.value + product_val = self.product_tf.value + amount_val = float(self.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() + + # フィールドをクリア + self.customer_tf.value = "" + self.product_tf.value = "" + self.amount_tf.value = "" + + # リスト更新 + self.load_sales() + + # 成功メッセージ + ErrorHandler.show_snackbar(self.page, "保存しました", ft.Colors.GREEN) + + logging.info(f"売上データ追加: {customer_val} {product_val} {amount_val}") + + except Exception as ex: + ErrorHandler.handle_error(ex, "売上データ保存エラー") + +class CustomerView(ft.Column): + """顧客管理ビュー""" + + def __init__(self, page: ft.Page): + super().__init__(expand=True, spacing=15, visible=False) + # pageプロパティを設定 + object.__setattr__(self, 'page', page) + + # UI部品 + self.title = ft.Text("顧客管理", size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900) + + # 登録フォーム + self.name_tf = ft.TextField(label="顧客名", width=200, autofocus=True) + self.phone_tf = ft.TextField(label="電話番号", width=200) + self.email_tf = ft.TextField(label="メールアドレス", width=250) + self.address_tf = ft.TextField(label="住所", width=300) + + self.add_btn = ft.Button("新規追加", on_click=self.add_customer, bgcolor=ft.Colors.GREEN, color=ft.Colors.WHITE) + self.import_btn = ft.Button("一括インポート", bgcolor=ft.Colors.BLUE, color=ft.Colors.WHITE) + + # 顧客一覧 + self.customer_list = ft.Column([], scroll=ft.ScrollMode.AUTO, height=200) + + self.controls = [ + self.title, + ft.Divider(), + ft.Text("新規登録", size=18, weight=ft.FontWeight.BOLD), + ft.Row([self.name_tf, self.phone_tf], spacing=10), + ft.Row([self.email_tf, self.address_tf], spacing=10), + ft.Row([self.add_btn, self.import_btn], spacing=10), + ft.Divider(), + ft.Text("顧客一覧", size=18), + ft.Text("キーボード操作: TABでフォーカス移動, SPACE/ENTERで追加", size=12, color=ft.Colors.GREY_600), + self.customer_list + ] + + # データ読み込み + self.load_customers() + + # キーボードショートカット設定 + self._setup_keyboard_shortcuts() + + def add_customer(self, e): + """顧客データ追加""" + if self.name_tf.value: + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + INSERT INTO customers (name, phone, email, address) + VALUES (?, ?, ?, ?) + ''', (self.name_tf.value, self.phone_tf.value, self.email_tf.value, self.address_tf.value)) + + conn.commit() + conn.close() + + # フィールドをクリア + self.name_tf.value = "" + self.phone_tf.value = "" + self.email_tf.value = "" + self.address_tf.value = "" + + # リスト更新 + self.load_customers() + + # 成功メッセージ + ErrorHandler.show_snackbar(self.page, "顧客を追加しました", ft.Colors.GREEN) + + logging.info(f"顧客データ追加: {self.name_tf.value}") + + except Exception as ex: + ErrorHandler.handle_error(ex, "顧客データ保存エラー") + + def load_customers(self): + """顧客データ読み込み""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + SELECT name, phone, email + FROM customers + ORDER BY name + LIMIT 20 + ''') + customers = cursor.fetchall() + conn.close() + + # リストを更新 + self.customer_list.controls.clear() + for customer in customers: + name, phone, email = customer + self.customer_list.controls.append( + ft.Container( + content=ft.Column([ + ft.Text(f"氏名: {name}", weight=ft.FontWeight.BOLD), + ft.Text(f"電話: {phone}", size=12), + ft.Text(f"メール: {email}", size=12), + ]), + padding=10, + bgcolor=ft.Colors.GREY_50, + border_radius=5, + margin=ft.margin.only(bottom=5) + ) + ) + + self.update() + + except Exception as e: + ErrorHandler.handle_error(e, "顧客データ読み込みエラー") + +class ProductView(ft.Column): + """商品管理ビュー""" + + def __init__(self, page: ft.Page): + super().__init__(expand=True, spacing=15, visible=False) + # pageプロパティを設定 + object.__setattr__(self, 'page', page) + + # UI部品 + self.title = ft.Text("商品管理", size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900) + + # 登録フォーム + self.name_tf = ft.TextField(label="商品名", width=200, autofocus=True) + self.category_tf = ft.TextField(label="カテゴリ", width=150) + self.price_tf = ft.TextField(label="価格", width=100, keyboard_type=ft.KeyboardType.NUMBER) + self.stock_tf = ft.TextField(label="在庫数", width=100, keyboard_type=ft.KeyboardType.NUMBER) + + self.add_btn = ft.Button("新規追加", on_click=self.add_product, bgcolor=ft.Colors.GREEN, color=ft.Colors.WHITE) + self.import_btn = ft.Button("一括インポート", bgcolor=ft.Colors.BLUE, color=ft.Colors.WHITE) + + # 商品一覧 + self.product_list = ft.Column([], scroll=ft.ScrollMode.AUTO, height=200) + + self.controls = [ + self.title, + ft.Divider(), + ft.Text("新規登録", size=18, weight=ft.FontWeight.BOLD), + ft.Row([self.name_tf, self.category_tf, self.price_tf, self.stock_tf], spacing=10), + ft.Row([self.add_btn, self.import_btn], spacing=10), + ft.Divider(), + ft.Text("商品一覧", size=18), + ft.Text("キーボード操作: TABでフォーカス移動, SPACE/ENTERで追加", size=12, color=ft.Colors.GREY_600), + self.product_list + ] + + # データ読み込み + self.load_products() + + # キーボードショートカット設定 + self._setup_keyboard_shortcuts() + + def add_product(self, e): + """商品データ追加""" + if self.name_tf.value and self.price_tf.value: + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + INSERT INTO products (name, category, price, stock) + VALUES (?, ?, ?, ?) + ''', (self.name_tf.value, self.category_tf.value, float(self.price_tf.value), + int(self.stock_tf.value) if self.stock_tf.value else 0)) + + conn.commit() + conn.close() + + # フィールドをクリア + self.name_tf.value = "" + self.category_tf.value = "" + self.price_tf.value = "" + self.stock_tf.value = "" + + # リスト更新 + self.load_products() + + # 成功メッセージ + ErrorHandler.show_snackbar(self.page, "商品を追加しました", ft.Colors.GREEN) + + logging.info(f"商品データ追加: {self.name_tf.value}") + + except Exception as ex: + ErrorHandler.handle_error(ex, "商品データ保存エラー") + + def load_products(self): + """商品データ読み込み""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + SELECT name, category, price, stock + FROM products + ORDER BY name + LIMIT 20 + ''') + products = cursor.fetchall() + conn.close() + + # リストを更新 + self.product_list.controls.clear() + for product in products: + name, category, price, stock = product + self.product_list.controls.append( + ft.Container( + content=ft.Column([ + ft.Text(f"商品名: {name}", weight=ft.FontWeight.BOLD), + ft.Text(f"カテゴリ: {category}", size=12), + ft.Text(f"価格: ¥{price:,}", size=12), + ft.Text(f"在庫: {stock}個", size=12), + ]), + padding=10, + bgcolor=ft.Colors.GREY_50, + border_radius=5, + margin=ft.margin.only(bottom=5) + ) + ) + + self.update() + + except Exception as e: + ErrorHandler.handle_error(e, "商品データ読み込みエラー") + +class SalesAssistantApp: + """メインアプリケーション""" + + def __init__(self, page: ft.Page): + self.page = page + ErrorHandler.current_page = page + + # ログ設定 + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler('app.log'), + logging.StreamHandler() + ] + ) + + # シグナルハンドラ設定 + signal.signal(signal.SIGINT, self._signal_handler) + signal.signal(signal.SIGTERM, self._signal_handler) + + # データベース初期化 + self._init_database() + self._generate_dummy_data() + + # ビュー作成 + self.dashboard_view = DashboardView(page) + self.sales_view = SalesView(page) + self.customer_view = CustomerView(page) + self.product_view = ProductView(page) + + # ナビゲーション設定 + self._build_navigation() + + # キーボードショートカット設定 + self._setup_keyboard_shortcuts() + + # 初期表示 + self.dashboard_view.visible = True + self.sales_view.visible = False + self.customer_view.visible = False + self.product_view.visible = False + + logging.info("アプリケーション起動完了") + print("🚀 Compiz対応販売アシスト1号起動完了") + + def _signal_handler(self, signum, frame): + """シグナルハンドラ""" + print(f"\nシグナル {signum} を受信しました") + self._cleanup_resources() + sys.exit(0) + + def _cleanup_resources(self): + """リソースクリーンアップ""" + try: + logging.info("アプリケーション終了処理開始") + print("✅ 正常終了処理完了") + logging.info("アプリケーション正常終了") + except Exception as e: + logging.error(f"クリーンアップエラー: {e}") + print(f"❌ クリーンアップエラー: {e}") + + def _init_database(self): + """データベース初期化""" + 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: + ErrorHandler.handle_error(e, "データベース初期化エラー") + + def _generate_dummy_data(self): + """ダミーデータ生成""" + 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: + ErrorHandler.handle_error(e, "ダミーデータ生成エラー") + + def _build_navigation(self): + """ナビゲーション構築""" + try: + # NavigationBar(Compiz対応) + self.page.navigation_bar = ft.NavigationBar( + selected_index=0, # 最初はダッシュボード + destinations=[ + ft.NavigationBarDestination( + icon=ft.Icons.DASHBOARD, + label="ダッシュボード" + ), + ft.NavigationBarDestination( + icon=ft.Icons.SHOPPING_CART, + label="売上" + ), + ft.NavigationBarDestination( + icon=ft.Icons.PEOPLE, + label="顧客" + ), + ft.NavigationBarDestination( + icon=ft.Icons.INVENTORY, + label="商品" + ) + ], + on_change=self._on_nav_change + ) + + # 全てのビューをページに追加 + self.page.add( + ft.Container( + content=ft.Column([ + self.dashboard_view, + self.sales_view, + self.customer_view, + self.product_view + ], expand=True), + padding=10, + expand=True + ) + ) + + except Exception as e: + ErrorHandler.handle_error(e, "ナビゲーション構築エラー") + + def _on_nav_change(self, e): + """ナビゲーション変更イベント""" + try: + index = e.control.selected_index + + # 全てのビューを非表示 + self.dashboard_view.visible = False + self.sales_view.visible = False + self.customer_view.visible = False + self.product_view.visible = False + + # 選択されたビューを表示 + if index == 0: + self.dashboard_view.visible = True + self.dashboard_view.load_stats() # データ更新 + elif index == 1: + self.sales_view.visible = True + self.sales_view.load_sales() # データ更新 + elif index == 2: + self.customer_view.visible = True + self.customer_view.load_customers() # データ更新 + elif index == 3: + self.product_view.visible = True + self.product_view.load_products() # データ更新 + + self.page.update() + logging.info(f"ページ遷移: index={index}") + + except Exception as ex: + ErrorHandler.handle_error(ex, "ナビゲーション変更エラー") + + def _setup_keyboard_shortcuts(self): + """キーボードショートカット設定""" + try: + def on_keyboard(e: ft.KeyboardEvent): + # SPACEまたはENTERで追加 + if e.key == " " or e.key == "Enter": + if self.sales_view.visible: + self.sales_view.add_sale(None) + elif self.customer_view.visible: + self.customer_view.add_customer(None) + elif self.product_view.visible: + self.product_view.add_product(None) + # 数字キーでページ遷移 + elif e.key == "1": + self.page.navigation_bar.selected_index = 0 + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + elif e.key == "2": + self.page.navigation_bar.selected_index = 1 + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + elif e.key == "3": + self.page.navigation_bar.selected_index = 2 + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + elif e.key == "4": + self.page.navigation_bar.selected_index = 3 + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + # ESCでダッシュボードに戻る + elif e.key == "Escape": + self.page.navigation_bar.selected_index = 0 + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + + self.page.on_keyboard_event = on_keyboard + logging.info("キーボードショートカット設定完了") + + except Exception as e: + ErrorHandler.handle_error(e, "キーボードショートカット設定エラー") + + def _setup_keyboard_shortcuts(self): + """各ビューのキーボードショートカット設定(ダミー)""" + pass + +def main(page: ft.Page): + """メイン関数""" + try: + # ウィンドウ設定 + page.title = "販売アシスト1号 (Compiz対応)" + page.window_width = 800 + page.window_height = 600 + page.theme_mode = ft.ThemeMode.LIGHT + + # ウィンドウクローズイベント + page.on_window_close = lambda _: SalesAssistantApp(page)._cleanup_resources() + + # アプリケーション起動 + app = SalesAssistantApp(page) + + except Exception as e: + ErrorHandler.handle_error(e, "アプリケーション起動エラー") + +if __name__ == "__main__": + ft.run(main) diff --git a/app_compiz_fixed_v2.py b/app_compiz_fixed_v2.py new file mode 100644 index 0000000..ef55140 --- /dev/null +++ b/app_compiz_fixed_v2.py @@ -0,0 +1,837 @@ +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 + +class DashboardView: + """ダッシュボードビュー""" + + def __init__(self, page: ft.Page): + self._page = None + + # UI構築 + self.title = ft.Text("ダッシュボード", size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900) + + # 統計カード + self.stats_row1 = ft.Row([], spacing=10) + self.stats_row2 = ft.Row([], spacing=10) + + # キーボードショートカット表示 + self.shortcuts = ft.Container( + content=ft.Column([ + ft.Text("キーボードショートカット", size=16, weight=ft.FontWeight.BOLD), + ft.Text("1: ダッシュボード", size=14), + ft.Text("2: 売上管理", size=14), + ft.Text("3: 顧客管理", size=14), + ft.Text("4: 商品管理", size=14), + ft.Text("ESC: ダッシュボードに戻る", size=14), + ]), + padding=10, + bgcolor=ft.Colors.GREY_100, + border_radius=10 + ) + + # データ読み込み + self.load_stats() + + @property + def page(self): + return self._page + + @page.setter + def page(self, value): + self._page = value + + def build(self): + """UIを構築して返す""" + return ft.Column([ + self.title, + ft.Divider(), + self.stats_row1, + self.stats_row2, + ft.Divider(), + self.shortcuts + ], expand=True, spacing=15) + + def load_stats(self): + """統計データ読み込み""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + # 各テーブルの件数取得 + cursor.execute("SELECT COUNT(*) FROM customers") + customers = cursor.fetchone()[0] + + cursor.execute("SELECT COUNT(*) FROM products") + products = cursor.fetchone()[0] + + cursor.execute("SELECT COUNT(*), COALESCE(SUM(total_price), 0) FROM sales") + sales_result = cursor.fetchone() + sales_count = sales_result[0] + total_sales = sales_result[1] + + conn.close() + + # カードを更新 + self.stats_row1.controls = [ + self._stat_card("総顧客数", customers, ft.Colors.BLUE), + self._stat_card("総商品数", products, ft.Colors.GREEN), + ] + + self.stats_row2.controls = [ + self._stat_card("総売上件数", sales_count, ft.Colors.ORANGE), + self._stat_card("総売上高", f"¥{total_sales:,.0f}", ft.Colors.PURPLE), + ] + + except Exception as e: + ErrorHandler.handle_error(e, "統計データ取得エラー") + + def _stat_card(self, title: str, value: str, color: ft.Colors): + """統計カード作成""" + return ft.Card( + content=ft.Container( + content=ft.Column([ + ft.Text(title, size=16, color=color), + ft.Text(value, size=20, weight=ft.FontWeight.BOLD), + ]), + padding=15 + ), + width=200 + ) + +class SalesView: + """売上管理ビュー""" + + def __init__(self, page: ft.Page): + self._page = None + + # UI部品 + self.title = ft.Text("売上管理", size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900) + + self.customer_tf = ft.TextField(label="顧客名", width=200, autofocus=True) + self.product_tf = ft.TextField(label="商品名", width=200) + self.amount_tf = ft.TextField(label="金額", width=150, keyboard_type=ft.KeyboardType.NUMBER) + self.add_btn = ft.Button("追加", on_click=self.add_sale, bgcolor=ft.Colors.BLUE, color=ft.Colors.WHITE) + + # 売上一覧 + self.sales_list = ft.Column([], scroll=ft.ScrollMode.AUTO, height=300) + + # データ読み込み + self.load_sales() + + @property + def page(self): + return self._page + + @page.setter + def page(self, value): + self._page = value + + def build(self): + """UIを構築して返す""" + return ft.Column([ + self.title, + ft.Divider(), + ft.Row([self.customer_tf, self.product_tf, self.amount_tf, self.add_btn]), + ft.Divider(), + ft.Text("売上一覧", size=18), + ft.Text("キーボード操作: TABでフォーカス移動, SPACE/ENTERで追加", size=12, color=ft.Colors.GREY_600), + self.sales_list + ], expand=True, spacing=15) + + def load_sales(self): + """売上データ読み込み""" + 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() + + # リストを更新 + self.sales_list.controls.clear() + for sale in sales: + customer, product, amount, date = sale + self.sales_list.controls.append( + ft.Text(f"{date}: {customer} - {product}: ¥{amount:,.0f}") + ) + + except Exception as e: + ErrorHandler.handle_error(e, "売上データ読み込みエラー") + + def add_sale(self, e): + """売上データ追加""" + if self.customer_tf.value and self.product_tf.value and self.amount_tf.value: + try: + # 保存前に値を取得 + customer_val = self.customer_tf.value + product_val = self.product_tf.value + amount_val = float(self.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() + + # フィールドをクリア + self.customer_tf.value = "" + self.product_tf.value = "" + self.amount_tf.value = "" + + # リスト更新 + self.load_sales() + + # 成功メッセージ + ErrorHandler.show_snackbar(self.page, "保存しました", ft.Colors.GREEN) + + logging.info(f"売上データ追加: {customer_val} {product_val} {amount_val}") + + except Exception as ex: + ErrorHandler.handle_error(ex, "売上データ保存エラー") + +class CustomerView: + """顧客管理ビュー""" + + def __init__(self, page: ft.Page): + self._page = None + + # UI部品 + self.title = ft.Text("顧客管理", size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900) + + # 登録フォーム + self.name_tf = ft.TextField(label="顧客名", width=200, autofocus=True) + self.phone_tf = ft.TextField(label="電話番号", width=200) + self.email_tf = ft.TextField(label="メールアドレス", width=250) + self.address_tf = ft.TextField(label="住所", width=300) + + self.add_btn = ft.Button("新規追加", on_click=self.add_customer, bgcolor=ft.Colors.GREEN, color=ft.Colors.WHITE) + self.import_btn = ft.Button("一括インポート", bgcolor=ft.Colors.BLUE, color=ft.Colors.WHITE) + + # 顧客一覧 + self.customer_list = ft.Column([], scroll=ft.ScrollMode.AUTO, height=250) + + # データ読み込み + self.load_customers() + + @property + def page(self): + return self._page + + @page.setter + def page(self, value): + self._page = value + + def build(self): + """UIを構築して返す""" + return ft.Column([ + self.title, + ft.Divider(), + ft.Text("新規登録", size=18, weight=ft.FontWeight.BOLD), + ft.Row([self.name_tf, self.phone_tf], spacing=10), + ft.Row([self.email_tf, self.address_tf], spacing=10), + ft.Row([self.add_btn, self.import_btn], spacing=10), + ft.Divider(), + ft.Text("顧客一覧", size=18), + ft.Text("キーボード操作: TABでフォーカス移動, SPACE/ENTERで追加", size=12, color=ft.Colors.GREY_600), + self.customer_list + ], expand=True, spacing=15) + + def add_customer(self, e): + """顧客データ追加""" + if self.name_tf.value: + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + INSERT INTO customers (name, phone, email, address) + VALUES (?, ?, ?, ?) + ''', (self.name_tf.value, self.phone_tf.value, self.email_tf.value, self.address_tf.value)) + + conn.commit() + conn.close() + + # フィールドをクリア + self.name_tf.value = "" + self.phone_tf.value = "" + self.email_tf.value = "" + self.address_tf.value = "" + + # リスト更新 + self.load_customers() + + # 成功メッセージ + ErrorHandler.show_snackbar(self.page, "顧客を追加しました", ft.Colors.GREEN) + + logging.info(f"顧客データ追加: {self.name_tf.value}") + + except Exception as ex: + ErrorHandler.handle_error(ex, "顧客データ保存エラー") + + def load_customers(self): + """顧客データ読み込み""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + SELECT name, phone, email + FROM customers + ORDER BY name + LIMIT 20 + ''') + customers = cursor.fetchall() + conn.close() + + # リストを更新 + self.customer_list.controls.clear() + for customer in customers: + name, phone, email = customer + self.customer_list.controls.append( + ft.Container( + content=ft.Column([ + ft.Text(f"氏名: {name}", weight=ft.FontWeight.BOLD), + ft.Text(f"電話: {phone}", size=12), + ft.Text(f"メール: {email}", size=12), + ]), + padding=10, + bgcolor=ft.Colors.GREY_50, + border_radius=5, + margin=ft.margin.only(bottom=5) + ) + ) + + except Exception as e: + ErrorHandler.handle_error(e, "顧客データ読み込みエラー") + +class ProductView: + """商品管理ビュー""" + + def __init__(self, page: ft.Page): + self._page = None + + # UI部品 + self.title = ft.Text("商品管理", size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900) + + # 登録フォーム + self.name_tf = ft.TextField(label="商品名", width=200, autofocus=True) + self.category_tf = ft.TextField(label="カテゴリ", width=150) + self.price_tf = ft.TextField(label="価格", width=100, keyboard_type=ft.KeyboardType.NUMBER) + self.stock_tf = ft.TextField(label="在庫数", width=100, keyboard_type=ft.KeyboardType.NUMBER) + + self.add_btn = ft.Button("新規追加", on_click=self.add_product, bgcolor=ft.Colors.GREEN, color=ft.Colors.WHITE) + self.import_btn = ft.Button("一括インポート", bgcolor=ft.Colors.BLUE, color=ft.Colors.WHITE) + + # 商品一覧 + self.product_list = ft.Column([], scroll=ft.ScrollMode.AUTO, height=250) + + # データ読み込み + self.load_products() + + @property + def page(self): + return self._page + + @page.setter + def page(self, value): + self._page = value + + def build(self): + """UIを構築して返す""" + return ft.Column([ + self.title, + ft.Divider(), + ft.Text("新規登録", size=18, weight=ft.FontWeight.BOLD), + ft.Row([self.name_tf, self.category_tf, self.price_tf, self.stock_tf], spacing=10), + ft.Row([self.add_btn, self.import_btn], spacing=10), + ft.Divider(), + ft.Text("商品一覧", size=18), + ft.Text("キーボード操作: TABでフォーカス移動, SPACE/ENTERで追加", size=12, color=ft.Colors.GREY_600), + self.product_list + ], expand=True, spacing=15) + + def add_product(self, e): + """商品データ追加""" + if self.name_tf.value and self.price_tf.value: + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + INSERT INTO products (name, category, price, stock) + VALUES (?, ?, ?, ?) + ''', (self.name_tf.value, self.category_tf.value, float(self.price_tf.value), + int(self.stock_tf.value) if self.stock_tf.value else 0)) + + conn.commit() + conn.close() + + # フィールドをクリア + self.name_tf.value = "" + self.category_tf.value = "" + self.price_tf.value = "" + self.stock_tf.value = "" + + # リスト更新 + self.load_products() + + # 成功メッセージ + ErrorHandler.show_snackbar(self.page, "商品を追加しました", ft.Colors.GREEN) + + logging.info(f"商品データ追加: {self.name_tf.value}") + + except Exception as ex: + ErrorHandler.handle_error(ex, "商品データ保存エラー") + + def load_products(self): + """商品データ読み込み""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + SELECT name, category, price, stock + FROM products + ORDER BY name + LIMIT 20 + ''') + products = cursor.fetchall() + conn.close() + + # リストを更新 + self.product_list.controls.clear() + for product in products: + name, category, price, stock = product + self.product_list.controls.append( + ft.Container( + content=ft.Column([ + ft.Text(f"商品名: {name}", weight=ft.FontWeight.BOLD), + ft.Text(f"カテゴリ: {category}", size=12), + ft.Text(f"価格: ¥{price:,}", size=12), + ft.Text(f"在庫: {stock}個", size=12), + ]), + padding=10, + bgcolor=ft.Colors.GREY_50, + border_radius=5, + margin=ft.margin.only(bottom=5) + ) + ) + + except Exception as e: + ErrorHandler.handle_error(e, "商品データ読み込みエラー") + +class SalesAssistantApp: + """メインアプリケーション""" + + def __init__(self, page: ft.Page): + self.page = page + ErrorHandler.current_page = page + + # ログ設定 + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler('app.log'), + logging.StreamHandler() + ] + ) + + # シグナルハンドラ設定 + signal.signal(signal.SIGINT, self._signal_handler) + signal.signal(signal.SIGTERM, self._signal_handler) + + # データベース初期化 + self._init_database() + self._generate_dummy_data() + + # ビュー作成 + self.dashboard_view = DashboardView(page) + self.sales_view = SalesView(page) + self.customer_view = CustomerView(page) + self.product_view = ProductView(page) + + # ナビゲーション設定 + self._build_navigation() + + # キーボードショートカット設定 + self._setup_keyboard_shortcuts() + + # 初期表示 + self.dashboard_container.visible = True + self.sales_container.visible = False + self.customer_container.visible = False + self.product_container.visible = False + + logging.info("アプリケーション起動完了") + print("🚀 Compiz対応販売アシスト1号起動完了") + + def _signal_handler(self, signum, frame): + """シグナルハンドラ""" + print(f"\nシグナル {signum} を受信しました") + self._cleanup_resources() + sys.exit(0) + + def _cleanup_resources(self): + """リソースクリーンアップ""" + try: + logging.info("アプリケーション終了処理開始") + print("✅ 正常終了処理完了") + logging.info("アプリケーション正常終了") + except Exception as e: + logging.error(f"クリーンアップエラー: {e}") + print(f"❌ クリーンアップエラー: {e}") + + def _init_database(self): + """データベース初期化""" + 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: + ErrorHandler.handle_error(e, "データベース初期化エラー") + + def _generate_dummy_data(self): + """ダミーデータ生成""" + 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: + ErrorHandler.handle_error(e, "ダミーデータ生成エラー") + + def _build_navigation(self): + """ナビゲーション構築""" + try: + # NavigationBar(Compiz対応) + self.page.navigation_bar = ft.NavigationBar( + selected_index=0, # 最初はダッシュボード + destinations=[ + ft.NavigationBarDestination( + icon=ft.Icons.DASHBOARD, + label="ダッシュボード" + ), + ft.NavigationBarDestination( + icon=ft.Icons.SHOPPING_CART, + label="売上" + ), + ft.NavigationBarDestination( + icon=ft.Icons.PEOPLE, + label="顧客" + ), + ft.NavigationBarDestination( + icon=ft.Icons.INVENTORY, + label="商品" + ) + ], + on_change=self._on_nav_change + ) + + # コンテナ作成 + self.dashboard_container = ft.Container( + content=self.dashboard_view.build(), + visible=True + ) + self.sales_container = ft.Container( + content=self.sales_view.build(), + visible=False + ) + self.customer_container = ft.Container( + content=self.customer_view.build(), + visible=False + ) + self.product_container = ft.Container( + content=self.product_view.build(), + visible=False + ) + + # 全てのコンテナをページに追加 + self.page.add( + ft.Container( + content=ft.Column([ + self.dashboard_container, + self.sales_container, + self.customer_container, + self.product_container + ], expand=True), + padding=10, + expand=True + ) + ) + + except Exception as e: + ErrorHandler.handle_error(e, "ナビゲーション構築エラー") + + def _on_nav_change(self, e): + """ナビゲーション変更イベント""" + try: + index = e.control.selected_index + + # 全てのコンテナを非表示 + self.dashboard_container.visible = False + self.sales_container.visible = False + self.customer_container.visible = False + self.product_container.visible = False + + # 選択されたコンテナを表示 + if index == 0: + self.dashboard_container.visible = True + self.dashboard_view.load_stats() # データ更新 + elif index == 1: + self.sales_container.visible = True + self.sales_view.load_sales() # データ更新 + elif index == 2: + self.customer_container.visible = True + self.customer_view.load_customers() # データ更新 + elif index == 3: + self.product_container.visible = True + self.product_view.load_products() # データ更新 + + self.page.update() + logging.info(f"ページ遷移: index={index}") + + except Exception as ex: + ErrorHandler.handle_error(ex, "ナビゲーション変更エラー") + + def _setup_keyboard_shortcuts(self): + """キーボードショートカット設定""" + try: + def on_keyboard(e: ft.KeyboardEvent): + # SPACEまたはENTERで追加 + if e.key == " " or e.key == "Enter": + if self.sales_container.visible: + self.sales_view.add_sale(None) + elif self.customer_container.visible: + self.customer_view.add_customer(None) + elif self.product_container.visible: + self.product_view.add_product(None) + # 数字キーでページ遷移 + elif e.key == "1": + self.page.navigation_bar.selected_index = 0 + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + elif e.key == "2": + self.page.navigation_bar.selected_index = 1 + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + elif e.key == "3": + self.page.navigation_bar.selected_index = 2 + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + elif e.key == "4": + self.page.navigation_bar.selected_index = 3 + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + # ESCでダッシュボードに戻る + elif e.key == "Escape": + self.page.navigation_bar.selected_index = 0 + self._on_nav_change(ft.ControlEvent(control=self.page.navigation_bar)) + + self.page.on_keyboard_event = on_keyboard + logging.info("キーボードショートカット設定完了") + + except Exception as e: + ErrorHandler.handle_error(e, "キーボードショートカット設定エラー") + +def main(page: ft.Page): + """メイン関数""" + try: + # ウィンドウ設定 + page.title = "販売アシスト1号 (Compiz対応)" + page.window_width = 800 + page.window_height = 600 + page.theme_mode = ft.ThemeMode.LIGHT + + # ウィンドウクローズイベント + page.on_window_close = lambda _: SalesAssistantApp(page)._cleanup_resources() + + # アプリケーション起動 + app = SalesAssistantApp(page) + + except Exception as e: + ErrorHandler.handle_error(e, "アプリケーション起動エラー") + +if __name__ == "__main__": + ft.run(main) diff --git a/app_compiz_shortcuts.py b/app_compiz_shortcuts.py new file mode 100644 index 0000000..e76efe6 --- /dev/null +++ b/app_compiz_shortcuts.py @@ -0,0 +1,388 @@ +""" +Compiz対応ショートカットキー画面 +Mate+Compiz環境で安定動作するUI +""" + +import flet as ft +import signal +import sys +import logging +from datetime import datetime + +class CompizShortcutsApp: + """Compiz対応ショートカットキーアプリケーション""" + + def __init__(self, page: ft.Page): + self.page = page + + # ログ設定 + 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) + + self.signal_handler = signal_handler # インスタンス変数として保存 + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + # ウィンドウ設定 + page.title = "Compiz対応ショートカットキー" + page.window_width = 800 + page.window_height = 600 + page.theme_mode = ft.ThemeMode.LIGHT + + # ウィンドウクローズイベント + page.on_window_close = lambda _: signal_handler(0, None) + + # メインコンテナ + self.main_container = ft.Column([], expand=True, spacing=20) + + # ショートカットキー設定 + self.setup_shortcuts() + + # UI構築 + self.build_ui() + + logging.info("Compiz対応ショートカットキーアプリ起動完了") + print("🚀 Compiz対応ショートカットキーアプリ起動完了") + + def setup_shortcuts(self): + """ショートカットキー設定""" + self.key_pressed = {} # キー押下状態を管理 + self.last_key_time = {} # 最後のキー押下時間を管理 + + def on_keyboard(e: ft.KeyboardEvent): + import time + current_time = time.time() + + # キーリピート防止(0.2秒以内の同じキーを無視) + if e.key in self.last_key_time and current_time - self.last_key_time[e.key] < 0.2: + return + + self.last_key_time[e.key] = current_time + + # 数字キーで機能呼び出し + if e.key == "1": + self.show_function("ダッシュボード", "統計情報の表示") + elif e.key == "2": + self.show_function("売上管理", "売上データの入力・管理") + elif e.key == "3": + self.show_function("顧客管理", "顧客マスタの編集") + elif e.key == "4": + self.show_function("商品管理", "商品マスタの編集") + elif e.key == "5": + self.show_function("伝票入力", "伝票データの入力") + elif e.key == "6": + self.show_function("テキストエディタ", "下書き・メモの作成") + elif e.key == "7": + self.show_function("GPS機能", "GPS情報の取得・管理") + elif e.key == "8": + self.show_function("PDF出力", "帳票のPDF出力") + elif e.key == "9": + self.show_function("設定", "アプリケーション設定") + elif e.key == "0": + self.show_function("終了", "アプリケーションの終了") + # ESCキーで終了 + elif e.key == "Escape": + self.show_function("終了", "アプリケーションの終了") + # SPACEで実行 + elif e.key == " ": + self.execute_current_function() + # ENTERで実行 + elif e.key == "Enter": + self.execute_current_function() + + self.page.on_keyboard_event = on_keyboard + logging.info("ショートカットキー設定完了") + + def build_ui(self): + """UIを構築""" + # タイトル + self.title = ft.Text( + "Compiz対応ショートカットキー", + size=36, + weight=ft.FontWeight.BOLD, + color=ft.Colors.BLUE_900, + text_align=ft.TextAlign.CENTER + ) + + # 説明テキスト + self.description = ft.Text( + "Mate+Compiz環境で安定動作するショートカットキー対応画面", + size=18, + color=ft.Colors.GREY_600, + text_align=ft.TextAlign.CENTER + ) + + # 現在の機能表示 + self.current_function = ft.Container( + content=ft.Text( + "機能: 未選択", + size=24, + weight=ft.FontWeight.BOLD, + color=ft.Colors.WHITE, + text_align=ft.TextAlign.CENTER + ), + padding=20, + bgcolor=ft.Colors.ORANGE, + border_radius=15, + margin=ft.Margin.symmetric(vertical=10) + ) + + # ショートカットキーガイド + self.shortcuts_guide = ft.Container( + content=ft.Column([ + ft.Text("ショートカットキー一覧", size=20, weight=ft.FontWeight.BOLD, text_align=ft.TextAlign.CENTER), + ft.Divider(height=2, thickness=2), + self.create_shortcut_row("1", "ダッシュボード", "統計情報の表示", ft.Colors.BLUE), + self.create_shortcut_row("2", "売上管理", "売上データの入力・管理", ft.Colors.GREEN), + self.create_shortcut_row("3", "顧客管理", "顧客マスタの編集", ft.Colors.ORANGE), + self.create_shortcut_row("4", "商品管理", "階層構造商品マスター編集", ft.Colors.PURPLE), + self.create_shortcut_row("5", "伝票入力", "伝票データの入力", ft.Colors.RED), + self.create_shortcut_row("6", "テキストエディタ", "下書き・メモの作成", ft.Colors.TEAL), + self.create_shortcut_row("7", "GPS機能", "GPS情報の取得・管理", ft.Colors.CYAN), + self.create_shortcut_row("8", "PDF出力", "帳票のPDF出力", ft.Colors.BROWN), + self.create_shortcut_row("9", "設定", "アプリケーション設定", ft.Colors.GREY), + self.create_shortcut_row("0", "終了", "アプリケーションの終了", ft.Colors.RED), + ft.Divider(height=2, thickness=2), + ft.Container( + content=ft.Column([ + ft.Text("操作方法:", size=16, weight=ft.FontWeight.BOLD), + ft.Text("• 数字キー: 機能を選択", size=14), + ft.Text("• SPACE/ENTER: 選択した機能を実行", size=14), + ft.Text("• ESC: アプリケーション終了", size=14), + ft.Text("• マウスクリックも可能", size=14), + ], spacing=5), + padding=15, + bgcolor=ft.Colors.BLUE_50, + border_radius=10 + ) + ], spacing=10), + padding=20, + bgcolor=ft.Colors.WHITE, + border_radius=15, + shadow=ft.BoxShadow( + spread_radius=1, + blur_radius=5, + color=ft.Colors.GREY_300, + offset=ft.Offset(0, 2) + ), + margin=ft.Margin.only(bottom=20) + ) + + # 実行ボタン(マウス用) + self.execute_btn = ft.Container( + content=ft.Button( + "実行", + on_click=self.execute_current_function, + style=ft.ButtonStyle( + bgcolor=ft.Colors.GREEN, + color=ft.Colors.WHITE, + elevation=5, + shape=ft.RoundedRectangleBorder(radius=10), + padding=ft.Padding.symmetric(horizontal=30, vertical=15) + ) + ), + alignment=ft.alignment.Alignment(0, 0), + margin=ft.Margin.symmetric(vertical=10) + ) + + # 環境情報 + self.env_info = ft.Container( + content=ft.Column([ + ft.Text("環境情報", size=16, weight=ft.FontWeight.BOLD), + ft.Text(f"OS: Linux Mint + Compiz"), + ft.Text(f"デスクトップ環境: Mate"), + ft.Text(f"対応: Compiz特殊操作に最適化"), + ft.Text(f"作成日時: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"), + ], spacing=5), + padding=15, + bgcolor=ft.Colors.BLUE_50, + border_radius=10, + shadow=ft.BoxShadow( + spread_radius=1, + blur_radius=3, + color=ft.Colors.GREY_200, + offset=ft.Offset(0, 1) + ) + ) + + # メインコンテナに追加 + self.main_container.controls = [ + ft.Container( + content=self.title, + margin=ft.Margin.only(bottom=10) + ), + ft.Container( + content=self.description, + margin=ft.Margin.only(bottom=20) + ), + ft.Divider(height=1, thickness=1), + self.current_function, + ft.Row([ + self.execute_btn, + self.env_info + ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN), + ft.Divider(height=1, thickness=1), + ft.Container( + content=self.shortcuts_guide, + expand=True + ) + ] + + # ページに追加 + self.page.add( + ft.Container( + content=self.main_container, + padding=20, + bgcolor=ft.Colors.GREY_100, + expand=True + ) + ) + + def create_shortcut_row(self, key: str, title: str, description: str, color: ft.Colors) -> ft.Container: + """ショートカットキー行を作成""" + return ft.Container( + content=ft.Row([ + ft.Container( + content=ft.Text(key, size=22, weight=ft.FontWeight.BOLD, color=ft.Colors.WHITE), + width=60, + height=60, + bgcolor=color, + alignment=ft.alignment.Alignment(0, 0), + border_radius=12, + shadow=ft.BoxShadow( + spread_radius=1, + blur_radius=3, + color=ft.Colors.with_opacity(0.3, color), + offset=ft.Offset(0, 2) + ) + ), + ft.Container( + content=ft.Column([ + ft.Text(title, size=18, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900), + ft.Text(description, size=14, color=ft.Colors.GREY_600) + ], spacing=3), + width=350, + padding=ft.Padding.symmetric(horizontal=15, vertical=10), + bgcolor=ft.Colors.GREY_50, + border_radius=10, + margin=ft.Margin.only(left=10) + ) + ], spacing=0), + margin=ft.Margin.only(bottom=10), + on_click=lambda _: self.show_function(title, description) + ) + + def show_function(self, title: str, description: str): + """機能情報を表示""" + self.current_function.content.value = f"機能: {title}" + self.current_function.content.color = ft.Colors.WHITE + self.current_function.bgcolor = ft.Colors.BLUE_900 + self.page.update() + logging.info(f"機能選択: {title}") + + def execute_current_function(self, e=None): + """現在の機能を実行""" + current_text = self.current_function.content.value + + if "ダッシュボード" in current_text: + self.show_message("ダッシュボード機能を起動します", ft.Colors.GREEN) + # 実際のアプリ起動 + self.launch_app("app_compiz_fixed.py") + logging.info("ダッシュボード機能実行") + + elif "売上管理" in current_text: + self.show_message("売上管理機能を起動します", ft.Colors.GREEN) + self.launch_app("app_simple_working.py") + logging.info("売上管理機能実行") + + elif "顧客管理" in current_text: + self.show_message("顧客管理機能を起動します", ft.Colors.GREEN) + self.launch_app("app_master_management.py") + logging.info("顧客管理機能実行") + + elif "商品管理" in current_text: + self.show_message("商品管理機能を起動します", ft.Colors.GREEN) + self.launch_app("app_hierarchical_product_master.py") + logging.info("商品管理機能実行") + + elif "伝票入力" in current_text: + self.show_message("伝票入力機能を起動します", ft.Colors.GREEN) + self.launch_app("app_slip_framework_demo.py") + logging.info("伝票入力機能実行") + + elif "テキストエディタ" in current_text: + self.show_message("テキストエディタ機能を起動します", ft.Colors.GREEN) + self.launch_app("app_text_editor.py") + logging.info("テキストエディタ機能実行") + + elif "GPS機能" in current_text: + self.show_message("GPS機能を起動します", ft.Colors.GREEN) + self.launch_app("app_theme_master.py") + logging.info("GPS機能実行") + + elif "PDF出力" in current_text: + self.show_message("PDF出力機能を起動します", ft.Colors.GREEN) + self.launch_app("app_framework_demo.py") + logging.info("PDF出力機能実行") + + elif "設定" in current_text: + self.show_message("設定機能を起動します", ft.Colors.GREEN) + self.launch_app("app_robust.py") + logging.info("設定機能実行") + + elif "終了" in current_text: + self.show_message("アプリケーションを終了します", ft.Colors.RED) + self.signal_handler(0, None) + + def launch_app(self, app_name: str): + """アプリを起動""" + import subprocess + import os + + try: + # 現在のディレクトリでアプリを起動 + script_dir = os.path.dirname(os.path.abspath(__file__)) + app_path = os.path.join(script_dir, app_name) + + # バックグラウンドでアプリ起動 + subprocess.Popen([ + "flet", "run", app_path + ], cwd=script_dir) + + except Exception as e: + logging.error(f"アプリ起動エラー: {e}") + self.show_message(f"アプリ起動に失敗しました: {e}", ft.Colors.RED) + + def show_message(self, message: str, color: ft.Colors): + """メッセージを表示""" + self.page.snack_bar = ft.SnackBar( + content=ft.Text(message), + bgcolor=color + ) + self.page.snack_bar.open = True + self.page.update() + +def main(page: ft.Page): + """メイン関数""" + try: + app = CompizShortcutsApp(page) + + except Exception as e: + logging.error(f"アプリケーション起動エラー: {e}") + +if __name__ == "__main__": + ft.run(main) diff --git a/app_framework_demo.py b/app_framework_demo.py new file mode 100644 index 0000000..391a5ba --- /dev/null +++ b/app_framework_demo.py @@ -0,0 +1,114 @@ +""" +テキストエディタフレームワークデモ +再利用可能なコンポーネントの使用例 +""" + +import flet as ft +import sqlite3 +import signal +import sys +import logging +from components.text_editor import TextEditor, create_draft_editor, create_memo_editor, create_note_editor + +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) + + # データベース初期化 + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS text_storage ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + category TEXT NOT NULL, + title TEXT NOT NULL, + content TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + conn.commit() + conn.close() + logging.info("データベース初期化完了") + + # ウィンドウ設定 + page.title = "テキストエディタフレームワークデモ" + page.window_width = 800 + page.window_height = 600 + page.theme_mode = ft.ThemeMode.LIGHT + + # ウィンドウクローズイベント + page.on_window_close = lambda _: signal_handler(0, None) + + # ナビゲーション設定 + current_editor = [0] + editors = [] + + # エディタ作成 + draft_editor = create_draft_editor(page) + memo_editor = create_memo_editor(page) + note_editor = create_note_editor(page) + + editors = [draft_editor, memo_editor, note_editor] + + # タブ切り替え + tabs = ft.Tabs( + selected_index=0, + tabs=[ + ft.Tab( + text="下書き", + content=draft_editor.build() + ), + ft.Tab( + text="メモ", + content=memo_editor.build() + ), + ft.Tab( + text="ノート", + content=note_editor.build() + ) + ], + expand=True + ) + + # ページ構築 + page.add( + ft.Column([ + ft.Text("テキストエディタフレームワーク", size=24, weight=ft.FontWeight.BOLD), + ft.Text("再利用可能なコンポーネントのデモ", size=16, color=ft.Colors.GREY_600), + ft.Divider(), + tabs + ], expand=True, spacing=10) + ) + + logging.info("テキストエディタフレームワーク起動完了") + print("🚀 テキストエディタフレームワーク起動完了") + + except Exception as e: + logging.error(f"アプリケーション起動エラー: {e}") + print(f"❌ アプリケーション起動エラー: {e}") + +if __name__ == "__main__": + import flet as ft + ft.run(main) diff --git a/app_hierarchical_product_master.py b/app_hierarchical_product_master.py new file mode 100644 index 0000000..72afd9e --- /dev/null +++ b/app_hierarchical_product_master.py @@ -0,0 +1,133 @@ +""" +階層構造商品マスターデモアプリケーション +入れ子構造とPDF巨大カッコ表示に対応 +""" + +import flet as ft +import signal +import sys +import logging +from components.hierarchical_product_master import create_hierarchical_product_master + +class HierarchicalProductMasterApp: + """階層構造商品マスターデモアプリケーション""" + + def __init__(self, page: ft.Page): + self.page = page + + # ログ設定 + 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) + + self.signal_handler = signal_handler + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + # ウィンドウ設定 + page.title = "階層構造商品マスター" + page.window_width = 1400 + page.window_height = 800 + page.theme_mode = ft.ThemeMode.LIGHT + + # ウィンドウクローズイベント + page.on_window_close = lambda _: signal_handler(0, None) + + # 階層商品マスター作成 + self.product_master = create_hierarchical_product_master(page) + + # ヘッダー + self.header = ft.Container( + content=ft.Column([ + ft.Row([ + ft.Icon( + ft.Icons.INVENTORY_2, + size=40, + color=ft.Colors.BLUE_900 + ), + ft.Column([ + ft.Text( + "階層構造商品マスター", + size=28, + weight=ft.FontWeight.BOLD, + color=ft.Colors.BLUE_900 + ), + ft.Text( + "入れ子構造とPDF巨大カッコ表示に対応した商品管理システム", + size=14, + color=ft.Colors.GREY_600 + ) + ], spacing=5) + ], alignment=ft.MainAxisAlignment.START), + ft.Divider(height=2, thickness=2) + ], spacing=10), + padding=20, + bgcolor=ft.Colors.BLUE_50, + border_radius=10, + margin=ft.Margin.only(bottom=20) + ) + + # 操作説明 + self.instructions = ft.Container( + content=ft.Column([ + ft.Text( + "操作方法", + size=16, + weight=ft.FontWeight.BOLD, + color=ft.Colors.BLUE_900 + ), + ft.Text("• 左側のツリーから商品を選択して編集", size=12), + ft.Text("• カテゴリの展開/折りたたみ:▶/▼アイコンをクリック", size=12), + ft.Text("• 新規追加:選択中のノードの子として追加", size=12), + ft.Text("• PDFプレビュー:階層構造をキャラクターベースで表示", size=12), + ft.Text("• 巨大カッコ:PDF出力時に階層を視覚的に表現", size=12), + ], spacing=5), + padding=15, + bgcolor=ft.Colors.GREY_50, + border_radius=10, + margin=ft.Margin.only(bottom=20) + ) + + # メインコンテナ + self.main_container = ft.Column([ + self.header, + self.instructions, + self.product_master.build() + ], expand=True, spacing=20) + + # ページに追加 + page.add( + ft.Container( + content=self.main_container, + padding=20, + bgcolor=ft.Colors.GREY_100, + expand=True + ) + ) + + logging.info("階層構造商品マスターアプリ起動完了") + print("🚀 階層構造商品マスターアプリ起動完了") + +def main(page: ft.Page): + """メイン関数""" + try: + app = HierarchicalProductMasterApp(page) + + except Exception as e: + logging.error(f"アプリケーション起動エラー: {e}") + +if __name__ == "__main__": + ft.run(main) diff --git a/app_master_management.py b/app_master_management.py new file mode 100644 index 0000000..bc58ef2 --- /dev/null +++ b/app_master_management.py @@ -0,0 +1,156 @@ +""" +マスタ管理アプリケーション +統合的なマスタ管理機能を提供 +""" + +import flet as ft +import sqlite3 +import signal +import sys +import logging +from components.master_editor import ( + CustomerMasterEditor, ProductMasterEditor, SalesSlipMasterEditor, + create_customer_master, create_product_master, create_sales_slip_master +) + +class MasterManagementApp: + """マスタ管理アプリケーション""" + + def __init__(self, page: ft.Page): + self.page = page + ErrorHandler.current_page = page + + # ログ設定 + 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) + + # データベース初期化 + self._init_database() + + # ウィンドウ設定 + page.title = "マスタ管理システム" + page.window_width = 1000 + page.window_height = 700 + page.theme_mode = ft.ThemeMode.LIGHT + + # ウィンドウクローズイベント + page.on_window_close = lambda _: signal_handler(0, None) + + # マスタエディタ作成 + self.customer_editor = create_customer_master(page) + self.product_editor = create_product_master(page) + self.sales_slip_editor = create_sales_slip_master(page) + + # 現在のエディタ + current_editor = [0] + + # タブインターフェース + tabs = ft.Tabs( + selected_index=0, + tabs=[ + ft.Tab( + text="顧客マスタ", + content=self.customer_editor.build() + ), + ft.Tab( + text="商品マスタ", + content=self.product_editor.build() + ), + ft.Tab( + text="伝票マスタ", + content=self.sales_slip_editor.build() + ) + ], + expand=True + ) + + # ページ構築 + page.add( + ft.Column([ + ft.Text("マスタ管理システム", size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900), + ft.Divider(), + ft.Text("各マスタデータの編集・管理が可能です", size=16), + ft.Divider(), + tabs + ], expand=True, spacing=15) + ) + + logging.info("マスタ管理システム起動完了") + print("🚀 マスタ管理システム起動完了") + + def _init_database(self): + """データベース初期化""" + 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, + updated_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, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS sales_slips ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + customer_name TEXT NOT NULL, + items TEXT NOT NULL, + total_amount REAL NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + conn.commit() + conn.close() + logging.info("マスタデータベース初期化完了") + + except Exception as e: + logging.error(f"データベース初期化エラー: {e}") + +def main(page: ft.Page): + """メイン関数""" + try: + app = MasterManagementApp(page) + + except Exception as e: + logging.error(f"アプリケーション起動エラー: {e}") + +if __name__ == "__main__": + ft.run(main) diff --git a/app_robust.py b/app_robust.py new file mode 100644 index 0000000..cfa4434 --- /dev/null +++ b/app_robust.py @@ -0,0 +1,681 @@ +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}") + + # SnackBarでユーザーに通知 + 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 + +class NavigationHistory: + """ナビゲーション履歴管理""" + + def __init__(self): + self.history: List[Dict] = [] + self.max_history = 10 + + def add_to_history(self, page_name: str, page_data: Dict = None): + """履歴に追加""" + history_item = { + 'page': page_name, + 'data': page_data, + 'timestamp': datetime.now().isoformat() + } + + # 重複を避ける + self.history = [item for item in self.history if item['page'] != page_name] + self.history.insert(0, history_item) + + # 履歴数を制限 + if len(self.history) > self.max_history: + self.history = self.history[:self.max_history] + + def get_last_page(self) -> Optional[Dict]: + """最後のページを取得""" + return self.history[0] if self.history else None + + def get_history(self) -> List[Dict]: + """履歴を取得""" + return self.history + +class SafePageManager: + """安全なページマネージャー""" + + def __init__(self, page: ft.Page): + self.page = page + self.current_page = None + self.navigation_history = NavigationHistory() + ErrorHandler.current_page = page # グローバル参照用 + + def safe_navigate(self, page_name: str, page_builder): + """安全なページ遷移""" + try: + # 現在のページ情報を保存 + current_data = {} + if self.current_page: + current_data = self._get_page_data() + + self.navigation_history.add_to_history(page_name, current_data) + + # 新しいページを構築 + page_instance = page_builder(self) + new_page = page_instance.build() + + # 安全なページ切り替え + self._safe_page_transition(new_page, page_name) + + except Exception as e: + ErrorHandler.handle_error(e, f"ページ遷移エラー ({page_name})") + + def _get_page_data(self) -> Dict: + """現在のページデータを取得""" + try: + if hasattr(self.current_page, 'get_data'): + return self.current_page.get_data() + return {} + except: + return {} + + def _safe_page_transition(self, new_page, page_name: str): + """安全なページ切り替え""" + try: + # 古いコンテンツをクリア + self.page.controls.clear() + + # 新しいコンテンツを追加 + self.page.add(new_page) + + # 現在のページを更新 + self.current_page = new_page + + # ページを更新 + self.page.update() + + logging.info(f"ページ遷移成功: {page_name}") + + except Exception as e: + ErrorHandler.handle_error(e, f"ページ表示エラー ({page_name})") + + def go_back(self): + """前のページに戻る""" + try: + history = self.navigation_history.get_history() + if len(history) > 1: + # 前のページに戻る + previous_page = history[1] + + # ページビルダーを呼び出し + if previous_page['page'] == 'dashboard': + self.safe_navigate('dashboard', DashboardPage) + elif previous_page['page'] == 'sales': + self.safe_navigate('sales', SalesPage) + elif previous_page['page'] == 'customers': + self.safe_navigate('customers', CustomerPage) + elif previous_page['page'] == 'products': + self.safe_navigate('products', ProductPage) + + # 履歴を更新 + self.navigation_history.history.pop(0) # 現在の履歴を削除 + + except Exception as e: + ErrorHandler.handle_error(e, "戻る処理エラー") + +class DashboardPage: + """ダッシュボードページ""" + + def __init__(self, page_manager): + self.page_manager = page_manager + + def build(self): + """ダッシュボードUI構築""" + try: + # 統計データ取得 + stats = self._get_statistics() + + return ft.Container( + content=ft.Column([ + ft.Text("ダッシュボード", size=24, weight=ft.FontWeight.BOLD), + ft.Divider(), + ft.Row([ + self._stat_card("総顧客数", stats['customers'], ft.Colors.BLUE), + self._stat_card("総商品数", stats['products'], ft.Colors.GREEN), + ], spacing=10), + ft.Row([ + self._stat_card("総売上件数", stats['sales'], ft.Colors.ORANGE), + self._stat_card("総売上高", f"¥{stats['total_sales']:,.0f}", ft.Colors.PURPLE), + ], spacing=10), + ]), + padding=20 + ) + except Exception as e: + ErrorHandler.handle_error(e, "ダッシュボード構築エラー") + return ft.Text("ダッシュボード読み込みエラー") + + def _stat_card(self, title: str, value: str, color: ft.Colors): + """統計カード作成""" + return ft.Card( + content=ft.Container( + content=ft.Column([ + ft.Text(title, size=16, color=color), + ft.Text(value, size=20, weight=ft.FontWeight.BOLD), + ]), + padding=15 + ), + width=200 + ) + + def _get_statistics(self) -> Dict: + """統計データ取得""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + # 各テーブルの件数取得 + cursor.execute("SELECT COUNT(*) FROM customers") + customers = cursor.fetchone()[0] + + cursor.execute("SELECT COUNT(*) FROM products") + products = cursor.fetchone()[0] + + cursor.execute("SELECT COUNT(*), COALESCE(SUM(total_price), 0) FROM sales") + sales_result = cursor.fetchone() + sales_count = sales_result[0] + total_sales = sales_result[1] + + conn.close() + + return { + 'customers': customers, + 'products': products, + 'sales': sales_count, + 'total_sales': total_sales + } + except Exception as e: + ErrorHandler.handle_error(e, "統計データ取得エラー") + return {'customers': 0, 'products': 0, 'sales': 0, 'total_sales': 0} + + def get_data(self) -> Dict: + """ページデータ取得""" + return {'page': 'dashboard'} + +class SalesPage: + """売上管理ページ""" + + def __init__(self, page_manager): + self.page_manager = page_manager + + def build(self): + """売上管理UI構築""" + try: + return ft.Container( + content=ft.Column([ + ft.Text("売上管理", size=24, weight=ft.FontWeight.BOLD), + ft.Divider(), + ft.Row([ + ft.TextField(label="顧客名", width=200), + ft.TextField(label="商品名", width=200), + ft.TextField(label="金額", width=150), + ft.Button("追加", bgcolor=ft.Colors.BLUE, color=ft.Colors.WHITE), + ]), + ft.Divider(), + ft.Text("売上一覧", size=18), + ft.Container( + content=ft.Column([], scroll=ft.ScrollMode.AUTO), + height=300 + ) + ]), + padding=20 + ) + except Exception as e: + ErrorHandler.handle_error(e, "売上管理ページ構築エラー") + return ft.Text("売上管理ページ読み込みエラー") + + def get_data(self) -> Dict: + """ページデータ取得""" + return {'page': 'sales'} + +class CustomerPage: + """顧客管理ページ""" + + def __init__(self, page_manager): + self.page_manager = page_manager + + def build(self): + """顧客管理UI構築""" + try: + return ft.Container( + content=ft.Column([ + ft.Text("顧客管理", size=24, weight=ft.FontWeight.BOLD), + ft.Divider(), + ft.Row([ + ft.Button("新規追加", bgcolor=ft.Colors.GREEN, color=ft.Colors.WHITE), + ft.Button("一括インポート", bgcolor=ft.Colors.BLUE, color=ft.Colors.WHITE), + ]), + ft.Divider(), + ft.Text("顧客一覧", size=18), + ft.Container( + content=ft.Column([], scroll=ft.ScrollMode.AUTO), + height=300 + ) + ]), + padding=20 + ) + except Exception as e: + ErrorHandler.handle_error(e, "顧客管理ページ構築エラー") + return ft.Text("顧客管理ページ読み込みエラー") + + def get_data(self) -> Dict: + """ページデータ取得""" + return {'page': 'customers'} + +class ProductPage: + """商品管理ページ""" + + def __init__(self, page_manager): + self.page_manager = page_manager + + def build(self): + """商品管理UI構築""" + try: + return ft.Container( + content=ft.Column([ + ft.Text("商品管理", size=24, weight=ft.FontWeight.BOLD), + ft.Divider(), + ft.Row([ + ft.Button("新規追加", bgcolor=ft.Colors.GREEN, color=ft.Colors.WHITE), + ft.Button("一括インポート", bgcolor=ft.Colors.BLUE, color=ft.Colors.WHITE), + ]), + ft.Divider(), + ft.Text("商品一覧", size=18), + ft.Container( + content=ft.Column([], scroll=ft.ScrollMode.AUTO), + height=300 + ) + ]), + padding=20 + ) + except Exception as e: + ErrorHandler.handle_error(e, "商品管理ページ構築エラー") + return ft.Text("商品管理ページ読み込みエラー") + + def get_data(self) -> Dict: + """ページデータ取得""" + return {'page': 'products'} + +class SalesAssistantApp: + """メインアプリケーション""" + + def __init__(self, page: ft.Page): + self.page = page + self.page_manager = SafePageManager(page) + + # ログ設定 + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler('app.log'), + logging.StreamHandler() + ] + ) + + # シグナルハンドラ設定 + signal.signal(signal.SIGINT, self._signal_handler) + signal.signal(signal.SIGTERM, self._signal_handler) + + # データベース初期化 + self._init_database() + self._generate_dummy_data() + + # ナビゲーションバー構築 + self._build_navigation() + + # 初期ページ表示 + self.page_manager.safe_navigate('dashboard', DashboardPage) + + def _signal_handler(self, signum, frame): + """シグナルハンドラ""" + print(f"\nシグナル {signum} を受信しました") + self._cleanup_resources() + sys.exit(0) + + def _cleanup_resources(self): + """リソースクリーンアップ""" + try: + logging.info("アプリケーション終了処理開始") + print("✅ 正常終了処理完了") + logging.info("アプリケーション正常終了") + except Exception as e: + logging.error(f"クリーンアップエラー: {e}") + print(f"❌ クリーンアップエラー: {e}") + + def _init_database(self): + """データベース初期化""" + 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_id INTEGER, + customer_name TEXT NOT NULL, + product_id INTEGER, + 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, + FOREIGN KEY (customer_id) REFERENCES customers(id), + FOREIGN KEY (product_id) REFERENCES products(id) + ) + ''') + + conn.commit() + conn.close() + logging.info("データベース初期化完了") + + except Exception as e: + ErrorHandler.handle_error(e, "データベース初期化エラー") + + def _generate_dummy_data(self): + """ダミーデータ生成""" + 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_id, customer_name, product_id, product_name, quantity, unit_price, total_price, date) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ''', (sale['customer_id'], sale['customer_name'], sale['product_id'], + sale['product_name'], sale['quantity'], sale['unit_price'], + sale['total_price'], sale['date'])) + + conn.commit() + print("✅ ダミーデータ生成完了") + + conn.close() + + except Exception as e: + ErrorHandler.handle_error(e, "ダミーデータ生成エラー") + + def _build_navigation(self): + """ナビゲーションバー構築""" + try: + # ナビゲーションボタン + nav_buttons = [ + ft.ElevatedButton( + "ダッシュボード", + on_click=lambda _: self.page_manager.safe_navigate('dashboard', DashboardPage), + bgcolor=ft.Colors.BLUE, + color=ft.Colors.WHITE + ), + ft.ElevatedButton( + "売上管理", + on_click=lambda _: self.page_manager.safe_navigate('sales', SalesPage), + bgcolor=ft.Colors.GREEN, + color=ft.Colors.WHITE + ), + ft.ElevatedButton( + "顧客管理", + on_click=lambda _: self.page_manager.safe_navigate('customers', CustomerPage), + bgcolor=ft.Colors.ORANGE, + color=ft.Colors.WHITE + ), + ft.ElevatedButton( + "商品管理", + on_click=lambda _: self.page_manager.safe_navigate('products', ProductPage), + bgcolor=ft.Colors.PURPLE, + color=ft.Colors.WHITE + ), + ft.ElevatedButton( + "戻る", + on_click=lambda _: self.page_manager.go_back(), + bgcolor=ft.Colors.RED, + color=ft.Colors.WHITE + ) + ] + + # ナビゲーションバー + self.page.navigation_bar = ft.NavigationBar( + destinations=[ + ft.NavigationBarDestination( + icon=ft.Icons.DASHBOARD, + label="ダッシュボード" + ), + ft.NavigationBarDestination( + icon=ft.Icons.SHOPPING_CART, + label="売上" + ), + ft.NavigationBarDestination( + icon=ft.Icons.PEOPLE, + label="顧客" + ), + ft.NavigationBarDestination( + icon=ft.Icons.INVENTORY, + label="商品" + ) + ], + on_change=self._on_nav_change + ) + + # 代替ナビゲーション(NavigationBarが動かない場合) + self.page.add( + ft.Container( + content=ft.Row([ + ft.Button( + "ダッシュボード", + on_click=lambda _: self.page_manager.safe_navigate('dashboard', DashboardPage), + bgcolor=ft.Colors.BLUE, + color=ft.Colors.WHITE + ), + ft.Button( + "売上管理", + on_click=lambda _: self.page_manager.safe_navigate('sales', SalesPage), + bgcolor=ft.Colors.GREEN, + color=ft.Colors.WHITE + ), + ft.Button( + "顧客管理", + on_click=lambda _: self.page_manager.safe_navigate('customers', CustomerPage), + bgcolor=ft.Colors.ORANGE, + color=ft.Colors.WHITE + ), + ft.Button( + "商品管理", + on_click=lambda _: self.page_manager.safe_navigate('products', ProductPage), + bgcolor=ft.Colors.PURPLE, + color=ft.Colors.WHITE + ), + ft.Button( + "戻る", + on_click=lambda _: self.page_manager.go_back(), + bgcolor=ft.Colors.RED, + color=ft.Colors.WHITE + ) + ], spacing=5), + padding=10, + bgcolor=ft.Colors.GREY_100 + ) + ) + + except Exception as e: + ErrorHandler.handle_error(e, "ナビゲーション構築エラー") + + def _on_nav_change(self, e): + """ナビゲーション変更イベント""" + try: + index = e.control.selected_index + pages = [DashboardPage, SalesPage, CustomerPage, ProductPage] + page_names = ['dashboard', 'sales', 'customers', 'products'] + + if 0 <= index < len(pages): + self.page_manager.safe_navigate(page_names[index], pages[index]) + except Exception as ex: + ErrorHandler.handle_error(ex, "ナビゲーション変更エラー") + +def main(page: ft.Page): + """メイン関数""" + try: + # ウィンドウ設定 + page.title = "販売アシスト1号" + page.window_width = 800 + page.window_height = 600 + page.theme_mode = ft.ThemeMode.LIGHT + + # ウィンドウクローズイベント + page.on_window_close = lambda _: SalesAssistantApp(page)._cleanup_resources() + + # アプリケーション起動 + app = SalesAssistantApp(page) + logging.info("アプリケーション起動完了") + print("🚀 頑健な販売アシスト1号起動完了") + + except Exception as e: + ErrorHandler.handle_error(e, "アプリケーション起動エラー") + +if __name__ == "__main__": + ft.run(main) diff --git a/app_simple_working.py b/app_simple_working.py new file mode 100644 index 0000000..eab0f55 --- /dev/null +++ b/app_simple_working.py @@ -0,0 +1,377 @@ +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) diff --git a/app_slip_framework_demo.py b/app_slip_framework_demo.py new file mode 100644 index 0000000..d605dfd --- /dev/null +++ b/app_slip_framework_demo.py @@ -0,0 +1,95 @@ +""" +伝票入力フレームワークデモアプリケーション +""" + +import flet as ft +import sqlite3 +import signal +import sys +import logging +from components.slip_entry_framework import SlipEntryFramework, create_slip_entry_framework + +class SlipFrameworkDemoApp: + """伝票入力フレームワークデモアプリケーション""" + + def __init__(self, page: ft.Page): + self.page = page + + # ログ設定 + 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) + + # データベース初期化 + self._init_database() + + # ウィンドウ設定 + page.title = "伝票入力フレームワークデモ" + page.window_width = 1000 + page.window_height = 700 + page.theme_mode = ft.ThemeMode.LIGHT + + # ウィンドウクローズイベント + page.on_window_close = lambda _: signal_handler(0, None) + + # 伝票入力フレームワーク作成 + self.slip_framework = create_slip_entry_framework(page) + + # ページ構築 + page.add( + self.slip_framework.build() + ) + + logging.info("伝票入力フレームワークデモ起動完了") + print("🚀 伝票入力フレームワークデモ起動完了") + + def _init_database(self): + """データベース初期化""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + # 伝票テーブル作成 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS slips ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + theme_name TEXT NOT NULL, + items_data TEXT NOT NULL, + total_amount REAL NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + conn.commit() + conn.close() + logging.info("伝票データベース初期化完了") + + except Exception as e: + logging.error(f"データベース初期化エラー: {e}") + +def main(page: ft.Page): + """メイン関数""" + try: + app = SlipFrameworkDemoApp(page) + + except Exception as e: + logging.error(f"アプリケーション起動エラー: {e}") + +if __name__ == "__main__": + ft.run(main) diff --git a/app_text_editor.py b/app_text_editor.py new file mode 100644 index 0000000..2f44099 --- /dev/null +++ b/app_text_editor.py @@ -0,0 +1,427 @@ +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 TextEditor: + """テキストエディタ""" + + def __init__(self, page: ft.Page): + self.page = page + + # UI部品 + self.title = ft.Text("下書きエディタ", size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900) + + # テキストエリア + self.text_area = ft.TextField( + label="下書き内容", + multiline=True, + min_lines=10, + max_lines=50, + value="", + autofocus=True, + width=600, + height=400 + ) + + # ボタン群 + self.save_btn = ft.Button( + "保存", + on_click=self.save_draft, + bgcolor=ft.Colors.GREEN, + color=ft.Colors.WHITE + ) + + self.clear_btn = ft.Button( + "クリア", + on_click=self.clear_text, + bgcolor=ft.Colors.ORANGE, + color=ft.Colors.WHITE + ) + + self.load_btn = ft.Button( + "読込", + on_click=self.load_draft, + bgcolor=ft.Colors.BLUE, + color=ft.Colors.WHITE + ) + + self.delete_btn = ft.Button( + "削除", + on_click=self.delete_draft, + bgcolor=ft.Colors.RED, + color=ft.Colors.WHITE + ) + + # 下書きリスト + self.draft_list = ft.Column([], scroll=ft.ScrollMode.AUTO, height=200) + + # 操作説明 + self.instructions = ft.Text( + "操作方法: TABでフォーカス移動、SPACE/ENTERで保存、マウスクリックでもボタン操作可能", + size=12, + color=ft.Colors.GREY_600 + ) + + # データ読み込み + self.load_drafts() + + def build(self): + """UIを構築して返す""" + return ft.Column([ + self.title, + ft.Divider(), + ft.Row([self.save_btn, self.clear_btn, self.load_btn, self.delete_btn], spacing=10), + ft.Divider(), + self.text_area, + ft.Divider(), + ft.Text("下書きリスト", size=18, weight=ft.FontWeight.BOLD), + self.instructions, + self.draft_list + ], expand=True, spacing=15) + + def save_draft(self, e): + """下書きを保存""" + if self.text_area.value.strip(): + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS drafts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + content TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + cursor.execute(''' + INSERT INTO drafts (title, content) + VALUES (?, ?) + ''', (f"下書き_{datetime.now().strftime('%Y%m%d_%H%M%S')}", self.text_area.value)) + + conn.commit() + conn.close() + + # 成功メッセージ + ErrorHandler.show_snackbar(self.page, "下書きを保存しました", ft.Colors.GREEN) + logging.info(f"下書き保存: {len(self.text_area.value)} 文字") + + # リスト更新 + self.load_drafts() + + except Exception as ex: + ErrorHandler.handle_error(ex, "下書き保存エラー") + + def clear_text(self, e): + """テキストをクリア""" + self.text_area.value = "" + self.page.update() + logging.info("テキストをクリア") + + def load_draft(self, e): + """下書きを読み込み""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS drafts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + content TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + cursor.execute(''' + SELECT id, title, content, created_at + FROM drafts + ORDER BY created_at DESC + LIMIT 10 + ''') + drafts = cursor.fetchall() + conn.close() + + if drafts: + # テキストエリアに設定 + self.text_area.value = drafts[0][2] # content + + # リスト更新 + self.draft_list.controls.clear() + for draft in drafts: + draft_id, title, content, created_at = draft + + # 下書きカード + draft_card = ft.Card( + content=ft.Container( + content=ft.Column([ + ft.Row([ + ft.Text(f"ID: {draft_id}", size=12, color=ft.Colors.GREY_600), + ft.Text(f"作成: {created_at}", size=12, color=ft.Colors.GREY_600) + ], spacing=5), + ft.Text(title, weight=ft.FontWeight.BOLD, size=14), + ft.Text(content[:100] + "..." if len(content) > 100 else content, size=12), + ]), + padding=10, + width=400 + ), + margin=ft.margin.only(bottom=5) + ) + + # 読込ボタン + load_btn = ft.Button( + "読込", + on_click=lambda _, did=draft_id: self.load_specific_draft(draft_id), + bgcolor=ft.Colors.BLUE, + color=ft.Colors.WHITE, + width=80 + ) + + self.draft_list.controls.append( + ft.Row([draft_card, load_btn], alignment=ft.MainAxisAlignment.SPACE_BETWEEN) + ) + + self.page.update() + logging.info(f"下書き読込完了: {len(drafts)} 件") + + else: + ErrorHandler.show_snackbar(self.page, "下書きがありません", ft.Colors.ORANGE) + + except Exception as ex: + ErrorHandler.handle_error(ex, "下書き読込エラー") + + def load_specific_draft(self, draft_id): + """特定の下書きを読み込む""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + SELECT content + FROM drafts + WHERE id = ? + ''', (draft_id,)) + + result = cursor.fetchone() + conn.close() + + if result: + self.text_area.value = result[0] + ErrorHandler.show_snackbar(self.page, f"下書き #{draft_id} を読み込みました", ft.Colors.GREEN) + logging.info(f"下書き読込: ID={draft_id}") + else: + ErrorHandler.show_snackbar(self.page, "下書きが見つかりません", ft.Colors.RED) + + except Exception as ex: + ErrorHandler.handle_error(ex, "下書き読込エラー") + + def delete_draft(self, e): + """下書きを削除""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + DELETE FROM drafts + WHERE id = (SELECT id FROM drafts ORDER BY created_at DESC LIMIT 1) + ''') + + conn.commit() + conn.close() + + # リスト更新 + self.load_drafts() + + ErrorHandler.show_snackbar(self.page, "下書きを削除しました", ft.Colors.GREEN) + logging.info("下書き削除完了") + + except Exception as ex: + ErrorHandler.handle_error(ex, "下書き削除エラー") + + def load_drafts(self): + """下書きリストを読み込む""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS drafts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + content TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + cursor.execute(''' + SELECT id, title, content, created_at + FROM drafts + ORDER BY created_at DESC + LIMIT 10 + ''') + drafts = cursor.fetchall() + conn.close() + + # リストを更新 + self.draft_list.controls.clear() + for draft in drafts: + draft_id, title, content, created_at = draft + + # 下書きカード + draft_card = ft.Card( + content=ft.Container( + content=ft.Column([ + ft.Row([ + ft.Text(f"ID: {draft_id}", size=12, color=ft.Colors.GREY_600), + ft.Text(f"作成: {created_at}", size=12, color=ft.Colors.GREY_600) + ], spacing=5), + ft.Text(title, weight=ft.FontWeight.BOLD, size=14), + ft.Text(content[:100] + "..." if len(content) > 100 else content, size=12), + ]), + padding=10, + width=400 + ), + margin=ft.margin.only(bottom=5) + ) + + # 読込ボタン + load_btn = ft.Button( + "読込", + on_click=lambda _, did=draft_id: self.load_specific_draft(draft_id), + bgcolor=ft.Colors.BLUE, + color=ft.Colors.WHITE, + width=80 + ) + + self.draft_list.controls.append( + ft.Row([draft_card, load_btn], alignment=ft.MainAxisAlignment.SPACE_BETWEEN) + ) + + self.page.update() + + except Exception as e: + ErrorHandler.handle_error(e, "下書きリスト読込エラー") + +class SimpleApp: + """シンプルなアプリケーション""" + + def __init__(self, page: ft.Page): + self.page = page + ErrorHandler.current_page = page + + # ログ設定 + 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) + + # データベース初期化 + self._init_database() + + # テキストエディタ作成 + self.text_editor = TextEditor(page) + + # ページ構築 + page.add( + self.text_editor.build() + ) + + logging.info("テキストエディタ起動完了") + print("🚀 テキストエディタ起動完了") + + def _init_database(self): + """データベース初期化""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + # 下書きテーブル作成 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS drafts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + content TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + conn.commit() + conn.close() + logging.info("データベース初期化完了") + + except Exception as e: + ErrorHandler.handle_error(e, "データベース初期化エラー") + +def main(page: ft.Page): + """メイン関数""" + try: + # ウィンドウ設定 + page.title = "テキストエディタ" + page.window_width = 800 + page.window_height = 600 + page.theme_mode = ft.ThemeMode.LIGHT + + # ウィンドウクローズイベント + page.on_window_close = lambda _: SimpleApp(page)._cleanup_resources() + + # アプリケーション起動 + app = SimpleApp(page) + + except Exception as e: + ErrorHandler.handle_error(e, "アプリケーション起動エラー") + +if __name__ == "__main__": + ft.run(main) diff --git a/app_theme_master.py b/app_theme_master.py new file mode 100644 index 0000000..d274d95 --- /dev/null +++ b/app_theme_master.py @@ -0,0 +1,521 @@ +""" +テーマ対応マスタ管理アプリケーション +UI統一 + SQLiteテーマ管理 +""" + +import flet as ft +import sqlite3 +import signal +import sys +import logging +from datetime import datetime +from typing import List, Dict, Optional + +class ThemeManager: + """テーマ管理クラス""" + + def __init__(self): + self.themes = {} + self.current_theme = None + self._load_themes() + + def _load_themes(self): + """テーマ情報をSQLiteから読み込む""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + # テーマテーブル作成 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS themes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, + title TEXT NOT NULL, + table_name TEXT NOT NULL, + fields TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # デフォルトテーマを挿入 + default_themes = [ + { + 'name': 'customers', + 'title': '顧客マスタ', + 'table_name': 'customers', + 'fields': '[{"name": "name", "label": "顧客名", "width": 200, "required": true}, {"name": "phone", "label": "電話番号", "width": 200}, {"name": "email", "label": "メールアドレス", "width": 250}, {"name": "address", "label": "住所", "width": 300}]' + }, + { + 'name': 'products', + 'title': '商品マスタ', + 'table_name': 'products', + 'fields': '[{"name": "name", "label": "商品名", "width": 200, "required": true}, {"name": "category", "label": "カテゴリ", "width": 150}, {"name": "price", "label": "価格", "width": 100, "keyboard_type": "number", "required": true}, {"name": "stock", "label": "在庫数", "width": 100, "keyboard_type": "number"}]' + }, + { + 'name': 'sales_slips', + 'title': '伝票マスタ', + 'table_name': 'sales_slips', + 'fields': '[{"name": "title", "label": "伝票タイトル", "width": 300, "required": true}, {"name": "customer_name", "label": "顧客名", "width": 200, "required": true}, {"name": "items", "label": "明細", "width": 400, "keyboard_type": "multiline", "required": true}, {"name": "total_amount", "label": "合計金額", "width": 150, "keyboard_type": "number", "required": true}]' + } + ] + + # デフォルトテーマがなければ挿入 + cursor.execute("SELECT COUNT(*) FROM themes") + if cursor.fetchone()[0] == 0: + for theme in default_themes: + cursor.execute(''' + INSERT INTO themes (name, title, table_name, fields) + VALUES (?, ?, ?, ?) + ''', (theme['name'], theme['title'], theme['table_name'], theme['fields'])) + conn.commit() + + # テーマを読み込み + cursor.execute("SELECT * FROM themes ORDER BY id") + themes_data = cursor.fetchall() + + for theme_data in themes_data: + theme_id, name, title, table_name, fields_json, created_at = theme_data + self.themes[name] = { + 'id': theme_id, + 'title': title, + 'table_name': table_name, + 'fields': eval(fields_json) # JSONをPythonオブジェクトに変換 + } + + conn.close() + logging.info(f"テーマ読込完了: {len(self.themes)}個") + + except Exception as e: + logging.error(f"テーマ読込エラー: {e}") + + def get_theme_names(self) -> List[str]: + """テーマ名リストを取得""" + return list(self.themes.keys()) + + def get_theme(self, name: str) -> Dict: + """指定されたテーマを取得""" + return self.themes.get(name, {}) + +class UniversalMasterEditor: + """汎用マスタ編集コンポーネント""" + + def __init__(self, page: ft.Page): + self.page = page + self.theme_manager = ThemeManager() + + # 現在のテーマ + self.current_theme_name = 'customers' + self.current_theme = self.theme_manager.get_theme(self.current_theme_name) + self.current_data = [] + self.editing_id = None + + # UI部品 + self.title = ft.Text("", size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900) + self.theme_dropdown = ft.Dropdown( + label="マスタ選択", + options=[], + value="customers", + on_change=self.change_theme + ) + + # フォーム + self.form_fields = ft.Column([], spacing=10) + + # ボタン群 + self.add_btn = ft.Button("追加", on_click=self.add_new, bgcolor=ft.Colors.BLUE, color=ft.Colors.WHITE) + self.save_btn = ft.Button("保存", on_click=self.save_data, bgcolor=ft.Colors.GREEN, 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) + + # データリスト + self.data_list = ft.Column([], scroll=ft.ScrollMode.AUTO, height=300) + + # 操作説明 + self.instructions = ft.Text( + "操作方法: マスタを選択してデータを編集・追加・削除", + size=12, + color=ft.Colors.GREY_600 + ) + + # 初期化 + self._update_theme_dropdown() + self._build_form() + self._load_data() + + def _update_theme_dropdown(self): + """テーマドロップダウンを更新""" + theme_names = self.theme_manager.get_theme_names() + self.theme_dropdown.options = [ + ft.dropdown.Option(theme['title'], name) + for name, theme in self.theme_manager.themes.items() + ] + self.page.update() + + def change_theme(self, e): + """テーマを切り替え""" + self.current_theme_name = e.control.value + self.current_theme = self.theme_manager.get_theme(self.current_theme_name) + self.editing_id = None + self.title.value = self.current_theme['title'] + self._build_form() + self._load_data() + self.page.update() + + def _build_form(self): + """入力フォームを構築""" + self.form_fields.controls.clear() + + for field in self.current_theme['fields']: + keyboard_type = ft.KeyboardType.TEXT + if field.get('keyboard_type') == 'number': + keyboard_type = ft.KeyboardType.NUMBER + elif field.get('keyboard_type') == 'multiline': + keyboard_type = ft.KeyboardType.MULTILINE + + field_control = ft.TextField( + label=field['label'], + value='', + width=field['width'], + keyboard_type=keyboard_type + ) + self.form_fields.controls.append(field_control) + + self.page.update() + + def _load_data(self): + """データを読み込む""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + table_name = self.current_theme['table_name'] + cursor.execute(f''' + SELECT * FROM {table_name} + ORDER BY id + ''') + + self.current_data = cursor.fetchall() + conn.close() + + # データリストを更新 + self.data_list.controls.clear() + for item in self.current_data: + item_id = item[0] + item_data = dict(zip([col[0] for col in cursor.description], item)) + + # データ行 + row_controls = [] + for field in self.current_theme['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.current_theme_name}データ読込エラー: {e}") + + def save_data(self, e): + """データを保存""" + try: + # フォームデータを収集 + form_data = {} + for i, field in enumerate(self.current_theme['fields']): + field_control = self.form_fields.controls[i] + form_data[field['name']] = field_control.value + + # 必須項目チェック + if field.get('required', False) and not field_control.value.strip(): + self._show_snackbar(f"{field['label']}は必須項目です", ft.Colors.RED) + return + + # データベースに保存 + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + table_name = self.current_theme['table_name'] + + if self.editing_id: + # 更新 + columns = ', '.join([f"{key} = ?" for key in form_data.keys()]) + cursor.execute(f''' + UPDATE {table_name} + SET {columns} + WHERE id = ? + ''', tuple(form_data.values()) + (self.editing_id,)) + + self._show_snackbar("データを更新しました", ft.Colors.GREEN) + logging.info(f"{table_name}データ更新完了: ID={self.editing_id}") + else: + # 新規追加 + columns = ', '.join([field['name'] for field in self.current_theme['fields']]) + placeholders = ', '.join(['?' for _ in self.current_theme['fields']]) + + cursor.execute(f''' + INSERT INTO {table_name} ({columns}) + VALUES ({placeholders}) + ''', tuple(form_data.values())) + + self._show_snackbar("データを追加しました", ft.Colors.GREEN) + logging.info(f"{table_name}データ追加完了") + + conn.commit() + conn.close() + + # フォームをクリア + self.clear_form(None) + + # データ再読み込み + self._load_data() + + except Exception as e: + logging.error(f"{self.current_theme_name}データ保存エラー: {e}") + self._show_snackbar("保存エラー", ft.Colors.RED) + + def add_new(self, e): + """新規データを追加""" + self.editing_id = None + self.clear_form(None) + self.form_fields.controls[0].focus() + logging.info(f"{self.current_theme_name}新規追加モード") + + def edit_item(self, item_id): + """データを編集""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + table_name = self.current_theme['table_name'] + cursor.execute(f''' + SELECT * FROM {table_name} + WHERE id = ? + ''', (item_id,)) + + result = cursor.fetchone() + conn.close() + + if result: + self.editing_id = item_id + + # フォームにデータを設定 + for i, field in enumerate(self.current_theme['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"{table_name}編集モード: ID={item_id}") + + except Exception as e: + logging.error(f"{self.current_theme_name}編集エラー: {e}") + + def delete_item(self, item_id): + """データを削除""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + table_name = self.current_theme['table_name'] + cursor.execute(f''' + DELETE FROM {table_name} + WHERE id = ? + ''', (item_id,)) + + conn.commit() + conn.close() + + # データ再読み込み + self._load_data() + + self._show_snackbar("データを削除しました", ft.Colors.GREEN) + logging.info(f"{table_name}削除完了: ID={item_id}") + + except Exception as e: + logging.error(f"{self.current_theme_name}削除エラー: {e}") + self._show_snackbar("削除エラー", ft.Colors.RED) + + def delete_selected(self, e): + """選択中のデータを削除""" + if self.editing_id: + self.delete_item(self.editing_id) + else: + self._show_snackbar("削除対象が選択されていません", ft.Colors.ORANGE) + + 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 _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([ + ft.Row([ + self.title, + self.theme_dropdown + ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN), + ft.Divider(), + ft.Text("入力フォーム", size=18, weight=ft.FontWeight.BOLD), + self.form_fields, + ft.Row([self.add_btn, self.save_btn, self.delete_btn, self.clear_btn], spacing=10), + ft.Divider(), + ft.Text(f"{self.current_theme['title']}一覧", size=18, weight=ft.FontWeight.BOLD), + self.instructions, + self.data_list + ], expand=True, spacing=15) + +class ThemeMasterApp: + """テーマ対応マスタ管理アプリケーション""" + + def __init__(self, page: ft.Page): + self.page = page + + # ログ設定 + 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) + + # データベース初期化 + self._init_database() + + # ウィンドウ設定 + page.title = "テーマ対応マスタ管理システム" + page.window_width = 1000 + page.window_height = 700 + page.theme_mode = ft.ThemeMode.LIGHT + + # ウィンドウクローズイベント + page.on_window_close = lambda _: signal_handler(0, None) + + # テーマ対応マスタエディタ作成 + self.theme_editor = UniversalMasterEditor(page) + + # ページ構築 + page.add( + self.theme_editor.build() + ) + + logging.info("テーマ対応マスタ管理システム起動完了") + print("🚀 テーマ対応マスタ管理システム起動完了") + + def _init_database(self): + """データベース初期化""" + 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, + updated_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, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS sales_slips ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + customer_name TEXT NOT NULL, + items TEXT NOT NULL, + total_amount REAL NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + conn.commit() + conn.close() + logging.info("マスタデータベース初期化完了") + + except Exception as e: + logging.error(f"データベース初期化エラー: {e}") + +def main(page: ft.Page): + """メイン関数""" + try: + app = ThemeMasterApp(page) + + except Exception as e: + logging.error(f"アプリケーション起動エラー: {e}") + +if __name__ == "__main__": + ft.run(main) diff --git a/app_universal_master.py b/app_universal_master.py new file mode 100644 index 0000000..dc8c6ed --- /dev/null +++ b/app_universal_master.py @@ -0,0 +1,122 @@ +""" +汎用マスタ管理アプリケーション +統合的なマスタ管理機能を提供 +""" + +import flet as ft +import sqlite3 +import signal +import sys +import logging +from components.universal_master_editor import UniversalMasterEditor, create_universal_master_editor + +class UniversalMasterApp: + """汎用マスタ管理アプリケーション""" + + def __init__(self, page: ft.Page): + self.page = page + ErrorHandler.current_page = page + + # ログ設定 + 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) + + # データベース初期化 + self._init_database() + + # ウィンドウ設定 + page.title = "汎用マスタ管理システム" + page.window_width = 1000 + page.window_height = 700 + page.theme_mode = ft.ThemeMode.LIGHT + + # ウィンドウクローズイベント + page.on_window_close = lambda _: signal_handler(0, None) + + # 汎用マスタエディタ作成 + self.universal_editor = create_universal_master_editor(page) + + # ページ構築 + page.add( + self.universal_editor.build() + ) + + logging.info("汎用マスタ管理システム起動完了") + print("🚀 汎用マスタ管理システム起動完了") + + def _init_database(self): + """データベース初期化""" + 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, + updated_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, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS sales_slips ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + customer_name TEXT NOT NULL, + items TEXT NOT NULL, + total_amount REAL NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + conn.commit() + conn.close() + logging.info("マスタデータベース初期化完了") + + except Exception as e: + logging.error(f"データベース初期化エラー: {e}") + +def main(page: ft.Page): + """メイン関数""" + try: + app = UniversalMasterApp(page) + + except Exception as e: + logging.error(f"アプリケーション起動エラー: {e}") + +if __name__ == "__main__": + ft.run(main) diff --git a/components/customer_master_with_gps.py b/components/customer_master_with_gps.py new file mode 100644 index 0000000..c6f888e --- /dev/null +++ b/components/customer_master_with_gps.py @@ -0,0 +1,312 @@ +""" +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) diff --git a/components/hierarchical_product_master.py b/components/hierarchical_product_master.py new file mode 100644 index 0000000..c818665 --- /dev/null +++ b/components/hierarchical_product_master.py @@ -0,0 +1,771 @@ +""" +階層構造商品マスタコンポーネント +入れ子構造とPDF巨大カッコ表示に対応 +""" + +import flet as ft +import sqlite3 +import json +from typing import List, Dict, Optional +from datetime import datetime + +class ProductNode: + """商品ノードクラス""" + + def __init__(self, id: int = None, name: str = "", parent_id: int = None, + level: int = 0, is_category: bool = True, price: float = 0.0, + stock: int = 0, description: str = ""): + self.id = id + self.name = name + self.parent_id = parent_id + self.level = level + self.is_category = is_category + self.price = price + self.stock = stock + self.description = description + self.children: List['ProductNode'] = [] + self.expanded = True + + def add_child(self, child: 'ProductNode'): + """子ノードを追加""" + child.parent_id = self.id + child.level = self.level + 1 + self.children.append(child) + + def remove_child(self, child_id: int): + """子ノードを削除""" + self.children = [child for child in self.children if child.id != child_id] + + def to_dict(self) -> Dict: + """辞書に変換""" + return { + 'id': self.id, + 'name': self.name, + 'parent_id': self.parent_id, + 'level': self.level, + 'is_category': self.is_category, + 'price': self.price, + 'stock': self.stock, + 'description': self.description, + 'expanded': self.expanded, + 'children': [child.to_dict() for child in self.children] + } + + @classmethod + def from_dict(cls, data: Dict) -> 'ProductNode': + """辞書から作成""" + node = cls( + id=data.get('id'), + name=data.get('name', ''), + parent_id=data.get('parent_id'), + level=data.get('level', 0), + is_category=data.get('is_category', True), + price=data.get('price', 0.0), + stock=data.get('stock', 0), + description=data.get('description', '') + ) + node.expanded = data.get('expanded', True) + + for child_data in data.get('children', []): + child = cls.from_dict(child_data) + node.add_child(child) + + return node + +class HierarchicalProductMaster: + """階層構造商品マスタエディタ""" + + def __init__(self, page: ft.Page): + self.page = page + self.root_nodes: List[ProductNode] = [] + self.selected_node: Optional[ProductNode] = None + self.next_id = 1 + + # UIコンポーネント + self.tree_view = ft.Column([], expand=True, spacing=2) + self.preview_area = ft.Column([], expand=True, spacing=5) + + # 入力フィールド + self.name_field = ft.TextField( + label="商品名/カテゴリ名", + width=300, + autofocus=True + ) + self.price_field = ft.TextField( + label="価格", + width=150, + value="0" + ) + self.stock_field = ft.TextField( + label="在庫数", + width=150, + value="0" + ) + self.description_field = ft.TextField( + label="説明", + width=400, + multiline=True, + height=80 + ) + self.is_category_checkbox = ft.Checkbox( + label="カテゴリとして扱う", + value=True + ) + + # ボタン + self.add_btn = ft.Button( + "追加", + on_click=self.add_node, + bgcolor=ft.Colors.GREEN, + color=ft.Colors.WHITE + ) + self.update_btn = ft.Button( + "更新", + on_click=self.update_node, + bgcolor=ft.Colors.BLUE, + color=ft.Colors.WHITE + ) + self.delete_btn = ft.Button( + "削除", + on_click=self.delete_node, + bgcolor=ft.Colors.RED, + color=ft.Colors.WHITE + ) + self.preview_btn = ft.Button( + "PDFプレビュー", + on_click=self.show_pdf_preview, + bgcolor=ft.Colors.ORANGE, + color=ft.Colors.WHITE + ) + + # データベース初期化 + self._init_database() + + # サンプルデータ作成 + self._create_sample_data() + + def _init_database(self): + """データベース初期化""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + # 階層商品マスタテーブル作成 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS hierarchical_products ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + parent_id INTEGER, + level INTEGER DEFAULT 0, + is_category BOOLEAN DEFAULT 1, + price REAL DEFAULT 0.0, + stock INTEGER DEFAULT 0, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (parent_id) REFERENCES hierarchical_products (id) + ) + ''') + + conn.commit() + conn.close() + + except Exception as e: + print(f"データベース初期化エラー: {e}") + + def _create_sample_data(self): + """サンプルデータ作成""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + # 既存データチェック + cursor.execute("SELECT COUNT(*) FROM hierarchical_products") + if cursor.fetchone()[0] > 0: + conn.close() + return + + print("サンプルデータを作成中...") + + # 食料品カテゴリ + cursor.execute(''' + INSERT INTO hierarchical_products (name, parent_id, level, is_category) + VALUES (?, ?, ?, ?) + ''', ("食料品", None, 0, True)) + + food_id = cursor.lastrowid + + # 生鮮食品サブカテゴリ + cursor.execute(''' + INSERT INTO hierarchical_products (name, parent_id, level, is_category) + VALUES (?, ?, ?, ?) + ''', ("生鮮食品", food_id, 1, True)) + + fresh_id = cursor.lastrowid + + # 野菜カテゴリ + cursor.execute(''' + INSERT INTO hierarchical_products (name, parent_id, level, is_category) + VALUES (?, ?, ?, ?) + ''', ("野菜", fresh_id, 2, True)) + + veg_id = cursor.lastrowid + + # 具体的な野菜商品 + vegetables = [ + ("キャベツ", veg_id, 3, False, 198.0, 50, "新鮮なキャベツ"), + ("人参", veg_id, 3, False, 128.0, 80, "甘い人参"), + ("じゃがいも", veg_id, 3, False, 98.0, 100, "ホクホクのじゃがいも"), + ("玉ねぎ", veg_id, 3, False, 88.0, 120, "辛みの少ない玉ねぎ") + ] + + for veg in vegetables: + cursor.execute(''' + INSERT INTO hierarchical_products + (name, parent_id, level, is_category, price, stock, description) + VALUES (?, ?, ?, ?, ?, ?, ?) + ''', veg) + + # 果物カテゴリ + cursor.execute(''' + INSERT INTO hierarchical_products (name, parent_id, level, is_category) + VALUES (?, ?, ?, ?) + ''', ("果物", fresh_id, 2, True)) + + fruit_id = cursor.lastrowid + + # 具体的な果物商品 + fruits = [ + ("りんご", fruit_id, 3, False, 158.0, 60, "シャリシャリのりんご"), + ("バナナ", fruit_id, 3, False, 198.0, 40, "甘いバナナ"), + ("みかん", fruit_id, 3, False, 298.0, 30, "みかん1箱") + ] + + for fruit in fruits: + cursor.execute(''' + INSERT INTO hierarchical_products + (name, parent_id, level, is_category, price, stock, description) + VALUES (?, ?, ?, ?, ?, ?, ?) + ''', fruit) + + # 加工食品サブカテゴリ + cursor.execute(''' + INSERT INTO hierarchical_products (name, parent_id, level, is_category) + VALUES (?, ?, ?, ?) + ''', ("加工食品", food_id, 1, True)) + + processed_id = cursor.lastrowid + + # 缶詰カテゴリ + cursor.execute(''' + INSERT INTO hierarchical_products (name, parent_id, level, is_category) + VALUES (?, ?, ?, ?) + ''', ("缶詰", processed_id, 2, True)) + + can_id = cursor.lastrowid + + # 具体的な缶詰商品 + cans = [ + ("ツナ缶", can_id, 3, False, 128.0, 100, "水煮ツナ缶"), + ("コーン缶", can_id, 3, False, 98.0, 80, "スイートコーン缶"), + ("ミックスビーンズ", can_id, 3, False, 158.0, 60, "ミックスビーンズ缶") + ] + + for can in cans: + cursor.execute(''' + INSERT INTO hierarchical_products + (name, parent_id, level, is_category, price, stock, description) + VALUES (?, ?, ?, ?, ?, ?, ?) + ''', can) + + # 日用品カテゴリ + cursor.execute(''' + INSERT INTO hierarchical_products (name, parent_id, level, is_category) + VALUES (?, ?, ?, ?) + ''', ("日用品", None, 0, True)) + + daily_id = cursor.lastrowid + + # 衛生用品サブカテゴリ + cursor.execute(''' + INSERT INTO hierarchical_products (name, parent_id, level, is_category) + VALUES (?, ?, ?, ?) + ''', ("衛生用品", daily_id, 1, True)) + + hygiene_id = cursor.lastrowid + + # 具体的な衛生用品商品 + hygiene_items = [ + ("ティッシュペーパー", hygiene_id, 2, False, 198.0, 200, "柔らかいティッシュ"), + ("トイレットペーパー", hygiene_id, 2, False, 298.0, 100, "ダブルロール12個入"), + ("ハンドソープ", hygiene_id, 2, False, 398.0, 50, "除菌ハンドソープ") + ] + + for item in hygiene_items: + cursor.execute(''' + INSERT INTO hierarchical_products + (name, parent_id, level, is_category, price, stock, description) + VALUES (?, ?, ?, ?, ?, ?, ?) + ''', item) + + conn.commit() + conn.close() + print("サンプルデータ作成完了") + + except Exception as e: + print(f"サンプルデータ作成エラー: {e}") + + def load_data(self): + """データベースからデータを読み込み""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + SELECT id, name, parent_id, level, is_category, price, stock, description + FROM hierarchical_products + ORDER BY level, parent_id, name + ''') + + rows = cursor.fetchall() + conn.close() + + # ノードマップ作成 + node_map = {} + self.root_nodes = [] + + for row in rows: + node = ProductNode( + id=row[0], + name=row[1], + parent_id=row[2], + level=row[3], + is_category=bool(row[4]), + price=row[5], + stock=row[6], + description=row[7] + ) + node_map[node.id] = node + + # 階層構造構築 + for node in node_map.values(): + if node.parent_id is None: + self.root_nodes.append(node) + else: + parent = node_map.get(node.parent_id) + if parent: + parent.add_child(node) + + # 次のIDを設定 + if rows: + self.next_id = max(row[0] for row in rows) + 1 + + self.update_tree_view() + + except Exception as e: + print(f"データ読み込みエラー: {e}") + + def save_data(self): + """データベースに保存""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + # 既存データ削除 + cursor.execute("DELETE FROM hierarchical_products") + + # 再帰的に保存 + def save_node(node: ProductNode): + cursor.execute(''' + INSERT INTO hierarchical_products + (id, name, parent_id, level, is_category, price, stock, description) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ''', ( + node.id, node.name, node.parent_id, node.level, + node.is_category, node.price, node.stock, node.description + )) + + for child in node.children: + save_node(child) + + for root in self.root_nodes: + save_node(root) + + conn.commit() + conn.close() + + except Exception as e: + print(f"データ保存エラー: {e}") + + def update_tree_view(self): + """ツリービュー更新""" + self.tree_view.controls.clear() + + for root in self.root_nodes: + self.tree_view.controls.append(self._create_tree_node(root)) + + self.tree_view.update() + + def _create_tree_node(self, node: ProductNode) -> ft.Container: + """ツリーノード作成""" + indent = " " * node.level + + # アイコン + if node.is_category: + if node.expanded and node.children: + icon = ft.Icons.FOLDER_OPEN + elif node.children: + icon = ft.Icons.FOLDER + else: + icon = ft.Icons.FOLDER_OUTLINE + else: + icon = ft.Icons.SHOPPING_CART + + # ノードコンテナ + node_container = ft.Container( + content=ft.Row([ + # 展開/折りたたみボタン + ft.IconButton( + icon=ft.Icons.EXPAND_MORE if node.expanded else ft.Icons.CHEVRON_RIGHT, + on_click=lambda _, n=node: self.toggle_node(n), + visible=len(node.children) > 0, + width=30, + height=30 + ), + # アイコン + ft.Icon( + icon, + color=ft.Colors.ORANGE if node.is_category else ft.Colors.BLUE, + size=20 + ), + # 名前 + ft.Text( + f"{indent}{node.name}", + size=14, + weight=ft.FontWeight.BOLD if node.is_category else ft.FontWeight.NORMAL, + expand=True + ), + # 価格(商品の場合) + ft.Text( + f"¥{node.price:.0f}" if not node.is_category else "", + size=12, + color=ft.Colors.GREY_600 + ), + # 在庫(商品の場合) + ft.Text( + f"在庫:{node.stock}" if not node.is_category else "", + size=12, + color=ft.Colors.GREY_600 + ), + # 選択ボタン + ft.IconButton( + icon=ft.Icons.EDIT, + on_click=lambda _, n=node: self.select_node(n), + icon_size=16, + tooltip="編集" + ) + ], spacing=5), + padding=ft.Padding.symmetric(horizontal=10, vertical=5), + bgcolor=ft.Colors.BLUE_50 if self.selected_node == node else ft.Colors.TRANSPARENT, + border_radius=5, + on_click=lambda _, n=node: self.select_node(n) + ) + + # 子ノード + children_container = ft.Column([], spacing=2) + if node.expanded: + for child in node.children: + children_container.controls.append(self._create_tree_node(child)) + + return ft.Column([ + node_container, + children_container + ], spacing=0) + + def toggle_node(self, node: ProductNode): + """ノード展開/折りたたみ""" + node.expanded = not node.expanded + self.update_tree_view() + + def select_node(self, node: ProductNode): + """ノード選択""" + self.selected_node = node + + # 入力フィールドに値を設定 + self.name_field.value = node.name + self.price_field.value = str(node.price) + self.stock_field.value = str(node.stock) + self.description_field.value = node.description + self.is_category_checkbox.value = node.is_category + + # 価格・在庫フィールドの有効/無効 + self.price_field.disabled = node.is_category + self.stock_field.disabled = node.is_category + + self.update_tree_view() + self.page.update() + + def add_node(self, e=None): + """ノード追加""" + if not self.name_field.value: + self.show_message("名前を入力してください", ft.Colors.RED) + return + + new_node = ProductNode( + id=self.next_id, + name=self.name_field.value, + parent_id=self.selected_node.id if self.selected_node else None, + level=self.selected_node.level + 1 if self.selected_node else 0, + is_category=self.is_category_checkbox.value, + price=float(self.price_field.value) if not self.is_category_checkbox.value else 0.0, + stock=int(self.stock_field.value) if not self.is_category_checkbox.value else 0, + description=self.description_field.value + ) + + self.next_id += 1 + + if self.selected_node: + self.selected_node.add_child(new_node) + if not self.selected_node.expanded: + self.selected_node.expanded = True + else: + self.root_nodes.append(new_node) + + self.update_tree_view() + self.clear_form() + self.save_data() + self.show_message("商品を追加しました", ft.Colors.GREEN) + + def update_node(self, e=None): + """ノード更新""" + if not self.selected_node or not self.name_field.value: + self.show_message("更新対象を選択してください", ft.Colors.RED) + return + + self.selected_node.name = self.name_field.value + self.selected_node.is_category = self.is_category_checkbox.value + + if not self.is_category_checkbox.value: + self.selected_node.price = float(self.price_field.value) + self.selected_node.stock = int(self.stock_field.value) + else: + self.selected_node.price = 0.0 + self.selected_node.stock = 0 + + self.selected_node.description = self.description_field.value + + self.update_tree_view() + self.save_data() + self.show_message("商品を更新しました", ft.Colors.GREEN) + + def delete_node(self, e=None): + """ノード削除""" + if not self.selected_node: + self.show_message("削除対象を選択してください", ft.Colors.RED) + return + + # 子ノードも削除 + def remove_from_parent(node: ProductNode, target: ProductNode): + if target in node.children: + node.remove_child(target.id) + return True + for child in node.children: + if remove_from_parent(child, target): + return True + return False + + # 親を探して削除 + removed = False + for root in self.root_nodes: + if root == self.selected_node: + self.root_nodes.remove(root) + removed = True + break + if remove_from_parent(root, self.selected_node): + removed = True + break + + if removed: + self.selected_node = None + self.update_tree_view() + self.clear_form() + self.save_data() + self.show_message("商品を削除しました", ft.Colors.GREEN) + + def clear_form(self): + """フォームクリア""" + self.name_field.value = "" + self.price_field.value = "0" + self.stock_field.value = "0" + self.description_field.value = "" + self.is_category_checkbox.value = True + self.price_field.disabled = True + self.stock_field.disabled = True + + def show_pdf_preview(self, e=None): + """PDFプレビュー表示""" + self.preview_area.controls.clear() + + # キャラクターベースの階層表示 + preview_text = self._generate_character_preview() + + self.preview_area.controls = [ + ft.Container( + content=ft.Column([ + ft.Text( + "商品マスタ一覧 - PDFプレビュー", + size=20, + weight=ft.FontWeight.BOLD, + text_align=ft.TextAlign.CENTER + ), + ft.Divider(), + ft.Container( + content=ft.Text( + preview_text, + size=12, + font_family="Courier New" + ), + padding=20, + bgcolor=ft.Colors.WHITE, + border=ft.Border.all(1, ft.Colors.GREY_300), + border_radius=5 + ) + ]), + padding=20, + bgcolor=ft.Colors.GREY_50, + border_radius=10 + ) + ] + + self.preview_area.update() + + def _generate_character_preview(self) -> str: + """キャラクターベースのプレビュー生成""" + lines = [] + lines.append("┌" + "─" * 60 + "┐") + lines.append("│" + "商品マスタ一覧".center(58, " ") + "│") + lines.append("├" + "─" * 60 + "┤") + + def add_node_lines(node: ProductNode, prefix: str = "", is_last: bool = True): + # 現在のノード + connector = "└──" if is_last else "├──" + + if node.is_category: + line = f"{prefix}{connector}【{node.name}】" + else: + line = f"{prefix}{connector}・{node.name} (¥{node.price:.0f}, 在庫:{node.stock})" + + lines.append("│" + line.ljust(58, " ") + "│") + + # 子ノード + if node.children: + child_prefix = prefix + (" " if is_last else "│ ") + for i, child in enumerate(node.children): + is_last_child = (i == len(node.children) - 1) + add_node_lines(child, child_prefix, is_last_child) + + # 全ルートノードを追加 + for i, root in enumerate(self.root_nodes): + is_last_root = (i == len(self.root_nodes) - 1) + add_node_lines(root, "", is_last_root) + + lines.append("└" + "─" * 60 + "┘") + + return "\n".join(lines) + + def show_message(self, message: str, color: ft.Colors): + """メッセージ表示""" + self.page.snack_bar = ft.SnackBar( + content=ft.Text(message), + bgcolor=color + ) + self.page.snack_bar.open = True + self.page.update() + + def build(self) -> ft.Row: + """UI構築""" + # 左側:ツリービュー + left_panel = ft.Container( + content=ft.Column([ + ft.Text( + "商品階層構造", + size=18, + weight=ft.FontWeight.BOLD + ), + ft.Divider(), + ft.Container( + content=self.tree_view, + border=ft.Border.all(1, ft.Colors.GREY_300), + border_radius=5, + padding=10, + height=400 + ) + ]), + width=400, + padding=10 + ) + + # 中央:入力フォーム + center_panel = ft.Container( + content=ft.Column([ + ft.Text( + "商品情報編集", + size=18, + weight=ft.FontWeight.BOLD + ), + ft.Divider(), + self.name_field, + ft.Row([ + self.price_field, + self.stock_field + ], spacing=10), + self.description_field, + self.is_category_checkbox, + ft.Divider(), + ft.Row([ + self.add_btn, + self.update_btn, + self.delete_btn + ], spacing=10) + ]), + width=400, + padding=10 + ) + + # 右側:プレビュー + right_panel = ft.Container( + content=ft.Column([ + ft.Row([ + ft.Text( + "PDFプレビュー", + size=18, + weight=ft.FontWeight.BOLD + ), + self.preview_btn + ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN), + ft.Divider(), + ft.Container( + content=self.preview_area, + border=ft.Border.all(1, ft.Colors.GREY_300), + border_radius=5, + padding=10, + height=400 + ) + ]), + width=500, + padding=10 + ) + + return ft.Row([ + left_panel, + ft.VerticalDivider(width=1), + center_panel, + ft.VerticalDivider(width=1), + right_panel + ], expand=True) + +def create_hierarchical_product_master(page: ft.Page) -> HierarchicalProductMaster: + """階層商品マスタ作成""" + master = HierarchicalProductMaster(page) + master.load_data() + return master diff --git a/components/master_editor.py b/components/master_editor.py new file mode 100644 index 0000000..7eb82da --- /dev/null +++ b/components/master_editor.py @@ -0,0 +1,340 @@ +""" +マスタ編集コンポーネント +再利用可能な顧客・商品・伝票マスタ編集機能 +""" + +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}") + ) diff --git a/components/slip_entry_framework.py b/components/slip_entry_framework.py new file mode 100644 index 0000000..133f709 --- /dev/null +++ b/components/slip_entry_framework.py @@ -0,0 +1,482 @@ +""" +伝票入力フレームワーク +表示形式を動的に切り替える伝票入力システム +""" + +import flet as ft +import sqlite3 +import logging +import json +from datetime import datetime +from typing import List, Dict, Optional, Any + +class DisplayTheme: + """表示形式テーマ""" + + def __init__(self, name: str, title: str, format_type: str, config: Dict): + self.name = name + self.title = title + self.format_type = format_type # 'table', 'card', 'list', 'custom' + self.config = config + +class SlipEntryRenderer: + """伝票入力レンダラ""" + + def __init__(self, page: ft.Page): + self.page = page + self.current_theme = None + self.items = [] + + def set_theme(self, theme: DisplayTheme): + """テーマを設定""" + self.current_theme = theme + + def render_table_format(self, items: List[Dict]) -> ft.Column: + """表形式でレンダリング""" + if not self.current_theme or self.current_theme.format_type != 'table': + return ft.Column([ft.Text("テーマが設定されていません")]) + + config = self.current_theme.config + columns = config.get('columns', []) + + # ヘッダー行 + header_row = ft.Row( + [ft.Text(col.get('label', ''), weight=ft.FontWeight.BOLD, width=col.get('width', 100)) + for col in columns], + spacing=5 + ) + + # データ行 + data_rows = [] + for item in items: + row_controls = [] + for col in columns: + field_name = col.get('field', '') + value = item.get(field_name, '') + align = col.get('align', 'left') + + text_control = ft.Text( + value=str(value), + width=col.get('width', 100), + text_align=ft.TextAlign.LEFT if align == 'left' else ft.TextAlign.RIGHT + ) + row_controls.append(text_control) + + data_rows.append(ft.Row(row_controls, spacing=5)) + + return ft.Column([header_row] + data_rows, spacing=5) + + def render_card_format(self, items: List[Dict]) -> ft.Column: + """カード形式でレンダリング""" + if not self.current_theme or self.current_theme.format_type != 'card': + return ft.Column([ft.Text("テーマが設定されていません")]) + + config = self.current_theme.config + card_width = config.get('card_width', 300) + card_height = config.get('card_height', 120) + + cards = [] + for i, item in enumerate(items): + card_content = [] + + # カード内のフィールド + for field_name, value in item.items(): + if field_name != 'id': + card_content.append(ft.Text(f"{field_name}: {value}", size=12)) + + card = ft.Card( + content=ft.Container( + content=ft.Column(card_content), + padding=10, + width=card_width, + height=card_height + ), + margin=ft.margin.only(bottom=5) + ) + cards.append(card) + + return ft.Column(cards, scroll=ft.ScrollMode.AUTO) + + def render_list_format(self, items: List[Dict]) -> ft.Column: + """リスト形式でレンダリング""" + if not self.current_theme or self.current_theme.format_type != 'list': + return ft.Column([ft.Text("テーマが設定されていません")]) + + config = self.current_theme.config + item_height = config.get('item_height', 60) + show_dividers = config.get('show_dividers', True) + compact_mode = config.get('compact_mode', False) + + list_items = [] + for item in items: + item_content = [] + + for field_name, value in item.items(): + if field_name != 'id': + item_content.append(ft.Text(f"{field_name}: {value}", size=12)) + + list_item = ft.Container( + content=ft.Column(item_content), + padding=10, + height=item_height if not compact_mode else None, + bgcolor=ft.Colors.GREY_50 if not compact_mode else None + ) + + list_items.append(list_item) + + if show_dividers: + list_items.append(ft.Divider(height=1)) + + return ft.Column(list_items, scroll=ft.ScrollMode.AUTO) + + def render_custom_format(self, items: List[Dict]) -> ft.Column: + """カスタム形式でレンダリング""" + if not self.current_theme or self.current_theme.format_type != 'custom': + return ft.Column([ft.Text("テーマが設定されていません")]) + + config = self.current_theme.config + + # カスタムレンダリングロジック + custom_items = [] + for item in items: + custom_content = [] + + # ユーザー定義のレンダリング + for field_name, value in item.items(): + if field_name != 'id': + field_config = config.get('fields', {}).get(field_name, {}) + field_type = field_config.get('type', 'text') + + if field_type == 'text': + custom_content.append(ft.Text(f"{field_name}: {value}", size=12)) + elif field_type == 'badge': + custom_content.append( + ft.Container( + content=ft.Text(value, size=10), + bgcolor=ft.Colors.BLUE, + padding=ft.padding.symmetric(5, 2), + border_radius=5 + ) + ) + elif field_type == 'progress': + custom_content.append( + ft.ProgressBar( + value=float(value) if value else 0, + width=200 + ) + ) + + custom_items.append(ft.Container( + content=ft.Column(custom_content), + padding=10, + border=ft.border.all(1, ft.Colors.GREY_300), + border_radius=5, + margin=ft.margin.only(bottom=10) + )) + + return ft.Column(custom_items, scroll=ft.ScrollMode.AUTO) + +class SlipEntryFramework: + """伝票入力フレームワーク""" + + def __init__(self, page: ft.Page): + self.page = page + self.renderer = SlipEntryRenderer(page) + self.themes = {} + self.current_theme_name = None + self.items = [] + + # UI部品 + self.title = ft.Text("伝票入力フレームワーク", size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900) + + # テーマ選択 + self.theme_dropdown = ft.Dropdown( + label="表示形式", + options=[], + on_change=self.change_theme + ) + + # 入力フォーム + self.form_fields = ft.Column([], spacing=10) + + # 表示エリア + self.display_area = ft.Column([], scroll=ft.ScrollMode.AUTO, height=300) + + # ボタン群 + self.add_item_btn = ft.Button("明細追加", on_click=self.add_item, bgcolor=ft.Colors.BLUE, color=ft.Colors.WHITE) + self.save_btn = ft.Button("保存", on_click=self.save_slip, bgcolor=ft.Colors.GREEN, color=ft.Colors.WHITE) + self.clear_btn = ft.Button("クリア", on_click=self.clear_form, bgcolor=ft.Colors.ORANGE, color=ft.Colors.WHITE) + + # 操作説明 + self.instructions = ft.Text( + "操作方法: 表示形式を選択して伝票を入力", + size=12, + color=ft.Colors.GREY_600 + ) + + # 初期化 + self._init_themes() + self._build_form() + + def _init_themes(self): + """テーマを初期化""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + # テーマテーブル作成 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS display_themes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, + title TEXT NOT NULL, + format_type TEXT NOT NULL, + layout_config TEXT NOT NULL, + is_active BOOLEAN DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # デフォルトテーマを挿入 + default_themes = [ + { + 'name': 'table_standard', + 'title': '表形式(標準)', + 'format_type': 'table', + 'layout_config': json.dumps({ + 'columns': [ + {'field': 'item_name', 'label': '商品名', 'width': 200}, + {'field': 'quantity', 'label': '数量', 'width': 100, 'align': 'right'}, + {'field': 'unit_price', 'label': '単価', 'width': 120, 'align': 'right'}, + {'field': 'amount', 'label': '金額', 'width': 120, 'align': 'right'} + ] + }) + }, + { + 'name': 'card_standard', + 'title': 'カード形式(標準)', + 'format_type': 'card', + 'layout_config': json.dumps({ + 'card_width': 350, + 'card_height': 100, + 'fields_layout': 'vertical' + }) + }, + { + 'name': 'list_standard', + 'title': 'リスト形式(標準)', + 'format_type': 'list', + 'layout_config': json.dumps({ + 'item_height': 80, + 'show_dividers': True, + 'compact_mode': False + }) + } + ] + + # デフォルトテーマがなければ挿入 + cursor.execute("SELECT COUNT(*) FROM display_themes") + if cursor.fetchone()[0] == 0: + for theme in default_themes: + cursor.execute(''' + INSERT INTO display_themes (name, title, format_type, layout_config) + VALUES (?, ?, ?, ?) + ''', (theme['name'], theme['title'], theme['format_type'], theme['layout_config'])) + conn.commit() + + # テーマを読み込み + cursor.execute("SELECT * FROM display_themes WHERE is_active = 1 ORDER BY id") + themes_data = cursor.fetchall() + + for theme_data in themes_data: + theme_id, name, title, format_type, layout_config, is_active, created_at = theme_data + self.themes[name] = DisplayTheme(name, title, format_type, json.loads(layout_config)) + + conn.close() + + # ドロップダウンを更新 + self.theme_dropdown.options = [ + ft.dropdown.Option(theme.title, name) + for name, theme in self.themes.items() + ] + + if self.themes: + first_theme = list(self.themes.keys())[0] + self.theme_dropdown.value = first_theme + self.current_theme_name = first_theme + self.current_theme = self.themes[first_theme] + self.renderer.set_theme(self.current_theme) + + self.page.update() + + except Exception as e: + logging.error(f"テーマ初期化エラー: {e}") + + def change_theme(self, e): + """テーマを変更""" + theme_name = e.control.value + if theme_name in self.themes: + self.current_theme_name = theme_name + self.current_theme = self.themes[theme_name] + self.renderer.set_theme(self.current_theme) + self._update_display() + logging.info(f"テーマ変更: {theme_name}") + + def _build_form(self): + """入力フォームを構築""" + self.form_fields.controls.clear() + + # 基本フィールド + fields = [ + {'name': 'item_name', 'label': '商品名', 'width': 200}, + {'name': 'quantity', 'label': '数量', 'width': 100}, + {'name': 'unit_price', 'label': '単価', 'width': 120}, + {'name': 'amount', 'label': '金額', 'width': 120} + ] + + for field in fields: + field_control = ft.TextField( + label=field['label'], + value='', + width=field['width'] + ) + self.form_fields.controls.append(field_control) + + self.page.update() + + def _update_display(self): + """表示エリアを更新""" + self.display_area.controls.clear() + + if self.items: + if self.current_theme.format_type == 'table': + self.display_area.controls.append(self.renderer.render_table_format(self.items)) + elif self.current_theme.format_type == 'card': + self.display_area.controls.append(self.renderer.render_card_format(self.items)) + elif self.current_theme.format_type == 'list': + self.display_area.controls.append(self.renderer.render_list_format(self.items)) + elif self.current_theme.format_type == 'custom': + self.display_area.controls.append(self.renderer.render_custom_format(self.items)) + + self.page.update() + + def add_item(self, e): + """明細を追加""" + # フォームデータを収集 + form_data = {} + for i, field in enumerate(self.form_fields.controls): + field_name = ['item_name', 'quantity', 'unit_price', 'amount'][i] + form_data[field_name] = field.control.value + + # 金額を計算 + try: + quantity = float(form_data.get('quantity', 0)) + unit_price = float(form_data.get('unit_price', 0)) + amount = quantity * unit_price + form_data['amount'] = str(amount) + except: + form_data['amount'] = '0' + + # リストに追加 + self.items.append(form_data) + + # 表示を更新 + self._update_display() + + # フォームをクリア + self.clear_form(None) + + logging.info(f"明細追加: {form_data}") + + def clear_form(self, e): + """フォームをクリア""" + for field_control in self.form_fields.controls: + field_control.value = '' + + self.page.update() + logging.info("フォームをクリア") + + def save_slip(self, e): + """伝票を保存""" + if not self.items: + return + + try: + # データベースに保存 + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + # 伝票テーブル作成 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS slips ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + theme_name TEXT NOT NULL, + items_data TEXT NOT NULL, + total_amount REAL NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # 合計金額を計算 + total_amount = sum(float(item.get('amount', 0)) for item in self.items) + + # 伝票を保存 + cursor.execute(''' + INSERT INTO slips (title, theme_name, items_data, total_amount) + VALUES (?, ?, ?, ?) + ''', ( + f"伝票_{datetime.now().strftime('%Y%m%d_%H%M%S')}", + self.current_theme_name, + json.dumps(self.items), + total_amount + )) + + conn.commit() + conn.close() + + # 成功メッセージ + self._show_snackbar("伝票を保存しました", ft.Colors.GREEN) + logging.info(f"伝票保存: {len(self.items)}件, 合計: {total_amount}") + + # クリア + self.items.clear() + self._update_display() + + except Exception as ex: + logging.error(f"伝票保存エラー: {ex}") + self._show_snackbar("保存エラー", ft.Colors.RED) + + 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([ + ft.Row([ + self.title, + self.theme_dropdown + ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN), + ft.Divider(), + ft.Text("明細入力", size=18, weight=ft.FontWeight.BOLD), + self.form_fields, + ft.Row([self.add_item_btn, self.save_btn, self.clear_btn], spacing=10), + ft.Divider(), + ft.Text("伝票プレビュー", size=18, weight=ft.FontWeight.BOLD), + self.instructions, + self.display_area + ], expand=True, spacing=15) + +# 使用例 +def create_slip_entry_framework(page: ft.Page) -> SlipEntryFramework: + """伝票入力フレームワークを作成""" + return SlipEntryFramework(page) diff --git a/components/text_editor.py b/components/text_editor.py new file mode 100644 index 0000000..6d598e9 --- /dev/null +++ b/components/text_editor.py @@ -0,0 +1,386 @@ +""" +テキストエディタコンポーネント +再利用可能なテキスト編集機能を提供 +""" + +import flet as ft +import sqlite3 +import logging +from datetime import datetime +from typing import List, Dict, Optional, Callable + +class TextEditor: + """再利用可能なテキストエディタコンポーネント""" + + def __init__( + self, + page: ft.Page, + title: str = "テキストエディタ", + placeholder: str = "テキストを入力してください", + min_lines: int = 10, + max_lines: int = 50, + width: int = 600, + height: int = 400, + on_save: Optional[Callable] = None, + on_load: Optional[Callable] = None, + on_delete: Optional[Callable] = None + ): + self.page = page + self.title_text = title + self.placeholder = placeholder + self.min_lines = min_lines + self.max_lines = max_lines + self.width = width + self.height = height + self.on_save = on_save + self.on_load = on_load + self.on_delete = on_delete + + # UI部品 + self.title = ft.Text(title, size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900) + + # テキストエリア + self.text_area = ft.TextField( + label=placeholder, + multiline=True, + min_lines=min_lines, + max_lines=max_lines, + value="", + autofocus=True, + width=width, + height=height + ) + + # ボタン群 + self.save_btn = ft.Button( + "保存", + on_click=self.save_text, + bgcolor=ft.Colors.GREEN, + color=ft.Colors.WHITE + ) + + self.clear_btn = ft.Button( + "クリア", + on_click=self.clear_text, + bgcolor=ft.Colors.ORANGE, + color=ft.Colors.WHITE + ) + + self.load_btn = ft.Button( + "読込", + on_click=self.load_latest, + bgcolor=ft.Colors.BLUE, + color=ft.Colors.WHITE + ) + + self.delete_btn = ft.Button( + "削除", + on_click=self.delete_latest, + bgcolor=ft.Colors.RED, + color=ft.Colors.WHITE + ) + + # テキストリスト + self.text_list = ft.Column([], scroll=ft.ScrollMode.AUTO, height=200) + + # 操作説明 + self.instructions = ft.Text( + "操作方法: TABでフォーカス移動、SPACE/ENTERで保存、マウスクリックでもボタン操作可能", + size=12, + color=ft.Colors.GREY_600 + ) + + # データ読み込み + self.load_texts() + + def build(self): + """UIを構築して返す""" + return ft.Column([ + self.title, + ft.Divider(), + ft.Row([self.save_btn, self.clear_btn, self.load_btn, self.delete_btn], spacing=10), + ft.Divider(), + self.text_area, + ft.Divider(), + ft.Text("保存済みテキスト", size=18, weight=ft.FontWeight.BOLD), + self.instructions, + self.text_list + ], expand=True, spacing=15) + + def save_text(self, e): + """テキストを保存""" + if self.text_area.value.strip(): + try: + # データベースに保存 + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS text_storage ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + category TEXT NOT NULL, + title TEXT NOT NULL, + content TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + cursor.execute(''' + INSERT INTO text_storage (category, title, content) + VALUES (?, ?, ?) + ''', (self.title_text, f"{self.title_text}_{datetime.now().strftime('%Y%m%d_%H%M%S')}", self.text_area.value)) + + conn.commit() + conn.close() + + # コールバック実行 + if self.on_save: + self.on_save(self.text_area.value) + + # 成功メッセージ + self._show_snackbar("テキストを保存しました", ft.Colors.GREEN) + logging.info(f"テキスト保存: {len(self.text_area.value)} 文字") + + # リスト更新 + self.load_texts() + + except Exception as ex: + logging.error(f"テキスト保存エラー: {ex}") + self._show_snackbar("保存エラー", ft.Colors.RED) + + def clear_text(self, e): + """テキストをクリア""" + self.text_area.value = "" + self.page.update() + logging.info("テキストをクリア") + + def load_latest(self, e): + """最新のテキストを読み込み""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS text_storage ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + category TEXT NOT NULL, + title TEXT NOT NULL, + content TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + cursor.execute(''' + SELECT id, title, content, created_at + FROM text_storage + WHERE category = ? + ORDER BY created_at DESC + LIMIT 1 + ''', (self.title_text,)) + + result = cursor.fetchone() + conn.close() + + if result: + text_id, title, content, created_at = result + self.text_area.value = content + + # コールバック実行 + if self.on_load: + self.on_load(content) + + self._show_snackbar(f"テキストを読み込みました (ID: {text_id})", ft.Colors.GREEN) + logging.info(f"テキスト読込: ID={text_id}") + else: + self._show_snackbar("保存済みテキストがありません", ft.Colors.ORANGE) + + except Exception as ex: + logging.error(f"テキスト読込エラー: {ex}") + self._show_snackbar("読込エラー", ft.Colors.RED) + + def delete_latest(self, e): + """最新のテキストを削除""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + DELETE FROM text_storage + WHERE id = (SELECT id FROM text_storage WHERE category = ? ORDER BY created_at DESC LIMIT 1) + ''', (self.title_text,)) + + conn.commit() + conn.close() + + # コールバック実行 + if self.on_delete: + self.on_delete() + + # リスト更新 + self.load_texts() + + self._show_snackbar("テキストを削除しました", ft.Colors.GREEN) + logging.info("テキスト削除完了") + + except Exception as ex: + logging.error(f"テキスト削除エラー: {ex}") + self._show_snackbar("削除エラー", ft.Colors.RED) + + def load_texts(self): + """テキストリストを読み込む""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS text_storage ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + category TEXT NOT NULL, + title TEXT NOT NULL, + content TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + cursor.execute(''' + SELECT id, title, content, created_at + FROM text_storage + WHERE category = ? + ORDER BY created_at DESC + LIMIT 10 + ''', (self.title_text,)) + + texts = cursor.fetchall() + conn.close() + + # リストを更新 + self.text_list.controls.clear() + for text in texts: + text_id, title, content, created_at = text + + # テキストカード + text_card = ft.Card( + content=ft.Container( + content=ft.Column([ + ft.Row([ + ft.Text(f"ID: {text_id}", size=12, color=ft.Colors.GREY_600), + ft.Text(f"作成: {created_at}", size=12, color=ft.Colors.GREY_600) + ], spacing=5), + ft.Text(title, weight=ft.FontWeight.BOLD, size=14), + ft.Text(content[:100] + "..." if len(content) > 100 else content, size=12), + ]), + padding=10, + width=400 + ), + margin=ft.margin.only(bottom=5) + ) + + # 読込ボタン + load_btn = ft.Button( + "読込", + on_click=lambda _, tid=text_id: self.load_specific_text(text_id), + bgcolor=ft.Colors.BLUE, + color=ft.Colors.WHITE, + width=80 + ) + + self.text_list.controls.append( + ft.Row([text_card, load_btn], alignment=ft.MainAxisAlignment.SPACE_BETWEEN) + ) + + self.page.update() + + except Exception as e: + logging.error(f"テキストリスト読込エラー: {e}") + + def load_specific_text(self, text_id): + """特定のテキストを読み込む""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + cursor.execute(''' + SELECT content + FROM text_storage + WHERE id = ? + ''', (text_id,)) + + result = cursor.fetchone() + conn.close() + + if result: + self.text_area.value = result[0] + + # コールバック実行 + if self.on_load: + self.on_load(result[0]) + + self._show_snackbar(f"テキスト #{text_id} を読み込みました", ft.Colors.GREEN) + logging.info(f"テキスト読込: ID={text_id}") + else: + self._show_snackbar("テキストが見つかりません", ft.Colors.RED) + + except Exception as ex: + logging.error(f"テキスト読込エラー: {ex}") + self._show_snackbar("読込エラー", ft.Colors.RED) + + def get_text(self) -> str: + """現在のテキストを取得""" + return self.text_area.value + + def set_text(self, text: str): + """テキストを設定""" + self.text_area.value = text + self.page.update() + + 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 create_draft_editor(page: ft.Page) -> TextEditor: + """下書きエディタを作成""" + return TextEditor( + page=page, + title="下書きエディタ", + placeholder="下書き内容を入力してください", + min_lines=15, + max_lines=50, + width=600, + height=400 + ) + +def create_memo_editor(page: ft.Page) -> TextEditor: + """メモエディタを作成""" + return TextEditor( + page=page, + title="メモ", + placeholder="メモを入力してください", + min_lines=10, + max_lines=30, + width=500, + height=300 + ) + +def create_note_editor(page: ft.Page) -> TextEditor: + """ノートエディタを作成""" + return TextEditor( + page=page, + title="ノート", + placeholder="ノートを入力してください", + min_lines=20, + max_lines=100, + width=700, + height=500 + ) diff --git a/components/universal_master_editor.py b/components/universal_master_editor.py new file mode 100644 index 0000000..6166228 --- /dev/null +++ b/components/universal_master_editor.py @@ -0,0 +1,348 @@ +""" +汎用マスタ編集コンポーネント +1つのコンポーネントで複数のマスタを管理 +""" + +import flet as ft +import sqlite3 +import logging +from datetime import datetime +from typing import List, Dict, Optional, Callable + +class UniversalMasterEditor: + """汎用マスタ編集コンポーネント""" + + def __init__(self, page: ft.Page): + self.page = page + + # マスタ定義 + self.masters = { + 'customers': { + 'title': '顧客マスタ', + 'table_name': 'customers', + 'fields': [ + {'name': 'name', 'label': '顧客名', 'width': 200, 'required': True}, + {'name': 'phone', 'label': '電話番号', 'width': 200}, + {'name': 'email', 'label': 'メールアドレス', 'width': 250}, + {'name': 'address', 'label': '住所', 'width': 300} + ] + }, + 'products': { + 'title': '商品マスタ', + 'table_name': 'products', + 'fields': [ + {'name': 'name', 'label': '商品名', 'width': 200, 'required': True}, + {'name': 'category', 'label': 'カテゴリ', 'width': 150}, + {'name': 'price', 'label': '価格', 'width': 100, 'keyboard_type': ft.KeyboardType.NUMBER, 'required': True}, + {'name': 'stock', 'label': '在庫数', 'width': 100, 'keyboard_type': ft.KeyboardType.NUMBER} + ] + }, + 'sales_slips': { + 'title': '伝票マスタ', + 'table_name': 'sales_slips', + 'fields': [ + {'name': 'title', 'label': '伝票タイトル', 'width': 300, 'required': True}, + {'name': 'customer_name', 'label': '顧客名', 'width': 200, 'required': True}, + {'name': 'items', 'label': '明細', 'width': 400, 'keyboard_type': ft.KeyboardType.MULTILINE, 'required': True}, + {'name': 'total_amount', 'label': '合計金額', 'width': 150, 'keyboard_type': ft.KeyboardType.NUMBER, 'required': True} + ] + } + } + + # 現在のマスタ + self.current_master = 'customers' + self.current_data = [] + self.editing_id = None + + # UI部品 + self.title = ft.Text("", size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900) + self.master_dropdown = ft.Dropdown( + label="マスタ選択", + options=[ + ft.dropdown.Option("顧客マスタ", "customers"), + ft.dropdown.Option("商品マスタ", "products"), + ft.dropdown.Option("伝票マスタ", "sales_slips") + ], + value="customers", + on_change=self.change_master + ) + + # フォーム + self.form_fields = ft.Column([], spacing=10) + + # ボタン群 + self.add_btn = ft.Button("追加", on_click=self.add_new, bgcolor=ft.Colors.BLUE, color=ft.Colors.WHITE) + self.save_btn = ft.Button("保存", on_click=self.save_data, bgcolor=ft.Colors.GREEN, 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) + + # データリスト + self.data_list = ft.Column([], scroll=ft.ScrollMode.AUTO, height=300) + + # 操作説明 + self.instructions = ft.Text( + "操作方法: マスタを選択してデータを編集・追加・削除", + size=12, + color=ft.Colors.GREY_600 + ) + + # 初期化 + self._build_form() + self._load_data() + + def change_master(self, e): + """マスタを切り替え""" + self.current_master = e.control.value + self.editing_id = None + self.title.value = self.masters[self.current_master]['title'] + self._build_form() + self._load_data() + self.page.update() + + def _build_form(self): + """入力フォームを構築""" + self.form_fields.controls.clear() + + for field in self.masters[self.current_master]['fields']: + field_control = ft.TextField( + label=field['label'], + value='', + width=field['width'], + 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() + + table_name = self.masters[self.current_master]['table_name'] + cursor.execute(f''' + SELECT * FROM {table_name} + ORDER BY id + ''') + + self.current_data = cursor.fetchall() + conn.close() + + # データリストを更新 + self.data_list.controls.clear() + for item in self.current_data: + item_id = item[0] + item_data = dict(zip([col[0] for col in cursor.description], item)) + + # データ行 + row_controls = [] + for field in self.masters[self.current_master]['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.current_master}データ読込エラー: {e}") + + def save_data(self, e): + """データを保存""" + try: + # フォームデータを収集 + form_data = {} + for i, field in enumerate(self.masters[self.current_master]['fields']): + field_control = self.form_fields.controls[i] + form_data[field['name']] = field_control.value + + # 必須項目チェック + if field.get('required', False) and not field_control.value.strip(): + self._show_snackbar(f"{field['label']}は必須項目です", ft.Colors.RED) + return + + # データベースに保存 + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + table_name = self.masters[self.current_master]['table_name'] + + if self.editing_id: + # 更新 + columns = ', '.join([f"{key} = ?" for key in form_data.keys()]) + cursor.execute(f''' + UPDATE {table_name} + SET {columns} + WHERE id = ? + ''', tuple(form_data.values()) + (self.editing_id,)) + + self._show_snackbar("データを更新しました", ft.Colors.GREEN) + logging.info(f"{table_name}データ更新完了: ID={self.editing_id}") + else: + # 新規追加 + columns = ', '.join([field['name'] for field in self.masters[self.current_master]['fields']]) + placeholders = ', '.join(['?' for _ in self.masters[self.current_master]['fields']]) + + cursor.execute(f''' + INSERT INTO {table_name} ({columns}) + VALUES ({placeholders}) + ''', tuple(form_data.values())) + + self._show_snackbar("データを追加しました", ft.Colors.GREEN) + logging.info(f"{table_name}データ追加完了") + + conn.commit() + conn.close() + + # フォームをクリア + self.clear_form(None) + + # データ再読み込み + self._load_data() + + except Exception as e: + logging.error(f"{self.current_master}データ保存エラー: {e}") + self._show_snackbar("保存エラー", ft.Colors.RED) + + def add_new(self, e): + """新規データを追加""" + self.editing_id = None + self.clear_form(None) + self.form_fields.controls[0].focus() + logging.info(f"{self.current_master}新規追加モード") + + def edit_item(self, item_id): + """データを編集""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + table_name = self.masters[self.current_master]['table_name'] + cursor.execute(f''' + SELECT * FROM {table_name} + WHERE id = ? + ''', (item_id,)) + + result = cursor.fetchone() + conn.close() + + if result: + self.editing_id = item_id + + # フォームにデータを設定 + for i, field in enumerate(self.masters[self.current_master]['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"{table_name}編集モード: ID={item_id}") + + except Exception as e: + logging.error(f"{self.current_master}編集エラー: {e}") + + def delete_item(self, item_id): + """データを削除""" + try: + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + + table_name = self.masters[self.current_master]['table_name'] + cursor.execute(f''' + DELETE FROM {table_name} + WHERE id = ? + ''', (item_id,)) + + conn.commit() + conn.close() + + # データ再読み込み + self._load_data() + + self._show_snackbar("データを削除しました", ft.Colors.GREEN) + logging.info(f"{table_name}削除完了: ID={item_id}") + + except Exception as e: + logging.error(f"{self.current_master}削除エラー: {e}") + self._show_snackbar("削除エラー", ft.Colors.RED) + + def delete_selected(self, e): + """選択中のデータを削除""" + if self.editing_id: + self.delete_item(self.editing_id) + else: + self._show_snackbar("削除対象が選択されていません", ft.Colors.ORANGE) + + 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 _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([ + ft.Row([ + self.title, + self.master_dropdown + ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN), + ft.Divider(), + ft.Text("入力フォーム", size=18, weight=ft.FontWeight.BOLD), + self.form_fields, + ft.Row([self.add_btn, self.save_btn, self.delete_btn, self.clear_btn], spacing=10), + ft.Divider(), + ft.Text(f"{self.masters[self.current_master]['title']}一覧", size=18, weight=ft.FontWeight.BOLD), + self.instructions, + self.data_list + ], expand=True, spacing=15) + +# 使用例 +def create_universal_master_editor(page: ft.Page) -> UniversalMasterEditor: + """汎用マスタ編集画面を作成""" + return UniversalMasterEditor(page) diff --git a/debug_test.py b/debug_test.py new file mode 100644 index 0000000..ed5b5ff --- /dev/null +++ b/debug_test.py @@ -0,0 +1,9 @@ +import flet as ft + +def main(page: ft.Page): + page.title = "テスト" + page.add(ft.Text("Hello World!")) + page.add(ft.ElevatedButton("クリック", on_click=lambda e: print("クリックされた"))) + +if __name__ == "__main__": + ft.app(target=main) diff --git a/main.py b/main.py index b5b709d..2fd8945 100644 --- a/main.py +++ b/main.py @@ -28,33 +28,21 @@ class SalesAssistant: min_extended_width=200, destinations=[ ft.NavigationRailDestination( - icon=ft.icons.DASHBOARD_OUTLINED, - selected_icon=ft.icons.DASHBOARD, label="ダッシュボード" ), ft.NavigationRailDestination( - icon=ft.icons.PEOPLE_OUTLINED, - selected_icon=ft.icons.PEOPLE, label="顧客管理" ), ft.NavigationRailDestination( - icon=ft.icons.INVENTORY_2_OUTLINED, - selected_icon=ft.icons.INVENTORY_2, label="商品管理" ), ft.NavigationRailDestination( - icon=ft.icons.RECEIPT_OUTLINED, - selected_icon=ft.icons.RECEIPT, label="売上管理" ), ft.NavigationRailDestination( - icon=ft.icons.DOWNLOAD_OUTLINED, - selected_icon=ft.icons.DOWNLOAD, label="データ出力" ), ft.NavigationRailDestination( - icon=ft.icons.GAVEL_OUTLINED, - selected_icon=ft.icons.GAVEL, label="コンプライアンス" ), ], @@ -175,7 +163,7 @@ class SalesAssistant: ft.Card( content=ft.Container( content=ft.Column([ - ft.Text("顧客数", size=16, color=ft.colors.BLUE), + ft.Text("顧客数", size=16, color=ft.Colors.BLUE), ft.Text(str(total_customers), size=32, weight=ft.FontWeight.BOLD) ]), padding=20, @@ -185,7 +173,7 @@ class SalesAssistant: ft.Card( content=ft.Container( content=ft.Column([ - ft.Text("商品数", size=16, color=ft.colors.GREEN), + ft.Text("商品数", size=16, color=ft.Colors.GREEN), ft.Text(str(total_products), size=32, weight=ft.FontWeight.BOLD) ]), padding=20, @@ -195,7 +183,7 @@ class SalesAssistant: ft.Card( content=ft.Container( content=ft.Column([ - ft.Text("売上件数", size=16, color=ft.colors.ORANGE), + ft.Text("売上件数", size=16, color=ft.Colors.ORANGE), ft.Text(str(total_sales), size=32, weight=ft.FontWeight.BOLD) ]), padding=20, @@ -205,7 +193,7 @@ class SalesAssistant: ft.Card( content=ft.Container( content=ft.Column([ - ft.Text("総売上", size=16, color=ft.colors.PURPLE), + ft.Text("総売上", size=16, color=ft.Colors.PURPLE), ft.Text(f"¥{total_revenue:,.0f}", size=32, weight=ft.FontWeight.BOLD) ]), padding=20, @@ -767,12 +755,12 @@ class SalesAssistant: file_path = self.exporter.export_to_json() self.page.snack_bar = ft.SnackBar( content=ft.Text(f"JSON出力完了: {file_path}"), - bgcolor=ft.colors.GREEN + 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 + bgcolor=ft.Colors.RED ) self.page.snack_bar.open = True @@ -785,12 +773,12 @@ class SalesAssistant: 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 + 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 + bgcolor=ft.Colors.RED ) self.page.snack_bar.open = True @@ -807,12 +795,12 @@ class SalesAssistant: message = "整合性チェック完了:\n" + "\n".join(results) self.page.snack_bar = ft.SnackBar( content=ft.Text(message), - bgcolor=ft.colors.GREEN + bgcolor=ft.Colors.GREEN ) except Exception as ex: self.page.snack_bar = ft.SnackBar( content=ft.Text(f"整合性チェックエラー: {str(ex)}"), - bgcolor=ft.colors.RED + bgcolor=ft.Colors.RED ) self.page.snack_bar.open = True @@ -824,7 +812,7 @@ class SalesAssistant: result = self.compliance.archive_old_data() if result["status"] == "SUCCESS": message = f"アーカイブ完了: {result['archived_count']}件" - bgcolor = ft.colors.GREEN + bgcolor = ft.Colors.GREEN else: message = f"アーカイブエラー: {result['error']}" bgcolor = ft.colors.RED @@ -836,7 +824,7 @@ class SalesAssistant: except Exception as ex: self.page.snack_bar = ft.SnackBar( content=ft.Text(f"アーカイブエラー: {str(ex)}"), - bgcolor=ft.colors.RED + bgcolor=ft.Colors.RED ) self.page.snack_bar.open = True @@ -877,7 +865,7 @@ class SalesAssistant: except Exception as ex: self.page.snack_bar = ft.SnackBar( content=ft.Text(f"レポート生成エラー: {str(ex)}"), - bgcolor=ft.colors.RED + bgcolor=ft.Colors.RED ) self.page.snack_bar.open = True self.page.update() diff --git a/main_simple.py b/main_simple.py new file mode 100644 index 0000000..f857a1b --- /dev/null +++ b/main_simple.py @@ -0,0 +1,311 @@ +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) diff --git a/minimal.py b/minimal.py new file mode 100644 index 0000000..5070bd9 --- /dev/null +++ b/minimal.py @@ -0,0 +1,177 @@ +import flet as ft +import sqlite3 +import signal +import sys +import logging +from datetime import datetime + +def init_db(): + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + cursor.execute(''' + CREATE TABLE IF NOT EXISTS sales ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + customer TEXT NOT NULL, + product TEXT NOT NULL, + amount REAL NOT NULL, + date TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + conn.commit() + conn.close() + return True + +def load_sales(): + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + cursor.execute(''' + SELECT customer, product, amount, date + FROM sales + ORDER BY created_at DESC + LIMIT 20 + ''') + sales = cursor.fetchall() + conn.close() + return sales + +def add_sale(customer, product, amount): + conn = sqlite3.connect('sales.db') + cursor = conn.cursor() + cursor.execute(''' + INSERT INTO sales (customer, product, amount, date) + VALUES (?, ?, ?, ?) + ''', (customer, product, float(amount), datetime.now().strftime("%Y-%m-%d"))) + conn.commit() + conn.close() + return True + +def cleanup_resources(): + """リソースをクリーンアップ""" + try: + logging.info("アプリケーション終了処理開始") + print("✅ 正常終了処理完了") + logging.info("アプリケーション正常終了") + except Exception as e: + logging.error(f"クリーンアップエラー: {e}") + print(f"❌ クリーンアップエラー: {e}") + +def signal_handler(signum, frame): + """シグナルハンドラ""" + print(f"\nシグナル {signum} を受信しました") + cleanup_resources() + sys.exit(0) + +def main(page: ft.Page): + # ログ設定 + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler('app.log'), + logging.StreamHandler() + ] + ) + + # シグナルハンドラ設定 + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + logging.info("アプリケーション起動") + + # データベース初期化 + try: + init_db() + logging.info("データベース初期化完了") + except Exception as e: + logging.error(f"データベース初期化エラー: {e}") + print(f"❌ データベース初期化エラー: {e}") + + page.title = "販売アシスト" + page.window_width = 400 + page.window_height = 600 + + # ウィンドウクローズイベント + def on_window_close(e): + logging.info("ウィンドウクローズイベント") + cleanup_resources() + + page.on_window_close = on_window_close + + # UI要素定義 + customer = ft.TextField(label="顧客名") + product = ft.TextField(label="商品名") + amount = ft.TextField(label="金額") + list_view = ft.Column() + + def add_sale_clicked(e): + if customer.value and product.value and amount.value: + try: + # 保存前に値を取得 + customer_val = customer.value + product_val = product.value + amount_val = amount.value + + # データベースに保存 + add_sale(customer_val, product_val, amount_val) + + # フィールドをクリア + customer.value = "" + product.value = "" + amount.value = "" + + # リスト更新 + update_list() + + # 成功メッセージ + 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 update_list(): + try: + list_view.controls.clear() + sales = load_sales() + for sale in sales: + customer, product, amount, date = sale + list_view.controls.append( + ft.Text(f"{date}: {customer} - {product}: ¥{amount:,.0f}") + ) + except Exception as e: + logging.error(f"リスト更新エラー: {e}") + + # ボタン定義(関数定義後) + add_btn = ft.Button("追加", on_click=add_sale_clicked) + + # 初期データ読み込み + update_list() + + logging.info("UI初期化完了") + print("🚀 アプリケーション起動完了") + + page.add( + ft.Text("売上管理", size=20), + customer, + product, + amount, + add_btn, + ft.Divider(), + ft.Text("売上一覧", size=16), + list_view + ) + +if __name__ == "__main__": + ft.app(target=main) diff --git a/run_4k.py b/run_4k.py new file mode 100755 index 0000000..a69fd8b --- /dev/null +++ b/run_4k.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""4K環境対応Fletアプリ起動スクリプト""" + +import os +import subprocess +import sys + +def main(): + # 4K環境向けスケーリング設定 + env = os.environ.copy() + + # Linux Mint + Compiz + 4K向け設定 + env.update({ + 'FLET_FORCE_PIXEL_RATIO': '2.0', # 4K用に2倍スケーリング + 'GDK_SCALE': '2', # GTKスケーリング + 'GDK_DPI_SCALE': '2', # DPIスケーリング + 'QT_SCALE_FACTOR': '2', # Qtスケーリング + 'QT_AUTO_SCREEN_SCALE_FACTOR': '2', # Qt自動スケーリング + 'DISPLAY': ':0', + 'XDG_SESSION_TYPE': 'x11', # X11強制 + 'GDK_BACKEND': 'x11' # X11バックエンド強制 + }) + + print("🚀 4K環境で販売アシスト1号を起動します...") + print(f"スケーリング設定: {dict((k, v) for k, v in env.items() if 'SCALE' in k or 'DPI' in k or 'RATIO' in k)}") + + try: + # Fletアプリを起動 + subprocess.run([ + sys.executable, 'main_simple.py' + ], env=env, check=True) + print("✅ アプリが正常に終了しました") + + except subprocess.CalledProcessError as e: + print(f"❌ 起動エラー: {e}") + return 1 + except KeyboardInterrupt: + print("\n👋 アプリを終了します") + return 0 + except Exception as e: + print(f"❌ 予期せぬエラー: {e}") + return 1 + + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/run_linux.py b/run_linux.py new file mode 100755 index 0000000..cc786d9 --- /dev/null +++ b/run_linux.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +"""Linux Mint向けFletアプリ起動スクリプト""" + +import os +import subprocess +import sys + +def main(): + # Linux Mint向け環境変数設定 + env = os.environ.copy() + env.update({ + 'FLET_FORCE_PIXEL_RATIO': '1.0', + 'GDK_SCALE': '1', + 'GDK_BACKEND': 'x11', + 'QT_SCALE_FACTOR': '1', + 'DISPLAY': ':0' + }) + + print("🚀 販売アシスト1号を起動します...") + print(f"環境変数: {dict(env)}") + + try: + # Fletアプリを起動 + subprocess.run([ + sys.executable, 'main_simple.py' + ], env=env, check=True) + print("✅ アプリが正常に終了しました") + + except subprocess.CalledProcessError as e: + print(f"❌ 起動エラー: {e}") + return 1 + except KeyboardInterrupt: + print("\n👋 アプリを終了します") + return 0 + except Exception as e: + print(f"❌ 予期せぬエラー: {e}") + return 1 + + return 0 + +if __name__ == "__main__": + sys.exit(main())