From 431052920e44b4244434eb00cff3bcab1a5580bc Mon Sep 17 00:00:00 2001 From: Nexa Admin Date: Wed, 11 Mar 2026 16:19:52 +0000 Subject: [PATCH] feat: separate fusion field service and LTC into standalone modules, update core modules - fusion_claims: separated field service logic, updated controllers/views - fusion_tasks: updated task views and map integration - fusion_authorizer_portal: added page 11 signing, schedule booking, migrations - fusion_shipping: new standalone shipping module (Canada Post, FedEx, DHL, Purolator) - fusion_ltc_management: new standalone LTC management module --- .../__pycache__/__init__.cpython-310.pyc | Bin 2895 -> 0 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 186 -> 0 bytes .../__pycache__/main.cpython-310.pyc | Bin 3683 -> 0 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 1931 -> 0 bytes .../account_account.cpython-310.pyc | Bin 2426 -> 0 bytes ...count_aged_partner_balance.cpython-310.pyc | Bin 18505 -> 0 bytes .../account_analytic_report.cpython-310.pyc | Bin 11806 -> 0 bytes .../__pycache__/account_asset.cpython-310.pyc | Bin 62463 -> 0 bytes .../account_bank_statement.cpython-310.pyc | Bin 8111 -> 0 bytes .../account_cash_flow_report.cpython-310.pyc | Bin 26920 -> 0 bytes .../account_chart_template.cpython-310.pyc | Bin 2367 -> 0 bytes .../account_deferred_reports.cpython-310.pyc | Bin 21905 -> 0 bytes .../account_fiscal_position.cpython-310.pyc | Bin 1214 -> 0 bytes .../account_fiscal_year.cpython-310.pyc | Bin 2049 -> 0 bytes .../account_general_ledger.cpython-310.pyc | Bin 26038 -> 0 bytes ...account_generic_tax_report.cpython-310.pyc | Bin 42140 -> 0 bytes .../account_journal.cpython-310.pyc | Bin 10766 -> 0 bytes .../account_journal_csv.cpython-310.pyc | Bin 2023 -> 0 bytes .../account_journal_dashboard.cpython-310.pyc | Bin 3176 -> 0 bytes .../account_journal_report.cpython-310.pyc | Bin 42390 -> 0 bytes .../__pycache__/account_move.cpython-310.pyc | Bin 54692 -> 0 bytes .../account_move_line.cpython-310.pyc | Bin 4299 -> 0 bytes ...urrency_revaluation_report.cpython-310.pyc | Bin 20341 -> 0 bytes .../account_partner_ledger.cpython-310.pyc | Bin 31058 -> 0 bytes .../account_payment.cpython-310.pyc | Bin 1817 -> 0 bytes .../account_reconcile_model.cpython-310.pyc | Bin 18640 -> 0 bytes ...count_reconcile_model_line.cpython-310.pyc | Bin 4584 -> 0 bytes .../account_report.cpython-310.pyc | Bin 238060 -> 0 bytes .../account_sales_report.cpython-310.pyc | Bin 16040 -> 0 bytes .../__pycache__/account_tax.cpython-310.pyc | Bin 9373 -> 0 bytes ...count_trial_balance_report.cpython-310.pyc | Bin 8425 -> 0 bytes .../__pycache__/balance_sheet.cpython-310.pyc | Bin 867 -> 0 bytes .../bank_rec_widget.cpython-310.pyc | Bin 51737 -> 0 bytes .../bank_rec_widget_line.cpython-310.pyc | Bin 12691 -> 0 bytes ...bank_reconciliation_report.cpython-310.pyc | Bin 23220 -> 0 bytes .../models/__pycache__/budget.cpython-310.pyc | Bin 4706 -> 0 bytes .../chart_template.cpython-310.pyc | Bin 1695 -> 0 bytes .../models/__pycache__/digest.cpython-310.pyc | Bin 1643 -> 0 bytes .../executive_summary_report.cpython-310.pyc | Bin 1025 -> 0 bytes .../__pycache__/ir_actions.cpython-310.pyc | Bin 718 -> 0 bytes .../__pycache__/ir_ui_menu.cpython-310.pyc | Bin 1099 -> 0 bytes .../__pycache__/mail_activity.cpython-310.pyc | Bin 1396 -> 0 bytes .../mail_activity_type.cpython-310.pyc | Bin 521 -> 0 bytes .../__pycache__/res_company.cpython-310.pyc | Bin 21903 -> 0 bytes .../res_config_settings.cpython-310.pyc | Bin 9593 -> 0 bytes .../__pycache__/res_currency.cpython-310.pyc | Bin 1301 -> 0 bytes .../__pycache__/res_partner.cpython-310.pyc | Bin 2442 -> 0 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 658 -> 0 bytes ...ount_auto_reconcile_wizard.cpython-310.pyc | Bin 7168 -> 0 bytes ..._bank_statement_import_csv.cpython-310.pyc | Bin 4269 -> 0 bytes .../account_change_lock_date.cpython-310.pyc | Bin 15249 -> 0 bytes .../account_reconcile_wizard.cpython-310.pyc | Bin 23754 -> 0 bytes ...file_download_error_wizard.cpython-310.pyc | Bin 1034 -> 0 bytes .../account_report_send.cpython-310.pyc | Bin 10408 -> 0 bytes .../__pycache__/asset_modify.cpython-310.pyc | Bin 14648 -> 0 bytes .../__pycache__/fiscal_year.cpython-310.pyc | Bin 984 -> 0 bytes .../multicurrency_revaluation.cpython-310.pyc | Bin 7263 -> 0 bytes .../report_export_wizard.cpython-310.pyc | Bin 4728 -> 0 bytes .../__pycache__/setup_wizards.cpython-310.pyc | Bin 970 -> 0 bytes fusion_authorizer_portal/__init__.py | 26 + fusion_authorizer_portal/__manifest__.py | 10 +- .../controllers/__init__.py | 3 +- .../controllers/portal_main.py | 222 +- .../controllers/portal_page11_sign.py | 206 + .../controllers/portal_schedule.py | 327 + .../data/appointment_invite_data.xml | 13 + .../migrations/19.0.2.3.0/end-migrate.py | 36 + .../migrations/19.0.2.4.0/end-migrate.py | 36 + .../migrations/19.0.2.5.0/end-migrate.py | 36 + fusion_authorizer_portal/models/assessment.py | 3 + .../models/loaner_checkout.py | 1 + .../models/res_partner.py | 5 +- fusion_authorizer_portal/models/sale_order.py | 3 + .../static/src/css/technician_portal.css | 158 +- .../src/js/chatter_message_authorizer.js | 3 + .../static/src/js/portal_schedule_booking.js | 343 + .../static/src/js/technician_location.js | 268 +- fusion_authorizer_portal/utils/pdf_filler.py | 50 +- .../views/portal_page11_sign_templates.xml | 413 ++ .../views/portal_schedule.xml | 348 + .../views/portal_technician_templates.xml | 418 +- .../views/portal_templates.xml | 479 +- fusion_claims/__init__.py | 1 - fusion_claims/__manifest__.py | 10 - fusion_claims/controllers/__init__.py | 2 - fusion_claims/controllers/portal.py | 157 - fusion_claims/data/ai_agent_data.xml | 162 +- .../device_codes/adp_mobility_manual.json | 510 +- fusion_claims/data/ir_actions_server_data.xml | 28 +- .../data/ir_config_parameter_data.xml | 32 - fusion_claims/data/ir_cron_data.xml | 55 +- fusion_claims/data/mail_template_data.xml | 98 +- fusion_claims/data/product_labor_data.xml | 2 +- fusion_claims/models/__init__.py | 10 +- fusion_claims/models/account_move.py | 6 +- fusion_claims/models/ai_agent_ext.py | 572 +- fusion_claims/models/email_builder_mixin.py | 242 - .../models/fusion_adp_device_code.py | 18 + fusion_claims/models/page11_sign_request.py | 389 ++ fusion_claims/models/push_subscription.py | 73 - fusion_claims/models/res_config_settings.py | 87 - fusion_claims/models/res_partner.py | 26 - fusion_claims/models/res_users.py | 26 - fusion_claims/models/sale_order.py | 70 +- fusion_claims/models/sale_order_line.py | 58 +- fusion_claims/models/task_sync.py | 438 -- fusion_claims/models/technician_location.py | 116 - fusion_claims/models/technician_task.py | 2292 +------ fusion_claims/report/report_actions.xml | 45 +- .../report/report_approved_items.xml | 162 + .../report/report_rental_agreement.xml | 365 -- .../report/sale_report_landscape.xml | 66 +- fusion_claims/report/sale_report_portrait.xml | 98 +- fusion_claims/security/ir.model.access.csv | 34 +- fusion_claims/security/security.xml | 83 - .../static/src/css/fusion_task_map_view.scss | 100 +- .../static/src/js/debug_required_fields.js | 20 + .../static/src/js/fusion_task_map_view.js | 660 +- .../src/js/google_address_autocomplete.js | 358 +- .../static/src/scss/fusion_claims.scss | 96 - .../static/src/xml/fusion_task_map_view.xml | 26 + fusion_claims/views/account_move_views.xml | 68 +- fusion_claims/views/adp_claims_views.xml | 127 +- fusion_claims/views/fusion_loaner_views.xml | 2 +- .../views/page11_sign_request_views.xml | 89 + .../views/res_config_settings_views.xml | 150 - fusion_claims/views/res_partner_views.xml | 28 - fusion_claims/views/sale_order_views.xml | 22 - fusion_claims/views/sale_portal_templates.xml | 416 -- fusion_claims/views/task_sync_views.xml | 80 - .../views/technician_location_views.xml | 128 - fusion_claims/views/technician_task_views.xml | 581 +- fusion_claims/wizard/__init__.py | 2 +- .../wizard/application_received_wizard.py | 72 +- .../application_received_wizard_views.xml | 30 +- .../wizard/assessment_completed_wizard.py | 227 +- .../assessment_completed_wizard_views.xml | 34 +- .../wizard/ready_for_submission_wizard.py | 4 +- .../ready_for_submission_wizard_views.xml | 2 +- fusion_claims/wizard/send_page11_wizard.py | 92 + .../wizard/send_page11_wizard_views.xml | 39 + .../wizard/status_change_reason_wizard.py | 141 +- .../status_change_reason_wizard_views.xml | 22 +- fusion_ltc_management/__init__.py | 7 + fusion_ltc_management/__manifest__.py | 76 + fusion_ltc_management/controllers/__init__.py | 5 + .../controllers/portal_repair.py | 12 +- .../controllers}/portal_repair_form.xml | 0 .../data/ltc_data.xml | 9 + .../data/ltc_report_data.xml | 8 +- fusion_ltc_management/models/__init__.py | 12 + .../models/ltc_cleanup.py | 0 .../models/ltc_facility.py | 0 .../models/ltc_form_submission.py | 0 .../models/ltc_repair.py | 2 +- .../models/res_config_settings.py | 29 + fusion_ltc_management/models/res_partner.py | 28 + fusion_ltc_management/models/sale_order.py | 30 + .../models/technician_task.py | 42 + .../report/report_actions.xml | 32 + .../report/report_ltc_nursing_station.xml | 0 .../report/sale_report_ltc_repair.xml | 1 - .../security/ir.model.access.csv | 19 + .../static/description/icon.png | Bin 0 -> 32239 bytes .../google_address_facility_autocomplete.js | 578 ++ .../views/ltc_cleanup_views.xml | 0 .../views/ltc_facility_views.xml | 0 .../views/ltc_form_submission_views.xml | 12 - .../views/ltc_repair_views.xml | 0 fusion_ltc_management/views/menus.xml | 95 + .../views/res_config_settings_views.xml | 24 + .../views/res_partner_views.xml | 37 + .../views/sale_order_views.xml | 17 + .../views/technician_task_views.xml | 17 + fusion_ltc_management/wizard/__init__.py | 5 + .../wizard/ltc_repair_create_so_wizard.py | 0 .../ltc_repair_create_so_wizard_views.xml | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 635 -> 0 bytes .../__pycache__/const.cpython-312.pyc | Bin 1530 -> 0 bytes .../__pycache__/utils.cpython-312.pyc | Bin 8833 -> 0 bytes fusion_shipping/README.md | 7 + fusion_shipping/__init__.py | 3 + fusion_shipping/__manifest__.py | 32 + fusion_shipping/api/__init__.py | 7 + fusion_shipping/api/canada_post/__init__.py | 2 + fusion_shipping/api/canada_post/response.py | 164 + fusion_shipping/api/canada_post/utils.py | 215 + fusion_shipping/api/dhl/__init__.py | 1 + fusion_shipping/api/dhl/request.py | 343 + .../api/dhl/wsdl/DCT-Response_global-2.0.xsd | 92 + .../api/dhl/wsdl/DCT-req_global-2.0.xsd | 47 + .../dhl/wsdl/DCTRequestdatatypes_global.xsd | 449 ++ .../dhl/wsdl/DCTResponsedatatypes_global.xsd | 876 +++ fusion_shipping/api/dhl/wsdl/datatypes.xsd | 2660 ++++++++ .../api/dhl/wsdl/datatypes_global_v10.xsd | 4596 +++++++++++++ .../api/dhl/wsdl/datatypes_global_v62.xsd | 2470 +++++++ fusion_shipping/api/dhl/wsdl/err-res.xsd | 18 + fusion_shipping/api/dhl/wsdl/rate.wsdl | 9 + fusion_shipping/api/dhl/wsdl/ship-10.0.wsdl | 10 + .../api/dhl/wsdl/ship-val-err-res-10.0.xsd | 19 + .../api/dhl/wsdl/ship-val-global-req-10.0.xsd | 212 + .../api/dhl/wsdl/ship-val-global-res-10.0.xsd | 341 + fusion_shipping/api/dhl_rest/__init__.py | 1 + fusion_shipping/api/dhl_rest/request.py | 240 + fusion_shipping/api/fedex/__init__.py | 1 + fusion_shipping/api/fedex/request.py | 502 ++ .../fedex/wsdl/prod/._RateService_v31.wsdl | Bin 0 -> 163 bytes .../fedex/wsdl/prod/._ShipService_v28.wsdl | Bin 0 -> 163 bytes .../api/fedex/wsdl/prod/RateService_v31.wsdl | 5047 ++++++++++++++ .../api/fedex/wsdl/prod/ShipService_v28.wsdl | 5778 +++++++++++++++++ .../fedex/wsdl/test/._RateService_v31.wsdl | Bin 0 -> 163 bytes .../fedex/wsdl/test/._ShipService_v28.wsdl | Bin 0 -> 163 bytes .../api/fedex/wsdl/test/RateService_v31.wsdl | 5047 ++++++++++++++ .../api/fedex/wsdl/test/ShipService_v28.wsdl | 5778 +++++++++++++++++ fusion_shipping/api/fedex_rest/__init__.py | 1 + fusion_shipping/api/fedex_rest/request.py | 656 ++ fusion_shipping/api/ups/__init__.py | 1 + fusion_shipping/api/ups/request.py | 637 ++ fusion_shipping/api/ups/wsdl/Error1.1.xsd | 59 + fusion_shipping/api/ups/wsdl/IFWS.xsd | 318 + fusion_shipping/api/ups/wsdl/RateWS.wsdl | 58 + .../api/ups/wsdl/RateWebServiceSchema.xsd | 619 ++ fusion_shipping/api/ups/wsdl/Ship.wsdl | 123 + .../api/ups/wsdl/ShipWebServiceSchema.xsd | 865 +++ fusion_shipping/api/ups/wsdl/UPSSecurity.xsd | 23 + fusion_shipping/api/ups/wsdl/Void.wsdl | 58 + .../api/ups/wsdl/VoidWebServiceSchema.xsd | 45 + fusion_shipping/api/ups/wsdl/common.xsd | 65 + fusion_shipping/api/ups_rest/__init__.py | 1 + fusion_shipping/api/ups_rest/request.py | 488 ++ fusion_shipping/data/fusion_shipping_data.xml | 560 ++ fusion_shipping/data/ir_cron_data.xml | 14 + fusion_shipping/data/ir_sequence_data.xml | 12 + fusion_shipping/models/__init__.py | 11 + fusion_shipping/models/delivery_carrier.py | 3237 +++++++++ .../models/fusion_order_package.py | 40 + fusion_shipping/models/fusion_shipment.py | 747 +++ .../models/fusion_tracking_event.py | 44 + fusion_shipping/models/payment_provider.py | 39 + fusion_shipping/models/product_packaging.py | 15 + fusion_shipping/models/res_company.py | 15 + fusion_shipping/models/res_partner.py | 11 + fusion_shipping/models/sale_order.py | 70 + fusion_shipping/models/stock_picking.py | 34 + fusion_shipping/models/uom_uom.py | 7 + fusion_shipping/pyproject.toml | 3 + fusion_shipping/security/ir.model.access.csv | 12 + fusion_shipping/static/description/S1.png | Bin 0 -> 115576 bytes fusion_shipping/static/description/S2.png | Bin 0 -> 110347 bytes fusion_shipping/static/description/S3.png | Bin 0 -> 183037 bytes .../static/description/canada_post_banner.gif | Bin 0 -> 1013899 bytes fusion_shipping/static/description/custom.css | 95 + fusion_shipping/static/description/dash.png | Bin 0 -> 382778 bytes fusion_shipping/static/description/icon.png | Bin 0 -> 65935 bytes .../description/images/icons_live_price.png | Bin 0 -> 9396 bytes .../images/icons_shipping_label.png | Bin 0 -> 12136 bytes .../images/icons_track_and_trace.png | Bin 0 -> 6436 bytes fusion_shipping/static/description/index.html | 173 + fusion_shipping/static/description/label.png | Bin 0 -> 68237 bytes .../static/description/up_arrow.png | Bin 0 -> 7846 bytes .../views/choose_delivery_carrier_views.xml | 89 + .../views/delivery_carrier_view.xml | 351 + .../views/fusion_shipment_views.xml | 323 + fusion_shipping/views/menus.xml | 70 + fusion_shipping/views/report_templates.xml | 77 + .../views/res_config_settings_views.xml | 41 + fusion_shipping/views/res_partner_views.xml | 15 + fusion_shipping/views/sale_order_views.xml | 49 + fusion_shipping/views/stock_picking_views.xml | 18 + fusion_shipping/wizard/__init__.py | 1 + .../wizard/choose_delivery_fusion_rate.py | 446 ++ fusion_tasks/static/description/icon.png | Bin 0 -> 43989 bytes .../static/src/js/fusion_task_map_view.js | 10 +- fusion_tasks/views/technician_task_views.xml | 18 +- 274 files changed, 52782 insertions(+), 7302 deletions(-) delete mode 100644 Fusion Accounting/__pycache__/__init__.cpython-310.pyc delete mode 100644 Fusion Accounting/controllers/__pycache__/__init__.cpython-310.pyc delete mode 100644 Fusion Accounting/controllers/__pycache__/main.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/__init__.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_account.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_aged_partner_balance.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_analytic_report.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_asset.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_bank_statement.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_cash_flow_report.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_chart_template.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_deferred_reports.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_fiscal_position.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_fiscal_year.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_general_ledger.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_generic_tax_report.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_journal.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_journal_csv.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_journal_dashboard.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_journal_report.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_move.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_move_line.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_multicurrency_revaluation_report.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_partner_ledger.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_payment.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_reconcile_model.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_reconcile_model_line.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_report.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_sales_report.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_tax.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/account_trial_balance_report.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/balance_sheet.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/bank_rec_widget.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/bank_rec_widget_line.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/bank_reconciliation_report.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/budget.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/chart_template.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/digest.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/executive_summary_report.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/ir_actions.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/ir_ui_menu.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/mail_activity.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/mail_activity_type.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/res_company.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/res_config_settings.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/res_currency.cpython-310.pyc delete mode 100644 Fusion Accounting/models/__pycache__/res_partner.cpython-310.pyc delete mode 100644 Fusion Accounting/wizard/__pycache__/__init__.cpython-310.pyc delete mode 100644 Fusion Accounting/wizard/__pycache__/account_auto_reconcile_wizard.cpython-310.pyc delete mode 100644 Fusion Accounting/wizard/__pycache__/account_bank_statement_import_csv.cpython-310.pyc delete mode 100644 Fusion Accounting/wizard/__pycache__/account_change_lock_date.cpython-310.pyc delete mode 100644 Fusion Accounting/wizard/__pycache__/account_reconcile_wizard.cpython-310.pyc delete mode 100644 Fusion Accounting/wizard/__pycache__/account_report_file_download_error_wizard.cpython-310.pyc delete mode 100644 Fusion Accounting/wizard/__pycache__/account_report_send.cpython-310.pyc delete mode 100644 Fusion Accounting/wizard/__pycache__/asset_modify.cpython-310.pyc delete mode 100644 Fusion Accounting/wizard/__pycache__/fiscal_year.cpython-310.pyc delete mode 100644 Fusion Accounting/wizard/__pycache__/multicurrency_revaluation.cpython-310.pyc delete mode 100644 Fusion Accounting/wizard/__pycache__/report_export_wizard.cpython-310.pyc delete mode 100644 Fusion Accounting/wizard/__pycache__/setup_wizards.cpython-310.pyc create mode 100644 fusion_authorizer_portal/controllers/portal_page11_sign.py create mode 100644 fusion_authorizer_portal/controllers/portal_schedule.py create mode 100644 fusion_authorizer_portal/data/appointment_invite_data.xml create mode 100644 fusion_authorizer_portal/migrations/19.0.2.3.0/end-migrate.py create mode 100644 fusion_authorizer_portal/migrations/19.0.2.4.0/end-migrate.py create mode 100644 fusion_authorizer_portal/migrations/19.0.2.5.0/end-migrate.py create mode 100644 fusion_authorizer_portal/static/src/js/portal_schedule_booking.js create mode 100644 fusion_authorizer_portal/views/portal_page11_sign_templates.xml create mode 100644 fusion_authorizer_portal/views/portal_schedule.xml delete mode 100644 fusion_claims/controllers/__init__.py delete mode 100644 fusion_claims/controllers/portal.py delete mode 100644 fusion_claims/models/email_builder_mixin.py create mode 100644 fusion_claims/models/page11_sign_request.py delete mode 100644 fusion_claims/models/push_subscription.py delete mode 100644 fusion_claims/models/res_users.py delete mode 100644 fusion_claims/models/task_sync.py delete mode 100644 fusion_claims/models/technician_location.py create mode 100644 fusion_claims/report/report_approved_items.xml delete mode 100644 fusion_claims/report/report_rental_agreement.xml create mode 100644 fusion_claims/static/src/js/debug_required_fields.js create mode 100644 fusion_claims/views/page11_sign_request_views.xml delete mode 100644 fusion_claims/views/sale_portal_templates.xml delete mode 100644 fusion_claims/views/task_sync_views.xml delete mode 100644 fusion_claims/views/technician_location_views.xml create mode 100644 fusion_claims/wizard/send_page11_wizard.py create mode 100644 fusion_claims/wizard/send_page11_wizard_views.xml create mode 100644 fusion_ltc_management/__init__.py create mode 100644 fusion_ltc_management/__manifest__.py create mode 100644 fusion_ltc_management/controllers/__init__.py rename {fusion_authorizer_portal => fusion_ltc_management}/controllers/portal_repair.py (94%) rename {fusion_authorizer_portal/views => fusion_ltc_management/controllers}/portal_repair_form.xml (100%) rename {fusion_claims => fusion_ltc_management}/data/ltc_data.xml (92%) rename {fusion_claims => fusion_ltc_management}/data/ltc_report_data.xml (80%) create mode 100644 fusion_ltc_management/models/__init__.py rename {fusion_claims => fusion_ltc_management}/models/ltc_cleanup.py (100%) rename {fusion_claims => fusion_ltc_management}/models/ltc_facility.py (100%) rename {fusion_claims => fusion_ltc_management}/models/ltc_form_submission.py (100%) rename {fusion_claims => fusion_ltc_management}/models/ltc_repair.py (99%) create mode 100644 fusion_ltc_management/models/res_config_settings.py create mode 100644 fusion_ltc_management/models/res_partner.py create mode 100644 fusion_ltc_management/models/sale_order.py create mode 100644 fusion_ltc_management/models/technician_task.py create mode 100644 fusion_ltc_management/report/report_actions.xml rename {fusion_claims => fusion_ltc_management}/report/report_ltc_nursing_station.xml (100%) rename {fusion_claims => fusion_ltc_management}/report/sale_report_ltc_repair.xml (99%) create mode 100644 fusion_ltc_management/security/ir.model.access.csv create mode 100644 fusion_ltc_management/static/description/icon.png create mode 100644 fusion_ltc_management/static/src/js/google_address_facility_autocomplete.js rename {fusion_claims => fusion_ltc_management}/views/ltc_cleanup_views.xml (100%) rename {fusion_claims => fusion_ltc_management}/views/ltc_facility_views.xml (100%) rename {fusion_claims => fusion_ltc_management}/views/ltc_form_submission_views.xml (93%) rename {fusion_claims => fusion_ltc_management}/views/ltc_repair_views.xml (100%) create mode 100644 fusion_ltc_management/views/menus.xml create mode 100644 fusion_ltc_management/views/res_config_settings_views.xml create mode 100644 fusion_ltc_management/views/res_partner_views.xml create mode 100644 fusion_ltc_management/views/sale_order_views.xml create mode 100644 fusion_ltc_management/views/technician_task_views.xml create mode 100644 fusion_ltc_management/wizard/__init__.py rename {fusion_claims => fusion_ltc_management}/wizard/ltc_repair_create_so_wizard.py (100%) rename {fusion_claims => fusion_ltc_management}/wizard/ltc_repair_create_so_wizard_views.xml (100%) delete mode 100644 fusion_poynt/__pycache__/__init__.cpython-312.pyc delete mode 100644 fusion_poynt/__pycache__/const.cpython-312.pyc delete mode 100644 fusion_poynt/__pycache__/utils.cpython-312.pyc create mode 100644 fusion_shipping/README.md create mode 100644 fusion_shipping/__init__.py create mode 100644 fusion_shipping/__manifest__.py create mode 100644 fusion_shipping/api/__init__.py create mode 100644 fusion_shipping/api/canada_post/__init__.py create mode 100644 fusion_shipping/api/canada_post/response.py create mode 100644 fusion_shipping/api/canada_post/utils.py create mode 100644 fusion_shipping/api/dhl/__init__.py create mode 100644 fusion_shipping/api/dhl/request.py create mode 100644 fusion_shipping/api/dhl/wsdl/DCT-Response_global-2.0.xsd create mode 100644 fusion_shipping/api/dhl/wsdl/DCT-req_global-2.0.xsd create mode 100644 fusion_shipping/api/dhl/wsdl/DCTRequestdatatypes_global.xsd create mode 100644 fusion_shipping/api/dhl/wsdl/DCTResponsedatatypes_global.xsd create mode 100644 fusion_shipping/api/dhl/wsdl/datatypes.xsd create mode 100644 fusion_shipping/api/dhl/wsdl/datatypes_global_v10.xsd create mode 100644 fusion_shipping/api/dhl/wsdl/datatypes_global_v62.xsd create mode 100644 fusion_shipping/api/dhl/wsdl/err-res.xsd create mode 100644 fusion_shipping/api/dhl/wsdl/rate.wsdl create mode 100644 fusion_shipping/api/dhl/wsdl/ship-10.0.wsdl create mode 100644 fusion_shipping/api/dhl/wsdl/ship-val-err-res-10.0.xsd create mode 100644 fusion_shipping/api/dhl/wsdl/ship-val-global-req-10.0.xsd create mode 100644 fusion_shipping/api/dhl/wsdl/ship-val-global-res-10.0.xsd create mode 100644 fusion_shipping/api/dhl_rest/__init__.py create mode 100644 fusion_shipping/api/dhl_rest/request.py create mode 100644 fusion_shipping/api/fedex/__init__.py create mode 100644 fusion_shipping/api/fedex/request.py create mode 100644 fusion_shipping/api/fedex/wsdl/prod/._RateService_v31.wsdl create mode 100644 fusion_shipping/api/fedex/wsdl/prod/._ShipService_v28.wsdl create mode 100644 fusion_shipping/api/fedex/wsdl/prod/RateService_v31.wsdl create mode 100644 fusion_shipping/api/fedex/wsdl/prod/ShipService_v28.wsdl create mode 100644 fusion_shipping/api/fedex/wsdl/test/._RateService_v31.wsdl create mode 100644 fusion_shipping/api/fedex/wsdl/test/._ShipService_v28.wsdl create mode 100644 fusion_shipping/api/fedex/wsdl/test/RateService_v31.wsdl create mode 100644 fusion_shipping/api/fedex/wsdl/test/ShipService_v28.wsdl create mode 100644 fusion_shipping/api/fedex_rest/__init__.py create mode 100644 fusion_shipping/api/fedex_rest/request.py create mode 100644 fusion_shipping/api/ups/__init__.py create mode 100644 fusion_shipping/api/ups/request.py create mode 100644 fusion_shipping/api/ups/wsdl/Error1.1.xsd create mode 100644 fusion_shipping/api/ups/wsdl/IFWS.xsd create mode 100644 fusion_shipping/api/ups/wsdl/RateWS.wsdl create mode 100644 fusion_shipping/api/ups/wsdl/RateWebServiceSchema.xsd create mode 100644 fusion_shipping/api/ups/wsdl/Ship.wsdl create mode 100644 fusion_shipping/api/ups/wsdl/ShipWebServiceSchema.xsd create mode 100644 fusion_shipping/api/ups/wsdl/UPSSecurity.xsd create mode 100644 fusion_shipping/api/ups/wsdl/Void.wsdl create mode 100644 fusion_shipping/api/ups/wsdl/VoidWebServiceSchema.xsd create mode 100644 fusion_shipping/api/ups/wsdl/common.xsd create mode 100644 fusion_shipping/api/ups_rest/__init__.py create mode 100644 fusion_shipping/api/ups_rest/request.py create mode 100644 fusion_shipping/data/fusion_shipping_data.xml create mode 100644 fusion_shipping/data/ir_cron_data.xml create mode 100644 fusion_shipping/data/ir_sequence_data.xml create mode 100644 fusion_shipping/models/__init__.py create mode 100644 fusion_shipping/models/delivery_carrier.py create mode 100644 fusion_shipping/models/fusion_order_package.py create mode 100644 fusion_shipping/models/fusion_shipment.py create mode 100644 fusion_shipping/models/fusion_tracking_event.py create mode 100644 fusion_shipping/models/payment_provider.py create mode 100644 fusion_shipping/models/product_packaging.py create mode 100644 fusion_shipping/models/res_company.py create mode 100644 fusion_shipping/models/res_partner.py create mode 100644 fusion_shipping/models/sale_order.py create mode 100644 fusion_shipping/models/stock_picking.py create mode 100644 fusion_shipping/models/uom_uom.py create mode 100644 fusion_shipping/pyproject.toml create mode 100644 fusion_shipping/security/ir.model.access.csv create mode 100644 fusion_shipping/static/description/S1.png create mode 100644 fusion_shipping/static/description/S2.png create mode 100644 fusion_shipping/static/description/S3.png create mode 100644 fusion_shipping/static/description/canada_post_banner.gif create mode 100644 fusion_shipping/static/description/custom.css create mode 100644 fusion_shipping/static/description/dash.png create mode 100644 fusion_shipping/static/description/icon.png create mode 100644 fusion_shipping/static/description/images/icons_live_price.png create mode 100644 fusion_shipping/static/description/images/icons_shipping_label.png create mode 100644 fusion_shipping/static/description/images/icons_track_and_trace.png create mode 100644 fusion_shipping/static/description/index.html create mode 100644 fusion_shipping/static/description/label.png create mode 100644 fusion_shipping/static/description/up_arrow.png create mode 100644 fusion_shipping/views/choose_delivery_carrier_views.xml create mode 100644 fusion_shipping/views/delivery_carrier_view.xml create mode 100644 fusion_shipping/views/fusion_shipment_views.xml create mode 100644 fusion_shipping/views/menus.xml create mode 100644 fusion_shipping/views/report_templates.xml create mode 100644 fusion_shipping/views/res_config_settings_views.xml create mode 100644 fusion_shipping/views/res_partner_views.xml create mode 100644 fusion_shipping/views/sale_order_views.xml create mode 100644 fusion_shipping/views/stock_picking_views.xml create mode 100644 fusion_shipping/wizard/__init__.py create mode 100644 fusion_shipping/wizard/choose_delivery_fusion_rate.py create mode 100644 fusion_tasks/static/description/icon.png diff --git a/Fusion Accounting/__pycache__/__init__.cpython-310.pyc b/Fusion Accounting/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 79be0acef212bbb84e50dcf8d9558451ead001cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2895 zcma)8&5qp05oR}khQs;U)k@J%Yy~UFusw;gYYRTeKpaGtv!_Id$hHsyL4(Wgo#BeZ z9yQ5bEx|E-@G*V^A7*k0kSDPp!Qj=WeFMXQrF`9-S!wNck{L|1Slw0iRd@AQBL{=P z!sn~o-%b9pVOihmmzO^_UjB$*{s{(dagth75)z_i3Ymt~$Tnm`j(O`WM5x<>X= zFZ6`B=m>vF!Y=P{|8px0c$Wuw_PF=Z8ud;IoR3K3O(hp;uBGFoiWG;`i)B_QnWjSJ zr&y-fb?(dQG|KpcSR1ZCK7t*K4|Pxy!vVT_V-m;+-4T!s9;g*7U&Uf!5F6+EsUJchOl| z3x8>ub-f$bp~w4U!Uuf#1^v+a`<1x|`wiH4YY$ePy0Zv)4`Z+P4*N@z5FO*m2j|ev z4O+rCfhGZ(0l)M=kj#IIWZ{jiB>{#FVDJ{hrB!=ugKr`32HS+@l1BaHb8g`fEiq)fWvS=zAnq(upagEXU;1i^H;a@GHj9c0a&V>MLRD8+kamp8 z&e{Y{=)I2{djDQSKYkegat&MwjWh?;e8R9XN&eAjtLdylH^GZdXdJx>jjhB1Dma?W z1aCa?v=AAdxrkIeX`H;|vKj7|MIke`N~h@`C&h%Fxn!w~IO9`;~}i_#Z|QAAJKOsGWiBu$GnHiHeB?K_$c~!+dLzsx^x6YuX_6OV_pFq{YiAT8O)iyJdUE;=*+JdARgMx?NtjU5DbYU;8aXVu#$ssuC z%a&{Cf^Zk}ZVl~{j92_n-8Nn{gMhcN21IH!U2T0p&-n_zgip;!h}(mOQ#-KeoA)-_ zppEv(%C0$9;hh{3YYxJ$dPQg9*RHmHWc>{!KX*TMGq-&nlVhuT-FRuU(O8Fk<7FHx zREVlkt=@pzV6^$>kK+F8|BY*6zz-r`;#=!oFYWH~##9-1cN>gL6hXjsu0JoJiT z*;U!cxLsojE}bNti)=(2x6D!CXnX4!T+SY7Gff27j9aZ4YUg}xZC2fUh+Dk9SElLq zy_S-3CARlORFoi&Q{Bve4qlQS$s`sdTj@TkeyfMy==Hz4I2|P-+`jardh5T&-2UD6 zUso$fv~_K~ZhVlWsRs9#c3Vyz#9FT~!ADwQpSpPL?NwLSD9}bqc93LI2C!bWeUTn4 z8H?m7+D}FPBHx!S(HlJ8Pp%k7+VjXx|0$+%Qek;v>v9t?e+ z@$NgLKwU+CjeD#T@S11jI7QDu7ex0cGwjjhCio;uOYtYAq-r|o5@Xa+(>ab58soU> zuG(F~M_qLbF2Y`08m1h>LHn{Qzb0s3b-9GYR$d=vLqs@i>i~Y4PAVMeF=vHjQ+Xu9 zp2_s-G}U~qnYkU_)WC;leVR?Ae0&GGoMQ+yV`?77(|_hTL*hNBuGUeqsdYaDu9gGR ze>QZk68|&$oZ12L$k2T0v;On7{wn4J+b6!SZHJ^?4L*JkfkyqtQWS#Hg|2i(HID8S z?Z$TW#+eO6Z~b$-i9TccKL=y#U2URS-MD%wH^EG`c@x}qEFEx JsBib*_z!1TUDp5r diff --git a/Fusion Accounting/controllers/__pycache__/__init__.cpython-310.pyc b/Fusion Accounting/controllers/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 5a2212712542bbec3d3211edb8fb0ba2ac26f8e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 186 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 diff --git a/Fusion Accounting/models/__pycache__/__init__.cpython-310.pyc b/Fusion Accounting/models/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 4630a103486ee45a969c2f3bfcf923f086aabf89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1931 zcmb`I$#Nq_5QbZ_Ez8VbF zfZ)WRiK;aWa6=CN`YN-kvNE%}6*rqT2cOT6zrXyl=QzLe;LA@LgYWU5Iv~&SN`VuW zs8nJugUeJF-U08>j_@vcmv)8szIwJ3ed-GjzylfxAA%3* zP1cmkf#M0g6G(p2~ed_+gW$KYc+7Cr%=(24LV_>@kC&%kGN zCVUP)r*q*8@C986UxF{`QuqpdMOVVt;A^@Tz5(CRjqok_mTrabz;|>fJOj^YCOikv zX&&Cwy^QZMexL{N!;jAV@wXE0!o2j>)~*}pQKmNEFb%EgNk>1cG}D<0O_Y7I*(r%} z`qr%uT0cIcnhf4*1#Jkn4KhqyEta$#?q?|-QF{#3C&;C-SCd>S2nezO<%;8E$ zYYf|xJi@9EzPB{IaPXDMMymOV%6)|+=a|)yeextRk;@y&zAKa~GF*|CJyv2?al+dc zIcgMWo%yaRTvHoSa`$!S$I;eX^l53ZH;Nk=oRou1Z4mKbN7Ue7*4d^M@y2$Yi#Dm} zApWpjRroyfY>=I|adGWtc3qeUNMMsZCcNq(|LWA;VlKLFOIS2BOME2qqyrtfW*fZl z>m>qjs%fX;07WURt0+Y?)P8`=o%yeR#{KC1&7iXNif>EnA2*u2%FE)2GaT4cr#is( z*;dSaG;WT~jY{({)X945t32hKo71!jr7uRVQql9xdZ%rFXr?{Qlhhoz# zoTYfyZ2#Q!^@sW6o0IT6KG|W}Wx>yYlT=vtS*k1tEc{E6)L9xVO_ml*o2A3jW$Cf> zSq3bJEJKzN%a~=tGG#epIc7OwIb}IxIcK?Gxn#Lwxn{Xxxn;RynX$}S?pYpK9wDEc z$mE+Fy?{z Uj$eN1PmqSwDE*&*ol>XtCsyMc%m4rY diff --git a/Fusion Accounting/models/__pycache__/account_account.cpython-310.pyc b/Fusion Accounting/models/__pycache__/account_account.cpython-310.pyc deleted file mode 100644 index e2905a03b76c0776a6748b27ad4c175978cb8eb8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2426 zcmai0OK;pZ5Edn^EU(w^*s+r|Wz!VM7U>$vp{KNnlOVk`?x6(=2q*|y!`+o5ksDH4 z#|sB2kX-#A?B0R`{V_fA+Ef2Q5fn{_)UF!`2&x5N!x?hsn+I1eF19`Ve!un-DEXD1Vl!KLW z%vq5O&PGyK99S(B&!oumf`f@bmT4|bdn(9sctgJG&Y3nBx>lS?!A-!$*etNVRP1?K zAPah1SEf_s6<2A&_&6<0l#WJRfbnza1}4wxdTfq*bbS*mUZmF1CLHo&G`TXf zUh$DsRd2tB0k3-Khf-O)QKXYehZqxS$xSC0V}zHx)dciql#nyToJY7$X8L-Or1{ zf|63j`?Y`{x`7J@wP`PoE0tzXF=V>|>hB9_{5^@Y7O1peO_m=z1!yj)BUo54pmIR1 zChOD9Q~;e3)isMmV8MG<=L(~I4tPJUi%MO!_iZzTXjE66WvO6|d8|Q%n7QfRWa|kE z|`BXsPym`9H%{Cukc%EGTLTn?`R7nl_mG2k{M&@c;z^|r&o<|2Jor=rTM zBeV+RKSj9u^bQ>qaz8B^fO}X}BV8#JSD&ETPWJ||&MYpgqY=MQa(ra8Wyh5qij!mS zk@wTW3vx^ju)>eAgfIeFPMH792M^0hu!ndqI6GKBe~w_SHVYvU8+eXLzGoc^_4?h{0VlCx-ri?W?@n;w5Bc*D8e zR2^o+9v=gpEH^>WlpV`E|Gg_~ShpJF<{n5{@KmT<_R{d-FjdMLufDLI4O*L`Hd(au zzlmp7tDfbsFUC8aixc}t!oIHBa>QOpVt-|RiG;q7<=)3%^mjXoNc6T7bVwVi(c3s^ zleo1+l$GtqY7qw$+Ly=a;2=@IQI> z#Oilu?%fBMw3LUFC3bf1%-l0GXU;iu&Y3f8WHKoYpKl+2`+T#gY5z(O-5(1NkKpV5 zCK9GGy{Z-I*Qn|>qiEF3qFJ+wmQH16HCBrk<1%km6E(YN%Y3Ywtfh)6nU7ZoYUyHH z*B;kcg4r)=%yx{G%vG~Eh>|2rp(GWS454IzrBRX&OR`Kqs}(YDVl3K1!4&aYgE>`C z#Ft#B$~>W$3c4`Qe)JT629_GUR`yG*>^peOaHm@K-HQ%h@XIKORnC{)dWArBf6VYf zPT0|kI@5{<(~BlEiWW1AF=nyYOIk7hvbLnNI7_@_7cmi*M48PFvNX%Qq!*LO4YDER zQtS}RvSH)~*a#a%t2E28J^0PAF*c6hK{mlA@jJx!vVHi?vi zm_=ftn4{5^!qJt&(TP2CYV2p^C{MX{XQ5nkn$uXkaG-8|d8Qu&QA?5Q0q~ex^&Rdt z@440cVMEYSw&Hmug@sb3TJ}7V^qtx&2*&Y5s?w;fHtJ5@_pWQ#bzylHOQd9)ulJ=8I?JHQI9TlBG!iQ`K=;5~y?^|h)I9H?yH_ zCWvE}@s_Tk<%6=N9k#S(OB*fc&=MR)tt6MF*uYCRV5KLuliF44F+HoX3~|^sqh$pq z`pa(VtIq`%>au`CCBrCLWFsgU4ogN+a)ISgGAjETozz+}f<|zke_tye#Mz!nZ8OQn zwsihAKNUU?5T|Y#S9LZqp|uihGDuiJ(gmG=iS50h@vqelU0b^t#DYXlV-w@r?`dow zJyAY~r`JqC+CPgnOamtzSb+S=u5#z1+gSHXjaAgrP_MccR>T3 zJN3#HG1&WD%y{P;m)g~jiJ@|hxW8Q@?C8~ES~d-7PNXB^tAoXQnApIg6ER2<+mFn$ z7HH3>T3CLjO==t3MV%Xg{sm*rz!Wfk=Q3vCc^G=BT3&Rjk{hlY!kBqL#4C;JdaZt4 zFAkn}8FT8OUn=kv-%uYSHCuH(pP1|TeR%e0BZ_N=p4LIsdh_%xi8^H15Pc=L?)q-I z>NXudGh1y`%2n?;GEpBQ5e`A5-TK9bTXE#pAk4Y5Ckh7NOG8RBmpc_&4Uuke=J0UX zVz9gn83c*tL4Gyr#XXcmri2}4oxA=O1uJak)*%QkmaA_b1W*MzzUpwd!G!iE2}@4N zn}qEf`u)P9wJ4-T%v(n*u`go%9VRAB&+&!n)GrEa-9zK#`Lb7%BqK~n4G~{or43J* z5I`d3`Pc}&ORj%jm>85uK;T!)6{leH5p*u%Y6B37uq(a?rNta`mdc>NQV$C3Xjc3H zy_!)ID%IC(iw=iaOqP`BES0#NHmxx~f`p#D_ahn63SaJ$2KF z8+!bmk=TenqT6N~wG;TeQ#W3;e$@QX&p-w}!i~r)hOgYDE+XSJG&{hm)KuDN@1>t9HT3gyx15k$Xp500Y z_GT=wSK_M=QOlLYmcC*KNfME3_XM;JS_{H(S#toq6YbL?=5uTwg`^mll3MN)py#DV zX^A&#C6@`a>8|nzF=`QC+eTlghzq1W;$Xv@?zD29E&%WmWbPcr<&`7!VA4S zw&I;=!cH9$!l8AjHR{)sioXUrGSX8>PZ4*Pyh>x$5kpEw`-*=TuZVQrx!iu@XE9=a z4vDZYm3bZVCzPI2^^oYE!iY(;+cpTPSajw>fZihUKBhP4Z^=%#194Y0^N*k#Lg;YS zEibxNl8me6D}+&g237oNN}i>ptw1Ttx$|A75<+-}Ui$=+=A%EuEJ9$F1OP>!JTfE@ zrQ-81K-Gq|0woq)PP-qteM^7E+%Pu{$beDsyvfWhy{<1~SLBl$+Oo`#qZMo%X&yj+ zK<%A`jRP5S*e6xV#v7K*L9uDsetaddX`@vNtp=zE$PS%UkyeUjWNk7)%Ozu3f5q@q z(wuddWW!t;!N_6eu)Tq?WG?G$->YV8 zG#FhOC1VH1(jlbzS7WW5#QYt=d@jhbJA=_Jv$e;!*j<1-25f&9`CpM(+`pkgB{N#% zD4FV%1Y=jt)!1f3H z6wM);MQakPy@#a4meJblTgd{O3HAgd!FVvap$9fbK86y?KiJC-UmN_9Oxe`rf?F;s! z_D}>(tuNYrBG?a^cVywlV5ka))}cs-obR$5`r!)6i|9jnvI|I%cqB-DSOs7P%{Q^E z`m6c`$QM8j(rYbNzTy=uPP`DK@G!6HCZK zzrpWDlgIkhX;L>N&PWB4>hwUQPH#fIz{cn()Jg;8(`Z@H`NR0(`zYxZll&0M9DF@m zlY?6Gy}(LGXXbhI!tX`WLk~m?8GJ|a^*#e#mh63q0nOJtOhi^9h;eVr=q`VLxCP-E zn6Qapxov2H6@zgB`w|x7!;rsFllH7-HsCn`%l-kVD+Yo$pmrG~P9gM2pq_+4F&HdG zC}FakT9H6zu;*YqTC#nz-ToQ0!V|TkHnJ9CH-zS(mV|IvizkTVZ9QJ<^g|GW7;go! zl~@}Z#C8bnvuKH@2pZZNEJM#1$Tl6E)c9-ax7}jsnzVE`Kq@qLrgi>&0& z5>zChuVGVx03n+)x6ns^WM4?~n?S}TYtC-b;B`Vi)4!-JIYZK%RG%cVT{k#ce?oV+ z(H;yoQu#=!JYJ)fClZ{{nwQ@L##~%?s|*@}M`j51+Tq#GAk@Gg!6@YD#Xw~9Liv?M z+f0*^9hE9Rw%NKe{P;M%IEh3g%e5*rHyF?&70C~m6_WA|-k^4}ah-C6O!ZpKZ}{b^ zNRtWXtuK;J>hU#H@3;~Vp~53U@afR5-oQeuwV2eN(cnP9nvf8c(^^o-zkv48JT2~3 zLtUGAP9$fS9q5Xy{P<@HDlN3};(o1p=@+~d(I_5+K}k|_S6p#UwH}f}Ld1Bvz6=w! z>eQu4!%tG>uOboY$lXOIF^t{yS|Kh?7BRH8?(i!_5+#Xfk!%<6D!rNt4bltF6}d(Q zlf$8{!D^9~x2Mb>pSqGb9PXnr0^>b`WK6e>EX>m}-FU~gZ2hoq=?C!_$2X0yL3O76 zeQVPCzJaIcZ$EPO+txsn{UX+(IWYaq+^MJM&*rDRLLbQya}vI#KUR`ljF`Wh(3 zax-ERSsj37CvEdpoD&54lG!y`Q`v`7EJ)S@dB};s%njkj-+3aii%*U4@LR#mp_6o z3o+Q)=e-gcwO?pPu%gijtAaC<=48kn^0k@p!L2jr za&vUb%g>$7OD7UJxxhe&n{h=Iyo8awc@v+W)ga;`v^<2b_W>kwHO5=IFJ;;WRDn$> zhDy3Y8-}uo8tB(sIx}C5h3jHL+QpiGk;-rToR~X1pMUm=`Gx$hgU%nzPfd5vqTuy` zIY$ttrlsd}wdz*N(9;TD{@(oAr=OahnhsANO7P~xAex{}s zo=ehO*WaBaqUOh=rEITsbO@8bKY!x%+^PAqC+4SVlPNK0(ZzX|adqax+P9qYR|G2=LCh&D$>Po?teY865j zx-F-z2h!kk_)$(HDGf&Hkp#gX;_J~~NUULGtp77&%9Ug2)=lf})Xndf(;$-WrG56v zb5Hav>9kazrIYe-KFl{(4@Yezc@_xwSgvkTpvCHee0KiS{E2ftMbz=sv|3!{MUw=+ zcx4Al*@;M|`=Aj-BAxyf-sXJorzCo{YxxOE7YRs0C~XHqB(dWdy2zvrC?pm2b9FAlR&x)ZVODCNBhS&HLXqoB&Es|=K{9Q{&rMfkiCaSC>7jR=jcHl zg+it?2V_p@jEYF^+!mCd>l7k&`L=qFhT0K+ZTS)|YS(i=#1p5VUO3lAK?g|qZd*(5 zf3riT?}|dgYX8e^F%ilbsaQfx^f1F?XHGu_fg<-$XrW6E9n3l2_GTYG{p3P>#a0^Y zyk4&6qumGF|63E}F5`U%P*0)dM z)cj-T^0LoUbLZyI%$>^jOT%46ZX_g0284T?B4V}8^~{Re(kGH)Gdl}H;(;A&`d5;m z$(~jFoe)4xh#Co>IliKY2F4@bsxu zeNgC~(`P!$XT%v2`DAR*{6sq+vBgQqjUJV)YaIPrO@9eksc)FKKiS6WFKn*I-({|i z)&7ZXQ(To)p$(|aD|CumhsKZagY6QzEgYt%Lp7ET+YteLoDOL>j5@r!@(GHyzGvSk(RB@yYxJ_{))mdbb?)FCDJcQg%g*!XmLb0b* zIqy_1L`UbPFaR`$U^&;9hA#gC-jSzJoDNJ74@#jF;W5+_)m^6K64k|F_d0bZEYgN3 z`~l4266n2zNmqM?b?dQX^FGC1t{0<1G0l2_mBgRHB2gwW=7Cjl&QD z<+r5ChGSBkEGGJlad@RAwDhI(P&Y%4=f6e6eu5J6M~guQmaRd7k&!)~FiRCK63%6( zvW{WGp<3~m93R21)hYsl96t>15iu7bj^J|T7nKnq85!aOjkQEBEa^VwAES=WQbM5! ze3_CjQ$l3GXOR?B^~I8+FE4b(%Kb)#MA^Wr%<)fA+tKdLsiZ=5MI2mKu6g`RXdz+> znVE=Zr2mVPC$2bFu3o~KV2R?+$WRO^j{goS`87&@my*{g`6MOlNW=tYF8#*nTD=l_ zxp+WrzCsE4HVQE{W9jku4JwtfG87jeJ;wYQBN@u0d_@?oA)8t^fv;bDd^ z`H#}So+4Pfg>X-bm>DDZcxpcRbMw4b$h8`M=wy$)L?`g|h)I-7b(W5g^smBsN52&5 zi9AlKT$ItbWm0GjooLB`37pg+oB;t-D~KC~OE&SUfe@=0!X|8&4y`Q;^kxIsR2*mw zZou?HvmL>F(RD7B+PX){=Z>S6trI#dY}p8A7iXrK!J)+`p6x|0ND3#Z-?W=Q+cv;3DZaaJ9Zve=hIBAu^zqXzS9Z{H^ zr8nPqD@y1e*X~PB{4AHUC-w!)OR+D8ihJ3j2ssN2O3_p}w#AaiwseYv!$MjMofQxX zQXa{j(}gGgD7uh{5usFX8yMl?qbcy=#ML8w(Is6hizu zAP%(B=qVE*oEmXFU^}L5S?FODb%U*;U@#bBh)fJJn+fD_94;e5>|lrvh1Y%!43}WZ z4GKn1y#~ew<0TLcByUs%DH*L}w6b_x&7ldX!>tkYmP9PdCQDfYOof z=e4g8%;!fC&$B0(07enKjrgfCmO%KxSTM1IL;Em>>6>d`mTj|Wo9${l9E|p~`}W$( z+JkF@Yj!{)D7Ww?5skPHdxeat!}%)iOO)0ThFN8_{mX-!k_@+ucAiJ@MsqaWs-kNa zMD;z#B_J7%j!ejTF5_p2byoBZ?Sj!rA#X7K0tHnY3kaN+cQJ4$VK5v<$V2i>NW_&6 zfukk;@^zj6B>*ZGAke~|t(I$xtbF`o^r#~KwB`%9B7ayN6tft`5UTm&*r|$eXXp*I zd6%KTK;F>_O4mVr*cr4ZC@SDZI{Fs+$C{bvj~$QL$z?s5AS@7TkwPIuGG$moLPfLaGx@xt;GR9$_An=6{V$bMZZ!aytM*Z6wpwTl)FVWREtj zW5?fR&irrj2LC%sq~!WeJ0~A$6*OnI9{jT#!1=2{G9f!9Tj%0 z^yU=89!Q5bufdNw>1Q@cA3H$|?eB;1FKqeR7$)-l9zMfu+l4pLMD zgd-NEI2o;Vr#nmzVIV5KvYO=```#W0#S?(`xGnuJz-Xo z$zM^9XhqEYWXyX6WaW_>Ye93!N{uoLosy!k$Dm8UJOxX#BTzZ*$@2%XmGa14C~|bE`{cU4c>*fOahb5o#_- z&MYKuCmuy2bC{4Ra~&4^N0hxlxS}Eh*y;Smf6#pRmb2WMXDSrH5PfW9Xupw~c7V(d zYL_L;pgwHk9u-uD7=zNl64!7+A<+3O-8y90rV#}P#Q|Zp1~zYtZfnVaV&iqXo`9E;_t`S9?}JAz2=s+Y%Z zc%;GMi^t&y$FT-XFzz~D9ugq$pLg;V9I{`)&B2OOt+pLKM?vi6TAl=FK4i6{vbEaY z?(y<3gvBrH_>O$tJDSI^Bn(pI5mEsY?r6Jxz+_wCsDx*U92oGM4=;kyHlK(#XCanXwA(Lmm_#XUI~kS zo6(5Eg7Ab_3ObndJ|aOZuZ+}QU56hLcavcK{5yz>e-BB)Y~~_s8euB!gAV>_RGw=N zwQUwQLw0}DxZ{|*v@|HaABzqgMTBq&&K=OnKSNz4x-6jqvg=Eil~O<^keDo~U@H|4 zqjoJN@5QLx&O#C>h4C^6Wudf+)Y66eR#+ciU+S{&($O5G<3esfe~yle;=aZ({D= zMUPR|BOU$>{epA#3nqUSzjXNsDm#Bx#;D>6q4AIa%!J92r|SpcczN{z3Nb?M$qqk@ zdz&jT-#Rt8jv#GvUs~gMJ2inz0W3wA7;vp19>k)WoGM93$O&8wu-HHlCuu|1r&t=j zKHV-0dqd#5?6r_tPzuav3EjY<#?K>Z8pn@G!(BR&n)cLl_fOUCpJMzTYC+a{)0jQ> zCMk-Df=aj^bC+QsQ=pNMk>kFL=s+7zI^3K@E=j4n*R3wLDas)2j31+fR$C0UiHw{D zexn^RNS?oFtCgOsZG^%91FyeG6tEwe95y{0cBPdXdwVZ7L~;P7b<0H5i{AX;Ew_f< z7y;oqgr9{4aIVZ0MulCH%1@~zQYA8d*I|Q{M7jh|c)3d90Cl^RKzDO3BD{bAd-WA=mgKz0wO_7!}4K4$pz)+{nR zUAdR`GTp1Oi*b47UMnVK8C|=_e;JS$<&)UPQer|afV^?ka|gP4S7fCT3*zRTY!aDl z;mvrS!U&rC2nO!I%NE=3y+?zkG1GodUn+a+5hRQP;1K2pQ{B2|BGMhacv0gy@SXCF zVV~BuIt+bWk3re2yiZMh85mn+0mc_Z#^+^5>bC4&B*Jl=yKZvog5Qq>v@fI9pwPE3 z^pfPeZgCoqRNeUu<=#Mo%NX07Pq`3!jT&P&~7Pr>R@zp?swByhWoU^@idEaYvGTOSnS51w{t}We+Jbggm-$1bWFj5gWrty!QW)cP<%JR^+=e0%S@0i3jii3yPGYHT zyYPRast(mlh5Me{G?Ray5J!{U3$#HnJhCg&ZWCb7_CN$j(oqr~jgT(tlk1hXi7mQi zYAne8AEvV;17d0rP%jzq-UX*9Rnj`tj#nbC|0>PGz`I$!dGD?>=zHyYiocDQ_zx(# zLCFs(`4J^QrsNGuvH;HaQ1Xs^Y$qM@f~a_0ZNeBx#zf*JZP=+?oKsx|#XTQF2z~fX zBoGIZmCv+qb@q^J^X?vr0JSK*`?;e~I7rjacR<~)T;O1M$=6xM*PU)essrsDx6q8^ zf2U|XK`->N?0DzJTXO9m6BzvO6|E`%YeuJ=p85YI?8=igC@|oiJ1}6^$Vjo~_)jmI zhLTk94Jsc-(mX*}-fg@;E2r=gbU~a!N--a$PIHv>vVkPY zZ&LXflIH#IHljYt=i~HZXNnzCoo|_#s%T%^%)tSGKRWQj?oNLrv9XE&2Ls(RTmS$7 diff --git a/Fusion Accounting/models/__pycache__/account_analytic_report.cpython-310.pyc b/Fusion Accounting/models/__pycache__/account_analytic_report.cpython-310.pyc deleted file mode 100644 index d76b15e4f169c64bbb00a3e6efa7e71294ee5853..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11806 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 diff --git a/Fusion Accounting/models/__pycache__/account_asset.cpython-310.pyc b/Fusion Accounting/models/__pycache__/account_asset.cpython-310.pyc deleted file mode 100644 index f76270ae798fba9264650ae35719c880b776c2c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62463 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 diff --git a/Fusion Accounting/models/__pycache__/account_bank_statement.cpython-310.pyc b/Fusion Accounting/models/__pycache__/account_bank_statement.cpython-310.pyc deleted file mode 100644 index 7d977e63698cad5fd4f50b7850bd34ccb8ee7778..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8111 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 diff --git a/Fusion Accounting/models/__pycache__/account_cash_flow_report.cpython-310.pyc b/Fusion Accounting/models/__pycache__/account_cash_flow_report.cpython-310.pyc deleted file mode 100644 index 174e91921ab113ba317c17269b68a2cb5feaf319..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26920 zcmeHwdu&|imEYVucb*&$hZIGTdS2O*#c^dydD)5AaV^`Dtwf;{S%wof>Fm|qAvMx) zhPrnsncT~4P^Rmb6uUN!R$Tce1O-lChdw%4wNbGIrKB-p<%YOS_)GlU*v+WL}V zXP0vJ6L!wdzpX9hzn)pv?1EiD|naeal49l*`Ba#c#qkW_7vU~ zd!Idx_qe^kRbAHY8T-K7#?pj6YtJFHW*@W<;XP>|wvXUFWgoSV;l0l`?c;b)+mG3g zAZjF_vz!cvF>cstU}YhQ($sF^CiJkKuUTFSDK5$!z8E);!JAz5GsgtKj9g^_|SinXkP0Wqqr-Rq}G%SzALIL(&wzl2_Qt zE$cg)mhp;S;a0}EX6s(TE6!!;S3X;x%WUURdKop1;i-6Kl(}zf+#9#Eb673wnsd<` z+s=C9Ea~RYc$L`S&wFFGVdt=V@^^Kwa)9lrB5&B1g0y9PC#%|V^Gi1;(ZZsH7SM~T zS9Lam)RZ=!scEh%O;FYK|t78>?O*II2i8=ZEyJ<%tdR5o8;!h6h`q!$u zfi#A``SJI&fSN=C;g5cT8G>kc9!@`VCv#l^69A37S%3)bx(?6+V7;Q*S%%o$H^w5? zT@AB$tKD078^Ijcv%b;aSZn!uyG#F7t9!#Yn%EJ3K`r4cYUqp_=g$_YKG-E+Fj(aI_*tF&d6636&6xAoCB!9nWZyFhxNUy z`K3mq*|A)=(NOckDRKT796m0GVoICQHlNvZRCjMrYG65`gewBCe+Qm+X3HSFCWPD3 z0lRZ<#WP+9APQi2&enDeK*=eA=Q<#A-phK%j)5u2Z0mM5{=PY#$b}ThRSt3`Q=LJo zEFgFRZv*cl-Z_D=Ij<|xMVt78Ff_!9uNZZe?a_MLXK-*Bw%N-KhF;SOxu zu741_b#-mMPoQ&iy^W=`5fxW!U~L7^S3x}m#QN5X3qaxe`vr(`Sqhv+xCBot_!*VA z+Pl?qoqd=+e+ppEU9QKB;ad`{@`BWBYYO7zqT2@EdAxIY7x2#GUBtV9 zcgfSYGj`D~Vf~jqfSK$1^(?58(vIdlX_v4U46e^M_KID;hh2h|51IwE#u#Z4tV<0K z=oGJvox$)5p78@rU%gjIq(^N?pR*^@VO}nmnag;kXV8u35@ma6C0jU@2la+=>Ab(l zeVhX&g&leav@%;?uUY>d15-AG-#Obhdycuj1_abH``21#VsK3x%NxM2YdISxk&qcK ztA%n@LNa~ZHLY}}Ye2eEERa7U@gbgNr?+Z0xQLQOh+5e(WiVei=hxkqZ7!pUs~`rj z`|44}pkSqBg*DBl)itrm`vA@6M-z=`x7=r=JnZ$8=DgFwZgg69-8>cS?o;ONWK&J_ zgoBYJ1R7|$nW{8Q6_jE2mL(HW{a}p>^;jL$$T1us3P;qA-o%h3U9B2NHV;Laq#UB! zQrjVqt-yz{!R^2Sft~fRxIjRET=_^}VoMkR`8iw1qSk`Nh^-;SHI|T_O^11!EKpr7 zP%JV9uERwJyc^(Nzq*DA2OtS)pF%KD{{?N4AFy-TZT0QevbEmn*YnOX1pCt*p}~>& z#~2zE;6%{M`809>ZSD_&f?CN9RdWJ`zKnEl;&H2RDj-*C`m{C+0>*f6^Rs(GoB=Q$ z)3zh4^lL*cFUJ!pWPE%C0P);%o=1cHYM4fW`9%;Q3|C;4uoaYM>sG7teQaJFFr?eg z1*RYZcV40MDxHgTBWc6hCkL^cSs9wkRUCz-3ADip7R3J zk14#sc@UVN80m8eLpw1GVOvSgCck;2Nry-WUqnVl<%Sv2Ni{X3JQS|@1bmBl%>q7+V33uW0$-->mPs&xqMW0%K@$cp8<-slWqr z4=jakZHF|zw)q(ue!{~}+wBty2V)bA;WZ1B1I**Ju!XE{U(ks_cisH4(*{dM!D2#f1)s^>@#og8wjJ+UG^XFag)<`v`74Y+5^cRilp>dK$amh5 zG|`?MmBu&MyK;_fFSkMT#}!1S_(yOkKz9)v?fFDH|M2c@Xm|a?yVs7`V|!MPv$N(9 z-q&*qcsvLHAhsFUcb`I%U&iASI9GtWD;l2~-0+zuQcrywKipi{bAJvUmzr4=AO470 zb9iKP{wa8D9wo4$KrI0oyqvkCZDqZzQ?;{Q%{Fj!&N>JBpt0^~D_U1m%+KpO7@MH5 zz6~bhcd}k?hX;lXLX90E=eP2G92!X2uYF_%ggm$G+S4nU11uc}UH8)@+fi!ajs|E~ zws@`qGZsfr&;us8RUxz!Y;m;(?BfU!OhY)g4+5XZI-w=O#^?#H8f*s_=;sD85;CnO zi2G+zn6+vKjFV^0E71WV3|ok3&u5)h9~_crgOdf?F?5W4);wqS*Vj5Etc6l%m5DAa z1kE|+nmBZtS6hsi699;M3*bFCM4JH{C9XV$Fo-P3DG^5MG&~sC%vbVTU>VsUf4`~U z)QLD8(t`rvAsf&a&idN*0dVh^pCd{k-SqrFK;j>;KN?SHh(fHh-aQ*H4>^Z(O=mKmEST z3fIAEtADL$%VA(43=8`j*ORK|!sXXK7quxEsZ@f`yoP}Rb5{5)AZ=5T?C@)ggZUQ% zDj2k{`RQa{bT*a!()kNlOsODjW!UOOPf|H{sZc7~ql|^1XJ}EXxZwr_g``sLQfY9e z3TIOFHf%)j9_%rT!tdPT%h8~PDZ`xcVpJ$IT#7MHY~ozq?gPy@_Nn-=&T`*;_59`Y z=4L5szDP-&&qTX0A8@Mza1(J{{hR;|Kig|81 zdL{q6`nd6f>cruI2mXKq$N*fR?12C?#gZkyiCD7awF|ZZm)i#W3RpnDqb+AwGzg!J zJ`NzBaUI0rbqKNU@+gwu{E<3}04v16QaFA*N<0`j!~ivb{Na)h=O07(=zw^HL2gn+ ztFR43wx)clvrvHch}+mL^Ww{H+-B#a7%+7zJ_+CEuSYA5%WqGRXsxUaLqXP{;Fb6Q zq|9@#OTB|zRRGn=`97&ZfY(6OVd-C09ONN$PDfy>;VPVp*&ZRh8ts6kY?Pbv!r!fi;J ze~Us~p2!a7c_cUTIH><{^UMRWW4e^oiHN0*;}N3kd+-Pu6_8v!@w|E^o0SJY>9&X`o4qV*~HVH^8a8FASZ#NDpL)MVp8iOo1-!Ir> z;NXsde_Ol`sls*%A(cDe1i)X0R9Zv$m_2^CC=z9sT-hE+n(7@5vgO_M4}=nM}ifW@u=v- z2(%;{n(&{Ccn69;osR+D=?EE;zZHLxw}rt~(xG|+3JF_|00`{Oyb>1rlVN!gOz|he zpx`a!=({<;)Vbz1AU(8Pzf7;%qkhSOU%-N2rZ))2hVS0EAC?so(7wUeL8yrqTjfFG zkp12T8ZzqSd|F~mY09BBY3p}8;^2WGVtO!Vh;=&Z7k3I$ss`B<+EP>wP`~k9z`+QJm2sc%Tqev9&9L zJHgE;LOfdjZ!x(aVU>4Bc;kIRLaraheuH9*MNf{-p>J@LPV)J7y&x9fKCe{ zc-J8?%nB+j^&u$&%GB;q3|hVp!9OWbpjC)Xfl$XJ7Oci?Dp3`@GSFuw6;^}*6=)HH z`7uGbRj7Osl>&uABw6vsy(-Erqb*~ok;t_|)EgjL`UUCs$059wR@q?r*;Oz!i{hKW zSM6?9kT6?Q-JU?6Ng!eHQz9f>-L47A@Z8n}ko%-JL8(?dBV3rA`a&NVK82D)7ir+% zC+@Vk`^B9>od@jMK#UvZSNRY(Cz7!Vdu}fCNfF=P-0DxE-hDgZ=72swX#H!F0vC92 z5xz%K0MKopvB10YprqU~7Mf-Em?af!)LX&0{Yx|0&5L}*r^Wv3PBc_?aX7a#*cY(0NPepbEVGU(}>M8RjIe7=IB(2x7 zxXcv8dnm&nQ34<)8#ondvLQ9wLjy*19Htn2Lv9t3Ibj^aK=N<`(;=gYAVrvvcMHui zsPAy4{Z#Zx;clZNk66P4o6#Z3yc3~=SR4EP(tC5xDCqiPA31DU+@JfkVa7Zp0l zC}8=DNDLbhH!B=Pa)i|$hNuU4qCX!Vs)7{mLg1;JLc$J455thbZpD>`&A?DCzIH`8 z)lqeE+^B90mN~pSR;{uMX)8i52KLi5p3^MeY_pZfn9 zoZDQOqDM;n_8~O?5SoMK9q1P?dIZ>U_d$j23bZG*Q9lr5 zdPskSZxEIr(#Fte_#o59dyuZ-G-4x0r=>?poDLrb?nm_wlkAEpb$*eo4THqGUy4X} zzaU=F>f^#L^{e4|L@_TC+~vPO{s_6c&Yyv^v_GsUb^2YJ8s2LD! zDrpXKv%kc!SLskU`rUxrJ)lJ$ClW1cI`wJi&oP&}coPh*~-t;aRBkfww(yjllU9 z3yFEuf0dtPR6Bp3j&P<$Q(OlAIKu`Q)t^Mz7+91pRk4kdq0itFA64zhu7(aTwXk1Q z+Sd^Gf?+M1y%7DD`Sa2GXZK`jQ#m4Ha;i&8DP}fXOHqI@ zvuhY@GNhr52d4BKWWrFE%aJ*q3W*rqTLrMaDa+-(HZ8b$dwIdEaS zEG+1AV36m%3hx7fvv{Atdk*iDcpt9&g&7cKZ10y9IgTB0bnBRR*gN`6X6vwb#5;!f5wvT^gX{dJHxuW%rAY`<9m;H( z-XYKY6w2-%WJ>q9QYkryzZK{7C^o6h;c6bz>nPpzs#yOs!P>7f9^cdWoAC~Kv)-I{ z5bOSlK+GcXY`HxtbAL#QR#;Ad1|^{{B3wCToq(V_tr|YXEqk@pBI+8;>oPy(mi#m?h8iG>0qu#M!eSif!JP z*k*5K;dZ1f%zNhF5@lh*lh7FM0Rz3OJIi<}Je$U!&=;m;nS96RzX3Grw2^T0`~wkR z0N?&KDgsM)=Z}bbn04u#PavIe=Cvp;LgMp^S zT&s{BL0~^Ja2}3?H0{BJ@^JDnWH-T(qLR5AFR&2uS16h&4rq>L={^K+vQQ zp%4@+3hqL@Cp!}NKAv2q*%N<{(&*VkY7wmbSY$D(j-e42Il{ZzBJL;2&fDRZ`6cd_ zVo;z9>o0-64Rj*ajX_bp{3?^~rgiC%&260ruXw1+KfD_)mWomwl-6Gq0nt!jxq#zb zKh0%{IM)YJSMsL^HYevFm6Bw*I>YxLb?NJ#faw1ffW+YCQA$LI_EEj0z|(*jO$2a`dQdD60xFh(6m}Y zG$~Ta@31M-MiD|%!YH!F`tjYxj587IiIzjef-p3_+?4n+W^>bEq6q990o#vPHVHeL4%2UMb9HHUuFCZgsZ$ERMn3nh5j|9Kzt~k z4N^`i0j(0x=3(Tl@}FmpR$**0{byti)IkyMY_^y!Bi-zzk*(-#N%F7w-eHPwA8hF; zF&LbrG>aPjk1w{rh%eYf)cUY=bO*O~VM$B>7VhifJ1^g`u6-|ykP<@52pPj&-U@7P zkKfB7w2IIPgw|kvds5O(A!J`FWEvs+Qz0`5IgkpOMaUdN(xo3n=pjir8KB<>bpsb>?V6xU(S ztb;pDOVgt)3T>XC97H5*!KJ1s6~$wL0i^F!0I^wEzWvlylm(J7YNgn`^K)X=$oac$ z`=4Oa`E$zllKFXMZ^ng94`tU#-JDXkaAAq*`&7_KHdqNz&-w!tuG zJa$f#l@_E@+@@^st0-Tv8nrY%%4E~cU}OS=@~-S!)hX&6$yg*{Xpz~(>c3wz z^dHr7`v0lr_4~Dg{@!#^|6#49|L<@3@BlH36J}maA2z-W8)7ds$sAiS1@5I0A@I+V7VaYL4)O1 z1}3Cn7eCO*L52fP?L*QGP6`hsK>_ej2X4PRMB7K2EEIeJhm&Sz1VsA0i0^40Y= zE)@qdR7zE$pLT%pvvlU@9HV2>ISR)wkWs>7{t3TA@vAa7>(CsG^XqhYX7zItJc#)z zirwI%G~uZM!xCE)3!D0^u4kzxrLgT|TK>Iujln~5QN)q`1Zj|pECH`Lq{^fsU z{ABYvV%!Tr)R%|~$DEYWS4~lzgpD z5R;Q6NZRhgxON){tA>qZfzy`%wc$L5lH^|-h@p6~2&t^EKEsGl(fJr1gU-*<`8b_V z(&3rPAz%JHI6 zJXZWfv55EK>WSi1@yX&w=I4t)TbwD*)($w#w^%Rw2LH>7g9*q8IkHsd_d>td>$s|| sEZiSIcNLbHK%9LJe7cTgPCyT75?h1+x}rw!d7OzdF6*cPRm^1nFP7|Y{{R30 diff --git a/Fusion Accounting/models/__pycache__/account_chart_template.cpython-310.pyc b/Fusion Accounting/models/__pycache__/account_chart_template.cpython-310.pyc deleted file mode 100644 index 41128ad17c8d32726b26643f0c8e678960884e4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2367 zcmbVO&2QX96rUN7cfDUU4Iye;D5yoiO4N%u0Ebo7s0f5qaw$U9ipp|5lf+xE*BN`G zB=Sn2MCu;^M-JIzuKXcfxXqOlXD%p0h4;pGvS|}2IGUIFc=JBz_uh^>ohE_rm-qkJ z`Uz&g;$r^eu(%5&b%6*XXhcSIKq<~y#75S@qU3WTEWrJEhSSo7>^P^-U}!gT74KZOwa=u z=}jOZ`0%Lp4V$7D5WyXy;D64(XWI;9(Jni=QblGso~YQ5&Nj+}W74%Wb3f4ZiKaWs z0kMXhe9;S{@k9jCB+aBsdt)&k_k1D7aoY1Uue=_{o4q2%y>iDhIq<3+xXGSw_C*+E z=+f#Hv>nrOlkDGLyC9dYw<%AhS6ciy^B^i-85Or5jRQYQSKTwgx>ITNgrE8PhL9Ul zDJeX;o5(noUKs%|6my44?#Os@$zgS~^6bK!XNO&`&^@()WW1y6rSbZj z@$h_<3Y#r(T{F;Phu}=A7raDCa6B6O!f;$DzovOAqYeGad68-flxuE+(JplxA%l*R z+mjGOgNJ7+S8cp;`y~>9uOz( >XkNEfX|%B=-j{P92eU;Cf@-`t+VtPLt~;LA^c z29l8jazqVUSq}9A(8>^*j~)UFtSL8mVl$fD%6L(vfXlA94gezuEC*EgNrlYhW8kb~ zIlGXvR&oMHll#EgC^?%i$aG517I6B&*}jys)hFBS91+@CSLmKe>kCjfPQ&T%Zv*)b zq24K>&IDP3+Wdqe+`lH2Y%>AkjxEizy+rEzri`WXqji(pV@owag0^kPD`KK9({$%l6ZQ%N;D1x6&^moJ3jN1n6lQxb zsp-Q?Q`gpZI!T~dy@p!|E47RwIiw})+Uh#)+(7a=kU^^|gHXxT8?bXsb;I+52>kTC zB7BHsBMz^bUeUIeSd(3+wP(%RdFnjl_Wqs!l3PrrCK1ltp0_>m zqw>V@!gx!nFjLsAj6J$jS{leGs;o7+H<34X;}b+!q`2=8P93)Ns>0p%Zf(%TM&uTa z$So7?7A>e~|EH}-VV}_)`4xgmch(-Knev0|OZ-t{B+k7yV}&V!PQP$y6}xYW&$vz7 Hbea7P))-lf diff --git a/Fusion Accounting/models/__pycache__/account_deferred_reports.cpython-310.pyc b/Fusion Accounting/models/__pycache__/account_deferred_reports.cpython-310.pyc deleted file mode 100644 index 4336285aab9900c2ca38c6d16337d7996606866e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21905 zcmcJ1d5j#{d0*Gj)pyTK&po_qZ}y5i+(X`7P~3weXLc#=4rP*(cH7?c_VlYB_OPdW z_^O7~v})qDNMNbh4Az$62sY)+I8hQtK@tRT;{1~YIglW6;vlh|q=F!^VgyhS1h5x2 zY)azh_kFLb`k3KxC2VHUudCkqz4yNN-LE@^LN9lvf=CXkaAIppNRoJr*5 zMFBa5FlS0=7ZT;-_W);NsjLM#;WTO+El)J-9$pL5Y1O>uRYy22uU1Y5)>;RTZeTT< zPD{9fRt=2WdNW9!?yRlV+5&AdUvwS0AZ16E^&qt(I~(iEn}KoRC*Q#P*qMdJh4beZ z&Q#Bwy0}n%^X$^DtW$5*37Y;tT2K0Oe}KeER5X#O=t8R)La&&@s3e6ck~b0+OB@#| zk-ni-Qcij`BQhd;BXM1;WX01WC-TV8asD%+C`N8*D9MNKqhgHT3;bRZ{=J@R?G0$%Y-cOi`%0cYZbHRZ#PQ#JX5$B!t zj`U7%xL#-Nbu4|$k#~>O>h;b>+ndKWa;166wdOCW3f)6dg)qO)&Ew+7qt5MxudF+5 z*RjnYr%+hM+;2XX*KR7K(P_KKYhE>+bF;m2T%qsrFsB-gTUFz_^Xr?zY<0!)hD)m6=DIUR zGo-1eweC+uE2S1_U>oM|+rwf#iRRH?3J;_B@XF0$9er&(;RCUE^sS_y*w%JHGA`?~ zD2%oy%q`1Lilnf7t6>nD6MCa|ZWf1nHi5i?D2DGN^uCgOA%T990>~ZzmxkJu2HWiF zGc#0P7-~N}W}e4158Ui0K}hO3W^QcS*tEP zE6sKrdl{r0g`;IdPGc2=iSQkJw=SJ}6NIbN4#wl$CNjNhz2kZyL{_t1@2ojNUVT?P zKttJ`ju4|M=A%d2`9N>B%SJFBl0P*E(07Du$x0u72{RAjO4V=dSk9UF~ssh?*XbYlUaJ8fLiM?ymMy z+2|g*ey#!rwKz?2n&vdaX_nI*r+H2boEAA9;dGSKF<-l`uM~mWrKP(g z;TlCq7esVg?RBf!cB&B>1HuGmySCpb)DvHR-oTD5kqwdpnMRnc@k*<9YBb54ElStHS>=?24PS=)+1pPohC?}vG8FIy^IHNWpHU< z_l+H6%k;HXdNu{f#4Teh3G!fE*R~DRGItU?+LlFR!w2791s`6uMDmtNoP9O5n%>U% z$sJ82QbTXqoy1nkPk|g*OL7V$VJV)%SCD0v;MP0qU=hdc8p&E&yYKJ9!W#>xFWQeC zRq$8qt=fj`l-+?!i|5b1i7H-gx#d9EfpF*mh1{W9Z!9ccv_E(5?2;Xw(D1u|3uEN% zons)#xurd@JL(1HFYPYwZx0-c;oS{IL?<2qMpTWc$X@Ua3>d-Yfdo;5eF5z2xgXLm zzrJvOfmgmJ>z5!}v_+GS+0e3{JAY>3y#3j?cQ3(S=O23<9+r5e`^Ti6#|bJ7>~=6Z zytBcm>kI*vlgA06#+c(O-d?@3;mA!sdV#sxfdKG4RmriWr4T8|UxQ@iglHw7qjJ42 zN&0f?8xUynq$@TYuYRf4YL(M`fMprgcNoCE>R%MfDxB+KaV;t zi33IO_>!jU;PYt{{N4KFVlhvAKGsz3D}QrGK{i^mt(-HUe22|lY z+7go+$dn`lG4w%^l~{il`NV zPxEm};Peu#j)EfHNRtl1v`Rk%HTXKI`o_{O$%mTKwth$p{<9!R@1(Z0n;>(ui7#P| zuIM6hAt4u0$TPpA2m1Wefp(d+Fq)iN50>UKCrKv!%DQ~{5_L$x>F-S?y07m)WwH@e zmp6NBrmPF|CtIC*t>wNvA9oA6HR)|Yf0VDI@3N_OX^CKCM0<^%NGFtUQo=w%p}Wrj z4b>)+zrBNPaYw0O7W%8qEEOXb|^Mrz05s3mEhyNEzBZ^mW}&d&c`9YitEf`&qS-6R3MrN3GONom$>B zR?SriC)+8?pM}_>dbx7K&p~v_oJ?G?@ttbuARqbKH~Zy}*XUTdgC?BX8d;!RdwDZr zWDq%p4Q+cJ>_S!cIv&oo-RMZ$snsvV72}L`M>acRK9&hip$9S)aYJqgW)n5*>u9*$ z!u-m-eUv$vt;(JOr$0(E)icLz`g-=5-Ks4+t!K)|!Wz#;HBdw~o(pR{iyHIu^W_+* zuf|Wrl);c2%JLJF@5|2SFe5lxK4A~Gqqa}M8$NqjoFrTMUhe?z}QXH%2Je3-j{4w483gAbphVUCkMNirLq$d|8Ehtzv1 z(S7@qo=`kWFt%%Jzem7+?<4#L`FdXSLBo&OJrcb<< zdC*^=2j)R?k;>Hn>K)Ego=b_r)hX%-^8Vb(bSqGGMh_5CQ<<1VB(b0&XnG< z$QDr-ewBVV!+Zo{Sga6mFWGM{L=AG{kfjFZuuak<8_kt(5HMtb&~;POKQce+J|A^^ zU!6y}bA~z^&=+#(ko*obUH7*zaW8Q_0V@XU2|~MNz#^eRywG92Fd>#eAb>Do!)jr| zK5xw8)WHt{u?1d-B*YH*BOpqs_Y}XU;`cPary)R;Ji`lnBRz!rJxL^t`zOKW+nSdY z5N<@yv%HkZ-!{Lkt!P`RU3FkiWsd~l67X^ejL}y=y{kTqtzyixdLs!y8TX&rRlkJ# z<6-UH{b&7@pPo+m8GIOad}r13$;1lfOkPvY6w&WNcUZT&aIG;*e*QZWO+ z&cA@|C%c+k&5PN~y89ngJ28in?}jA>Dv^JHmIq*rE@F)i0=`4!Tom?glk7*NoJF7V zL*&C9h442mIum*8Lk0ShK#nh_c-*c&q)=MIsN1p=F$K^DBx8-P1Sl(4-4@(2s4 z2Tw=0_*&?;utAOGW-wW+lg$PPQF$|}oc%pw5ErBQTB=Tvb{!ZoU^xqNwbnJ*XRGd| z&b9JLkaRadA%g?e@B~I~m1Y|Tp<1iiMd6Vuj(5|Gc7;i5|9;3Ctxq~6-9nO1A&DBv z0wv^{2@LoqnOR6duSa8`;}wbAT{MfP<5rdLb+4M?%J6*qYFf2*xPC=BH*Cov-K{bi z&5RfVpjW5_j>Q)65kD~YD(7Ub`_b5M@BED17=JYwu z*33C^>Ms#%tJpD$(nt zY`O=9bs^zFXWIrzCSnFcos8c>4%zIi$~7XbVVzW1N9Cz{Y4ngqjoeiz>(CZ0iVnF2 zJ(S2jmlQ#MDt;;|nIYnDY^QF5#EFp|ZAVj@Ddv&!4KNYaYV@)$#$XW7`B~}vx$P|G zQNlRmezswRM(>1}07=XFxgBjazm@m%tA*9#Rw3+R5(M#={Q`1FC?9U%f|&9~#WYr8 zg5oax0vy~Lt>5;Tm_r{WaiF2EXcT`T4y*BgHSsOz$sz?Nl?IDCf)ZOCYFgr<+gQW$ z!?hzgV)i*;yIq6N%=PR}0}%;VJ589zk1sb{P*I_}9*35yj#sV4Gy{i)RZvn$>#b(} zG6EUu4)jq(Gt^?wQW(oHpN-X%3+wPoH<~bMwKk90*BqOO2dlM>POH_q1`^D!JNKB) zbPe4?iN+{RX|HxRBs@BbW*xJYzcsD^?V8ROtE=`h$&2oZc-W_GCgCUSH^{U~b0pn) zXr9XsjUG{vC@v!u_Cm$Dug5+bKh*D@D|PKs6Iwp4S4TGCzsE$v@p+jQ+z1a*WB`>y z0OAozN$&d_gzK0M+RnYlgLm4kO`AP_*un-l3l|SJgIDVs?Hj%cHkNd;KKC0{?Zog5 zLQIJpJ!LBn_n>vT*7RWb2)B%R;hr$o;cEvcB}^FFGFVVL@u)%m_$1IPxA78=8My<0+d(hzEhvzuxypi63>j|9`G_Ji`<4 zz1P-kQWx!zsh(hDQ9{uZuKjM9{qCM^xVd}6CUm1dpkR22_P~knQHdS`4L!X}^a@qk z5X}r8TIstd__Ww?ob@_mkQ5*|;Gsz?+*&4(F>pge^j_Z$%Mv>bVlu^IELbHZ3Di`DzULXi&SLKkXHzro{-OK1A_=l z(>0XIuTt_35&(xlr%hLG;w6~a3wgK&rgn_l{CP?qLvnX4T50lo&J!_~8LN%>$b;14=%6-!wdq{z`IaKV*pm*Tmr%Vj7rIKpJt+3I z3WsnAihD5_87d483}vu?e%bM^IZivUfK(6}?pT3Inu^kvfHIK7DR4G)gc3k*0Idu$ zkYDl25}DeU5v zf06203eF(KZe$pe(zK+g=mrKQy@0i_g3B5Qxn>*k*U&?`s5F2l@htxmCFIo&3gHpG zT5D}MZY5GFc^SxW9E<;)<8>;d;fd+1(Vv4FBR!ia&#O|3^*Y4tb)*2)mAQy1^}?N^ zu(hBtP$vHl0Xt5~FH^!sC}0<>c|AP;Hz;%8Kr%6@Q^1*NB8be?M)gs=!T4kpp;8p} z3Bxpsn)Q)sOz0&&Z6IIM4U5zWoG>aQ$29%xcdb9`J{6(m$2w~RG=%60`QlC!U<9<# z5KF5=!cOG|CB%xftD(eLZBibNDJ%Ck z&?`9o1~V&DivEv(o1Y3 zkUxXrC|yKpw!akS$fQ6VrzpS!O1h>ciqJJv zVuW?gQ3|cTt&0-tljBSB7XiQg29h8Z5?lE+Wi3$h7?SQt7#KAUqJ!~#dGxh zHz@gAl#raExCax5zelgk1ynS!3g+o?fqY1qnO>7n6q0G?a6>31{{cFChEV8HJTPr# z;|dvA$Yi4KcgUGq%#mH~n;!LntNEXh$ZN?5(6SgsddC z;6z7zMCGfp*~DrR=8ep@MUfQmyu4MQavu@wQ`Po!kVU7L_GC~*EL@Vb5YwQ)Z@^x~A&@uHH^Hiuy^8%N z1C2>Srd{F2U%~_I<%xHmpfHG=+A6{jJkWZ?PQsx{My&V^vhohOIY9~<=$t%%v=bDv zfG`U92`Gf3seN1f1?2{{`ovTcqQUw|f(g<}39WAi8I}Pk6!Ky@6_l#W8_kx8XecR2 zEPTnoiB|GEl<-j@gR68X`6VQ<3{$Tv(5-~0z+x+B7}dsJ)xa$uP71C z?Pc~LVNsqB=p9Xx2z197nu+|kG%=n*uCKG#X$nLe5RYH9{-}HQ69aP>nh;O8fR9Tu z3!}*-BJjcFaU>)3h~5U!BSZmX7zK>pj)uXFp~r)^a+q5E7V>_ICQIg8?3Y#i0qvCj zL+s7|fou=lo8fKvhcx^&l10{o%1OCGIhQDDQt~NEUZaFb{XfN1kotn+65Z6%rH=iW zTULJmzfZMD14A!FxRvfv7!bqYW`R4TrEq|hE4Yi`DUp{q)}=?UX0NIIkJJe%eozF? z&@&x_;1CNcN<48ywJ)xLH8vJ^T2wK~!QO$T^aSQKK^}6ATbE6~bfVM^gpVq(Cx?2o z<}%Tfg+fFp^@f?z*pT=m{}BQ9k16?W9+Z!TT0mOL%B(t->}-jZ3J$!8i%5VPEDFHg z=yIJpWR+h0r_xR)!#)QaRYu}>#SJTSaRv|AKle%@F6)K3|2vunpSzjreNgE&!pqlD zZ&b^d5bbVRcV`e7%T5lY7F-!-njGgdq``o$;IQIbxk2y*4aWlAp?B2%ftl)l`o}rO z10jUOW@8ENdAyLI4!KO*(mj|uz*OK%7v>7?&Oo-(SHXy3r>6^SxWWeM33pU)Q{a1k zNeMM1-jwx47HRMunD7^_DZC_}Bk9mwl>ZzlRA^;}gq%&JLjF}s{u(7uQSxm{{yGv6 z=b8j9L*VpGL^GX18GBPHMSmByqu^g=8>M!?@qnbL;lO42HMIFUZOjxNvKo1v!bQiE zc~3X3Kj}WR-!g})X(atC59GiakD%u`rtmJt>Qj?)p)zE&&G5XG2fHC%Hn*A%zMcMv0sw+RRwkgG)zej<{s zxy9-vBzjYfkX((c66AL(A;NKdHUUA0{8JP0Tc&RXexBMxQJjibV0tTy@)-zlS?+PN zL6GU^Apn$*=7OR%q?wuPRx1-wh#LG9fTtQcS)C+^V8zM zZ40w1_=WAm;-H^v0^2dG!uAnd<0$&Y?ZbXy)%G3&#vdYonx7GeZ?h2QXCR_ITzv$- zt>Sdz>%3O0k8VFEjoGLt7U4cTm;Cw>vLwAe8d*6U*pcc)PP<*K2L~X6*#^$F)2f&WUWD92 zY!QJ`wf2e=BzZJ2vK;Jf2pVO*y?gLA3QfggsB;HC&8!p64Jb6d`lp z>i?apPlDN!G7gViCtjm@rbZb2uzR_=9+?e6S?C5TUqG$aFFEzgL6VyWDOeH64pz2; z44d6Jo?$L#rvr8@w$){0JC5K@Bk2|PK(a4LR?^tm^-VwsLD$9?=XSp}U~50SbYbEA zMf>d1#dDEeURlxNfpO9Q=xuwN7 z&f;d$nRE8ixr?u#U3x99G9d90|48C9j*n|2VTmtKcMza3r9jK*2ThwHb6e+lS8X>s>^6oK>zCv~XI9>%7+;G_lUQwtoY#0wL<&BdhT_8>pp)a)MLGqe} zQ8Oq;QI5R9u0no7CT0I+f#4OYUktA*Rh1|T$&^k{d8B9F4pQOz_9+fz?3Vu(34Xkx z-xwDKN%A?XAik(Ua0H|AOu{+Rs^faMn%CGcdb*VmKe6ysk3>!m+2l8crhiBfFs}YS zp1w)A`XxNrFf~)G%Y-(EluRgTJkw378CEK7?NP2#0!C-?q=dip-C`DMCJTSSaai!O zX4XV(;XU&QqZ!<&+Fua756%ym@@VZ-_?*VaC4>Qkhk8bud-%lqJLf^{@O4>J#f4(Dphj5q#yUQ*q{bm6jN>YI2KSMG=L3Cu$;4XdoZ!uxmUYIb&a^e?B!aK9! zt($JNNT=JpsiBEK*o^DW#s)f!ANu}Yh;0(q@!5h@4oosF7d#<8;TpINI6Eo-@!sa z22OhkrGtdhD+g_ok;$j%Arodjl0E5)F29qWa?6Bln8MAQGzYk*eIPB&Z^u>=Uh+D) z5TdUvVTsfY$Y@&`Jg38FL^*+9y@jYJzKi3Y!VMbk1F=;(l84pXfdp>$knEX)0T6Li zD8nxdC}~nQ`~peI_*O0WEo@O#{cdO2koX9o9D(tVn%&kopi=$6iT)>eE`TS8J43V%4Z2zM%GRVmDRR&a{J}iweoSK~ zGyZfqhc|c*vzXzeKMTkoq504}B8WfG>vxLMc{>jY8c40Hh~CDBkZtVaRLWye{!Jz1+?-qleU z+Z4uq#myrfq}+q4d-8sIPsn)PsFU!0Q1MaLgFgVBkUx!tDA&Lr5m2JYACk~P)Pt_g z^3BFOh}1w{kZiO%HLs7l^+?>8QT~5G;y$E`=|uO`0|{HSPD)PbV;SLPp!>oni^QQ8 zF^vmF1~N!6rA6es9Eh<`ypzFEgh9xM8P|tNLS0$nH3a*pp(@zN=kcX3og`UO>sw-$ zf_@6+V^~@CLR$$-Pk#iH?CBqWjmg%}tW&?@~h6O$lW@ z!RVkIXXlX@B-zz2zeweR5{HroC1lrRF;>zI;Xqyc7~K0%I^~N)@;_017Iss(&4cLi zPI*p=t0a-i6pdb?rzR!58*J|EMaK6TX6d@m?gC}Rtc(&?(2Ws~?*CBLQVdsI#JgO{ zHa4J@;}?ILok|upL*s8HL(iPef&%>_K<_63?XAk<$`2?pgQD z6ujW*ucXau=6kA@lDZ+>gst+q5^nLRe^>{9XnvGWJ-}~Hx>>vvJWUuw7t7ir*+v;r z>F}2i1?ehV+N;$dOTXD7Ht-W4RhZiG;|{eJS(gzu|9hy4ip@5D%c1GX|BP3qkRGQe z657d*LW&+4aFw~A%*=c}lCM@Pv}WAzP{B<~euI+Vq~xDaLVjNP-zfPSCBIL}G9|PZ z_`Meu#ZNJ2Q6xWa*9TOEPk0eeBptf1B1tFIdZqPj$~*#baon87Z$B9HfnLKO=stEn zm3}6DKAlPzvQz2Lrbo;dP?{-el6u>dep2rr+DP{OEkTMAPXz}f^0J`R^nqV`>qZxM z=cApP54HKBAKsHA7_ECG>MQK_kw(% zg_aMab>A-XaJ-Ndaq|lG{|MEfxAL_76M)25ZRn?uaA(O?0?8CvWbpe=Evy7gInmE` zs&GM*ehg8Po~eAG7Cq5{S5}n~DhATl9rZ(S%<(E&&hu|IV53d^6eldIPY0d-n6_u?z5>2CIeRU>~b_ zh(>~B2@F`k7`y@LNcSA1t61MTa|2iM576>{;67+ETN`9jB_^WbYC$JeF;B2j5HJaW zBU#{HK$lgJe!wM{-Z=!`1#k#z2wSsTFQX_c()=JcQJB2XOq|DAI^GHA)9r3G-ySX& zBDU~Djf0i^QZkiVTto@RlJ>+P8&!u^54If3jY_(fcQN8iX4eGn z4~-Gx60Y8M@N6NYq%b+U>|`>_I-!(VYC2&q7FR~>&Ya}VVn)nK5K9u;!&B|yb?bER zCYpxhvl{c6^M(H!KfRUF_HFmP!R8Y26`_7aQ^FMHT=tn{_hIT#9(&G=5wBAe#*9t* zjF`Un$(cA4w{)@JobVF*UcXj4CGXcuklt4pxqooHRk2??954O7Ir;z_tiegCEVl^I$(dfHKNXyecHo=ZMiA_!#xSQVLx)sXSoCfmjgZZqeaUyt|yk;DHNV%_>eq~Smb zVH*O)DvN~rO(BkpFj=hlsEQ+{HV3YP_n!US9iaSsKG*hpc=J;j#<1f%^!J} za%w`p)VcZ{l4IYYkI~a!d+MdcFq)E#29zRiq(){2W@ZIe)(9Gz9oSlKrcUMr4kbSjX0gUQ!Wz7B z>|UBdliAOS@B9J2h)-4b$0$u$R3=6KRER?OmNKJJqU=G!Qzn(26^y4+(GZkfP~>Hj z@ffWamkoy}uw@Q}lYlZ37>oucGXe`D+swo~u8hFJJeUn9hq+gfk3Kiy+|uV3oR`=V zYlEc2mLc=*l@+vk=lwEUVXN%k2b0}rYwyS<4Z7?BU|CnI-^Xz=&dY;D#!-62BT?Ot zYEf^XWxaD0F4YPiYAEY4%J>W4P_`^ZlD|=|;HTq6a8|XRmysyFeL!1n&iO4b$>Vg) zI0MEjhuo{3d+5C9n*!@7mVhX0$kS2fKFwL}#Rm>z=)?cJsg?wny|~CmQGTwRy_$(B zc)eZ7KTeA=0~oU8LiP(* z6#a;?BA5NB45zmSsrKu__otGuh8k+9qjTjxPNVEKi+0yA2?K~rs!u$o8{w9Kew*p48#2PSJUTWcWiU{kUtYk`Eo8QO5l-Ad_9irlSq zt_#vPoeOJ1-a8ZP17)2POT5&Pm+Q!<2J1qlR;s^S-gNB^d3*vb1qDfYbcTKMnwM`m z&lej^_GShzhFowjg8hvbN0LjgEW8*>m@qEBYt8iJBZwfkfpFyb_Uy|hhJN`qEFa0M z1shb?hP*bM*=Y4mt&Z0Ig3Ub6zij4lUez{8aaG>}2#?pV0!!~@(2-tVlz`nEiKGw+ z3Llxo$B37LYQ5^wQ85P8IjRn$Gq{JvTk?t=1o1FQ8Qh3|L_K9BtTMjX5uZTUT+^Lv zI=QA_i3f11jK@1-T{F8o0v}a1p8D6h)B$p@ID^_mqgh?bx5Z*qx#f+Zbdp0%A$VNQ z6ZfK=^#Ke()c~Yd47~GVEbiF%4t-ZAq>~Z&o&?TRow906y$@QbJ$vtfu#@K8jUZq6b@=G9Y*EVeG(zf0$vrR3!u7B3` za>kcWw`;w zB^DrCHe6~@`^Ghpwg9<((>9l>);;uFs*6Lh$A5~s&Q9ubC<~=JvQAgvJgRHt0H+$y c@Q+ga_^(7@VAdD1i%q7dcH4MJ+vd9Y4~Cc(sQ>@~ diff --git a/Fusion Accounting/models/__pycache__/account_general_ledger.cpython-310.pyc b/Fusion Accounting/models/__pycache__/account_general_ledger.cpython-310.pyc deleted file mode 100644 index 471fe845c9d6f9e0fcc47e6548b16d1b5578f2aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26038 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 diff --git a/Fusion Accounting/models/__pycache__/account_generic_tax_report.cpython-310.pyc b/Fusion Accounting/models/__pycache__/account_generic_tax_report.cpython-310.pyc deleted file mode 100644 index 1f072fe12717461243f16133b231ac87557e1401..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42140 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 K9zJbOXZ{yx0yjkf diff --git a/Fusion Accounting/models/__pycache__/account_journal.cpython-310.pyc b/Fusion Accounting/models/__pycache__/account_journal.cpython-310.pyc deleted file mode 100644 index bfd967303e3e118f94c67b7f97a4a3aec42ead36..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10766 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 diff --git a/Fusion Accounting/models/__pycache__/account_journal_csv.cpython-310.pyc b/Fusion Accounting/models/__pycache__/account_journal_csv.cpython-310.pyc deleted file mode 100644 index be2ec20c6d6444d5f7a762eca96d824b97299fa3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2023 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 Ck6M@j diff --git a/Fusion Accounting/models/__pycache__/account_journal_report.cpython-310.pyc b/Fusion Accounting/models/__pycache__/account_journal_report.cpython-310.pyc deleted file mode 100644 index ecab447204759e70a270ab65fa57e52f518a74d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42390 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 T?nOA#~( zP`gXZVyT(5(oE8ay!01HkNHD=z+0wI?JwwLrfJVzkRok4Ni(GkV6j--z4zR6Kj3_E zF>vtv+s%Jpj2aGw0Wi@I>|45mo7S=Z-3^K-9LJM*Sa<+RZyq#OHs}{tfSURnI;QX_$zZ%^2)rT)}Y*JQEU%8(l_ zK@;^Foyc@WG;X+R6&Hj*s;|Mj1uWSGgF(q+hW#84vOv?uXwS+tYlhIWI+=XsUmN)5tkN#t`nYbIJ~x zsEk0=%TCCQYe3Z__ryDOkTE<2Q+4%NSWCuBGH-5@|lrQt%{u z4*yQ{l-w|;cIl;Nz{kJsjy&M|`5=Y=RI#9lkF) zxV&cgew3Lee-Eokl>?)xvKlDI!{y6h%53^rcchgcd7jkvJAHW^8N4rm@iElLen6b%)?+q;(ih zX{@e6%vDm7NWWAM*eVSv?MN!8Rub;L&A6$hi5sf|1&16R~P ztB}0CH$HXf*duMa3hh=KOP(J=;*8XViB!xk z9n(8_nCf;<1t-AL0PX zN%oL#B6R>_?xcvRP9*YudPZ-KV<|jK$jE4N%F=BjF85T!L+Ey<4%l`*j<|WX-#kVbWl{Q4WiM2CaCS&`^9`G|p1jT!sTN{tJ z`S$ymS9qS-}3 z=5A|gsO`P;Q`&*H=>iL`0xMmfcH>OAe7DMq$8jr zSOkdxUlIh3LLI)2DYZ@~Y6YJ|c~@rgSzq0Gga?`&>VACr|HOGBvN2y&WcIWO%Fqpw z*$X#TMO{9VF+O(CO~A#SC{2NAzb`YPtnL+BSRYfOm@g?B5>kcN&6BKAFI(Z)zNQWo zi}ou?OeCy~_eTd(MyrLQ6-cxl8D;7M*+o%Q)P*Fhwf(*-f@zPdUZ*wJXie3Am#JUUcyZjT#WGDN z{hFoG4`?|-Wg9?BJ({{v-|?8omR#S3`Ch;ls!PlZR+(M32~v3UIcRlJ=vW-9sQtz% zmgz|opPI5yEwZ+sKdr#d8W^HWf>a;T{{~ojGlaG}S7Ual;-3+$5n EU$>pYy#N3J diff --git a/Fusion Accounting/models/__pycache__/account_multicurrency_revaluation_report.cpython-310.pyc b/Fusion Accounting/models/__pycache__/account_multicurrency_revaluation_report.cpython-310.pyc deleted file mode 100644 index 0ecd201cc5f5b137109f2d18c7571760d139b88f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20341 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 Z!aB;ImQ;O-D9$&1kLq-*t)v~+{}Xtv%25CS diff --git a/Fusion Accounting/models/__pycache__/account_partner_ledger.cpython-310.pyc b/Fusion Accounting/models/__pycache__/account_partner_ledger.cpython-310.pyc deleted file mode 100644 index e289a3f7eb5fef0d120c9414a1f4d250be10d8dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31058 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 PeMT&BR{#ruqT7-nU}$i`kdoySCs!sH={oT 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 G`u_p&`zuZW diff --git a/Fusion Accounting/models/__pycache__/account_reconcile_model_line.cpython-310.pyc b/Fusion Accounting/models/__pycache__/account_reconcile_model_line.cpython-310.pyc deleted file mode 100644 index 5c9b74444ffa06db5cbfd8993a19092623e625af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4584 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'; + document.body.appendChild(div); + modalEl = div; + document.getElementById('fusionLocationRetryBtn').addEventListener('click', function () { + hideModal(); + window.fusionGetLocation().catch(function () { + showModal(); + }); + }); } - function isTechnicianPortal() { - // Check if we're on a technician portal page - return window.location.pathname.indexOf('/my/technician') !== -1; + function showModal() { + ensureModal(); + modalEl.style.display = ''; } - function logLocation() { - if (!isWorkingHours()) { - return; - } - if (document.hidden) { - return; - } - if (!navigator.geolocation) { - return; - } + function hideModal() { + if (modalEl) modalEl.style.display = 'none'; + } - navigator.geolocation.getCurrentPosition( - function (position) { - var data = { - jsonrpc: '2.0', - method: 'call', - params: { + // ===================================================================== + // PERMISSION-DENIED BANNER (persistent warning for background logger) + // ===================================================================== + + var bannerEl = null; + + function showDeniedBanner() { + if (bannerEl) return; + bannerEl = document.createElement('div'); + bannerEl.id = 'fusionLocationBanner'; + bannerEl.style.cssText = + 'position:fixed;top:0;left:0;right:0;z-index:9999;background:#dc3545;color:#fff;' + + 'padding:10px 16px;text-align:center;font-size:0.9rem;font-weight:600;box-shadow:0 2px 8px rgba(0,0,0,.2);'; + bannerEl.innerHTML = + '' + + 'Location access is denied. Your location is not being tracked. ' + + 'Please enable location in browser settings.'; + document.body.appendChild(bannerEl); + } + + // ===================================================================== + // getLocation() -- public API + // ===================================================================== + + function getLocation() { + return new Promise(function (resolve, reject) { + if (!navigator.geolocation) { + reject(new Error('Geolocation is not supported by this browser.')); + return; + } + navigator.geolocation.getCurrentPosition( + function (position) { + permissionDenied = false; + if (bannerEl) { bannerEl.remove(); bannerEl = null; } + resolve({ latitude: position.coords.latitude, longitude: position.coords.longitude, accuracy: position.coords.accuracy || 0, + }); + }, + function (error) { + permissionDenied = true; + showDeniedBanner(); + console.error('Fusion Location: GPS error', error.code, error.message); + reject(error); + }, + { enableHighAccuracy: true, timeout: 15000, maximumAge: 30000 } + ); + }); + } + + window.fusionGetLocation = getLocation; + + // ===================================================================== + // NAVIGATE -- opens Google Maps app on iOS/Android, browser fallback + // ===================================================================== + + function openGoogleMapsNav(el) { + var addr = (el.dataset.navAddr || '').trim(); + var fallbackUrl = el.dataset.navUrl || ''; + if (!addr && !fallbackUrl) return; + + var dest = encodeURIComponent(addr) || fallbackUrl.split('destination=')[1]; + var isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent); + var isAndroid = /Android/i.test(navigator.userAgent); + + if (isIOS) { + window.location.href = 'comgooglemaps://?daddr=' + dest + '&directionsmode=driving'; + } else if (isAndroid) { + window.location.href = 'google.navigation:q=' + dest; + } else { + window.open(fallbackUrl, '_blank'); + } + } + + window.openGoogleMapsNav = openGoogleMapsNav; + + // ===================================================================== + // BACKGROUND LOGGER (tied to clock-in / clock-out status) + // ===================================================================== + + function isTechnicianPortal() { + return window.location.pathname.indexOf('/my/technician') !== -1; + } + + function checkClockStatus() { + fetch('/my/technician/clock-status', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ jsonrpc: '2.0', method: 'call', params: {} }), + }) + .then(function (r) { return r.json(); }) + .then(function (data) { + var wasClocked = isClockedIn; + isClockedIn = !!(data.result && data.result.clocked_in); + if (isClockedIn && !wasClocked) { + // Just clocked in — start tracking immediately + startLocationTimer(); + } else if (!isClockedIn && wasClocked) { + // Just clocked out — stop tracking + stopLocationTimer(); + } + }) + .catch(function () { + /* network error: keep current state */ + }); + } + + function logLocation() { + if (!isClockedIn || document.hidden || !navigator.geolocation) return; + + getLocation().then(function (coords) { + fetch('/my/technician/location/log', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + method: 'call', + params: { + latitude: coords.latitude, + longitude: coords.longitude, + accuracy: coords.accuracy, } - }; - fetch('/my/technician/location/log', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data), - }).catch(function () { - // Silently fail - location logging is best-effort - }); - }, - function () { - // Geolocation permission denied or error - silently ignore - }, - { enableHighAccuracy: true, timeout: 10000, maximumAge: 60000 } - ); + }), + }) + .then(function (r) { return r.json(); }) + .then(function (data) { + if (data.result && !data.result.success) { + console.warn('Fusion Location: server rejected log', data.result); + } + }) + .catch(function (err) { + console.warn('Fusion Location: network error', err); + }); + }).catch(function () { + /* permission denied -- banner already shown */ + }); + } + + function startLocationTimer() { + if (locationTimer) return; // already running + logLocation(); // immediate first log + locationTimer = setInterval(logLocation, INTERVAL_MS); + } + + function stopLocationTimer() { + if (locationTimer) { + clearInterval(locationTimer); + locationTimer = null; + } } function startLocationLogging() { - if (!isTechnicianPortal()) { - return; - } + if (!isTechnicianPortal()) return; - // Log immediately on page load - logLocation(); + // Check clock status immediately, then every 60s + checkClockStatus(); + clockCheckTimer = setInterval(checkClockStatus, CLOCK_CHECK_MS); - // Set interval for periodic logging - locationTimer = setInterval(logLocation, INTERVAL_MS); - - // Pause/resume on tab visibility change + // Pause/resume on tab visibility document.addEventListener('visibilitychange', function () { if (document.hidden) { - // Tab hidden - clear interval to save battery - if (locationTimer) { - clearInterval(locationTimer); - locationTimer = null; - } - } else { - // Tab visible again - log immediately and restart interval - logLocation(); - if (!locationTimer) { - locationTimer = setInterval(logLocation, INTERVAL_MS); - } + stopLocationTimer(); + } else if (isClockedIn) { + startLocationTimer(); } }); } - // Start when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', startLocationLogging); } else { diff --git a/fusion_authorizer_portal/utils/pdf_filler.py b/fusion_authorizer_portal/utils/pdf_filler.py index feaca15..f87283c 100644 --- a/fusion_authorizer_portal/utils/pdf_filler.py +++ b/fusion_authorizer_portal/utils/pdf_filler.py @@ -51,19 +51,25 @@ class PDFTemplateFiller: for page_idx in range(num_pages): page = original.getPage(page_idx) page_num = page_idx + 1 # 1-based page number - page_w = float(page.mediaBox.getWidth()) - page_h = float(page.mediaBox.getHeight()) + mb = page.mediaBox + page_w = float(mb.getWidth()) + page_h = float(mb.getHeight()) + origin_x = float(mb.getLowerLeft_x()) + origin_y = float(mb.getLowerLeft_y()) fields = fields_by_page.get(page_num, []) if fields: - # Create a transparent overlay for this page overlay_buf = BytesIO() - c = canvas.Canvas(overlay_buf, pagesize=(page_w, page_h)) + c = canvas.Canvas( + overlay_buf, + pagesize=(origin_x + page_w, origin_y + page_h), + ) for field in fields: PDFTemplateFiller._draw_field( - c, field, context, signatures, page_w, page_h + c, field, context, signatures, + page_w, page_h, origin_x, origin_y, ) c.save() @@ -80,7 +86,8 @@ class PDFTemplateFiller: return result.getvalue() @staticmethod - def _draw_field(c, field, context, signatures, page_w, page_h): + def _draw_field(c, field, context, signatures, + page_w, page_h, origin_x=0, origin_y=0): """Draw a single field onto the reportlab canvas. Args: @@ -90,6 +97,8 @@ class PDFTemplateFiller: signatures: dict of {field_key: binary} for signature fields page_w: page width in PDF points page_h: page height in PDF points + origin_x: mediaBox lower-left X (accounts for non-zero origin) + origin_y: mediaBox lower-left Y (accounts for non-zero origin) """ field_key = field.get('field_key') or field.get('field_name', '') field_type = field.get('field_type', 'text') @@ -98,11 +107,12 @@ class PDFTemplateFiller: if not value and field_type != 'signature': return - # Convert percentage positions to absolute PDF coordinates - # pos_x/pos_y are 0.0-1.0 ratios from top-left - # PDF coordinate system: origin at bottom-left, Y goes up - abs_x = field['pos_x'] * page_w - abs_y = page_h - (field['pos_y'] * page_h) # flip Y axis + # Convert percentage positions to absolute PDF coordinates. + # pos_x/pos_y are 0.0-1.0 ratios from top-left of the visible page. + # PDF coordinate system: origin at bottom-left, Y goes up. + # origin_x/origin_y account for PDFs whose mediaBox doesn't start at (0,0). + abs_x = field['pos_x'] * page_w + origin_x + abs_y = (origin_y + page_h) - (field['pos_y'] * page_h) font_name = field.get('font_name', 'Helvetica') font_size = field.get('font_size', 10.0) @@ -124,10 +134,22 @@ class PDFTemplateFiller: elif field_type == 'checkbox': if value: - c.setFont('ZapfDingbats', font_size) + # Draw a cross mark (✗) that fills the checkbox box + cb_w = field.get('width', 0.015) * page_w cb_h = field.get('height', 0.018) * page_h - cb_y = abs_y - cb_h + (cb_h - font_size) / 2 - c.drawString(abs_x, cb_y, '4') + # Inset slightly so the cross doesn't touch the box edges + pad = min(cb_w, cb_h) * 0.15 + x1 = abs_x + pad + y1 = abs_y - cb_h + pad + x2 = abs_x + cb_w - pad + y2 = abs_y - pad + c.saveState() + c.setStrokeColorRGB(0, 0, 0) + c.setLineWidth(1.5) + # Draw X (two diagonal lines) + c.line(x1, y1, x2, y2) + c.line(x1, y2, x2, y1) + c.restoreState() elif field_type == 'signature': sig_data = signatures.get(field_key) diff --git a/fusion_authorizer_portal/views/portal_page11_sign_templates.xml b/fusion_authorizer_portal/views/portal_page11_sign_templates.xml new file mode 100644 index 0000000..3e4fa9c --- /dev/null +++ b/fusion_authorizer_portal/views/portal_page11_sign_templates.xml @@ -0,0 +1,413 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/fusion_authorizer_portal/views/portal_schedule.xml b/fusion_authorizer_portal/views/portal_schedule.xml new file mode 100644 index 0000000..eed387c --- /dev/null +++ b/fusion_authorizer_portal/views/portal_schedule.xml @@ -0,0 +1,348 @@ + + + + + + + + + + + + diff --git a/fusion_authorizer_portal/views/portal_technician_templates.xml b/fusion_authorizer_portal/views/portal_technician_templates.xml index d89e700..58ce9be 100644 --- a/fusion_authorizer_portal/views/portal_technician_templates.xml +++ b/fusion_authorizer_portal/views/portal_technician_templates.xml @@ -18,6 +18,41 @@ + + +
+
+
+
+
+
+ Clocked In + Not Clocked In +
+
00:00:00
+
+
+ +
+ +
+ +
@@ -32,10 +67,6 @@
Done
-
-
-
Travel min
-
@@ -55,21 +86,24 @@
- -

+

-

+

min drive @@ -102,8 +136,11 @@

+ + + @@ -1086,7 +1191,42 @@

Welcome back, !

- + + + +
+
+
+
+
+
+ Clocked In + Not Clocked In +
+
00:00:00
+
+
+ +
+ +
+ +
@@ -1377,6 +1517,113 @@ + + + @@ -3699,4 +3946,232 @@ + + + + + diff --git a/fusion_claims/__init__.py b/fusion_claims/__init__.py index 33ce5b6..5795da1 100644 --- a/fusion_claims/__init__.py +++ b/fusion_claims/__init__.py @@ -4,7 +4,6 @@ # Part of the Fusion Claim Assistant product family. from . import models -from . import controllers from . import wizard diff --git a/fusion_claims/__manifest__.py b/fusion_claims/__manifest__.py index d533070..a222f58 100644 --- a/fusion_claims/__manifest__.py +++ b/fusion_claims/__manifest__.py @@ -103,7 +103,6 @@ 'views/res_company_views.xml', 'views/res_config_settings_views.xml', 'views/sale_order_views.xml', - 'views/sale_portal_templates.xml', 'views/account_move_views.xml', 'views/account_journal_views.xml', 'wizard/adp_export_wizard_views.xml', @@ -129,17 +128,12 @@ 'wizard/odsp_submit_to_odsp_wizard_views.xml', 'wizard/odsp_pre_approved_wizard_views.xml', 'wizard/odsp_ready_delivery_wizard_views.xml', - 'wizard/ltc_repair_create_so_wizard_views.xml', 'wizard/send_page11_wizard_views.xml', 'views/res_partner_views.xml', 'views/pdf_template_inherit_views.xml', 'views/dashboard_views.xml', 'views/client_profile_views.xml', 'wizard/xml_import_wizard_views.xml', - 'views/ltc_facility_views.xml', - 'views/ltc_repair_views.xml', - 'views/ltc_cleanup_views.xml', - 'views/ltc_form_submission_views.xml', 'views/adp_claims_views.xml', 'views/submission_history_views.xml', 'views/fusion_loaner_views.xml', @@ -149,7 +143,6 @@ 'report/report_templates.xml', 'report/sale_report_portrait.xml', 'report/sale_report_landscape.xml', - 'report/sale_report_ltc_repair.xml', 'report/invoice_report_portrait.xml', 'report/invoice_report_landscape.xml', 'report/report_proof_of_delivery.xml', @@ -161,9 +154,6 @@ 'report/report_accessibility_contract.xml', 'report/report_mod_quotation.xml', 'report/report_mod_invoice.xml', - 'data/ltc_data.xml', - 'report/report_ltc_nursing_station.xml', - 'data/ltc_report_data.xml', 'data/mail_template_data.xml', 'data/ai_agent_data.xml', ], diff --git a/fusion_claims/controllers/__init__.py b/fusion_claims/controllers/__init__.py deleted file mode 100644 index 811abc3..0000000 --- a/fusion_claims/controllers/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- -from . import portal diff --git a/fusion_claims/controllers/portal.py b/fusion_claims/controllers/portal.py deleted file mode 100644 index 0f3c858..0000000 --- a/fusion_claims/controllers/portal.py +++ /dev/null @@ -1,157 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2024-2025 Nexa Systems Inc. -# License OPL-1 (Odoo Proprietary License v1.0) -# Part of the Fusion Claim Assistant product family. - -import binascii - -from odoo import fields, http, _ -from odoo.exceptions import AccessError, MissingError -from odoo.http import request -from odoo.addons.sale.controllers.portal import CustomerPortal - - -class FusionCustomerPortal(CustomerPortal): - - def _is_adp_order(self, order_sudo): - return ( - hasattr(order_sudo, '_is_adp_sale') - and order_sudo._is_adp_sale() - ) - - def _is_adp_reg(self, order_sudo): - return self._is_adp_order(order_sudo) and order_sudo.x_fc_client_type == 'REG' - - def _get_adp_payable(self, order_sudo): - if self._is_adp_reg(order_sudo): - return order_sudo.x_fc_client_portion_total or 0 - return order_sudo.amount_total - - # ------------------------------------------------------------------ - # View Details: render ADP landscape report for ADP orders - # ------------------------------------------------------------------ - @http.route() - def portal_order_page( - self, order_id, report_type=None, access_token=None, - message=False, download=False, payment_amount=None, - amount_selection=None, **kw - ): - if report_type in ('html', 'pdf', 'text'): - try: - order_sudo = self._document_check_access( - 'sale.order', order_id, access_token=access_token, - ) - except (AccessError, MissingError): - return request.redirect('/my') - - if self._is_adp_order(order_sudo): - return self._show_report( - model=order_sudo, - report_type=report_type, - report_ref='fusion_claims.action_report_saleorder_landscape', - download=download, - ) - - return super().portal_order_page( - order_id, - report_type=report_type, - access_token=access_token, - message=message, - download=download, - payment_amount=payment_amount, - amount_selection=amount_selection, - **kw, - ) - - # ------------------------------------------------------------------ - # Payment amount overrides - # ------------------------------------------------------------------ - def _determine_is_down_payment(self, order_sudo, amount_selection, payment_amount): - if self._is_adp_reg(order_sudo): - payable = self._get_adp_payable(order_sudo) - if amount_selection == 'down_payment': - return True - elif amount_selection == 'full_amount': - return False - return ( - order_sudo.prepayment_percent < 1.0 if payment_amount is None - else payment_amount < payable - ) - return super()._determine_is_down_payment(order_sudo, amount_selection, payment_amount) - - def _get_payment_values(self, order_sudo, is_down_payment=False, payment_amount=None, **kwargs): - values = super()._get_payment_values( - order_sudo, - is_down_payment=is_down_payment, - payment_amount=payment_amount, - **kwargs, - ) - if not self._is_adp_reg(order_sudo): - return values - - client_portion = self._get_adp_payable(order_sudo) - if client_portion <= 0: - return values - - current_amount = values.get('amount', 0) - if current_amount > client_portion: - values['amount'] = order_sudo.currency_id.round(client_portion) - return values - - # ------------------------------------------------------------------ - # Signature: attach ADP report instead of default Odoo quotation - # ------------------------------------------------------------------ - @http.route() - def portal_quote_accept(self, order_id, access_token=None, name=None, signature=None): - access_token = access_token or request.httprequest.args.get('access_token') - try: - order_sudo = self._document_check_access('sale.order', order_id, access_token=access_token) - except (AccessError, MissingError): - return {'error': _('Invalid order.')} - - if not order_sudo._has_to_be_signed(): - return {'error': _('The order is not in a state requiring customer signature.')} - if not signature: - return {'error': _('Signature is missing.')} - - try: - order_sudo.write({ - 'signed_by': name, - 'signed_on': fields.Datetime.now(), - 'signature': signature, - }) - request.env.cr.flush() - except (TypeError, binascii.Error): - return {'error': _('Invalid signature data.')} - - if not order_sudo._has_to_be_paid(): - order_sudo._validate_order() - - if self._is_adp_order(order_sudo): - report_ref = 'fusion_claims.action_report_saleorder_landscape' - else: - report_ref = 'sale.action_report_saleorder' - - pdf = request.env['ir.actions.report'].sudo()._render_qweb_pdf( - report_ref, [order_sudo.id] - )[0] - - order_sudo.message_post( - attachments=[('%s.pdf' % order_sudo.name, pdf)], - author_id=( - order_sudo.partner_id.id - if request.env.user._is_public() - else request.env.user.partner_id.id - ), - body=_('Order signed by %s', name), - message_type='comment', - subtype_xmlid='mail.mt_comment', - ) - - query_string = '&message=sign_ok' - if order_sudo._has_to_be_paid(): - query_string += '&allow_payment=yes' - return { - 'force_refresh': True, - 'redirect_url': order_sudo.get_portal_url(query_string=query_string), - } diff --git a/fusion_claims/data/ai_agent_data.xml b/fusion_claims/data/ai_agent_data.xml index 14fd457..3a09574 100644 --- a/fusion_claims/data/ai_agent_data.xml +++ b/fusion_claims/data/ai_agent_data.xml @@ -26,7 +26,7 @@ ai['result'] = record._fc_tool_search_clients(search_term, city_filter, conditio ai['result'] = record._fc_tool_client_details(profile_id) - Get detailed information about a specific client profile including personal info, medical status, benefits, claims history, ADP application history, funding history with invoice status, and previously funded devices. Requires profile_id from a previous search. + Get detailed information about a specific client profile including personal info, medical status, benefits, claims history, and ADP application history. Requires profile_id from a previous search. {"type": "object", "properties": {"profile_id": {"type": "number", "description": "ID of the client profile to get details for"}}, "required": ["profile_id"]} @@ -39,75 +39,21 @@ ai['result'] = record._fc_tool_client_details(profile_id) ai['result'] = record._fc_tool_claims_stats() - Get aggregated statistics about Fusion Claims data: total profiles, total orders, breakdown by sale type with amounts, breakdown by ADP workflow status, and top cities by client count. No parameters needed. + Get aggregated statistics about Fusion Claims data: total profiles, total orders, breakdown by sale type, breakdown by workflow status, and top cities by client count. No parameters needed. {"type": "object", "properties": {}, "required": []} - - - Fusion: Client Status Lookup - code - - - -ai['result'] = record._fc_tool_client_status(client_name) - - Look up a client's complete status by name. Returns all their orders with current ADP status, invoice details (ADP and client portions, paid/unpaid), document checklist, funding warnings, and recommended next steps for each order. Use this when someone asks "what's the status of [name]" or "how is [name]'s case going". - {"type": "object", "properties": {"client_name": {"type": "string", "description": "The client's name (first name, last name, or full name) to look up"}}, "required": ["client_name"]} - - - - - Fusion: ADP Billing Period - code - - - -ai['result'] = record._fc_tool_adp_billing_period(period) - - Get ADP billing summary for a posting period. Shows total invoiced amount to ADP, paid vs unpaid amounts, number of orders billed, submission deadline, and expected payment date. Use when asked about ADP billing, posting, or invoicing for a period. - {"type": "object", "properties": {"period": {"type": "string", "description": "Which period to query: 'current' (default), 'previous', 'next', or a specific date in YYYY-MM-DD format"}}, "required": []} - - - - - Fusion: Demographics & Analytics - code - - - -ai['result'] = record._fc_tool_demographics(analysis_type, city_filter, sale_type_filter) - - Run demographic and analytical queries on client data. Returns age group breakdowns, device popularity by age, city demographics with average age and funding, benefit type analysis, top devices with average client age, and overall funding summaries. Use for questions like "average applications by age group", "what devices do clients over 75 use", "demographics by city", "how old are our clients on average". - {"type": "object", "properties": {"analysis_type": {"type": "string", "description": "Type of analysis: 'full' (all reports), 'age_groups' (clients/apps by age), 'devices_by_age' (device popularity per age bracket), 'city_demographics' (per-city stats with avg age), 'benefits' (benefit type breakdown), 'top_devices' (most popular devices with avg client age), 'funding_summary' (overall totals and averages)"}, "city_filter": {"type": "string", "description": "Optional: filter city demographics to a specific city"}, "sale_type_filter": {"type": "string", "description": "Optional: filter by sale type (adp, odsp, wsib, etc.)"}}, "required": []} - - - + Fusion Claims Client Intelligence - Query client profiles, ADP claims, funding history, billing periods, demographics, and device information. - You help users find information about ADP clients, claims, medical conditions, devices, funding history, billing periods, and demographics. Use the Fusion tools to query data. - -Common questions and which tool to use: -- "What is the status of [name]?" -> Use Client Status Lookup (Tool 4) -- "What is the ADP billing for this period?" -> Use ADP Billing Period (Tool 5) -- "Tell me about [name]'s funding history" -> Use Client Status Lookup first, then Client Details for more depth -- "How many claims do we have?" -> Use Claims Statistics (Tool 3) -- "Find clients in [city]" -> Use Search Client Profiles (Tool 1) -- "Average applications by age group" -> Use Demographics (Tool 6) with analysis_type="age_groups" -- "What devices do seniors use?" -> Use Demographics (Tool 6) with analysis_type="devices_by_age" -- "What is the average age of our clients?" -> Use Demographics (Tool 6) with analysis_type="funding_summary" -- "Show demographics for Brampton" -> Use Demographics (Tool 6) with analysis_type="city_demographics" and city_filter="Brampton" -- "How many ODSP clients do we have?" -> Use Demographics (Tool 6) with analysis_type="benefits" + Query client profiles, ADP claims, funding history, medical conditions, and device information. + You help users find information about ADP clients, claims, medical conditions, devices, and funding history. Use the Fusion search/details/stats tools to query data. @@ -116,108 +62,32 @@ Common questions and which tool to use: Fusion Claims Intelligence - Ask about clients, ADP claims, funding history, billing periods, and devices. + Ask about clients, ADP claims, funding history, medical conditions, and devices. gpt-4.1 analytical - You are Fusion Claims Intelligence, an AI assistant for ADP claims management at a mobility equipment company. + You are Fusion Claims Intelligence, an AI assistant for ADP claims management. -You help staff find information about clients, their order status, medical conditions, mobility devices, funding history, billing periods, and claim status. - -CRITICAL - Response Formatting Rules: -You are displayed inside a narrow chat panel. Follow these rules strictly: - -1. TABLES: Maximum 3 columns. Never create tables with 4+ columns -- the panel is too narrow. - - For data with many fields, use TWO-COLUMN key-value tables (Label | Value) - - Split wide data across multiple small tables with headings between them - -2. Use ### headings to separate sections -3. Use **bold** for labels and important values -4. Use `code` for order numbers and IDs (e.g., `S30168`) -5. Never output plain unformatted text walls - -CORRECT FORMAT - Client status (multiple small tables, not one wide table): - -### Gurpreet Singh -**City:** Brampton | **Health Card:** 1234-567-890 - -#### Order `S30168` -- ADP -| Detail | Value | -|--------|-------| -| **Status** | Assessment Completed | -| **Total** | $5,624.00 | -| **ADP Portion** | $4,218.00 | -| **Client Portion** | $1,406.00 | -| **Next Step** | Send ADP application | - -CORRECT FORMAT - Demographics (3-column max): - -### Applications by Age Group -| Age Group | Clients | Avg Apps | -|-----------|---------|----------| -| Under 18 | 2 | 2.00 | -| 18-30 | 8 | 1.00 | -| 75+ | 596 | 1.08 | - -For extra columns, add a second table: - -### Funding by Age Group -| Age Group | Avg ADP | Avg Total | -|-----------|---------|-----------| -| Under 18 | $82.00 | $82.00 | -| 75+ | $473.15 | $1,216.15 | - -CORRECT FORMAT - Billing period: - -### ADP Billing: Feb 20 - Mar 5, 2026 -| Metric | Value | -|--------|-------| -| **Total Invoiced** | $29,447.35 | -| **Paid** | $25,000.00 | -| **Unpaid** | $4,447.35 | -| **Invoices** | 20 | -| **Deadline** | Wed, Mar 4 at 6 PM | - -WRONG (too many columns, will look cramped): -| Age | Clients | Apps | Avg Apps | Avg ADP | Avg Total | +You help staff find information about clients, medical conditions, mobility devices, funding history, and claim status. Capabilities: 1. Search client profiles by name, health card number, city, or medical condition -2. Get detailed client information including funding history, invoice status, and previously funded devices +2. Get detailed client information including claims history and ADP applications 3. Provide aggregated statistics about claims, funding types, and demographics -4. Look up a client's complete status by name -- including all orders, invoices, documents, and next steps -5. Query ADP billing period summaries -- total invoiced to ADP, paid vs unpaid, submission deadlines -6. Run demographic analytics -- age group breakdowns, device popularity by age, city demographics, benefit analysis, funding summaries - -How to handle common requests: -- "What is the status of [name]?" -> Use the Client Status Lookup tool with the client's name -- "What is the ADP billing this period?" -> Use the ADP Billing Period tool with period="current" -- "What was the ADP billing last period?" -> Use the ADP Billing Period tool with period="previous" -- "Show me [name]'s funding history" -> Use Client Status Lookup, then Client Details for full history -- "Average applications by age group" -> Use Demographics tool with analysis_type="age_groups" -- "What devices do clients over 75 use?" -> Use Demographics tool with analysis_type="devices_by_age" -- "What is the average age of our clients?" -> Use Demographics tool with analysis_type="funding_summary" -- "Demographics for [city]" -> Use Demographics tool with city_filter -- If a client is not found by profile, the system also searches by contact/partner name Response guidelines: -- ALWAYS keep tables to 3 columns maximum. Use key-value (2-column) tables for summaries. -- Split wide data into multiple narrow tables with headings between them - Be concise and data-driven -- Format monetary values with $ and commas (e.g., $1,250.00) -- When listing orders, show each order as its own key-value table section -- When showing billing summaries, use a key-value table +- Format monetary values with $ and commas - Include key identifiers (name, health card, city) when listing clients -- If asked about a specific client, use Client Status Lookup first (it searches by name) -- Always indicate if invoices are paid or unpaid +- Include order number, status, and amounts when discussing claims +- If asked about a specific client, search first, then get details +- Always provide the profile ID for record lookup Key terminology: -- ADP = Assistive Devices Program (Ontario government funding) -- Client Type REG = Regular (75% ADP / 25% Client split), ODS/OWP/ACS = 100% ADP funded -- Posting Period = 14-day ADP billing cycle; submission deadline is Wednesday 6 PM before posting day +- ADP = Assistive Devices Program (Ontario government) +- Client Type REG = Regular (75% ADP / 25% Client), ODS/OWP/ACS = 100% ADP - Sale Types: ADP, ODSP, WSIB, Insurance, March of Dimes, Muscular Dystrophy, Hardship Funding -- Sections: 2a = Walkers, 2b = Manual Wheelchairs, 2c = Power Bases/Scooters, 2d = Seating -- Previously Funded = devices the client has received ADP funding for before (affects eligibility) +- Sections: 2a = Walkers, 2b = Manual Wheelchairs, 2c = Power Bases/Scooters, 2d = Seating diff --git a/fusion_claims/data/device_codes/adp_mobility_manual.json b/fusion_claims/data/device_codes/adp_mobility_manual.json index be7ff25..042433d 100644 --- a/fusion_claims/data/device_codes/adp_mobility_manual.json +++ b/fusion_claims/data/device_codes/adp_mobility_manual.json @@ -1914,7 +1914,8 @@ "Device Code": "SEAND0025", "Quantity": 2, "ADP Price": 138.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Arm Support Hardware", @@ -1923,7 +1924,8 @@ "Device Code": "SEICF301L", "Quantity": 2, "ADP Price": 60.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Arm Support Options", @@ -1932,7 +1934,8 @@ "Device Code": "SEAND0030", "Quantity": 2, "ADP Price": 65.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Arm Support Options", @@ -1941,7 +1944,8 @@ "Device Code": "SEMCF601L", "Quantity": 2, "ADP Price": 52.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Arm Support Options", @@ -1950,7 +1954,8 @@ "Device Code": "SEMCF602L", "Quantity": 2, "ADP Price": 35.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Arm Support(s)", @@ -1959,7 +1964,8 @@ "Device Code": "SEAND0005", "Quantity": 2, "ADP Price": 237.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Arm Support(s)", @@ -1968,7 +1974,8 @@ "Device Code": "SEAND0010", "Quantity": 2, "ADP Price": 91.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Arm Support(s)", @@ -1977,7 +1984,8 @@ "Device Code": "SEAND0015", "Quantity": 2, "ADP Price": 65.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Arm Support(s)", @@ -1986,7 +1994,8 @@ "Device Code": "SEACF101L", "Quantity": 2, "ADP Price": 129.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Arm Support(s)", @@ -1995,7 +2004,8 @@ "Device Code": "SEACF102L", "Quantity": 2, "ADP Price": 216.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Arm Support(s)", @@ -2004,7 +2014,8 @@ "Device Code": "SEACF103L", "Quantity": 2, "ADP Price": 65.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Arm Support(s)", @@ -2013,7 +2024,8 @@ "Device Code": "SEACF104L", "Quantity": 2, "ADP Price": 259.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Auto Correction System*", @@ -2031,7 +2043,8 @@ "Device Code": "SEMCF101L", "Quantity": 2, "ADP Price": 104.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Back Cover", @@ -2040,7 +2053,8 @@ "Device Code": "SEMCF102L", "Quantity": 2, "ADP Price": 155.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Back Cover", @@ -2049,7 +2063,8 @@ "Device Code": "SEMCF103L", "Quantity": 2, "ADP Price": 198.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Back Cover", @@ -2058,7 +2073,8 @@ "Device Code": "SEMCF104L", "Quantity": 2, "ADP Price": 233.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Back Hardware", @@ -2067,7 +2083,8 @@ "Device Code": "SEBND0010", "Quantity": 2, "ADP Price": 345.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Back Hardware", @@ -2076,7 +2093,8 @@ "Device Code": "SEICF160L", "Quantity": 1, "ADP Price": 168.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Back Support", @@ -2373,7 +2391,8 @@ "Device Code": "SEBCF110L", "Quantity": 1, "ADP Price": 259.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Back Support", @@ -2382,7 +2401,8 @@ "Device Code": "SEBCF112L", "Quantity": 1, "ADP Price": 431.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Back Support", @@ -2391,7 +2411,8 @@ "Device Code": "SEBCF113L", "Quantity": 1, "ADP Price": 673.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Back Support", @@ -2400,7 +2421,8 @@ "Device Code": "SEBCF114L", "Quantity": 1, "ADP Price": 561.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Back Support", @@ -2409,7 +2431,8 @@ "Device Code": "SEBCF115L", "Quantity": 1, "ADP Price": 819.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Back Support", @@ -2418,7 +2441,8 @@ "Device Code": "SEBCF116L", "Quantity": 1, "ADP Price": 810.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Back Support", @@ -2427,7 +2451,8 @@ "Device Code": "SEBCF117L", "Quantity": 1, "ADP Price": 1122.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Back Support", @@ -2436,7 +2461,8 @@ "Device Code": "SEBCF118L", "Quantity": 1, "ADP Price": 1259.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Back Support", @@ -5460,7 +5486,8 @@ "Device Code": "SEBND0015", "Quantity": 6, "ADP Price": 47.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Back Support Options", @@ -5469,7 +5496,8 @@ "Device Code": "SEBND0020", "Quantity": 6, "ADP Price": 116.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Back Support Options", @@ -5478,7 +5506,8 @@ "Device Code": "SEMCF200L", "Quantity": 1, "ADP Price": 194.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Back Support Options", @@ -5487,7 +5516,8 @@ "Device Code": "SEMCF201L", "Quantity": 1, "ADP Price": 129.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Back Support Options", @@ -5496,7 +5526,8 @@ "Device Code": "SEMCF202L", "Quantity": 1, "ADP Price": 86.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Back Support Options", @@ -5505,7 +5536,8 @@ "Device Code": "SEUCF003L", "Quantity": 1, "ADP Price": 138.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Caster Pin Locks (pair)", @@ -5586,7 +5618,8 @@ "Device Code": "SEMCF900L", "Quantity": 1, "ADP Price": 431.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Complete Assembly", @@ -5775,7 +5808,8 @@ "Device Code": "SEFND0030", "Quantity": 2, "ADP Price": 86.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Foot/Leg Support Hardware", @@ -5784,7 +5818,8 @@ "Device Code": "SEICF501L", "Quantity": 2, "ADP Price": 104.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Foot/Leg Support Options", @@ -5793,7 +5828,8 @@ "Device Code": "SEFND0035", "Quantity": 2, "ADP Price": 43.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Foot/Leg Support Options", @@ -5802,7 +5838,8 @@ "Device Code": "SE0000322", "Quantity": 2, "ADP Price": 65.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Foot/Leg Support Options", @@ -5811,7 +5848,8 @@ "Device Code": "SEMCF801L", "Quantity": 1, "ADP Price": 69.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Foot/Leg Support Options", @@ -5820,7 +5858,8 @@ "Device Code": "SEMCF802L", "Quantity": 2, "ADP Price": 121.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Foot/Leg Support Options", @@ -5829,7 +5868,8 @@ "Device Code": "SEMCF803L", "Quantity": 2, "ADP Price": 52.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Foot/Leg Support(s)", @@ -5838,7 +5878,8 @@ "Device Code": "SEFND0005", "Quantity": 1, "ADP Price": 246.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Foot/Leg Support(s)", @@ -5847,7 +5888,8 @@ "Device Code": "SEFND0010", "Quantity": 2, "ADP Price": 43.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Foot/Leg Support(s)", @@ -5856,7 +5898,8 @@ "Device Code": "SEFND0015", "Quantity": 1, "ADP Price": 95.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Foot/Leg Support(s)", @@ -5865,7 +5908,8 @@ "Device Code": "SEFND0020", "Quantity": 2, "ADP Price": 207.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Foot/Leg Support(s)", @@ -5874,7 +5918,8 @@ "Device Code": "SEFCF101L", "Quantity": 1, "ADP Price": 129.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Foot/Leg Support(s)", @@ -5883,7 +5928,8 @@ "Device Code": "SEFCF102L", "Quantity": 1, "ADP Price": 380.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Foot/Leg Support(s)", @@ -5892,7 +5938,8 @@ "Device Code": "SEFCF103L", "Quantity": 1, "ADP Price": 256.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Foot/Leg Support(s)", @@ -5901,7 +5948,8 @@ "Device Code": "SEFCF106L", "Quantity": 2, "ADP Price": 173.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Foot/Leg Support(s)", @@ -5910,7 +5958,8 @@ "Device Code": "SEFCF107L", "Quantity": 2, "ADP Price": 168.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Forearm Crutches", @@ -6243,7 +6292,8 @@ "Device Code": "SEHCF040L", "Quantity": 1, "ADP Price": 108.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest", @@ -6252,7 +6302,8 @@ "Device Code": "SEHCF050L", "Quantity": 1, "ADP Price": 173.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest", @@ -6261,7 +6312,8 @@ "Device Code": "SEHCF060L", "Quantity": 1, "ADP Price": 267.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest", @@ -6270,7 +6322,8 @@ "Device Code": "SEHCF070L", "Quantity": 1, "ADP Price": 164.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest", @@ -6279,7 +6332,8 @@ "Device Code": "SEHCF080L", "Quantity": 1, "ADP Price": 362.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest", @@ -6288,7 +6342,8 @@ "Device Code": "SEHCF090L", "Quantity": 1, "ADP Price": 267.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest", @@ -7674,7 +7729,8 @@ "Device Code": "SEHND0005", "Quantity": 1, "ADP Price": 121.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Headrest/Neckrest Hardware", @@ -7683,7 +7739,8 @@ "Device Code": "SEHND0010", "Quantity": 1, "ADP Price": 138.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Headrest/Neckrest Hardware", @@ -7692,7 +7749,8 @@ "Device Code": "SEHND0015", "Quantity": 1, "ADP Price": 259.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Headrest/Neckrest Hardware", @@ -7701,7 +7759,8 @@ "Device Code": "SEHND0020", "Quantity": 1, "ADP Price": 86.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Headrest/Neckrest Hardware", @@ -7710,7 +7769,8 @@ "Device Code": "SEICF006L", "Quantity": 1, "ADP Price": 60.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest Hardware", @@ -7719,7 +7779,8 @@ "Device Code": "SEICF007L", "Quantity": 1, "ADP Price": 134.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest Hardware", @@ -7728,7 +7789,8 @@ "Device Code": "SEICF008L", "Quantity": 1, "ADP Price": 225.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest Hardware", @@ -7737,7 +7799,8 @@ "Device Code": "SEICF009L", "Quantity": 1, "ADP Price": 190.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest Hardware", @@ -8187,7 +8250,8 @@ "Device Code": "SE0002008", "Quantity": 1, "ADP Price": 145.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest Options", @@ -8196,7 +8260,8 @@ "Device Code": "SE0002009", "Quantity": 1, "ADP Price": 59.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest Options", @@ -8205,7 +8270,8 @@ "Device Code": "SE0002010", "Quantity": 1, "ADP Price": 112.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest Options", @@ -8214,7 +8280,8 @@ "Device Code": "SE0002011", "Quantity": 1, "ADP Price": 59.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest Options", @@ -8223,7 +8290,8 @@ "Device Code": "SE0002012", "Quantity": 1, "ADP Price": 83.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest Options", @@ -8232,7 +8300,8 @@ "Device Code": "SE0002013", "Quantity": 1, "ADP Price": 424.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest Options", @@ -8241,7 +8310,8 @@ "Device Code": "SE0002014", "Quantity": 1, "ADP Price": 629.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest Options", @@ -8250,7 +8320,8 @@ "Device Code": "SE0002015", "Quantity": 1, "ADP Price": 109.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest Options", @@ -8259,7 +8330,8 @@ "Device Code": "SE0002016", "Quantity": 1, "ADP Price": 94.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest Options", @@ -8268,7 +8340,8 @@ "Device Code": "SE0002037", "Quantity": 1, "ADP Price": 94.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest Options", @@ -8277,7 +8350,8 @@ "Device Code": "SEMCF001L", "Quantity": 2, "ADP Price": 104.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest Options", @@ -8286,7 +8360,8 @@ "Device Code": "SEMCF002L", "Quantity": 2, "ADP Price": 155.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest Options", @@ -8295,7 +8370,8 @@ "Device Code": "SEMCF005L", "Quantity": 2, "ADP Price": 173.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest Options", @@ -8304,7 +8380,8 @@ "Device Code": "SEMCF006L", "Quantity": 2, "ADP Price": 259.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Headrest/Neckrest Options", @@ -8466,7 +8543,8 @@ "Device Code": "SEICF201L", "Quantity": 2, "ADP Price": 52.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Lateral Support Hardware", @@ -8475,7 +8553,8 @@ "Device Code": "SEICF202L", "Quantity": 2, "ADP Price": 173.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Lateral Support Hardware", @@ -8484,7 +8563,8 @@ "Device Code": "SEICF203L", "Quantity": 2, "ADP Price": 86.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Lateral Support Options", @@ -8493,7 +8573,8 @@ "Device Code": "SEMCF651L", "Quantity": 2, "ADP Price": 52.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Lateral Support(s)", @@ -8502,7 +8583,8 @@ "Device Code": "SELND0005", "Quantity": 4, "ADP Price": 104.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Lateral Support(s)", @@ -8511,7 +8593,8 @@ "Device Code": "SELND0010", "Quantity": 4, "ADP Price": 155.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Lateral Support(s)", @@ -8520,7 +8603,8 @@ "Device Code": "SELND0015", "Quantity": 4, "ADP Price": 166.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Lateral Support(s)", @@ -8529,7 +8613,8 @@ "Device Code": "SELND0020", "Quantity": 2, "ADP Price": 47.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Lateral Support(s)", @@ -8538,7 +8623,8 @@ "Device Code": "SELCF101L", "Quantity": 2, "ADP Price": 69.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Lateral Support(s)", @@ -8547,7 +8633,8 @@ "Device Code": "SELCF102L", "Quantity": 2, "ADP Price": 151.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Lateral Support(s)", @@ -8556,7 +8643,8 @@ "Device Code": "SELCF103L", "Quantity": 2, "ADP Price": 82.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Manual Elevating Legrests (pair)", @@ -10185,7 +10273,8 @@ "Device Code": "SESND1043", "Quantity": 1, "ADP Price": 259.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Pommel/Adductors", @@ -10194,7 +10283,8 @@ "Device Code": "SEMCF701L", "Quantity": 1, "ADP Price": 138.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Pommel/Adductors", @@ -10203,7 +10293,8 @@ "Device Code": "SEMCF702L", "Quantity": 2, "ADP Price": 104.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Pommel/Adductors", @@ -10212,7 +10303,8 @@ "Device Code": "SEMCF703L", "Quantity": 2, "ADP Price": 164.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Pommel Hardware", @@ -10221,7 +10313,8 @@ "Device Code": "SEICF401L", "Quantity": 1, "ADP Price": 73.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Pommel Hardware", @@ -10230,7 +10323,8 @@ "Device Code": "SEICF402L", "Quantity": 1, "ADP Price": 151.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Pommel Hardware", @@ -10239,7 +10333,8 @@ "Device Code": "SEICF403L", "Quantity": 1, "ADP Price": 259.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Positioning Belts", @@ -10248,7 +10343,8 @@ "Device Code": "SERND0001", "Quantity": 1, "ADP Price": 121.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Positioning Belts", @@ -10257,7 +10353,8 @@ "Device Code": "SERND0010", "Quantity": 2, "ADP Price": 52.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Positioning Belts", @@ -10266,7 +10363,8 @@ "Device Code": "SERND0020", "Quantity": 2, "ADP Price": 129.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Positioning Belts", @@ -10275,7 +10373,8 @@ "Device Code": "SERND0030", "Quantity": 6, "ADP Price": 56.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Positioning Belts", @@ -10284,7 +10383,8 @@ "Device Code": "SERND0035", "Quantity": 1, "ADP Price": 397.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Positioning Belts", @@ -10293,7 +10393,8 @@ "Device Code": "SERCF101L", "Quantity": 1, "ADP Price": 52.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Positioning Belts", @@ -10302,7 +10403,8 @@ "Device Code": "SERCF103L", "Quantity": 1, "ADP Price": 129.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Positioning Belts", @@ -10311,7 +10413,8 @@ "Device Code": "SERCF104L", "Quantity": 1, "ADP Price": 181.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Positioning Belts", @@ -10320,7 +10423,8 @@ "Device Code": "SERCF105L", "Quantity": 12, "ADP Price": 47.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Positioning Belts Options", @@ -10329,7 +10433,8 @@ "Device Code": "SEMCF501L", "Quantity": 12, "ADP Price": 47.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Positioning Belts Options", @@ -10338,7 +10443,8 @@ "Device Code": "SEMCF502L", "Quantity": 12, "ADP Price": 47.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Positioning Belts Options", @@ -10347,7 +10453,8 @@ "Device Code": "SEMCF503L", "Quantity": 4, "ADP Price": 142.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Power Add-On Device", @@ -12066,7 +12173,8 @@ "Device Code": "SESCF101L", "Quantity": 1, "ADP Price": 259.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Cushion", @@ -12075,7 +12183,8 @@ "Device Code": "SESCF102L", "Quantity": 1, "ADP Price": 388.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Cushion", @@ -12084,7 +12193,8 @@ "Device Code": "SESCF103L", "Quantity": 1, "ADP Price": 518.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Cushion", @@ -12093,7 +12203,8 @@ "Device Code": "SESCF104L", "Quantity": 1, "ADP Price": 621.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Cushion", @@ -12102,7 +12213,8 @@ "Device Code": "SESCF105L", "Quantity": 1, "ADP Price": 1259.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Cushion", @@ -16350,7 +16462,8 @@ "Device Code": "SESND1050", "Quantity": 2, "ADP Price": 65.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Seat Cushion Cover(s)", @@ -16359,7 +16472,8 @@ "Device Code": "SESND1055", "Quantity": 2, "ADP Price": 99.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Seat Cushion Cover(s)", @@ -16368,7 +16482,8 @@ "Device Code": "SESND1060", "Quantity": 2, "ADP Price": 134.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Seat Cushion Cover(s)", @@ -16377,7 +16492,8 @@ "Device Code": "SEMCF105L", "Quantity": 2, "ADP Price": 108.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Cushion Cover(s)", @@ -16386,7 +16502,8 @@ "Device Code": "SEMCF106L", "Quantity": 2, "ADP Price": 155.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Cushion Cover(s)", @@ -16395,7 +16512,8 @@ "Device Code": "SEMCF107L", "Quantity": 2, "ADP Price": 216.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Hardware", @@ -16404,7 +16522,8 @@ "Device Code": "SESND1010", "Quantity": 1, "ADP Price": 259.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Seat Hardware", @@ -16413,7 +16532,8 @@ "Device Code": "SEICF140L", "Quantity": 2, "ADP Price": 86.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Hardware", @@ -16422,7 +16542,8 @@ "Device Code": "SEICF150L", "Quantity": 2, "ADP Price": 168.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Hardware", @@ -16431,7 +16552,8 @@ "Device Code": "SEICF170L", "Quantity": 1, "ADP Price": 302.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Hardware", @@ -16440,7 +16562,8 @@ "Device Code": "SEICF180L", "Quantity": 1, "ADP Price": 492.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Hardware", @@ -16449,7 +16572,8 @@ "Device Code": "SEICF190L", "Quantity": 1, "ADP Price": 86.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Hardware", @@ -16458,7 +16582,8 @@ "Device Code": "SEICF191L", "Quantity": 8, "ADP Price": 22.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Hardware", @@ -16467,7 +16592,8 @@ "Device Code": "SEICF192L", "Quantity": 1, "ADP Price": 52.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Options", @@ -16476,7 +16602,8 @@ "Device Code": "SESND1015", "Quantity": 1, "ADP Price": 69.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Seat Options", @@ -16485,7 +16612,8 @@ "Device Code": "SESND1020", "Quantity": 1, "ADP Price": 216.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Seat Options", @@ -16494,7 +16622,8 @@ "Device Code": "SESND1035", "Quantity": 1, "ADP Price": 155.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Seat Options", @@ -16503,7 +16632,8 @@ "Device Code": "SESND1040", "Quantity": 6, "ADP Price": 69.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Seat Options", @@ -16512,7 +16642,8 @@ "Device Code": "SESND1041", "Quantity": 1, "ADP Price": 173.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Seat Options", @@ -16521,7 +16652,8 @@ "Device Code": "SESND1045", "Quantity": 1, "ADP Price": 358.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Seat Options", @@ -16530,7 +16662,8 @@ "Device Code": "SEMCF203L", "Quantity": 2, "ADP Price": 129.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Options", @@ -16539,7 +16672,8 @@ "Device Code": "SEMCF204L", "Quantity": 2, "ADP Price": 185.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Options", @@ -16548,7 +16682,8 @@ "Device Code": "SEMCF205L", "Quantity": 1, "ADP Price": 259.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Options", @@ -16557,7 +16692,8 @@ "Device Code": "SEMCF206L", "Quantity": 2, "ADP Price": 194.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Options", @@ -16566,7 +16702,8 @@ "Device Code": "SEMCF207L", "Quantity": 1, "ADP Price": 65.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Options", @@ -16575,7 +16712,8 @@ "Device Code": "SEMCF208L", "Quantity": 1, "ADP Price": 173.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Options", @@ -16584,7 +16722,8 @@ "Device Code": "SEMCF209L", "Quantity": 1, "ADP Price": 328.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Options", @@ -16593,7 +16732,8 @@ "Device Code": "SEMCF210L", "Quantity": 2, "ADP Price": 86.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Options", @@ -16602,7 +16742,8 @@ "Device Code": "SEMCF212L", "Quantity": 1, "ADP Price": 194.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Options", @@ -16611,7 +16752,8 @@ "Device Code": "SEMCF401L", "Quantity": 2, "ADP Price": 52.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Options", @@ -16620,7 +16762,8 @@ "Device Code": "SEMCF402L", "Quantity": 2, "ADP Price": 129.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Options", @@ -16629,7 +16772,8 @@ "Device Code": "SEMCF403L", "Quantity": 2, "ADP Price": 35.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Options", @@ -16638,7 +16782,8 @@ "Device Code": "SEMCF404L", "Quantity": 2, "ADP Price": 39.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Options", @@ -16647,7 +16792,8 @@ "Device Code": "SEMCF405L", "Quantity": 2, "ADP Price": 43.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Options", @@ -16656,7 +16802,8 @@ "Device Code": "SEMCF406L", "Quantity": 2, "ADP Price": 26.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Options", @@ -16665,7 +16812,8 @@ "Device Code": "SEMCF407L", "Quantity": 2, "ADP Price": 30.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Options", @@ -16674,7 +16822,8 @@ "Device Code": "SEUCF005L", "Quantity": 1, "ADP Price": 138.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Seat Package 1 for Power Bases", @@ -16683,7 +16832,8 @@ "Device Code": "WEPN", "Quantity": 1, "ADP Price": 663.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Seat Package 2 for Power Bases", @@ -16692,7 +16842,8 @@ "Device Code": "WEPO", "Quantity": 1, "ADP Price": 682.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "SE - Custom Modifications", @@ -16701,7 +16852,8 @@ "Device Code": "SEMCF990L", "Quantity": 1, "ADP Price": 40.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "SE - Custom Modifications", @@ -16710,7 +16862,8 @@ "Device Code": "SEMND1005", "Quantity": 1, "ADP Price": 69.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "SE - Custom Modifications", @@ -16719,7 +16872,8 @@ "Device Code": "SEMND2005", "Quantity": 1, "ADP Price": 40.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "SE - Custom Modifications", @@ -16728,7 +16882,8 @@ "Device Code": "SEUCF200L", "Quantity": 1, "ADP Price": 69.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Specialty Controls 1 Non Standard Joystick*", @@ -16836,7 +16991,8 @@ "Device Code": "SETND0005", "Quantity": 1, "ADP Price": 164.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Tray", @@ -16845,7 +17001,8 @@ "Device Code": "SETND0010", "Quantity": 1, "ADP Price": 280.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Tray", @@ -16854,7 +17011,8 @@ "Device Code": "SETND0015", "Quantity": 2, "ADP Price": 203.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Tray", @@ -16863,7 +17021,8 @@ "Device Code": "SETND0020", "Quantity": 2, "ADP Price": 272.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Tray", @@ -16872,7 +17031,8 @@ "Device Code": "SETCF101L", "Quantity": 1, "ADP Price": 302.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Tray", @@ -16881,7 +17041,8 @@ "Device Code": "SETCF102L", "Quantity": 1, "ADP Price": 220.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Tray", @@ -16890,7 +17051,8 @@ "Device Code": "SETCF103L", "Quantity": 1, "ADP Price": 362.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Tray", @@ -16899,7 +17061,8 @@ "Device Code": "SETCF104L", "Quantity": 1, "ADP Price": 311.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Tray Options", @@ -16908,7 +17071,8 @@ "Device Code": "SETND0030", "Quantity": 4, "ADP Price": 56.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Tray Options", @@ -16917,7 +17081,8 @@ "Device Code": "SETND0035", "Quantity": 2, "ADP Price": 65.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Modular" }, { "Device Type": "Tray Options", @@ -16926,7 +17091,8 @@ "Device Code": "SEMCF301L", "Quantity": 1, "ADP Price": 69.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Tray Options", @@ -16935,7 +17101,8 @@ "Device Code": "SEMCF303L", "Quantity": 2, "ADP Price": 69.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Tray Options", @@ -16944,7 +17111,8 @@ "Device Code": "SEMCF304L", "Quantity": 2, "ADP Price": 125.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Tray Options", @@ -16953,7 +17121,8 @@ "Device Code": "SEMCF305L", "Quantity": 2, "ADP Price": 73.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Tray Options", @@ -16962,7 +17131,8 @@ "Device Code": "SEMCF306L", "Quantity": 2, "ADP Price": 52.0, - "SN Required": "No" + "SN Required": "No", + "Build Type": "Custom Fabricated" }, { "Device Type": "Unilateral Hand Brake", diff --git a/fusion_claims/data/ir_actions_server_data.xml b/fusion_claims/data/ir_actions_server_data.xml index 6139945..1fd8dc1 100644 --- a/fusion_claims/data/ir_actions_server_data.xml +++ b/fusion_claims/data/ir_actions_server_data.xml @@ -1,30 +1,4 @@ - - - - Sync to Invoices - - - form,list - code - -if records: - # Filter to only ADP sales - adp_records = records.filtered(lambda r: r.x_fc_is_adp_sale and r.state == 'sale') - if adp_records: - action = adp_records.action_sync_adp_fields() - else: - action = { - 'type': 'ir.actions.client', - 'tag': 'display_notification', - 'params': { - 'title': 'No ADP Sales', - 'message': 'Selected orders are not confirmed ADP sales.', - 'type': 'warning', - 'sticky': False, - } - } - - + diff --git a/fusion_claims/data/ir_config_parameter_data.xml b/fusion_claims/data/ir_config_parameter_data.xml index 100f86d..d46c59b 100644 --- a/fusion_claims/data/ir_config_parameter_data.xml +++ b/fusion_claims/data/ir_config_parameter_data.xml @@ -45,26 +45,6 @@ True - - - fusion_claims.store_open_hour - 9.0 - - - fusion_claims.store_close_hour - 18.0 - - - - - fusion_claims.push_enabled - False - - - fusion_claims.push_advance_minutes - 30 - - fusion_claims.field_sale_type @@ -147,17 +127,5 @@ 1-888-222-5099 - - - fusion_claims.sync_instance_id - - - - - - fusion_claims.ltc_form_password - - - diff --git a/fusion_claims/data/ir_cron_data.xml b/fusion_claims/data/ir_cron_data.xml index 8beb2ef..b0c835f 100644 --- a/fusion_claims/data/ir_cron_data.xml +++ b/fusion_claims/data/ir_cron_data.xml @@ -6,16 +6,6 @@ --> - - - Fusion Claims: Sync ADP Fields - - code - model._cron_sync_adp_fields() - 1 - hours - - Fusion Claims: Renew Delivery Reminders @@ -134,50 +124,17 @@ - - - Fusion Claims: Calculate Technician Travel Times - + + + Fusion Claims: Expire Page 11 Signing Requests + code - model._cron_calculate_travel_times() + model._cron_expire_requests() 1 days True - + - - - Fusion Claims: Technician Push Notifications - - code - model._cron_send_push_notifications() - 15 - minutes - True - - - - - Fusion Claims: Sync Remote Tasks (Pull) - - code - model._cron_pull_remote_tasks() - 5 - minutes - True - - - - - Fusion Claims: Cleanup Old Shadow Tasks - - code - model._cron_cleanup_old_shadows() - 1 - days - True - - diff --git a/fusion_claims/data/mail_template_data.xml b/fusion_claims/data/mail_template_data.xml index 572b87d..e17efdf 100644 --- a/fusion_claims/data/mail_template_data.xml +++ b/fusion_claims/data/mail_template_data.xml @@ -20,34 +20,34 @@ {{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }} {{ object.partner_id.id }} -
+
-
+

-

ADP Quotation

-

- Please find attached your quotation . +

ADP Quotation

+

+ Please find attached your quotation .

- - - + + + - + - - + + - +
Quotation Details
Reference
Date
Quotation Details
Reference
Date
Authorizer
Authorizer
Client Portion (25%)
ADP Portion (75%)
Client Portion (25%)
ADP Portion (75%)
Total
Total
-
-

Attached: ADP Quotation (PDF)

+
+

Attached: ADP Quotation (PDF)

-
-

Please review the attached quotation. If you have any questions or need assistance, do not hesitate to contact us.

+
+

Please review the attached quotation. If you have any questions or need assistance, do not hesitate to contact us.

--
@@ -70,34 +70,34 @@ {{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }} {{ object.partner_id.id }} -
+
-
+

-

Order Confirmed

-

- Your ADP sales order has been confirmed. +

Order Confirmed

+

+ Your ADP sales order has been confirmed.

- - - + + + - + - - + + - +
Order Details
Reference
Date
Order Details
Reference
Date
Authorizer
Authorizer
Client Portion (25%)
ADP Portion (75%)
Client Portion (25%)
ADP Portion (75%)
Total
Total
-
-

Attached: Sales Order Confirmation (PDF)

+
+

Attached: Sales Order Confirmation (PDF)

-
-

Your order is being processed. We will keep you updated on the delivery status and any updates from the Assistive Devices Program.

+
+

Your order is being processed. We will keep you updated on the delivery status and any updates from the Assistive Devices Program.

--
@@ -120,42 +120,42 @@ {{ (object.invoice_user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }} {{ object.partner_id.id }} -
+
-
+

-

Invoice

-

- Please find attached your invoice . +

Invoice

+

+ Please find attached your invoice .

- - - + + + - + - - +
Invoice Details
Invoice
Date
Invoice Details
Invoice
Date
Due Date
Due Date
Type +
Type Client Portion ADP Portion
Amount Due
Amount Due
-
-

Attached: Invoice (PDF)

+
+

Attached: Invoice (PDF)

-
-

This invoice represents your client portion for the ADP-funded equipment. The remaining amount will be billed directly to the Assistive Devices Program.

+
+

This invoice represents your client portion for the ADP-funded equipment. The remaining amount will be billed directly to the Assistive Devices Program.

-
-

Please review the attached invoice and process payment at your earliest convenience. Contact us if you have any questions.

+
+

Please review the attached invoice and process payment at your earliest convenience. Contact us if you have any questions.

diff --git a/fusion_claims/data/product_labor_data.xml b/fusion_claims/data/product_labor_data.xml index 1af8108..1cb8bd8 100644 --- a/fusion_claims/data/product_labor_data.xml +++ b/fusion_claims/data/product_labor_data.xml @@ -9,7 +9,7 @@ - + diff --git a/fusion_claims/models/__init__.py b/fusion_claims/models/__init__.py index 89cae4c..8a8f7c2 100644 --- a/fusion_claims/models/__init__.py +++ b/fusion_claims/models/__init__.py @@ -3,7 +3,6 @@ # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Claim Assistant product family. -from . import email_builder_mixin from . import adp_posting_schedule from . import res_company from . import res_config_settings @@ -27,12 +26,5 @@ from . import client_chat from . import ai_agent_ext from . import dashboard from . import res_partner -from . import res_users from . import technician_task -from . import task_sync -from . import technician_location -from . import push_subscription -from . import ltc_facility -from . import ltc_repair -from . import ltc_cleanup -from . import ltc_form_submission \ No newline at end of file +from . import page11_sign_request \ No newline at end of file diff --git a/fusion_claims/models/account_move.py b/fusion_claims/models/account_move.py index 3ed5517..48500ee 100644 --- a/fusion_claims/models/account_move.py +++ b/fusion_claims/models/account_move.py @@ -105,9 +105,11 @@ class AccountMove(models.Model): try: report = self.env.ref('fusion_claims.action_report_mod_invoice') pdf_content, _ = report._render_qweb_pdf(report.id, [self.id]) - client_name = (so.partner_id.name or 'Client').replace(' ', '_').replace(',', '') + name_parts = (so.partner_id.name or 'Client').strip().split() + first = name_parts[0] if name_parts else 'Client' + last = name_parts[-1] if len(name_parts) > 1 else '' att = Attachment.create({ - 'name': f'Invoice - {client_name} - {self.name}.pdf', + 'name': f'{first}_{last}_MOD_Invoice_{self.name}.pdf', 'type': 'binary', 'datas': base64.b64encode(pdf_content), 'res_model': 'account.move', diff --git a/fusion_claims/models/ai_agent_ext.py b/fusion_claims/models/ai_agent_ext.py index e72f056..520c092 100644 --- a/fusion_claims/models/ai_agent_ext.py +++ b/fusion_claims/models/ai_agent_ext.py @@ -4,74 +4,16 @@ import json import logging -from datetime import date, timedelta from odoo import api, models _logger = logging.getLogger(__name__) -PREV_FUNDED_FIELDS = { - 'prev_funded_forearm': 'Forearm Crutches', - 'prev_funded_wheeled': 'Wheeled Walker', - 'prev_funded_manual': 'Manual Wheelchair', - 'prev_funded_power': 'Power Wheelchair', - 'prev_funded_addon': 'Power Add-On Device', - 'prev_funded_scooter': 'Power Scooter', - 'prev_funded_seating': 'Positioning Devices', - 'prev_funded_tilt': 'Power Tilt System', - 'prev_funded_recline': 'Power Recline System', - 'prev_funded_legrests': 'Power Elevating Leg Rests', - 'prev_funded_frame': 'Paediatric Standing Frame', - 'prev_funded_stroller': 'Paediatric Specialty Stroller', -} - -STATUS_NEXT_STEPS = { - 'quotation': 'Schedule assessment with the client', - 'assessment_scheduled': 'Complete the assessment', - 'assessment_completed': 'Prepare and send ADP application to client', - 'waiting_for_application': 'Follow up with client to return signed application', - 'application_received': 'Review application and prepare for submission', - 'ready_submission': 'Submit application to ADP', - 'submitted': 'Wait for ADP acceptance (typically within 24 hours)', - 'accepted': 'Wait for ADP approval decision', - 'rejected': 'Review rejection reason and correct the application', - 'resubmitted': 'Wait for ADP acceptance of resubmission', - 'needs_correction': 'Review and correct the application per ADP feedback', - 'approved': 'Prepare order for delivery', - 'approved_deduction': 'Prepare order for delivery (note: approved with deduction)', - 'ready_delivery': 'Schedule and complete delivery to client', - 'ready_bill': 'Create and submit ADP invoice', - 'billed': 'Monitor for ADP payment', - 'case_closed': 'No further action required', - 'on_hold': 'Check hold reason and follow up when ready to resume', - 'denied': 'Review denial reason; consider appeal or alternative funding', - 'withdrawn': 'No further action unless client wants to reinstate', - 'cancelled': 'No further action required', - 'expired': 'Contact client about reapplication if still needed', -} - -BASE_DEVICE_LABELS = { - 'adultWalkertype1': 'Adult Walker Type 1', - 'adultWalkertype2': 'Adult Walker Type 2', - 'adultWalkertype3': 'Adult Walker Type 3', - 'adultlightwtStdwheelchair': 'Lightweight Standard Wheelchair', - 'adultlightwtPermwheelchair': 'Lightweight Permanent Wheelchair', - 'adultTiltwheelchair': 'Adult Tilt Wheelchair', - 'adultStdwheelchair': 'Adult Standard Wheelchair', - 'adultHighperfwheelchair': 'Adult High Performance Wheelchair', - 'adultType2': 'Adult Power Type 2', - 'adultType3': 'Adult Power Type 3', - 'powerScooter': 'Power Scooter', -} - class AIAgentFusionClaims(models.Model): """Extend ai.agent with Fusion Claims tool methods.""" _inherit = 'ai.agent' - # ------------------------------------------------------------------ - # Tool 1: Search Client Profiles - # ------------------------------------------------------------------ def _fc_tool_search_clients(self, search_term=None, city_filter=None, condition_filter=None): """AI Tool: Search client profiles.""" Profile = self.env['fusion.client.profile'].sudo() @@ -104,37 +46,20 @@ class AIAgentFusionClaims(models.Model): }) return json.dumps({'count': len(results), 'profiles': results}) - # ------------------------------------------------------------------ - # Tool 2: Get Client Details (enriched with funding history) - # ------------------------------------------------------------------ def _fc_tool_client_details(self, profile_id): - """AI Tool: Get detailed client information with funding history.""" + """AI Tool: Get detailed client information.""" Profile = self.env['fusion.client.profile'].sudo() profile = Profile.browse(int(profile_id)) if not profile.exists(): return json.dumps({'error': 'Profile not found'}) + # Get orders orders = [] if profile.partner_id: - Invoice = self.env['account.move'].sudo() for o in self.env['sale.order'].sudo().search([ ('partner_id', '=', profile.partner_id.id), ('x_fc_sale_type', '!=', False), - ], limit=20, order='date_order desc'): - invoices = Invoice.search([ - ('x_fc_source_sale_order_id', '=', o.id), - ('move_type', '=', 'out_invoice'), - ]) - inv_summary = [] - for inv in invoices: - inv_summary.append({ - 'number': inv.name or '', - 'portion': inv.x_fc_adp_invoice_portion or 'full', - 'amount': float(inv.amount_total), - 'paid': inv.payment_state in ('paid', 'in_payment'), - 'date': str(inv.invoice_date) if inv.invoice_date else '', - }) - + ], limit=20): orders.append({ 'name': o.name, 'sale_type': o.x_fc_sale_type, @@ -143,19 +68,11 @@ class AIAgentFusionClaims(models.Model): 'client_total': float(o.x_fc_client_portion_total), 'total': float(o.amount_total), 'date': str(o.date_order.date()) if o.date_order else '', - 'billing_date': str(o.x_fc_billing_date) if o.x_fc_billing_date else '', - 'previous_funding_date': str(o.x_fc_previous_funding_date) if o.x_fc_previous_funding_date else '', - 'funding_warning': o.x_fc_funding_warning_message or '', - 'funding_warning_level': o.x_fc_funding_warning_level or '', - 'invoices': inv_summary, }) + # Get applications apps = [] for a in profile.application_data_ids[:10]: - funded_devices = [ - label for field, label in PREV_FUNDED_FIELDS.items() - if getattr(a, field, False) - ] apps.append({ 'date': str(a.application_date) if a.application_date else '', 'device': a.base_device or '', @@ -163,17 +80,8 @@ class AIAgentFusionClaims(models.Model): 'reason': a.reason_for_application or '', 'condition': (a.medical_condition or '')[:100], 'authorizer': f'{a.authorizer_first_name or ""} {a.authorizer_last_name or ""}'.strip(), - 'previously_funded': funded_devices if funded_devices else ['None'], }) - ai_summary = '' - ai_risk = '' - try: - ai_summary = profile.ai_summary or '' - ai_risk = profile.ai_risk_flags or '' - except Exception: - pass - return json.dumps({ 'profile': { 'id': profile.id, @@ -198,18 +106,12 @@ class AIAgentFusionClaims(models.Model): 'total_adp': float(profile.total_adp_funded), 'total_client': float(profile.total_client_portion), 'total_amount': float(profile.total_amount), - 'applications_count': profile.application_count, 'last_assessment': str(profile.last_assessment_date) if profile.last_assessment_date else '', - 'ai_summary': ai_summary, - 'ai_risk_flags': ai_risk, }, 'orders': orders, 'applications': apps, }) - # ------------------------------------------------------------------ - # Tool 3: Get Aggregated Stats (migrated from read_group) - # ------------------------------------------------------------------ def _fc_tool_claims_stats(self): """AI Tool: Get aggregated claims statistics.""" SO = self.env['sale.order'].sudo() @@ -218,46 +120,40 @@ class AIAgentFusionClaims(models.Model): total_profiles = Profile.search_count([]) total_orders = SO.search_count([('x_fc_sale_type', '!=', False)]) + # By sale type + type_data = SO.read_group( + [('x_fc_sale_type', '!=', False)], + ['x_fc_sale_type', 'amount_total:sum'], + ['x_fc_sale_type'], + ) by_type = {} - try: - type_results = SO._read_group( - [('x_fc_sale_type', '!=', False)], - groupby=['x_fc_sale_type'], - aggregates=['__count', 'amount_total:sum'], - ) - for sale_type, count, total_amount in type_results: - by_type[sale_type or 'unknown'] = { - 'count': count, - 'total': float(total_amount or 0), - } - except Exception as e: - _logger.warning('Stats by_type failed: %s', e) + for r in type_data: + by_type[r['x_fc_sale_type']] = { + 'count': r['x_fc_sale_type_count'], + 'total': float(r['amount_total'] or 0), + } + # By status + status_data = SO.read_group( + [('x_fc_sale_type', '!=', False), ('x_fc_adp_application_status', '!=', False)], + ['x_fc_adp_application_status'], + ['x_fc_adp_application_status'], + ) by_status = {} - try: - status_results = SO._read_group( - [('x_fc_sale_type', '!=', False), ('x_fc_adp_application_status', '!=', False)], - groupby=['x_fc_adp_application_status'], - aggregates=['__count'], - ) - for status, count in status_results: - by_status[status or 'unknown'] = count - except Exception as e: - _logger.warning('Stats by_status failed: %s', e) + for r in status_data: + by_status[r['x_fc_adp_application_status']] = r['x_fc_adp_application_status_count'] + # By city (top 10) + city_data = Profile.read_group( + [('city', '!=', False)], + ['city'], + ['city'], + limit=10, + orderby='city_count desc', + ) by_city = {} - try: - city_results = Profile._read_group( - [('city', '!=', False)], - groupby=['city'], - aggregates=['__count'], - limit=10, - order='__count desc', - ) - for city, count in city_results: - by_city[city or 'unknown'] = count - except Exception as e: - _logger.warning('Stats by_city failed: %s', e) + for r in city_data: + by_city[r['city']] = r['city_count'] return json.dumps({ 'total_profiles': total_profiles, @@ -266,405 +162,3 @@ class AIAgentFusionClaims(models.Model): 'by_status': by_status, 'top_cities': by_city, }) - - # ------------------------------------------------------------------ - # Tool 4: Client Status Lookup (by name, not order number) - # ------------------------------------------------------------------ - def _fc_tool_client_status(self, client_name): - """AI Tool: Look up a client's complete status by name.""" - if not client_name or not client_name.strip(): - return json.dumps({'error': 'Please provide a client name to search for'}) - - client_name = client_name.strip() - Profile = self.env['fusion.client.profile'].sudo() - SO = self.env['sale.order'].sudo() - Invoice = self.env['account.move'].sudo() - - profiles = Profile.search([ - '|', - ('first_name', 'ilike', client_name), - ('last_name', 'ilike', client_name), - ], limit=5) - - if not profiles: - partners = self.env['res.partner'].sudo().search([ - ('name', 'ilike', client_name), - ], limit=5) - if not partners: - return json.dumps({'error': f'No client found matching "{client_name}"'}) - - results = [] - for partner in partners: - orders = SO.search([ - ('partner_id', '=', partner.id), - ('x_fc_sale_type', '!=', False), - ], order='date_order desc', limit=20) - if not orders: - continue - results.append(self._build_client_status_result( - partner_name=partner.name, - partner_id=partner.id, - profile=None, - orders=orders, - Invoice=Invoice, - )) - if not results: - return json.dumps({'error': f'No orders found for "{client_name}"'}) - return json.dumps({'clients': results}) - - results = [] - for profile in profiles: - orders = SO.search([ - ('partner_id', '=', profile.partner_id.id), - ('x_fc_sale_type', '!=', False), - ], order='date_order desc', limit=20) if profile.partner_id else SO - results.append(self._build_client_status_result( - partner_name=profile.display_name, - partner_id=profile.partner_id.id if profile.partner_id else None, - profile=profile, - orders=orders if profile.partner_id else SO.browse(), - Invoice=Invoice, - )) - - return json.dumps({'clients': results}) - - def _build_client_status_result(self, partner_name, partner_id, profile, orders, Invoice): - """Build a complete status result for one client.""" - order_data = [] - for o in orders: - invoices = Invoice.search([ - ('x_fc_source_sale_order_id', '=', o.id), - ('move_type', '=', 'out_invoice'), - ]) - adp_inv = invoices.filtered(lambda i: i.x_fc_adp_invoice_portion == 'adp') - client_inv = invoices.filtered(lambda i: i.x_fc_adp_invoice_portion == 'client') - - docs = { - 'original_application': bool(o.x_fc_original_application), - 'signed_pages': bool(o.x_fc_signed_pages_11_12), - 'xml_file': bool(o.x_fc_xml_file), - 'proof_of_delivery': bool(o.x_fc_proof_of_delivery), - } - - status = o.x_fc_adp_application_status or '' - next_step = STATUS_NEXT_STEPS.get(status, '') - - order_data.append({ - 'order': o.name, - 'sale_type': o.x_fc_sale_type, - 'status': status, - 'date': str(o.date_order.date()) if o.date_order else '', - 'total': float(o.amount_total), - 'adp_total': float(o.x_fc_adp_portion_total), - 'client_total': float(o.x_fc_client_portion_total), - 'billing_date': str(o.x_fc_billing_date) if o.x_fc_billing_date else '', - 'funding_warning': o.x_fc_funding_warning_message or '', - 'adp_invoice': { - 'number': adp_inv[0].name if adp_inv else '', - 'amount': float(adp_inv[0].amount_total) if adp_inv else 0, - 'paid': adp_inv[0].payment_state in ('paid', 'in_payment') if adp_inv else False, - } if adp_inv else None, - 'client_invoice': { - 'number': client_inv[0].name if client_inv else '', - 'amount': float(client_inv[0].amount_total) if client_inv else 0, - 'paid': client_inv[0].payment_state in ('paid', 'in_payment') if client_inv else False, - } if client_inv else None, - 'documents': docs, - 'next_step': next_step, - }) - - result = { - 'name': partner_name, - 'partner_id': partner_id, - 'orders': order_data, - 'order_count': len(order_data), - } - - if profile: - result['profile_id'] = profile.id - result['health_card'] = profile.health_card_number or '' - result['city'] = profile.city or '' - result['condition'] = (profile.medical_condition or '')[:100] - result['total_adp_funded'] = float(profile.total_adp_funded) - result['total_client_funded'] = float(profile.total_client_portion) - - return result - - # ------------------------------------------------------------------ - # Tool 5: ADP Billing Period Summary - # ------------------------------------------------------------------ - def _fc_tool_adp_billing_period(self, period=None): - """AI Tool: Get ADP billing summary for a posting period.""" - Mixin = self.env['fusion_claims.adp.posting.schedule.mixin'] - Invoice = self.env['account.move'].sudo() - - today = date.today() - frequency = Mixin._get_adp_posting_frequency() - - if not period or period == 'current': - posting_date = Mixin._get_current_posting_date(today) - elif period == 'previous': - current = Mixin._get_current_posting_date(today) - posting_date = current - timedelta(days=frequency) - elif period == 'next': - posting_date = Mixin._get_next_posting_date(today) - else: - try: - ref_date = date.fromisoformat(period) - posting_date = Mixin._get_current_posting_date(ref_date) - except (ValueError, TypeError): - return json.dumps({'error': f'Invalid period: "{period}". Use "current", "previous", "next", or a date (YYYY-MM-DD).'}) - - period_start = posting_date - period_end = posting_date + timedelta(days=frequency - 1) - submission_deadline = Mixin._get_posting_week_wednesday(posting_date) - expected_payment = Mixin._get_expected_payment_date(posting_date) - - adp_invoices = Invoice.search([ - ('x_fc_adp_invoice_portion', '=', 'adp'), - ('move_type', '=', 'out_invoice'), - ('invoice_date', '>=', str(period_start)), - ('invoice_date', '<=', str(period_end)), - ]) - - total_invoiced = sum(adp_invoices.mapped('amount_total')) - total_paid = sum(adp_invoices.filtered( - lambda i: i.payment_state in ('paid', 'in_payment') - ).mapped('amount_total')) - total_unpaid = total_invoiced - total_paid - - source_orders = adp_invoices.mapped('x_fc_source_sale_order_id') - - invoice_details = [] - for inv in adp_invoices[:25]: - so = inv.x_fc_source_sale_order_id - invoice_details.append({ - 'invoice': inv.name or '', - 'order': so.name if so else '', - 'client': inv.partner_id.name or '', - 'amount': float(inv.amount_total), - 'paid': inv.payment_state in ('paid', 'in_payment'), - 'date': str(inv.invoice_date) if inv.invoice_date else '', - }) - - return json.dumps({ - 'period': { - 'posting_date': str(posting_date), - 'start': str(period_start), - 'end': str(period_end), - 'submission_deadline': f'{submission_deadline.strftime("%A, %B %d, %Y")} 6:00 PM', - 'expected_payment_date': str(expected_payment), - }, - 'summary': { - 'total_invoices': len(adp_invoices), - 'total_invoiced': float(total_invoiced), - 'total_paid': float(total_paid), - 'total_unpaid': float(total_unpaid), - 'orders_billed': len(source_orders), - }, - 'invoices': invoice_details, - }) - - # ------------------------------------------------------------------ - # Tool 6: Demographics & Analytics - # ------------------------------------------------------------------ - def _fc_tool_demographics(self, analysis_type=None, city_filter=None, sale_type_filter=None): - """AI Tool: Run demographic and analytical queries on client data.""" - cr = self.env.cr - results = {} - - if not analysis_type: - analysis_type = 'full' - - if analysis_type in ('full', 'age_groups'): - cr.execute(""" - SELECT - CASE - WHEN age < 18 THEN 'Under 18' - WHEN age BETWEEN 18 AND 30 THEN '18-30' - WHEN age BETWEEN 31 AND 45 THEN '31-45' - WHEN age BETWEEN 46 AND 60 THEN '46-60' - WHEN age BETWEEN 61 AND 75 THEN '61-75' - ELSE '75+' - END AS age_group, - COUNT(DISTINCT p.id) AS clients, - COUNT(app.id) AS applications, - ROUND(COUNT(app.id)::numeric / NULLIF(COUNT(DISTINCT p.id), 0), 2) AS avg_applications, - COALESCE(ROUND(SUM(p.total_adp_funded) / NULLIF(COUNT(DISTINCT p.id), 0), 2), 0) AS avg_adp_funded, - COALESCE(ROUND(SUM(p.total_amount) / NULLIF(COUNT(DISTINCT p.id), 0), 2), 0) AS avg_total - FROM fusion_client_profile p - CROSS JOIN LATERAL ( - SELECT EXTRACT(YEAR FROM AGE(CURRENT_DATE, p.date_of_birth))::int AS age - ) a - LEFT JOIN fusion_adp_application_data app ON app.profile_id = p.id - WHERE p.date_of_birth IS NOT NULL - GROUP BY age_group - ORDER BY MIN(age) - """) - rows = cr.fetchall() - results['age_groups'] = [ - { - 'age_group': r[0], 'clients': r[1], 'applications': r[2], - 'avg_applications': float(r[3]), 'avg_adp_funded': float(r[4]), - 'avg_total': float(r[5]), - } for r in rows - ] - - if analysis_type in ('full', 'devices_by_age'): - cr.execute(""" - SELECT - CASE - WHEN age < 45 THEN 'Under 45' - WHEN age BETWEEN 45 AND 60 THEN '45-60' - WHEN age BETWEEN 61 AND 75 THEN '61-75' - ELSE '75+' - END AS age_group, - app.base_device, - COUNT(*) AS count - FROM fusion_client_profile p - CROSS JOIN LATERAL ( - SELECT EXTRACT(YEAR FROM AGE(CURRENT_DATE, p.date_of_birth))::int AS age - ) a - JOIN fusion_adp_application_data app ON app.profile_id = p.id - WHERE p.date_of_birth IS NOT NULL - AND app.base_device IS NOT NULL AND app.base_device != '' - GROUP BY age_group, app.base_device - ORDER BY age_group, count DESC - """) - rows = cr.fetchall() - devices_by_age = {} - for age_group, device, count in rows: - if age_group not in devices_by_age: - devices_by_age[age_group] = [] - label = BASE_DEVICE_LABELS.get(device, device) - devices_by_age[age_group].append({'device': label, 'count': count}) - results['devices_by_age'] = devices_by_age - - if analysis_type in ('full', 'city_demographics'): - city_clause = "" - params = [] - if city_filter: - city_clause = "AND LOWER(p.city) = LOWER(%s)" - params = [city_filter] - - cr.execute(f""" - SELECT - p.city, - COUNT(DISTINCT p.id) AS clients, - COUNT(app.id) AS applications, - ROUND(AVG(a.age), 1) AS avg_age, - COALESCE(ROUND(SUM(p.total_adp_funded) / NULLIF(COUNT(DISTINCT p.id), 0), 2), 0) AS avg_adp_funded - FROM fusion_client_profile p - CROSS JOIN LATERAL ( - SELECT EXTRACT(YEAR FROM AGE(CURRENT_DATE, p.date_of_birth))::int AS age - ) a - LEFT JOIN fusion_adp_application_data app ON app.profile_id = p.id - WHERE p.city IS NOT NULL AND p.city != '' - AND p.date_of_birth IS NOT NULL - {city_clause} - GROUP BY p.city - ORDER BY clients DESC - LIMIT 15 - """, params) - rows = cr.fetchall() - results['city_demographics'] = [ - { - 'city': r[0], 'clients': r[1], 'applications': r[2], - 'avg_age': float(r[3]), 'avg_adp_funded': float(r[4]), - } for r in rows - ] - - if analysis_type in ('full', 'benefits'): - cr.execute(""" - SELECT - CASE - WHEN p.benefit_type = 'odsp' THEN 'ODSP' - WHEN p.benefit_type = 'owp' THEN 'Ontario Works' - WHEN p.benefit_type = 'acsd' THEN 'ACSD' - WHEN p.receives_social_assistance THEN 'Social Assistance (other)' - ELSE 'Regular (no assistance)' - END AS benefit_category, - COUNT(DISTINCT p.id) AS clients, - COUNT(app.id) AS applications, - ROUND(AVG(a.age), 1) AS avg_age, - COALESCE(ROUND(AVG(p.total_adp_funded), 2), 0) AS avg_adp_funded - FROM fusion_client_profile p - CROSS JOIN LATERAL ( - SELECT EXTRACT(YEAR FROM AGE(CURRENT_DATE, p.date_of_birth))::int AS age - ) a - LEFT JOIN fusion_adp_application_data app ON app.profile_id = p.id - WHERE p.date_of_birth IS NOT NULL - GROUP BY benefit_category - ORDER BY clients DESC - """) - rows = cr.fetchall() - results['benefits_breakdown'] = [ - { - 'category': r[0], 'clients': r[1], 'applications': r[2], - 'avg_age': float(r[3]), 'avg_adp_funded': float(r[4]), - } for r in rows - ] - - if analysis_type in ('full', 'top_devices'): - cr.execute(""" - SELECT - app.base_device, - COUNT(*) AS count, - COUNT(DISTINCT app.profile_id) AS unique_clients, - ROUND(AVG(a.age), 1) AS avg_age - FROM fusion_adp_application_data app - JOIN fusion_client_profile p ON p.id = app.profile_id - CROSS JOIN LATERAL ( - SELECT EXTRACT(YEAR FROM AGE(CURRENT_DATE, p.date_of_birth))::int AS age - ) a - WHERE app.base_device IS NOT NULL AND app.base_device != '' AND app.base_device != 'none' - AND p.date_of_birth IS NOT NULL - GROUP BY app.base_device - ORDER BY count DESC - LIMIT 15 - """) - rows = cr.fetchall() - results['top_devices'] = [ - { - 'device': BASE_DEVICE_LABELS.get(r[0], r[0]), - 'device_code': r[0], - 'applications': r[1], - 'unique_clients': r[2], - 'avg_client_age': float(r[3]), - } for r in rows - ] - - if analysis_type in ('full', 'funding_summary'): - cr.execute(""" - SELECT - COUNT(*) AS total_profiles, - ROUND(AVG(a.age), 1) AS avg_age, - ROUND(SUM(p.total_adp_funded), 2) AS total_adp_funded, - ROUND(SUM(p.total_client_portion), 2) AS total_client_portion, - ROUND(SUM(p.total_amount), 2) AS grand_total, - ROUND(AVG(p.total_adp_funded), 2) AS avg_adp_per_client, - ROUND(AVG(p.total_client_portion), 2) AS avg_client_per_client, - ROUND(AVG(p.claim_count), 2) AS avg_claims_per_client, - MIN(a.age) AS youngest, - MAX(a.age) AS oldest - FROM fusion_client_profile p - CROSS JOIN LATERAL ( - SELECT EXTRACT(YEAR FROM AGE(CURRENT_DATE, p.date_of_birth))::int AS age - ) a - WHERE p.date_of_birth IS NOT NULL - """) - r = cr.fetchone() - results['funding_summary'] = { - 'total_profiles': r[0], - 'avg_age': float(r[1]), - 'total_adp_funded': float(r[2]), - 'total_client_portion': float(r[3]), - 'grand_total': float(r[4]), - 'avg_adp_per_client': float(r[5]), - 'avg_client_per_client': float(r[6]), - 'avg_claims_per_client': float(r[7]), - 'youngest_client_age': r[8], - 'oldest_client_age': r[9], - } - - return json.dumps(results) diff --git a/fusion_claims/models/email_builder_mixin.py b/fusion_claims/models/email_builder_mixin.py deleted file mode 100644 index 9fa3023..0000000 --- a/fusion_claims/models/email_builder_mixin.py +++ /dev/null @@ -1,242 +0,0 @@ -# -*- coding: utf-8 -*- -# Fusion Claims - Professional Email Builder Mixin -# Provides consistent, dark/light mode safe email templates across all modules. - -from odoo import models - - -class FusionEmailBuilderMixin(models.AbstractModel): - _name = 'fusion.email.builder.mixin' - _description = 'Fusion Email Builder Mixin' - - # ------------------------------------------------------------------ - # Color constants - # ------------------------------------------------------------------ - _EMAIL_COLORS = { - 'info': '#2B6CB0', - 'success': '#38a169', - 'attention': '#d69e2e', - 'urgent': '#c53030', - } - - # ------------------------------------------------------------------ - # Public API - # ------------------------------------------------------------------ - - def _email_build( - self, - title, - summary, - sections=None, - note=None, - note_color=None, - email_type='info', - attachments_note=None, - button_url=None, - button_text='View Case Details', - sender_name=None, - extra_html='', - ): - """Build a complete professional email HTML string. - - Args: - title: Email heading (e.g. "Application Approved") - summary: One-sentence summary HTML (may contain tags) - sections: list of (heading, rows) where rows is list of (label, value) - e.g. [('Case Details', [('Client', 'John'), ('Case', 'S30073')])] - note: Optional note/next-steps text (plain or HTML) - note_color: Override left-border color for note (default uses email_type) - email_type: 'info' | 'success' | 'attention' | 'urgent' - attachments_note: Optional string listing attached files - button_url: Optional CTA button URL - button_text: CTA button label - sender_name: Name for sign-off (defaults to current user) - extra_html: Any additional HTML to insert before sign-off - """ - accent = self._EMAIL_COLORS.get(email_type, self._EMAIL_COLORS['info']) - company = self._get_company_info() - - parts = [] - # -- Wrapper open + accent bar - parts.append( - f'
' - f'
' - f'
' - ) - - # -- Company name - parts.append( - f'

{company["name"]}

' - ) - - # -- Title - parts.append( - f'

{title}

' - ) - - # -- Summary - parts.append( - f'

{summary}

' - ) - - # -- Sections (details tables) - if sections: - for heading, rows in sections: - parts.append(self._email_section(heading, rows)) - - # -- Note / Next Steps - if note: - nc = note_color or accent - parts.append(self._email_note(note, nc)) - - # -- Extra HTML - if extra_html: - parts.append(extra_html) - - # -- Attachment note - if attachments_note: - parts.append(self._email_attachment_note(attachments_note)) - - # -- CTA Button - if button_url: - parts.append(self._email_button(button_url, button_text, accent)) - - # -- Sign-off - signer = sender_name or (self.env.user.name if self.env.user else '') - parts.append( - f'

' - f'Best regards,
' - f'{signer}
' - f'{company["name"]}

' - ) - - # -- Close content card - parts.append('
') - - # -- Footer - footer_parts = [company['name']] - if company['phone']: - footer_parts.append(company['phone']) - if company['email']: - footer_parts.append(company['email']) - footer_text = ' · '.join(footer_parts) - - parts.append( - f'
' - f'

' - f'{footer_text}
' - f'This is an automated notification from the ADP Claims Management System.

' - f'
' - ) - - # -- Close wrapper - parts.append('
') - - return ''.join(parts) - - # ------------------------------------------------------------------ - # Building blocks - # ------------------------------------------------------------------ - - def _email_section(self, heading, rows): - """Build a labeled details table section. - - Args: - heading: Section title (e.g. "Case Details") - rows: list of (label, value) tuples. Value can be plain text or HTML. - """ - if not rows: - return '' - - html = ( - '' - f'' - ) - - for label, value in rows: - if value is None or value == '' or value is False: - continue - html += ( - f'' - f'' - f'' - f'' - ) - - html += '
{heading}
{label}{value}
' - return html - - def _email_note(self, text, color='#2B6CB0'): - """Build a left-border accent note block.""" - return ( - f'
' - f'

{text}

' - f'
' - ) - - def _email_button(self, url, text='View Case Details', color='#2B6CB0'): - """Build a centered CTA button.""" - return ( - f'

' - f'{text}

' - ) - - def _email_attachment_note(self, description): - """Build a dashed-border attachment callout. - - Args: - description: e.g. "ADP Application (PDF), XML Data File" - """ - return ( - f'
' - f'

' - f'Attached: {description}

' - f'
' - ) - - def _email_status_badge(self, label, color='#2B6CB0'): - """Return an inline status badge/pill HTML snippet.""" - # Pick a light background tint for the badge - bg_map = { - '#38a169': '#f0fff4', - '#2B6CB0': '#ebf4ff', - '#d69e2e': '#fefcbf', - '#c53030': '#fff5f5', - } - bg = bg_map.get(color, '#ebf4ff') - return ( - f'' - f'{label}' - ) - - # ------------------------------------------------------------------ - # Helpers - # ------------------------------------------------------------------ - - def _get_company_info(self): - """Return company name, phone, email for email templates.""" - company = getattr(self, 'company_id', None) or self.env.company - return { - 'name': company.name or 'Our Company', - 'phone': company.phone or '', - 'email': company.email or '', - } - - def _email_is_enabled(self): - """Check if email notifications are enabled in settings.""" - ICP = self.env['ir.config_parameter'].sudo() - val = ICP.get_param('fusion_claims.enable_email_notifications', 'True') - return val.lower() in ('true', '1', 'yes') diff --git a/fusion_claims/models/fusion_adp_device_code.py b/fusion_claims/models/fusion_adp_device_code.py index 0a6c201..0f67f9b 100644 --- a/fusion_claims/models/fusion_adp_device_code.py +++ b/fusion_claims/models/fusion_adp_device_code.py @@ -57,6 +57,12 @@ class FusionADPDeviceCode(models.Model): index=True, help='Device manufacturer', ) + build_type = fields.Selection( + [('modular', 'Modular'), ('custom_fabricated', 'Custom Fabricated')], + string='Build Type', + index=True, + help='Build type for positioning/seating devices: Modular or Custom Fabricated', + ) device_description = fields.Char( string='Device Description', help='Detailed device description from mobility manual', @@ -242,6 +248,16 @@ class FusionADPDeviceCode(models.Model): device_type = self._clean_text(item.get('Device Type', '') or item.get('device_type', '')) manufacturer = self._clean_text(item.get('Manufacturer', '') or item.get('manufacturer', '')) device_description = self._clean_text(item.get('Device Description', '') or item.get('device_description', '')) + + # Parse build type (Modular / Custom Fabricated) + build_type_raw = self._clean_text(item.get('Build Type', '') or item.get('build_type', '')) + build_type = False + if build_type_raw: + bt_lower = build_type_raw.lower().strip() + if bt_lower in ('modular', 'mod'): + build_type = 'modular' + elif bt_lower in ('custom fabricated', 'custom_fabricated', 'custom'): + build_type = 'custom_fabricated' # Parse quantity qty_val = item.get('Quantity', 1) or item.get('Qty', 1) or item.get('quantity', 1) @@ -277,6 +293,8 @@ class FusionADPDeviceCode(models.Model): 'last_updated': fields.Datetime.now(), 'active': True, } + if build_type: + vals['build_type'] = build_type if existing: existing.write(vals) diff --git a/fusion_claims/models/page11_sign_request.py b/fusion_claims/models/page11_sign_request.py new file mode 100644 index 0000000..dcf97ae --- /dev/null +++ b/fusion_claims/models/page11_sign_request.py @@ -0,0 +1,389 @@ +# -*- coding: utf-8 -*- +# Copyright 2024-2026 Nexa Systems Inc. +# License OPL-1 (Odoo Proprietary License v1.0) + +import base64 +import logging +import uuid +from datetime import timedelta + +from markupsafe import Markup + +from odoo import models, fields, api, _ +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + +SIGNER_TYPE_SELECTION = [ + ('client', 'Client (Self)'), + ('spouse', 'Spouse'), + ('parent', 'Parent'), + ('legal_guardian', 'Legal Guardian'), + ('poa', 'Power of Attorney'), + ('public_trustee', 'Public Trustee'), +] + +SIGNER_TYPE_TO_RELATIONSHIP = { + 'spouse': 'Spouse', + 'parent': 'Parent', + 'legal_guardian': 'Legal Guardian', + 'poa': 'Power of Attorney', + 'public_trustee': 'Public Trustee', +} + + +class Page11SignRequest(models.Model): + _name = 'fusion.page11.sign.request' + _description = 'ADP Page 11 Remote Signing Request' + _inherit = ['fusion.email.builder.mixin'] + _order = 'create_date desc' + + sale_order_id = fields.Many2one( + 'sale.order', string='Sale Order', + required=True, ondelete='cascade', index=True, + ) + access_token = fields.Char( + string='Access Token', required=True, copy=False, + default=lambda self: str(uuid.uuid4()), index=True, + ) + state = fields.Selection([ + ('draft', 'Draft'), + ('sent', 'Sent'), + ('signed', 'Signed'), + ('expired', 'Expired'), + ('cancelled', 'Cancelled'), + ], string='Status', default='draft', required=True, tracking=True) + + signer_email = fields.Char(string='Recipient Email', required=True) + signer_type = fields.Selection( + SIGNER_TYPE_SELECTION, string='Signer Type', + default='client', required=True, + ) + signer_name = fields.Char(string='Signer Name') + signer_relationship = fields.Char(string='Relationship to Client') + + signature_data = fields.Binary(string='Signature', attachment=True) + signed_pdf = fields.Binary(string='Signed PDF', attachment=True) + signed_pdf_filename = fields.Char(string='Signed PDF Filename') + signed_date = fields.Datetime(string='Signed Date') + sent_date = fields.Datetime(string='Sent Date') + expiry_date = fields.Datetime(string='Expiry Date') + + consent_declaration_accepted = fields.Boolean(string='Declaration Accepted') + consent_signed_by = fields.Selection([ + ('applicant', 'Applicant'), + ('agent', 'Agent'), + ], string='Signed By') + + client_first_name = fields.Char(string='Client First Name') + client_last_name = fields.Char(string='Client Last Name') + client_health_card = fields.Char(string='Health Card Number') + client_health_card_version = fields.Char(string='Health Card Version') + + agent_first_name = fields.Char(string='Agent First Name') + agent_last_name = fields.Char(string='Agent Last Name') + agent_middle_initial = fields.Char(string='Agent Middle Initial') + agent_phone = fields.Char(string='Agent Phone') + agent_unit = fields.Char(string='Agent Unit Number') + agent_street_number = fields.Char(string='Agent Street Number') + agent_street = fields.Char(string='Agent Street Name') + agent_city = fields.Char(string='Agent City') + agent_province = fields.Char(string='Agent Province', default='Ontario') + agent_postal_code = fields.Char(string='Agent Postal Code') + + custom_message = fields.Text(string='Custom Message') + + company_id = fields.Many2one( + 'res.company', string='Company', + related='sale_order_id.company_id', store=True, + ) + + def name_get(self): + return [ + (r.id, f"Page 11 - {r.sale_order_id.name} ({r.state})") + for r in self + ] + + def _send_signing_email(self): + """Build and send the signing request email.""" + self.ensure_one() + base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + sign_url = f'{base_url}/page11/sign/{self.access_token}' + order = self.sale_order_id + + client_name = order.partner_id.name or 'N/A' + sections = [ + ('Case Details', [ + ('Client', client_name), + ('Case Reference', order.name), + ]), + ] + + if order.x_fc_authorizer_id: + sections[0][1].append(('Authorizer', order.x_fc_authorizer_id.name)) + + if order.x_fc_assessment_start_date: + sections[0][1].append(( + 'Assessment Date', + order.x_fc_assessment_start_date.strftime('%B %d, %Y'), + )) + + note_parts = [] + if self.custom_message: + note_parts.append(self.custom_message) + days_left = 7 + if self.expiry_date: + delta = self.expiry_date - fields.Datetime.now() + days_left = max(1, delta.days) + note_parts.append( + f'This link will expire in {days_left} days. ' + 'Please complete the signing at your earliest convenience.' + ) + note_text = '

'.join(note_parts) + + body_html = self._email_build( + title='Page 11 Signature Required', + summary=( + f'{order.company_id.name} requires your signature on the ' + f'ADP Consent and Declaration form for {client_name}.' + ), + sections=sections, + note=note_text, + email_type='info', + button_url=sign_url, + button_text='Sign Now', + sender_name=self.env.user.name, + ) + + mail_values = { + 'subject': f'{order.company_id.name} - Page 11 Signature Required ({order.name})', + 'body_html': body_html, + 'email_to': self.signer_email, + 'email_from': ( + self.env.user.email_formatted + or order.company_id.email_formatted + ), + 'auto_delete': True, + } + mail = self.env['mail.mail'].sudo().create(mail_values) + mail.send() + + self.write({ + 'state': 'sent', + 'sent_date': fields.Datetime.now(), + }) + + signer_display = self.signer_name or self.signer_email + order.message_post( + body=Markup( + 'Page 11 signing request sent to %s (%s).' + ) % (signer_display, self.signer_email), + message_type='notification', + subtype_xmlid='mail.mt_note', + ) + + def _generate_signed_pdf(self): + """Generate the signed Page 11 PDF using the PDF template engine.""" + self.ensure_one() + order = self.sale_order_id + + assessment = self.env['fusion.assessment'].search([ + ('sale_order_id', '=', order.id), + ], limit=1, order='create_date desc') + + if assessment: + ctx = assessment._get_pdf_context() + else: + ctx = self._build_pdf_context_from_order() + + if self.client_first_name: + ctx['client_first_name'] = self.client_first_name + if self.client_last_name: + ctx['client_last_name'] = self.client_last_name + if self.client_health_card: + ctx['client_health_card'] = self.client_health_card + if self.client_health_card_version: + ctx['client_health_card_version'] = self.client_health_card_version + + ctx.update({ + 'consent_signed_by': self.consent_signed_by or '', + 'consent_applicant': self.consent_signed_by == 'applicant', + 'consent_agent': self.consent_signed_by == 'agent', + 'consent_declaration_accepted': self.consent_declaration_accepted, + 'consent_date': str(fields.Date.today()), + }) + + if self.consent_signed_by == 'agent': + ctx.update({ + 'agent_first_name': self.agent_first_name or '', + 'agent_last_name': self.agent_last_name or '', + 'agent_middle_initial': self.agent_middle_initial or '', + 'agent_unit': self.agent_unit or '', + 'agent_street_number': self.agent_street_number or '', + 'agent_street_name': self.agent_street or '', + 'agent_city': self.agent_city or '', + 'agent_province': self.agent_province or '', + 'agent_postal_code': self.agent_postal_code or '', + 'agent_home_phone': self.agent_phone or '', + 'agent_relationship': self.signer_relationship or '', + 'agent_rel_spouse': self.signer_type == 'spouse', + 'agent_rel_parent': self.signer_type == 'parent', + 'agent_rel_poa': self.signer_type == 'poa', + 'agent_rel_guardian': self.signer_type in ('legal_guardian', 'public_trustee'), + }) + + signatures = {} + if self.signature_data: + signatures['signature_page_11'] = base64.b64decode(self.signature_data) + + template = self.env['fusion.pdf.template'].search([ + ('state', '=', 'active'), + ('name', 'ilike', 'adp_page_11'), + ], limit=1) + + if not template: + template = self.env['fusion.pdf.template'].search([ + ('state', '=', 'active'), + ('name', 'ilike', 'page 11'), + ], limit=1) + + if not template: + _logger.warning("No active PDF template found for Page 11") + return None + + try: + pdf_bytes = template.generate_filled_pdf(ctx, signatures) + if pdf_bytes: + first, last = order._get_client_name_parts() + filename = f'{first}_{last}_Page11_Signed.pdf' + self.write({ + 'signed_pdf': base64.b64encode(pdf_bytes), + 'signed_pdf_filename': filename, + }) + return pdf_bytes + except Exception as e: + _logger.error("Failed to generate Page 11 PDF: %s", e) + return None + + def _build_pdf_context_from_order(self): + """Build a PDF context dict from the sale order when no assessment exists.""" + order = self.sale_order_id + partner = order.partner_id + first, last = order._get_client_name_parts() + return { + 'client_first_name': first, + 'client_last_name': last, + 'client_name': partner.name or '', + 'client_street': partner.street or '', + 'client_city': partner.city or '', + 'client_state': partner.state_id.name if partner.state_id else 'Ontario', + 'client_postal_code': partner.zip or '', + 'client_phone': partner.phone or partner.mobile or '', + 'client_email': partner.email or '', + 'client_type': order.x_fc_client_type or '', + 'client_type_reg': order.x_fc_client_type == 'REG', + 'client_type_ods': order.x_fc_client_type == 'ODS', + 'client_type_acs': order.x_fc_client_type == 'ACS', + 'client_type_owp': order.x_fc_client_type == 'OWP', + 'reference': order.name or '', + 'authorizer_name': order.x_fc_authorizer_id.name if order.x_fc_authorizer_id else '', + 'authorizer_phone': order.x_fc_authorizer_id.phone if order.x_fc_authorizer_id else '', + 'authorizer_email': order.x_fc_authorizer_id.email if order.x_fc_authorizer_id else '', + 'claim_authorization_date': str(order.x_fc_claim_authorization_date) if order.x_fc_claim_authorization_date else '', + 'assessment_start_date': str(order.x_fc_assessment_start_date) if order.x_fc_assessment_start_date else '', + 'assessment_end_date': str(order.x_fc_assessment_end_date) if order.x_fc_assessment_end_date else '', + } + + def _update_sale_order(self): + """Copy signing data from this request to the sale order.""" + self.ensure_one() + order = self.sale_order_id + vals = { + 'x_fc_page11_signer_type': self.signer_type, + 'x_fc_page11_signer_name': self.signer_name, + 'x_fc_page11_signed_date': fields.Date.today(), + } + if self.signer_type != 'client': + vals['x_fc_page11_signer_relationship'] = ( + self.signer_relationship + or SIGNER_TYPE_TO_RELATIONSHIP.get(self.signer_type, '') + ) + if self.signed_pdf: + vals['x_fc_signed_pages_11_12'] = self.signed_pdf + vals['x_fc_signed_pages_filename'] = self.signed_pdf_filename + + order.with_context( + skip_page11_check=True, + skip_document_chatter=True, + ).write(vals) + + signer_display = self.signer_name or 'N/A' + if self.signed_pdf: + att = self.env['ir.attachment'].sudo().create({ + 'name': self.signed_pdf_filename or 'Page11_Signed.pdf', + 'datas': self.signed_pdf, + 'res_model': 'sale.order', + 'res_id': order.id, + 'mimetype': 'application/pdf', + }) + order.message_post( + body=Markup( + 'Page 11 has been signed by %s (%s).' + ) % (signer_display, self.signer_email), + attachment_ids=[att.id], + message_type='notification', + subtype_xmlid='mail.mt_note', + ) + else: + order.message_post( + body=Markup( + 'Page 11 has been signed by %s (%s). ' + 'PDF generation was not available.' + ) % (signer_display, self.signer_email), + message_type='notification', + subtype_xmlid='mail.mt_note', + ) + + def action_cancel(self): + """Cancel a pending signing request.""" + for rec in self: + if rec.state in ('draft', 'sent'): + rec.state = 'cancelled' + + def action_resend(self): + """Resend the signing email.""" + for rec in self: + if rec.state in ('sent', 'expired'): + rec.expiry_date = fields.Datetime.now() + timedelta(days=7) + rec.access_token = str(uuid.uuid4()) + rec._send_signing_email() + + def action_request_new_signature(self): + """Create a new signing request (e.g. to re-sign after corrections).""" + self.ensure_one() + if self.state == 'signed': + self.state = 'cancelled' + return { + 'type': 'ir.actions.act_window', + 'name': 'Request Page 11 Signature', + 'res_model': 'fusion_claims.send.page11.wizard', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_sale_order_id': self.sale_order_id.id, + 'default_signer_email': self.signer_email, + 'default_signer_name': self.signer_name, + 'default_signer_type': self.signer_type, + }, + } + + @api.model + def _cron_expire_requests(self): + """Mark expired unsigned requests.""" + expired = self.search([ + ('state', '=', 'sent'), + ('expiry_date', '<', fields.Datetime.now()), + ]) + if expired: + expired.write({'state': 'expired'}) + _logger.info("Expired %d Page 11 signing requests", len(expired)) diff --git a/fusion_claims/models/push_subscription.py b/fusion_claims/models/push_subscription.py deleted file mode 100644 index 19f9033..0000000 --- a/fusion_claims/models/push_subscription.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2024-2026 Nexa Systems Inc. -# License OPL-1 (Odoo Proprietary License v1.0) - -""" -Web Push Subscription model for storing browser push notification subscriptions. -""" - -from odoo import models, fields, api -import logging - -_logger = logging.getLogger(__name__) - - -class FusionPushSubscription(models.Model): - _name = 'fusion.push.subscription' - _description = 'Web Push Subscription' - _order = 'create_date desc' - - user_id = fields.Many2one( - 'res.users', - string='User', - required=True, - ondelete='cascade', - index=True, - ) - endpoint = fields.Text( - string='Endpoint URL', - required=True, - ) - p256dh_key = fields.Text( - string='P256DH Key', - required=True, - ) - auth_key = fields.Text( - string='Auth Key', - required=True, - ) - browser_info = fields.Char( - string='Browser Info', - help='User agent or browser identification', - ) - active = fields.Boolean( - default=True, - ) - - _constraints = [ - models.Constraint( - 'unique(endpoint)', - 'This push subscription endpoint already exists.', - ), - ] - - @api.model - def register_subscription(self, user_id, endpoint, p256dh_key, auth_key, browser_info=None): - """Register or update a push subscription.""" - existing = self.sudo().search([('endpoint', '=', endpoint)], limit=1) - if existing: - existing.write({ - 'user_id': user_id, - 'p256dh_key': p256dh_key, - 'auth_key': auth_key, - 'browser_info': browser_info or existing.browser_info, - 'active': True, - }) - return existing - return self.sudo().create({ - 'user_id': user_id, - 'endpoint': endpoint, - 'p256dh_key': p256dh_key, - 'auth_key': auth_key, - 'browser_info': browser_info, - }) diff --git a/fusion_claims/models/res_config_settings.py b/fusion_claims/models/res_config_settings.py index 32013fd..83e65e5 100644 --- a/fusion_claims/models/res_config_settings.py +++ b/fusion_claims/models/res_config_settings.py @@ -317,16 +317,6 @@ class ResConfigSettings(models.TransientModel): help='The user who signs Page 12 on behalf of the company', ) - # ========================================================================= - # GOOGLE MAPS API SETTINGS - # ========================================================================= - - fc_google_maps_api_key = fields.Char( - string='Google Maps API Key', - config_parameter='fusion_claims.google_maps_api_key', - help='API key for Google Maps Places autocomplete in address fields', - ) - # ------------------------------------------------------------------ # AI CLIENT INTELLIGENCE # ------------------------------------------------------------------ @@ -349,62 +339,6 @@ class ResConfigSettings(models.TransientModel): help='Automatically parse ADP XML files when uploaded and create/update client profiles', ) - # ------------------------------------------------------------------ - # TECHNICIAN MANAGEMENT - # ------------------------------------------------------------------ - fc_store_open_hour = fields.Float( - string='Store Open Time', - config_parameter='fusion_claims.store_open_hour', - help='Store opening time for technician scheduling (e.g. 9.0 = 9:00 AM)', - ) - fc_store_close_hour = fields.Float( - string='Store Close Time', - config_parameter='fusion_claims.store_close_hour', - help='Store closing time for technician scheduling (e.g. 18.0 = 6:00 PM)', - ) - fc_google_distance_matrix_enabled = fields.Boolean( - string='Enable Distance Matrix', - config_parameter='fusion_claims.google_distance_matrix_enabled', - help='Enable Google Distance Matrix API for travel time calculations between technician tasks', - ) - fc_technician_start_address = fields.Char( - string='Technician Start Address', - config_parameter='fusion_claims.technician_start_address', - help='Default start location for technician travel calculations (e.g. warehouse/office address)', - ) - fc_location_retention_days = fields.Char( - string='Location History Retention (Days)', - config_parameter='fusion_claims.location_retention_days', - help='How many days to keep technician location history. ' - 'Leave empty = 30 days (1 month). ' - '0 = delete at end of each day. ' - '1+ = keep for that many days.', - ) - - # ------------------------------------------------------------------ - # WEB PUSH NOTIFICATIONS - # ------------------------------------------------------------------ - fc_push_enabled = fields.Boolean( - string='Enable Push Notifications', - config_parameter='fusion_claims.push_enabled', - help='Enable web push notifications for technician tasks', - ) - fc_vapid_public_key = fields.Char( - string='VAPID Public Key', - config_parameter='fusion_claims.vapid_public_key', - help='Public key for Web Push VAPID authentication (auto-generated)', - ) - fc_vapid_private_key = fields.Char( - string='VAPID Private Key', - config_parameter='fusion_claims.vapid_private_key', - help='Private key for Web Push VAPID authentication (auto-generated)', - ) - fc_push_advance_minutes = fields.Integer( - string='Notification Advance (min)', - config_parameter='fusion_claims.push_advance_minutes', - help='Send push notifications this many minutes before a scheduled task', - ) - # ------------------------------------------------------------------ # TWILIO SMS SETTINGS # ------------------------------------------------------------------ @@ -477,16 +411,6 @@ class ResConfigSettings(models.TransientModel): help='Default ODSP office contact for new ODSP cases', ) - # ========================================================================= - # PORTAL FORMS - # ========================================================================= - - fc_ltc_form_password = fields.Char( - string='LTC Form Access Password', - config_parameter='fusion_claims.ltc_form_password', - help='Minimum 4 characters. Share with facility staff to access the repair form.', - ) - # ========================================================================= # PORTAL BRANDING # ========================================================================= @@ -609,15 +533,11 @@ class ResConfigSettings(models.TransientModel): # an existing non-empty value (e.g. API keys, user-customized settings). _protected_keys = [ 'fusion_claims.ai_api_key', - 'fusion_claims.google_maps_api_key', 'fusion_claims.vendor_code', 'fusion_claims.ai_model', 'fusion_claims.adp_posting_base_date', 'fusion_claims.application_reminder_days', 'fusion_claims.application_reminder_2_days', - 'fusion_claims.store_open_hour', - 'fusion_claims.store_close_hour', - 'fusion_claims.technician_start_address', ] # Snapshot existing values BEFORE super().set_values() runs _existing = {} @@ -656,13 +576,6 @@ class ResConfigSettings(models.TransientModel): # Office notification recipients are stored via related field on res.company # No need to store in ir.config_parameter - # Validate LTC form password length - form_pw = self.fc_ltc_form_password or '' - if form_pw and len(form_pw.strip()) < 4: - raise ValidationError( - 'LTC Form Access Password must be at least 4 characters.' - ) - # Store designated vendor signer (Many2one - manual handling) if self.fc_designated_vendor_signer: ICP.set_param('fusion_claims.designated_vendor_signer', diff --git a/fusion_claims/models/res_partner.py b/fusion_claims/models/res_partner.py index efa2d5c..9c60e4a 100644 --- a/fusion_claims/models/res_partner.py +++ b/fusion_claims/models/res_partner.py @@ -8,13 +8,6 @@ from odoo import models, fields, api class ResPartner(models.Model): _inherit = 'res.partner' - x_fc_start_address = fields.Char( - string='Start Location', - help='Technician daily start location (home, warehouse, etc.). ' - 'Used as origin for first travel time calculation. ' - 'If empty, the company default HQ address is used.', - ) - # ========================================================================== # CONTACT TYPE # ========================================================================== @@ -76,25 +69,6 @@ class ResPartner(models.Model): store=True, ) - # ========================================================================== - # LTC FIELDS - # ========================================================================== - x_fc_ltc_facility_id = fields.Many2one( - 'fusion.ltc.facility', - string='LTC Home', - tracking=True, - help='Long-Term Care Home this resident belongs to', - ) - x_fc_ltc_room_number = fields.Char( - string='Room Number', - tracking=True, - ) - x_fc_ltc_family_contact_ids = fields.One2many( - 'fusion.ltc.family.contact', - 'partner_id', - string='Family Contacts', - ) - @api.depends('x_fc_contact_type') def _compute_is_odsp_office(self): for partner in self: diff --git a/fusion_claims/models/res_users.py b/fusion_claims/models/res_users.py deleted file mode 100644 index f17757b..0000000 --- a/fusion_claims/models/res_users.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2024-2026 Nexa Systems Inc. -# License OPL-1 (Odoo Proprietary License v1.0) - -from odoo import models, fields, api - - -class ResUsers(models.Model): - _inherit = 'res.users' - - x_fc_is_field_staff = fields.Boolean( - string='Field Staff', - default=False, - help='Check this to show the user in the Technician/Field Staff dropdown when scheduling tasks.', - ) - x_fc_start_address = fields.Char( - related='partner_id.x_fc_start_address', - readonly=False, - string='Start Location', - ) - x_fc_tech_sync_id = fields.Char( - string='Tech Sync ID', - help='Shared identifier for this technician across Odoo instances. ' - 'Must be the same value on all instances for the same person.', - copy=False, - ) \ No newline at end of file diff --git a/fusion_claims/models/sale_order.py b/fusion_claims/models/sale_order.py index 85dc5ee..3532584 100644 --- a/fusion_claims/models/sale_order.py +++ b/fusion_claims/models/sale_order.py @@ -15,7 +15,9 @@ _logger = logging.getLogger(__name__) class SaleOrder(models.Model): _name = 'sale.order' _inherit = ['sale.order', 'fusion_claims.adp.posting.schedule.mixin', 'fusion.email.builder.mixin'] - _rec_names_search = ['name', 'partner_id.name'] + @property + def _rec_names_search(self): + return ['name', 'partner_id.name'] @api.depends('name', 'partner_id.name') def _compute_display_name(self): @@ -35,22 +37,6 @@ class SaleOrder(models.Model): help='True only for ADP or ADP/ODSP sale types', ) - # ========================================================================== - # LTC REPAIR LINK - # ========================================================================== - x_fc_ltc_repair_id = fields.Many2one( - 'fusion.ltc.repair', - string='LTC Repair', - tracking=True, - ondelete='set null', - index=True, - ) - x_fc_is_ltc_repair_sale = fields.Boolean( - compute='_compute_is_ltc_repair_sale', - store=True, - string='Is LTC Repair Sale', - ) - # ========================================================================== # INVOICE COUNT FIELDS (Separate ADP and Client invoices) # ========================================================================== @@ -418,11 +404,6 @@ class SaleOrder(models.Model): for order in self: order.x_fc_is_adp_sale = order._is_adp_sale() - @api.depends('x_fc_ltc_repair_id') - def _compute_is_ltc_repair_sale(self): - for order in self: - order.x_fc_is_ltc_repair_sale = bool(order.x_fc_ltc_repair_id) - # ========================================================================== # SALE TYPE AND CLIENT TYPE FIELDS # ========================================================================== @@ -1836,8 +1817,7 @@ class SaleOrder(models.Model): domain, groupby=groupby, aggregates=aggregates, having=having, offset=offset, limit=limit, order=order, ) - groupby_list = list(groupby) if not isinstance(groupby, (list, tuple)) else groupby - if groupby_list and groupby_list[0] == 'x_fc_adp_application_status': + if groupby and groupby[0] == 'x_fc_adp_application_status': status_order = self._STATUS_ORDER result = sorted(result, key=lambda r: status_order.get(r[0], 999)) return result @@ -4723,48 +4703,6 @@ class SaleOrder(models.Model): return invoice - - # ========================================================================== - # PORTAL PAYMENT AMOUNT (ADP Client Portion) - # ========================================================================== - def _get_prepayment_required_amount(self): - """Override to return client portion for ADP orders. - - For ADP REG clients, the customer should only prepay their 25% - portion, not the full order amount that includes ADP's 75%. - """ - self.ensure_one() - if self._is_adp_sale() and self.x_fc_client_type == 'REG': - client_portion = self.x_fc_client_portion_total or 0 - if client_portion > 0: - return self.currency_id.round(client_portion * self.prepayment_percent) - return super()._get_prepayment_required_amount() - - def _has_to_be_paid(self): - """Override to use client portion for ADP payment threshold check. - - Standard Odoo checks amount_total > 0. For ADP orders where - the client type is not REG (100% ADP funded), the customer - has nothing to pay and the quotation should auto-confirm. - """ - self.ensure_one() - if self._is_adp_sale(): - client_type = self.x_fc_client_type or '' - if client_type and client_type != 'REG': - return False - if client_type == 'REG': - client_portion = self.x_fc_client_portion_total or 0 - if client_portion <= 0: - return False - return ( - self.state in ['draft', 'sent'] - and not self.is_expired - and self.require_payment - and client_portion > 0 - and not self._is_confirmation_amount_reached() - ) - return super()._has_to_be_paid() - # ========================================================================== # OVERRIDE _get_invoiceable_lines TO INCLUDE ALL SECTIONS AND NOTES # ========================================================================== diff --git a/fusion_claims/models/sale_order_line.py b/fusion_claims/models/sale_order_line.py index c7118ef..5bc61a8 100644 --- a/fusion_claims/models/sale_order_line.py +++ b/fusion_claims/models/sale_order_line.py @@ -29,10 +29,11 @@ class SaleOrderLine(models.Model): @api.depends('product_id', 'product_id.default_code') def _compute_adp_device_type(self): - """Compute ADP device type from the product's device code.""" + """Compute ADP device type and build type from the product's device code.""" ADPDevice = self.env['fusion.adp.device.code'].sudo() for line in self: device_type = '' + build_type = False if line.product_id: # Get the device code from product (default_code or custom field) device_code = line._get_adp_device_code() @@ -44,7 +45,9 @@ class SaleOrderLine(models.Model): ], limit=1) if adp_device: device_type = adp_device.device_type or '' + build_type = adp_device.build_type or False line.x_fc_adp_device_type = device_type + line.x_fc_adp_build_type = build_type # ========================================================================== # SERIAL NUMBER AND DEVICE PLACEMENT @@ -110,6 +113,16 @@ class SaleOrderLine(models.Model): store=True, help='Device type from ADP mobility manual (for approval matching)', ) + x_fc_adp_build_type = fields.Selection( + selection=[ + ('modular', 'Modular'), + ('custom_fabricated', 'Custom Fabricated'), + ], + string='Build Type', + compute='_compute_adp_device_type', + store=True, + help='Build type from ADP mobility manual (Modular or Custom Fabricated)', + ) # ========================================================================== # COMPUTED ADP PORTIONS @@ -306,6 +319,49 @@ class SaleOrderLine(models.Model): # 5. Final fallback - return default_code even if not in ADP database return self.product_id.default_code or '' + def _get_adp_code_for_report(self): + """Return the ADP device code for display on reports. + + Uses the product's x_fc_adp_device_code field (not default_code). + Returns 'NON-FUNDED' for non-ADP products. + """ + self.ensure_one() + if not self.product_id: + return 'NON-FUNDED' + if self.product_id.is_non_adp_funded(): + return 'NON-FUNDED' + product_tmpl = self.product_id.product_tmpl_id + code = '' + if hasattr(product_tmpl, 'x_fc_adp_device_code'): + code = getattr(product_tmpl, 'x_fc_adp_device_code', '') or '' + if not code and hasattr(product_tmpl, 'x_adp_code'): + code = getattr(product_tmpl, 'x_adp_code', '') or '' + if not code: + return 'NON-FUNDED' + ADPDevice = self.env['fusion.adp.device.code'].sudo() + if ADPDevice.search_count([('device_code', '=', code), ('active', '=', True)]) > 0: + return code + return 'NON-FUNDED' + + def _get_adp_device_type(self): + """Live lookup of device type from the ADP device code table. + + Returns 'No Funding Available' for non-ADP products. + """ + self.ensure_one() + if not self.product_id or self.product_id.is_non_adp_funded(): + return 'No Funding Available' + code = self._get_adp_code_for_report() + if code == 'NON-FUNDED': + return 'No Funding Available' + if self.x_fc_adp_device_type: + return self.x_fc_adp_device_type + adp_device = self.env['fusion.adp.device.code'].sudo().search([ + ('device_code', '=', code), + ('active', '=', True), + ], limit=1) + return adp_device.device_type if adp_device else 'No Funding Available' + def _get_serial_number(self): """Get serial number from mapped field or native field.""" self.ensure_one() diff --git a/fusion_claims/models/task_sync.py b/fusion_claims/models/task_sync.py deleted file mode 100644 index e985119..0000000 --- a/fusion_claims/models/task_sync.py +++ /dev/null @@ -1,438 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2024-2026 Nexa Systems Inc. -# License OPL-1 (Odoo Proprietary License v1.0) - -""" -Cross-instance technician task sync. - -Enables two Odoo instances (e.g. Westin and Mobility) that share the same -field technicians to see each other's delivery tasks, preventing double-booking. - -Remote tasks appear as read-only "shadow" records in the local calendar. -The existing _find_next_available_slot() automatically sees shadow tasks, -so collision detection works without changes to the scheduling algorithm. - -Technicians are matched across instances using the x_fc_tech_sync_id field -on res.users. Set the same value (e.g. "gordy") on both instances for the -same person -- no mapping table needed. -""" - -from odoo import models, fields, api, _ -from odoo.exceptions import UserError -import logging -import requests -from datetime import timedelta - -_logger = logging.getLogger(__name__) - -SYNC_TASK_FIELDS = [ - 'x_fc_sync_uuid', 'name', 'technician_id', 'additional_technician_ids', - 'task_type', 'status', - 'scheduled_date', 'time_start', 'time_end', 'duration_hours', - 'address_street', 'address_street2', 'address_city', 'address_zip', - 'address_lat', 'address_lng', 'priority', 'partner_id', -] - - -class FusionTaskSyncConfig(models.Model): - _name = 'fusion.task.sync.config' - _description = 'Task Sync Remote Instance' - - name = fields.Char('Instance Name', required=True, - help='e.g. Westin Healthcare, Mobility Specialties') - instance_id = fields.Char('Instance ID', required=True, - help='Short identifier, e.g. westin or mobility') - url = fields.Char('Odoo URL', required=True, - help='e.g. http://192.168.1.40:8069') - database = fields.Char('Database', required=True) - username = fields.Char('API Username', required=True) - api_key = fields.Char('API Key', required=True) - active = fields.Boolean(default=True) - last_sync = fields.Datetime('Last Successful Sync', readonly=True) - last_sync_error = fields.Text('Last Error', readonly=True) - - # ------------------------------------------------------------------ - # JSON-RPC helpers - # ------------------------------------------------------------------ - - def _jsonrpc(self, service, method, args): - """Execute a JSON-RPC call against the remote Odoo instance.""" - self.ensure_one() - url = f"{self.url.rstrip('/')}/jsonrpc" - payload = { - 'jsonrpc': '2.0', - 'method': 'call', - 'id': 1, - 'params': { - 'service': service, - 'method': method, - 'args': args, - }, - } - try: - resp = requests.post(url, json=payload, timeout=15) - resp.raise_for_status() - result = resp.json() - if result.get('error'): - err = result['error'].get('data', {}).get('message', str(result['error'])) - raise UserError(f"Remote error: {err}") - return result.get('result') - except requests.exceptions.ConnectionError: - _logger.warning("Task sync: cannot connect to %s", self.url) - return None - except requests.exceptions.Timeout: - _logger.warning("Task sync: timeout connecting to %s", self.url) - return None - - def _authenticate(self): - """Authenticate with the remote instance and return the uid.""" - self.ensure_one() - uid = self._jsonrpc('common', 'authenticate', - [self.database, self.username, self.api_key, {}]) - if not uid: - _logger.error("Task sync: authentication failed for %s", self.name) - return uid - - def _rpc(self, model, method, args, kwargs=None): - """Execute a method on the remote instance via execute_kw. - execute_kw(db, uid, password, model, method, [args], {kwargs}) - """ - self.ensure_one() - uid = self._authenticate() - if not uid: - return None - call_args = [self.database, uid, self.api_key, model, method, args] - if kwargs: - call_args.append(kwargs) - return self._jsonrpc('object', 'execute_kw', call_args) - - # ------------------------------------------------------------------ - # Tech sync ID helpers - # ------------------------------------------------------------------ - - def _get_local_tech_map(self): - """Build {local_user_id: x_fc_tech_sync_id} for all local field staff.""" - techs = self.env['res.users'].sudo().search([ - ('x_fc_is_field_staff', '=', True), - ('x_fc_tech_sync_id', '!=', False), - ('active', '=', True), - ]) - return {u.id: u.x_fc_tech_sync_id for u in techs} - - def _get_remote_tech_map(self): - """Build {x_fc_tech_sync_id: remote_user_id} from the remote instance.""" - self.ensure_one() - remote_users = self._rpc('res.users', 'search_read', [ - [('x_fc_is_field_staff', '=', True), - ('x_fc_tech_sync_id', '!=', False), - ('active', '=', True)], - ], {'fields': ['id', 'x_fc_tech_sync_id']}) - if not remote_users: - return {} - return { - ru['x_fc_tech_sync_id']: ru['id'] - for ru in remote_users - if ru.get('x_fc_tech_sync_id') - } - - def _get_local_syncid_to_uid(self): - """Build {x_fc_tech_sync_id: local_user_id} for local field staff.""" - techs = self.env['res.users'].sudo().search([ - ('x_fc_is_field_staff', '=', True), - ('x_fc_tech_sync_id', '!=', False), - ('active', '=', True), - ]) - return {u.x_fc_tech_sync_id: u.id for u in techs} - - # ------------------------------------------------------------------ - # Connection test - # ------------------------------------------------------------------ - - def action_test_connection(self): - """Test the connection to the remote instance.""" - self.ensure_one() - uid = self._authenticate() - if uid: - remote_map = self._get_remote_tech_map() - local_map = self._get_local_tech_map() - matched = set(local_map.values()) & set(remote_map.keys()) - return { - 'type': 'ir.actions.client', - 'tag': 'display_notification', - 'params': { - 'title': 'Connection Successful', - 'message': f'Connected to {self.name}. ' - f'{len(matched)} technician(s) matched by sync ID.', - 'type': 'success', - 'sticky': False, - }, - } - raise UserError(f"Cannot connect to {self.name}. Check URL, database, and API key.") - - # ------------------------------------------------------------------ - # PUSH: send local task changes to remote instance - # ------------------------------------------------------------------ - - def _get_local_instance_id(self): - """Return this instance's own ID from config parameters.""" - return self.env['ir.config_parameter'].sudo().get_param( - 'fusion_claims.sync_instance_id', '') - - @api.model - def _push_tasks(self, tasks, operation='create'): - """Push local task changes to all active remote instances. - Called from technician_task create/write overrides. - Non-blocking: errors are logged, not raised. - """ - configs = self.sudo().search([('active', '=', True)]) - if not configs: - return - local_id = configs[0]._get_local_instance_id() - if not local_id: - return - for config in configs: - try: - config._push_tasks_to_remote(tasks, operation, local_id) - except Exception: - _logger.exception("Task sync push to %s failed", config.name) - - def _push_tasks_to_remote(self, tasks, operation, local_instance_id): - """Push task data to a single remote instance. - - Maps additional_technician_ids via sync IDs so the remote instance - also blocks those technicians' schedules. - """ - self.ensure_one() - local_map = self._get_local_tech_map() - remote_map = self._get_remote_tech_map() - if not local_map or not remote_map: - return - - ctx = {'context': {'skip_task_sync': True, 'skip_travel_recalc': True}} - - for task in tasks: - sync_id = local_map.get(task.technician_id.id) - if not sync_id: - continue - remote_tech_uid = remote_map.get(sync_id) - if not remote_tech_uid: - continue - - # Map additional technicians to remote user IDs - remote_additional_ids = [] - for tech in task.additional_technician_ids: - add_sync_id = local_map.get(tech.id) - if add_sync_id: - remote_add_uid = remote_map.get(add_sync_id) - if remote_add_uid: - remote_additional_ids.append(remote_add_uid) - - task_data = { - 'x_fc_sync_uuid': task.x_fc_sync_uuid, - 'x_fc_sync_source': local_instance_id, - 'x_fc_sync_remote_id': task.id, - 'name': f"[{local_instance_id.upper()}] {task.name}", - 'technician_id': remote_tech_uid, - 'additional_technician_ids': [(6, 0, remote_additional_ids)], - 'task_type': task.task_type, - 'status': task.status, - 'scheduled_date': str(task.scheduled_date) if task.scheduled_date else False, - 'time_start': task.time_start, - 'time_end': task.time_end, - 'duration_hours': task.duration_hours, - 'address_street': task.address_street or '', - 'address_street2': task.address_street2 or '', - 'address_city': task.address_city or '', - 'address_zip': task.address_zip or '', - 'address_lat': float(task.address_lat or 0), - 'address_lng': float(task.address_lng or 0), - 'priority': task.priority or 'normal', - 'x_fc_sync_client_name': task.partner_id.name if task.partner_id else '', - } - - existing = self._rpc( - 'fusion.technician.task', 'search', - [[('x_fc_sync_uuid', '=', task.x_fc_sync_uuid)]], - {'limit': 1}) - - if operation in ('create', 'write'): - if existing: - self._rpc('fusion.technician.task', 'write', - [existing, task_data], ctx) - elif operation == 'create': - task_data['sale_order_id'] = False - self._rpc('fusion.technician.task', 'create', - [[task_data]], ctx) - - elif operation == 'unlink' and existing: - self._rpc('fusion.technician.task', 'write', - [existing, {'status': 'cancelled', 'active': False}], ctx) - - # ------------------------------------------------------------------ - # PULL: cron-based full reconciliation - # ------------------------------------------------------------------ - - @api.model - def _cron_pull_remote_tasks(self): - """Cron job: pull tasks from all active remote instances.""" - configs = self.sudo().search([('active', '=', True)]) - for config in configs: - try: - config._pull_tasks_from_remote() - config.sudo().write({ - 'last_sync': fields.Datetime.now(), - 'last_sync_error': False, - }) - except Exception as e: - _logger.exception("Task sync pull from %s failed", config.name) - config.sudo().write({'last_sync_error': str(e)}) - - def _pull_tasks_from_remote(self): - """Pull all active tasks for matched technicians from the remote instance.""" - self.ensure_one() - local_syncid_to_uid = self._get_local_syncid_to_uid() - if not local_syncid_to_uid: - return - - remote_map = self._get_remote_tech_map() - if not remote_map: - return - - matched_sync_ids = set(local_syncid_to_uid.keys()) & set(remote_map.keys()) - if not matched_sync_ids: - _logger.info("Task sync: no matched technicians between local and %s", self.name) - return - - remote_tech_ids = [remote_map[sid] for sid in matched_sync_ids] - remote_syncid_by_uid = {v: k for k, v in remote_map.items()} - - cutoff = fields.Date.today() - timedelta(days=7) - remote_tasks = self._rpc( - 'fusion.technician.task', 'search_read', - [[ - '|', - ('technician_id', 'in', remote_tech_ids), - ('additional_technician_ids', 'in', remote_tech_ids), - ('scheduled_date', '>=', str(cutoff)), - ('x_fc_sync_source', '=', False), - ]], - {'fields': SYNC_TASK_FIELDS + ['id']}) - - if remote_tasks is None: - return - - Task = self.env['fusion.technician.task'].sudo().with_context( - skip_task_sync=True, skip_travel_recalc=True) - - remote_uuids = set() - for rt in remote_tasks: - sync_uuid = rt.get('x_fc_sync_uuid') - if not sync_uuid: - continue - remote_uuids.add(sync_uuid) - - remote_tech_raw = rt['technician_id'] - remote_uid = remote_tech_raw[0] if isinstance(remote_tech_raw, (list, tuple)) else remote_tech_raw - tech_sync_id = remote_syncid_by_uid.get(remote_uid) - local_uid = local_syncid_to_uid.get(tech_sync_id) if tech_sync_id else None - if not local_uid: - continue - - partner_raw = rt.get('partner_id') - client_name = partner_raw[1] if isinstance(partner_raw, (list, tuple)) and len(partner_raw) > 1 else '' - - # Map additional technicians from remote to local - local_additional_ids = [] - remote_add_raw = rt.get('additional_technician_ids', []) - if remote_add_raw and isinstance(remote_add_raw, list): - for add_uid in remote_add_raw: - add_sync_id = remote_syncid_by_uid.get(add_uid) - if add_sync_id: - local_add_uid = local_syncid_to_uid.get(add_sync_id) - if local_add_uid: - local_additional_ids.append(local_add_uid) - - vals = { - 'x_fc_sync_uuid': sync_uuid, - 'x_fc_sync_source': self.instance_id, - 'x_fc_sync_remote_id': rt['id'], - 'name': f"[{self.instance_id.upper()}] {rt.get('name', '')}", - 'technician_id': local_uid, - 'additional_technician_ids': [(6, 0, local_additional_ids)], - 'task_type': rt.get('task_type', 'delivery'), - 'status': rt.get('status', 'scheduled'), - 'scheduled_date': rt.get('scheduled_date'), - 'time_start': rt.get('time_start', 9.0), - 'time_end': rt.get('time_end', 10.0), - 'duration_hours': rt.get('duration_hours', 1.0), - 'address_street': rt.get('address_street', ''), - 'address_street2': rt.get('address_street2', ''), - 'address_city': rt.get('address_city', ''), - 'address_zip': rt.get('address_zip', ''), - 'address_lat': rt.get('address_lat', 0), - 'address_lng': rt.get('address_lng', 0), - 'priority': rt.get('priority', 'normal'), - 'x_fc_sync_client_name': client_name, - } - - existing = Task.search([('x_fc_sync_uuid', '=', sync_uuid)], limit=1) - if existing: - existing.write(vals) - else: - vals['sale_order_id'] = False - Task.create([vals]) - - stale_shadows = Task.search([ - ('x_fc_sync_source', '=', self.instance_id), - ('x_fc_sync_uuid', 'not in', list(remote_uuids)), - ('scheduled_date', '>=', str(cutoff)), - ('active', '=', True), - ]) - if stale_shadows: - stale_shadows.write({'active': False, 'status': 'cancelled'}) - _logger.info("Deactivated %d stale shadow tasks from %s", - len(stale_shadows), self.instance_id) - - # ------------------------------------------------------------------ - # CLEANUP - # ------------------------------------------------------------------ - - @api.model - def _cron_cleanup_old_shadows(self): - """Remove shadow tasks older than 30 days (completed/cancelled).""" - cutoff = fields.Date.today() - timedelta(days=30) - old_shadows = self.env['fusion.technician.task'].sudo().search([ - ('x_fc_sync_source', '!=', False), - ('scheduled_date', '<', str(cutoff)), - ('status', 'in', ['completed', 'cancelled']), - ]) - if old_shadows: - count = len(old_shadows) - old_shadows.unlink() - _logger.info("Cleaned up %d old shadow tasks", count) - - # ------------------------------------------------------------------ - # Manual trigger - # ------------------------------------------------------------------ - - def action_sync_now(self): - """Manually trigger a full sync for this config.""" - self.ensure_one() - self._pull_tasks_from_remote() - self.sudo().write({ - 'last_sync': fields.Datetime.now(), - 'last_sync_error': False, - }) - shadow_count = self.env['fusion.technician.task'].sudo().search_count([ - ('x_fc_sync_source', '=', self.instance_id), - ]) - return { - 'type': 'ir.actions.client', - 'tag': 'display_notification', - 'params': { - 'title': 'Sync Complete', - 'message': f'Synced from {self.name}. {shadow_count} shadow task(s) now visible.', - 'type': 'success', - 'sticky': False, - }, - } diff --git a/fusion_claims/models/technician_location.py b/fusion_claims/models/technician_location.py deleted file mode 100644 index c680fb2..0000000 --- a/fusion_claims/models/technician_location.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2024-2026 Nexa Systems Inc. -# License OPL-1 (Odoo Proprietary License v1.0) - -""" -Fusion Technician Location -GPS location logging for field technicians. -""" - -from odoo import models, fields, api, _ -import logging - -_logger = logging.getLogger(__name__) - - -class FusionTechnicianLocation(models.Model): - _name = 'fusion.technician.location' - _description = 'Technician Location Log' - _order = 'logged_at desc' - - user_id = fields.Many2one( - 'res.users', - string='Technician', - required=True, - index=True, - ondelete='cascade', - ) - latitude = fields.Float( - string='Latitude', - digits=(10, 7), - required=True, - ) - longitude = fields.Float( - string='Longitude', - digits=(10, 7), - required=True, - ) - accuracy = fields.Float( - string='Accuracy (m)', - help='GPS accuracy in meters', - ) - logged_at = fields.Datetime( - string='Logged At', - default=fields.Datetime.now, - required=True, - index=True, - ) - source = fields.Selection([ - ('portal', 'Portal'), - ('app', 'Mobile App'), - ], string='Source', default='portal') - - @api.model - def log_location(self, latitude, longitude, accuracy=None): - """Log the current user's location. Called from portal JS.""" - return self.sudo().create({ - 'user_id': self.env.user.id, - 'latitude': latitude, - 'longitude': longitude, - 'accuracy': accuracy or 0, - 'source': 'portal', - }) - - @api.model - def get_latest_locations(self): - """Get the most recent location for each technician (for map view).""" - self.env.cr.execute(""" - SELECT DISTINCT ON (user_id) - user_id, latitude, longitude, accuracy, logged_at - FROM fusion_technician_location - WHERE logged_at > NOW() - INTERVAL '24 hours' - ORDER BY user_id, logged_at DESC - """) - rows = self.env.cr.dictfetchall() - result = [] - for row in rows: - user = self.env['res.users'].sudo().browse(row['user_id']) - result.append({ - 'user_id': row['user_id'], - 'name': user.name, - 'latitude': row['latitude'], - 'longitude': row['longitude'], - 'accuracy': row['accuracy'], - 'logged_at': str(row['logged_at']), - }) - return result - - @api.model - def _cron_cleanup_old_locations(self): - """Remove location logs based on configurable retention setting. - - Setting (fusion_claims.location_retention_days): - - Empty / not set => keep 30 days (default) - - "0" => delete at end of day (keep today only) - - "1" .. "N" => keep for N days - """ - ICP = self.env['ir.config_parameter'].sudo() - raw = (ICP.get_param('fusion_claims.location_retention_days') or '').strip() - - if raw == '': - retention_days = 30 # default: 1 month - else: - try: - retention_days = max(int(raw), 0) - except (ValueError, TypeError): - retention_days = 30 - - cutoff = fields.Datetime.subtract(fields.Datetime.now(), days=retention_days) - old_records = self.search([('logged_at', '<', cutoff)]) - count = len(old_records) - if count: - old_records.unlink() - _logger.info( - "Cleaned up %d technician location records (retention=%d days)", - count, retention_days, - ) diff --git a/fusion_claims/models/technician_task.py b/fusion_claims/models/technician_task.py index 20a263b..3c18d8e 100644 --- a/fusion_claims/models/technician_task.py +++ b/fusion_claims/models/technician_task.py @@ -3,160 +3,28 @@ # License OPL-1 (Odoo Proprietary License v1.0) """ -Fusion Technician Task -Scheduling and task management for field technicians. -Replaces Monday.com for technician schedule tracking. +Fusion Technician Task - Claims Extension +Adds sale order, purchase order, and rental inspection +features to the base fusion.technician.task model. """ from odoo import models, fields, api, _ from odoo.exceptions import UserError, ValidationError -from odoo.osv import expression from markupsafe import Markup import logging -import json -import uuid -import requests -from datetime import datetime as dt_datetime, timedelta -import urllib.parse _logger = logging.getLogger(__name__) -class FusionTechnicianTask(models.Model): - _name = 'fusion.technician.task' - _description = 'Technician Task' - _order = 'scheduled_date, sequence, time_start, id' - _inherit = ['mail.thread', 'mail.activity.mixin'] - _rec_name = 'name' - - def _compute_display_name(self): - """Richer display name: Client - Type | 9:00 AM - 10:00 AM [+2 techs].""" - type_labels = dict(self._fields['task_type'].selection) - for task in self: - client = task.x_fc_sync_client_name if task.x_fc_sync_source else (task.partner_id.name or '') - ttype = type_labels.get(task.task_type, task.task_type or '') - start = self._float_to_time_str(task.time_start) - end = self._float_to_time_str(task.time_end) - parts = [client, ttype] - label = ' - '.join(p for p in parts if p) - if start and end: - label += f' | {start} - {end}' - extra = len(task.additional_technician_ids) - if extra: - label += f' [+{extra} tech{"s" if extra > 1 else ""}]' - task.display_name = label or task.name +class FusionTechnicianTaskClaims(models.Model): + _inherit = 'fusion.technician.task' # ------------------------------------------------------------------ - # STORE HOURS HELPER + # LINKED ORDER FIELDS # ------------------------------------------------------------------ - def _get_store_hours(self): - """Return (open_hour, close_hour) from settings. Defaults 9.0 / 18.0.""" - ICP = self.env['ir.config_parameter'].sudo() - try: - open_h = float(ICP.get_param('fusion_claims.store_open_hour', '9.0') or '9.0') - except (ValueError, TypeError): - open_h = 9.0 - try: - close_h = float(ICP.get_param('fusion_claims.store_close_hour', '18.0') or '18.0') - except (ValueError, TypeError): - close_h = 18.0 - return (open_h, close_h) - - # ------------------------------------------------------------------ - # CORE FIELDS - # ------------------------------------------------------------------ - name = fields.Char( - string='Task Reference', - required=True, - copy=False, - readonly=True, - default=lambda self: _('New'), - ) - active = fields.Boolean(default=True) - - # Cross-instance sync fields - x_fc_sync_source = fields.Char( - 'Source Instance', readonly=True, index=True, - help='Origin instance ID if this is a synced shadow task (e.g. westin, mobility)', - ) - x_fc_sync_remote_id = fields.Integer( - 'Remote Task ID', readonly=True, - help='ID of the task on the remote instance', - ) - x_fc_sync_uuid = fields.Char( - 'Sync UUID', readonly=True, index=True, copy=False, - help='Unique ID for cross-instance deduplication', - ) - x_fc_is_shadow = fields.Boolean( - 'Shadow Task', compute='_compute_is_shadow', store=True, - help='True if this task was synced from another instance', - ) - x_fc_sync_client_name = fields.Char( - 'Synced Client Name', readonly=True, - help='Client name from the remote instance (shadow tasks only)', - ) - - x_fc_source_label = fields.Char( - 'Source', compute='_compute_is_shadow', store=True, - ) - - @api.depends('x_fc_sync_source') - def _compute_is_shadow(self): - local_id = self.env['ir.config_parameter'].sudo().get_param( - 'fusion_claims.sync_instance_id', '') - for task in self: - task.x_fc_is_shadow = bool(task.x_fc_sync_source) - task.x_fc_source_label = task.x_fc_sync_source or local_id - - technician_id = fields.Many2one( - 'res.users', - string='Technician', - required=True, - tracking=True, - domain="[('x_fc_is_field_staff', '=', True)]", - help='Lead technician responsible for this task', - ) - technician_name = fields.Char( - related='technician_id.name', - string='Technician Name', - store=True, - ) - additional_technician_ids = fields.Many2many( - 'res.users', - 'technician_task_additional_tech_rel', - 'task_id', - 'user_id', - string='Additional Technicians', - domain="[('x_fc_is_field_staff', '=', True)]", - tracking=True, - help='Additional technicians assigned to assist on this task', - ) - all_technician_ids = fields.Many2many( - 'res.users', - compute='_compute_all_technician_ids', - string='All Technicians', - help='Lead + additional technicians combined', - ) - additional_tech_count = fields.Integer( - compute='_compute_all_technician_ids', - string='Extra Techs', - ) - all_technician_names = fields.Char( - compute='_compute_all_technician_ids', - string='All Technician Names', - ) - - @api.depends('technician_id', 'additional_technician_ids') - def _compute_all_technician_ids(self): - for task in self: - all_techs = task.technician_id | task.additional_technician_ids - task.all_technician_ids = all_techs - task.additional_tech_count = len(task.additional_technician_ids) - task.all_technician_names = ', '.join(all_techs.mapped('name')) - sale_order_id = fields.Many2one( 'sale.order', - string='Related Case', + string='Related SO', tracking=True, ondelete='restrict', help='Sale order / case linked to this task', @@ -169,7 +37,7 @@ class FusionTechnicianTask(models.Model): purchase_order_id = fields.Many2one( 'purchase.order', - string='Related Purchase Order', + string='Related PO', tracking=True, ondelete='restrict', help='Purchase order linked to this task (e.g. manufacturer pickup)', @@ -180,251 +48,8 @@ class FusionTechnicianTask(models.Model): store=True, ) - task_type = fields.Selection([ - ('delivery', 'Delivery'), - ('repair', 'Repair'), - ('pickup', 'Pickup'), - ('troubleshoot', 'Troubleshooting'), - ('assessment', 'Assessment'), - ('installation', 'Installation'), - ('maintenance', 'Maintenance'), - ('ltc_visit', 'LTC Visit'), - ('other', 'Other'), - ], string='Task Type', required=True, default='delivery', tracking=True) - - facility_id = fields.Many2one( - 'fusion.ltc.facility', - string='LTC Facility', - tracking=True, - help='LTC Home for this visit', - ) - # ------------------------------------------------------------------ - # SCHEDULING - # ------------------------------------------------------------------ - scheduled_date = fields.Date( - string='Scheduled Date', - tracking=True, - default=fields.Date.context_today, - index=True, - ) - time_start = fields.Float( - string='Start Time', - help='Start time in hours (e.g. 9.5 = 9:30 AM)', - default=9.0, - ) - time_end = fields.Float( - string='End Time', - help='End time in hours (e.g. 10.5 = 10:30 AM)', - default=10.0, - ) - time_start_display = fields.Char( - string='Start', - compute='_compute_time_displays', - ) - time_end_display = fields.Char( - string='End', - compute='_compute_time_displays', - ) - # Legacy 12h selection fields -- kept for DB compatibility, hidden on form - time_start_12h = fields.Selection( - selection='_get_time_selection', - string='Start Time (12h)', - compute='_compute_time_12h', - inverse='_inverse_time_start_12h', - store=True, - ) - time_end_12h = fields.Selection( - selection='_get_time_selection', - string='End Time (12h)', - compute='_compute_time_12h', - inverse='_inverse_time_end_12h', - store=True, - ) - sequence = fields.Integer( - string='Sequence', - default=10, - help='Order of task within the day', - ) - duration_hours = fields.Float( - string='Duration', - default=1.0, - help='Task duration in hours. Auto-calculates end time.', - ) - - # Task type -> default duration mapping - TASK_TYPE_DURATIONS = { - 'delivery': 1.0, - 'repair': 2.0, - 'pickup': 0.5, - 'troubleshoot': 1.5, - 'assessment': 1.5, - 'installation': 2.0, - 'maintenance': 1.5, - 'ltc_visit': 3.0, - 'other': 1.0, - } - - # Previous task travel warning banner - prev_task_summary_html = fields.Html( - string='Previous Task', - compute='_compute_prev_task_summary', - sanitize=False, - ) - - # Datetime fields for calendar view (computed from date + float time) - datetime_start = fields.Datetime( - string='Start', - compute='_compute_datetimes', - inverse='_inverse_datetime_start', - store=True, - help='Combined start datetime for calendar display', - ) - datetime_end = fields.Datetime( - string='End', - compute='_compute_datetimes', - inverse='_inverse_datetime_end', - store=True, - help='Combined end datetime for calendar display', - ) - - # Schedule info helper for the form - schedule_info_html = fields.Html( - string='Schedule Info', - compute='_compute_schedule_info', - sanitize=False, - ) - - # ------------------------------------------------------------------ - # STATUS - # ------------------------------------------------------------------ - status = fields.Selection([ - ('pending', 'Pending'), - ('scheduled', 'Scheduled'), - ('en_route', 'En Route'), - ('in_progress', 'In Progress'), - ('completed', 'Completed'), - ('cancelled', 'Cancelled'), - ('rescheduled', 'Rescheduled'), - ], string='Status', default='scheduled', required=True, tracking=True, index=True) - - priority = fields.Selection([ - ('0', 'Normal'), - ('1', 'Urgent'), - ('2', 'Emergency'), - ], string='Priority', default='0') - - color = fields.Integer( - string='Color Index', - compute='_compute_color', - ) - - # ------------------------------------------------------------------ - # CLIENT / ADDRESS - # ------------------------------------------------------------------ - partner_id = fields.Many2one( - 'res.partner', - string='Client', - tracking=True, - help='Client for this task', - ) - partner_phone = fields.Char( - related='partner_id.phone', - string='Client Phone', - ) - - # Address fields - computed from shipping address or manually set - address_partner_id = fields.Many2one( - 'res.partner', - string='Task Address', - help='Partner record containing the task address (usually shipping address)', - ) - address_street = fields.Char(string='Street') - address_street2 = fields.Char(string='Unit/Suite #') - address_city = fields.Char(string='City') - address_state_id = fields.Many2one('res.country.state', string='Province') - address_zip = fields.Char(string='Postal Code') - address_buzz_code = fields.Char(string='Buzz Code', help='Building buzzer code for entry') - address_display = fields.Text( - string='Full Address', - compute='_compute_address_display', - ) - - # Geocoding - address_lat = fields.Float(string='Latitude', digits=(10, 7)) - address_lng = fields.Float(string='Longitude', digits=(10, 7)) - - # ------------------------------------------------------------------ - # TASK DETAILS - # ------------------------------------------------------------------ - description = fields.Text( - string='Task Description', - help='What needs to be done', - ) - equipment_needed = fields.Text( - string='Equipment / Materials Needed', - help='Tools and materials the technician should bring', - ) - pod_required = fields.Boolean( - string='POD Required', - default=False, - help='Proof of Delivery signature required', - ) - - # ------------------------------------------------------------------ - # COMPLETION - # ------------------------------------------------------------------ - completion_notes = fields.Html( - string='Completion Notes', - help='Notes from the technician about what was done', - ) - completion_datetime = fields.Datetime( - string='Completed At', - tracking=True, - ) - voice_note_audio = fields.Binary( - string='Voice Recording', - attachment=True, - ) - voice_note_transcription = fields.Text( - string='Voice Transcription', - ) - - # ------------------------------------------------------------------ - # TRAVEL - # ------------------------------------------------------------------ - travel_time_minutes = fields.Integer( - string='Travel Time (min)', - help='Estimated travel time from previous task in minutes', - ) - travel_distance_km = fields.Float( - string='Travel Distance (km)', - digits=(8, 1), - ) - travel_origin = fields.Char( - string='Travel From', - help='Origin address for travel calculation', - ) - previous_task_id = fields.Many2one( - 'fusion.technician.task', - string='Previous Task', - help='The task before this one in the schedule (for travel calculation)', - ) - - # ------------------------------------------------------------------ - # PUSH NOTIFICATION TRACKING - # ------------------------------------------------------------------ - push_notified = fields.Boolean( - string='Push Notified', - default=False, - help='Whether a push notification was sent for this task', - ) - push_notified_datetime = fields.Datetime( - string='Notified At', - ) - - # ------------------------------------------------------------------ - # RENTAL INSPECTION (added by fusion_rental) + # RENTAL INSPECTION # ------------------------------------------------------------------ rental_inspection_condition = fields.Selection([ ('excellent', 'Excellent'), @@ -448,533 +73,9 @@ class FusionTechnicianTask(models.Model): ) # ------------------------------------------------------------------ - # COMPUTED FIELDS + # ONCHANGES # ------------------------------------------------------------------ - # ------------------------------------------------------------------ - # SLOT AVAILABILITY HELPERS - # ------------------------------------------------------------------ - - def _find_next_available_slot(self, tech_id, date, preferred_start=9.0, - duration=1.0, exclude_task_id=False, - dest_lat=0, dest_lng=0): - """Find the next available time slot for a technician on a given date. - - Scans all non-cancelled tasks for that tech+date, sorts them, and - walks through the day (9 AM - 6 PM) looking for a gap that fits - the requested duration PLUS travel time from the previous task. - - :param tech_id: res.users id of the technician - :param date: date object for the day to check - :param preferred_start: float hour to start looking from (default 9.0) - :param duration: required slot length in hours (default 1.0) - :param exclude_task_id: task id to exclude (when editing an existing task) - :param dest_lat: latitude of the destination (new task location) - :param dest_lng: longitude of the destination (new task location) - :returns: (start_float, end_float) or (False, False) if fully booked - """ - STORE_OPEN, STORE_CLOSE = self._get_store_hours() - - if not tech_id or not date: - return (preferred_start, preferred_start + duration) - - domain = [ - '|', - ('technician_id', '=', tech_id), - ('additional_technician_ids', 'in', [tech_id]), - ('scheduled_date', '=', date), - ('status', 'not in', ['cancelled']), - ] - if exclude_task_id: - domain.append(('id', '!=', exclude_task_id)) - - booked = self.sudo().search(domain, order='time_start') - - # Build sorted list of (start, end, lat, lng) intervals - intervals = [] - for b in booked: - intervals.append(( - max(b.time_start, STORE_OPEN), - min(b.time_end, STORE_CLOSE), - b.address_lat or 0, - b.address_lng or 0, - )) - - def _travel_hours(from_lat, from_lng, to_lat, to_lng): - """Calculate travel time in hours between two locations. - Returns 0 if coordinates are missing. Rounds up to 15-min.""" - if not from_lat or not from_lng or not to_lat or not to_lng: - return 0 - travel_min = self._quick_travel_time( - from_lat, from_lng, to_lat, to_lng) - if travel_min > 0: - import math - return math.ceil(travel_min / 15.0) * 0.25 - return 0 - - def _travel_from_prev(iv_lat, iv_lng): - """Travel from a previous booked task TO the new task.""" - return _travel_hours(iv_lat, iv_lng, dest_lat, dest_lng) - - def _travel_to_next(next_lat, next_lng): - """Travel FROM the new task TO the next booked task.""" - return _travel_hours(dest_lat, dest_lng, next_lat, next_lng) - - def _check_gap_fits(cursor, dur, idx): - """Check if a slot at 'cursor' for 'dur' hours fits before - the interval at index 'idx' (accounting for travel TO that task).""" - if idx >= len(intervals): - return cursor + dur <= STORE_CLOSE - next_start, _ne, next_lat, next_lng = intervals[idx] - travel_fwd = _travel_to_next(next_lat, next_lng) - return cursor + dur + travel_fwd <= next_start - - # Walk through gaps, starting from preferred_start - cursor = max(preferred_start, STORE_OPEN) - - for i, (iv_start, iv_end, iv_lat, iv_lng) in enumerate(intervals): - if cursor + duration <= iv_start: - # Check travel time from new task end TO next booked task - if _check_gap_fits(cursor, duration, i): - return (cursor, cursor + duration) - # Not enough travel time -- try pushing start earlier or skip - # If we can't fit here, fall through to jump past this interval - # Jump past this booked interval + travel buffer from prev to new - new_cursor = max(cursor, iv_end) - travel = _travel_from_prev(iv_lat, iv_lng) - new_cursor += travel - # Snap to nearest 15 min - new_cursor = round(new_cursor * 4) / 4 - cursor = new_cursor - - # Check gap after last interval (no next task, so no forward travel needed) - if cursor + duration <= STORE_CLOSE: - return (cursor, cursor + duration) - - # No gap found from preferred_start onward -- wrap and try from start - if preferred_start > STORE_OPEN: - cursor = STORE_OPEN - for i, (iv_start, iv_end, iv_lat, iv_lng) in enumerate(intervals): - if cursor + duration <= iv_start: - if _check_gap_fits(cursor, duration, i): - return (cursor, cursor + duration) - new_cursor = max(cursor, iv_end) - travel = _travel_from_prev(iv_lat, iv_lng) - new_cursor += travel - new_cursor = round(new_cursor * 4) / 4 - cursor = new_cursor - if cursor + duration <= STORE_CLOSE: - return (cursor, cursor + duration) - - return (False, False) - - def _get_available_gaps(self, tech_id, date, exclude_task_id=False): - """Return a list of available (start, end) gaps for a technician on a date. - - Used by schedule_info_html to show green "available" badges. - Considers tasks where the tech is either lead or additional. - """ - STORE_OPEN, STORE_CLOSE = self._get_store_hours() - - if not tech_id or not date: - return [(STORE_OPEN, STORE_CLOSE)] - - domain = [ - '|', - ('technician_id', '=', tech_id), - ('additional_technician_ids', 'in', [tech_id]), - ('scheduled_date', '=', date), - ('status', 'not in', ['cancelled']), - ] - if exclude_task_id: - domain.append(('id', '!=', exclude_task_id)) - - booked = self.sudo().search(domain, order='time_start') - intervals = [(max(b.time_start, STORE_OPEN), min(b.time_end, STORE_CLOSE)) - for b in booked] - - gaps = [] - cursor = STORE_OPEN - for iv_start, iv_end in intervals: - if cursor < iv_start: - gaps.append((cursor, iv_start)) - cursor = max(cursor, iv_end) - if cursor < STORE_CLOSE: - gaps.append((cursor, STORE_CLOSE)) - return gaps - - @api.model - def _get_time_selection(self): - """Generate 12-hour time slots every 15 minutes, store hours only (9 AM - 6 PM).""" - times = [] - for hour in range(9, 18): # 9 AM to 5:45 PM - for minute in (0, 15, 30, 45): - float_val = hour + minute / 60.0 - key = f'{float_val:.2f}' - period = 'AM' if hour < 12 else 'PM' - display_hour = hour % 12 or 12 - label = f'{display_hour}:{minute:02d} {period}' - times.append((key, label)) - # Add 6:00 PM as end-time option - times.append(('18.00', '6:00 PM')) - return times - - @api.depends('time_start', 'time_end') - def _compute_time_12h(self): - """Sync the 12h selection fields from the raw float values.""" - for task in self: - task.time_start_12h = f'{(task.time_start or 9.0):.2f}' - task.time_end_12h = f'{(task.time_end or 10.0):.2f}' - - def _inverse_time_start_12h(self): - for task in self: - if task.time_start_12h: - task.time_start = float(task.time_start_12h) - - def _inverse_time_end_12h(self): - for task in self: - if task.time_end_12h: - task.time_end = float(task.time_end_12h) - - @api.depends('time_start', 'time_end') - def _compute_time_displays(self): - """Convert float hours to readable time strings.""" - for task in self: - task.time_start_display = self._float_to_time_str(task.time_start) - task.time_end_display = self._float_to_time_str(task.time_end) - - @api.onchange('task_type') - def _onchange_task_type_duration(self): - """Set default duration based on task type.""" - if self.task_type: - self.duration_hours = self.TASK_TYPE_DURATIONS.get(self.task_type, 1.0) - # Also recalculate end time - if self.time_start: - _open, close = self._get_store_hours() - self.time_end = min(self.time_start + self.duration_hours, close) - - @api.onchange('time_start', 'duration_hours') - def _onchange_compute_end_time(self): - """Auto-compute end time from start + duration. Also run overlap check.""" - if self.time_start and self.duration_hours: - _open, close = self._get_store_hours() - new_end = min(self.time_start + self.duration_hours, close) - self.time_end = new_end - # Run overlap snap if we have enough data - if self.technician_id and self.scheduled_date and self.time_start and self.time_end: - result = self._snap_if_overlap() - if result: - return result - - @api.depends('scheduled_date', 'time_start', 'time_end') - def _compute_datetimes(self): - """Combine date + float time into proper Datetime fields for calendar. - time_start/time_end are LOCAL hours; datetime_start/end must be UTC for Odoo.""" - import pytz - user_tz = pytz.timezone(self.env.user.tz or 'UTC') - for task in self: - if task.scheduled_date: - # Build local datetime, then convert to UTC - base = dt_datetime.combine(task.scheduled_date, dt_datetime.min.time()) - store_open, _close = task._get_store_hours() - local_start = user_tz.localize(base + timedelta(hours=task.time_start or store_open)) - local_end = user_tz.localize(base + timedelta(hours=task.time_end or (store_open + 1.0))) - task.datetime_start = local_start.astimezone(pytz.utc).replace(tzinfo=None) - task.datetime_end = local_end.astimezone(pytz.utc).replace(tzinfo=None) - else: - task.datetime_start = False - task.datetime_end = False - - def _inverse_datetime_start(self): - """When datetime_start is changed (e.g. from calendar drag), update date + time.""" - import pytz - user_tz = pytz.timezone(self.env.user.tz or 'UTC') - for task in self: - if task.datetime_start: - local_dt = pytz.utc.localize(task.datetime_start).astimezone(user_tz) - task.scheduled_date = local_dt.date() - task.time_start = local_dt.hour + local_dt.minute / 60.0 - - def _inverse_datetime_end(self): - """When datetime_end is changed (e.g. from calendar resize), update time_end.""" - import pytz - user_tz = pytz.timezone(self.env.user.tz or 'UTC') - for task in self: - if task.datetime_end: - local_dt = pytz.utc.localize(task.datetime_end).astimezone(user_tz) - task.time_end = local_dt.hour + local_dt.minute / 60.0 - - @api.depends('technician_id', 'scheduled_date') - def _compute_schedule_info(self): - """Show booked + available time slots for the technician on the selected date.""" - for task in self: - if not task.technician_id or not task.scheduled_date: - task.schedule_info_html = '' - continue - - exclude_id = task.id if task.id else 0 - # Find other tasks for the same technician+date (lead or additional) - others = self.sudo().search([ - '|', - ('technician_id', '=', task.technician_id.id), - ('additional_technician_ids', 'in', [task.technician_id.id]), - ('scheduled_date', '=', task.scheduled_date), - ('status', 'not in', ['cancelled']), - ('id', '!=', exclude_id), - ], order='time_start') - - if not others: - s_open, s_close = self._get_store_hours() - open_str = self._float_to_time_str(s_open) - close_str = self._float_to_time_str(s_close) - task.schedule_info_html = Markup( - f'
' - f' All slots available ({open_str} - {close_str})
' - ) - continue - - # Booked badges - booked_lines = [] - for o in others: - start_str = self._float_to_time_str(o.time_start) - end_str = self._float_to_time_str(o.time_end) - type_label = dict(self._fields['task_type'].selection).get(o.task_type, o.task_type) - client_name = o.partner_id.name or '' - booked_lines.append( - f'' - f'{start_str} - {end_str} ({type_label}{" - " + client_name if client_name else ""})' - f'' - ) - - # Available gaps badges - gaps = self._get_available_gaps( - task.technician_id.id, task.scheduled_date, - exclude_task_id=exclude_id, - ) - avail_lines = [] - for g_start, g_end in gaps: - # Only show gaps >= 15 min - if g_end - g_start >= 0.25: - avail_lines.append( - f'' - f'{self._float_to_time_str(g_start)} - {self._float_to_time_str(g_end)}' - f'' - ) - - html_parts = [ - '
', - ' Booked: ', - ' '.join(booked_lines), - ] - if avail_lines: - html_parts.append( - '
' - 'Available: ' - + ' '.join(avail_lines) - ) - elif not avail_lines: - html_parts.append( - '
' - 'Fully booked' - ) - html_parts.append('
') - - task.schedule_info_html = Markup(''.join(html_parts)) - - @api.depends('technician_id', 'scheduled_date', 'time_start', - 'address_lat', 'address_lng', 'address_street') - def _compute_prev_task_summary(self): - """Show previous task info + travel time warning with color coding.""" - for task in self: - if not task.technician_id or not task.scheduled_date: - task.prev_task_summary_html = '' - continue - - exclude_id = task.id if task.id else 0 - # Find the task that ends just before this one starts (lead or additional) - prev_tasks = self.sudo().search([ - '|', - ('technician_id', '=', task.technician_id.id), - ('additional_technician_ids', 'in', [task.technician_id.id]), - ('scheduled_date', '=', task.scheduled_date), - ('status', 'not in', ['cancelled']), - ('id', '!=', exclude_id), - ('time_end', '<=', task.time_start or 99.0), - ], order='time_end desc', limit=1) - - if not prev_tasks: - # Check if this is the first task of the day -- show start location info - task.prev_task_summary_html = Markup( - '
' - ' First task of the day -- ' - 'travel calculated from start location.
' - ) - continue - - prev = prev_tasks[0] - prev_start = self._float_to_time_str(prev.time_start) - prev_end = self._float_to_time_str(prev.time_end) - type_label = dict(self._fields['task_type'].selection).get( - prev.task_type, prev.task_type or '') - client_name = prev.partner_id.name or '' - prev_addr = prev.address_display or 'No address' - - # Calculate gap between prev task end and this task start - s_open, _s_close = self._get_store_hours() - gap_hours = (task.time_start or s_open) - (prev.time_end or s_open) - gap_minutes = int(gap_hours * 60) - - # Try to get travel time if both have coordinates - travel_minutes = 0 - travel_text = '' - if (prev.address_lat and prev.address_lng and - task.address_lat and task.address_lng): - travel_minutes = self._quick_travel_time( - prev.address_lat, prev.address_lng, - task.address_lat, task.address_lng, - ) - if travel_minutes > 0: - travel_text = f'{travel_minutes} min drive' - else: - travel_text = 'Could not calculate travel time' - elif prev.address_street and task.address_street: - travel_text = 'Save to calculate travel time' - else: - travel_text = 'Address missing -- cannot calculate travel' - - # Determine color coding - if travel_minutes > 0 and gap_minutes >= travel_minutes: - bg_class = 'alert-success' # Green -- enough time - icon = 'fa-check-circle' - status_text = ( - f'{gap_minutes} min gap -- enough travel time ' - f'(~{travel_minutes} min drive)' - ) - elif travel_minutes > 0 and gap_minutes > 0: - bg_class = 'alert-warning' # Yellow -- tight - icon = 'fa-exclamation-triangle' - status_text = ( - f'{gap_minutes} min gap -- tight! ' - f'Travel is ~{travel_minutes} min drive' - ) - elif travel_minutes > 0 and gap_minutes <= 0: - bg_class = 'alert-danger' # Red -- impossible - icon = 'fa-times-circle' - status_text = ( - f'No gap! Previous task ends at {prev_end}. ' - f'Travel is ~{travel_minutes} min drive' - ) - else: - bg_class = 'alert-info' # Blue -- no travel data yet - icon = 'fa-info-circle' - status_text = travel_text - - html = ( - f'
' - f' ' - f'Previous: {prev.name} ' - f'({type_label}) {prev_start} - {prev_end}' - f'{" -- " + client_name if client_name else ""}' - f'
' - f' {prev_addr}' - f'
' - f' {status_text}' - f'
' - ) - task.prev_task_summary_html = Markup(html) - - def _quick_travel_time(self, from_lat, from_lng, to_lat, to_lng): - """Quick inline travel time calculation using Google Distance Matrix API. - Returns travel time in minutes, or 0 if unavailable.""" - try: - api_key = self.env['ir.config_parameter'].sudo().get_param( - 'fusion_claims.google_maps_api_key', '') - if not api_key: - return 0 - - url = 'https://maps.googleapis.com/maps/api/distancematrix/json' - params = { - 'origins': f'{from_lat},{from_lng}', - 'destinations': f'{to_lat},{to_lng}', - 'mode': 'driving', - 'avoid': 'tolls', - 'departure_time': 'now', - 'key': api_key, - } - resp = requests.get(url, params=params, timeout=5) - data = resp.json() - if data.get('status') == 'OK': - elements = data['rows'][0]['elements'][0] - if elements.get('status') == 'OK': - # Use duration_in_traffic if available, else duration - duration = elements.get( - 'duration_in_traffic', elements.get('duration', {})) - seconds = duration.get('value', 0) - return max(1, int(seconds / 60)) - except Exception: - _logger.warning('Failed to calculate travel time', exc_info=True) - return 0 - - @api.depends('status') - def _compute_color(self): - color_map = { - 'pending': 5, # purple - 'scheduled': 0, # grey - 'en_route': 4, # blue - 'in_progress': 2, # orange - 'completed': 10, # green - 'cancelled': 1, # red - 'rescheduled': 3, # yellow - } - for task in self: - task.color = color_map.get(task.status, 0) - - @api.depends('address_street', 'address_street2', 'address_city', - 'address_state_id', 'address_zip') - def _compute_address_display(self): - for task in self: - street = task.address_street or '' - # If the street field already contains a full address (has a comma), - # use it directly -- Google Places stores the formatted address here. - if ',' in street and ( - (task.address_city and task.address_city in street) or - (task.address_zip and task.address_zip in street) - ): - # Street already has full address; just append unit if separate - if task.address_street2 and task.address_street2 not in street: - task.address_display = f"{street}, {task.address_street2}" - else: - task.address_display = street - else: - # Build from components (manual entry or legacy data) - parts = [ - street, - task.address_street2, - task.address_city, - task.address_state_id.name if task.address_state_id else '', - task.address_zip, - ] - task.address_display = ', '.join([p for p in parts if p]) - - # ------------------------------------------------------------------ - # ONCHANGE - Auto-fill address from client - # ------------------------------------------------------------------ - - @api.onchange('partner_id') - def _onchange_partner_id(self): - """Auto-fill address fields from the selected client's address.""" - if self.partner_id: - addr = self.partner_id - self.address_partner_id = addr.id - self.address_street = addr.street or '' - self.address_street2 = addr.street2 or '' - self.address_city = addr.city or '' - self.address_state_id = addr.state_id.id if addr.state_id else False - self.address_zip = addr.zip or '' - self.address_lat = addr.x_fc_latitude if hasattr(addr, 'x_fc_latitude') and addr.x_fc_latitude else 0 - self.address_lng = addr.x_fc_longitude if hasattr(addr, 'x_fc_longitude') and addr.x_fc_longitude else 0 - @api.onchange('sale_order_id') def _onchange_sale_order_id(self): """Auto-fill client and address from the sale order's shipping address.""" @@ -997,41 +98,8 @@ class FusionTechnicianTask(models.Model): addr = order.dest_address_id or order.partner_id self._fill_address_from_partner(addr) - @api.onchange('facility_id') - def _onchange_facility_id(self): - """Auto-fill address from the LTC facility.""" - if self.facility_id and self.task_type == 'ltc_visit': - fac = self.facility_id - self.address_street = fac.street or '' - self.address_street2 = fac.street2 or '' - self.address_city = fac.city or '' - self.address_state_id = fac.state_id.id if fac.state_id else False - self.address_zip = fac.zip or '' - self.description = self.description or _( - 'LTC Visit at %s', fac.name - ) - - @api.onchange('task_type') - def _onchange_task_type_ltc(self): - if self.task_type == 'ltc_visit': - self.sale_order_id = False - self.purchase_order_id = False - - def _fill_address_from_partner(self, addr): - """Populate address fields from a partner record.""" - if not addr: - return - self.address_partner_id = addr.id - self.address_street = addr.street or '' - self.address_street2 = addr.street2 or '' - self.address_city = addr.city or '' - self.address_state_id = addr.state_id.id if addr.state_id else False - self.address_zip = addr.zip or '' - self.address_lat = addr.x_fc_latitude if hasattr(addr, 'x_fc_latitude') and addr.x_fc_latitude else 0 - self.address_lng = addr.x_fc_longitude if hasattr(addr, 'x_fc_longitude') and addr.x_fc_longitude else 0 - # ------------------------------------------------------------------ - # CONSTRAINTS + VALIDATION + # CONSTRAINTS # ------------------------------------------------------------------ @api.constrains('sale_order_id', 'purchase_order_id') @@ -1046,427 +114,78 @@ class FusionTechnicianTask(models.Model): "A task must be linked to either a Sale Order (Case) or a Purchase Order." )) + # ------------------------------------------------------------------ + # HOOK OVERRIDES + # ------------------------------------------------------------------ - @api.constrains('technician_id', 'additional_technician_ids', - 'scheduled_date', 'time_start', 'time_end') - def _check_no_overlap(self): - """Prevent overlapping bookings for the same technician on the same date. + def _get_linked_order(self): + """Return the linked sale or purchase order.""" + return self.sale_order_id or self.purchase_order_id or False - Checks both the lead technician and all additional technicians. - """ - for task in self: - if task.status == 'cancelled': - continue - if task.x_fc_sync_source: - continue - # Validate time range - if task.time_start >= task.time_end: - raise ValidationError(_("Start time must be before end time.")) - # Validate store hours - s_open, s_close = self._get_store_hours() - if task.time_start < s_open or task.time_end > s_close: - open_str = self._float_to_time_str(s_open) - close_str = self._float_to_time_str(s_close) - raise ValidationError(_( - "Tasks must be scheduled within store hours (%s - %s)." - ) % (open_str, close_str)) - # Validate not in the past (only for new/scheduled local tasks) - if task.status == 'scheduled' and task.scheduled_date and not task.x_fc_sync_source: - today = fields.Date.context_today(self) - if task.scheduled_date < today: - raise ValidationError(_("Cannot schedule tasks in the past.")) - if task.scheduled_date == today: - now = fields.Datetime.now() - current_hour = now.hour + now.minute / 60.0 - if task.time_start < current_hour: - pass # Allow editing existing tasks that started earlier today - # Check overlap for lead + additional technicians - all_tech_ids = (task.technician_id | task.additional_technician_ids).ids - for tech_id in all_tech_ids: - tech_name = self.env['res.users'].browse(tech_id).name - overlapping = self.sudo().search([ - '|', - ('technician_id', '=', tech_id), - ('additional_technician_ids', 'in', [tech_id]), - ('scheduled_date', '=', task.scheduled_date), - ('status', 'not in', ['cancelled']), - ('id', '!=', task.id), - ('time_start', '<', task.time_end), - ('time_end', '>', task.time_start), - ], limit=1) - if overlapping: - start_str = self._float_to_time_str(overlapping.time_start) - end_str = self._float_to_time_str(overlapping.time_end) - raise ValidationError(_( - "%(tech)s has a time conflict with %(task)s " - "(%(start)s - %(end)s). Please choose a different time.", - tech=tech_name, - task=overlapping.name, - start=start_str, - end=end_str, - )) - - # Check travel time gaps for lead technician only - # (additional techs travel with the lead, same destination) - next_task = self.sudo().search([ - '|', - ('technician_id', '=', task.technician_id.id), - ('additional_technician_ids', 'in', [task.technician_id.id]), - ('scheduled_date', '=', task.scheduled_date), - ('status', 'not in', ['cancelled']), - ('id', '!=', task.id), - ('time_start', '>=', task.time_end), - ], order='time_start', limit=1) - if next_task and task.address_lat and task.address_lng and \ - next_task.address_lat and next_task.address_lng: - travel_min = self._quick_travel_time( - task.address_lat, task.address_lng, - next_task.address_lat, next_task.address_lng, - ) - if travel_min > 0: - gap_min = int((next_task.time_start - task.time_end) * 60) - if gap_min < travel_min: - raise ValidationError(_( - "Not enough travel time to the next task!\n\n" - "This task ends at %(end)s, and %(next)s starts " - "at %(next_start)s (%(gap)d min gap).\n" - "Travel time is ~%(travel)d minutes.\n\n" - "Please allow at least %(travel)d minutes between tasks.", - end=self._float_to_time_str(task.time_end), - next=next_task.name, - next_start=self._float_to_time_str(next_task.time_start), - gap=gap_min, - travel=travel_min, - )) - - prev_task = self.sudo().search([ - '|', - ('technician_id', '=', task.technician_id.id), - ('additional_technician_ids', 'in', [task.technician_id.id]), - ('scheduled_date', '=', task.scheduled_date), - ('status', 'not in', ['cancelled']), - ('id', '!=', task.id), - ('time_end', '<=', task.time_start), - ], order='time_end desc', limit=1) - if prev_task and task.address_lat and task.address_lng and \ - prev_task.address_lat and prev_task.address_lng: - travel_min = self._quick_travel_time( - prev_task.address_lat, prev_task.address_lng, - task.address_lat, task.address_lng, - ) - if travel_min > 0: - gap_min = int((task.time_start - prev_task.time_end) * 60) - if gap_min < travel_min: - raise ValidationError(_( - "Not enough travel time from the previous task!\n\n" - "%(prev)s ends at %(prev_end)s, and this task starts " - "at %(start)s (%(gap)d min gap).\n" - "Travel time is ~%(travel)d minutes.\n\n" - "Please allow at least %(travel)d minutes between tasks.", - prev=prev_task.name, - prev_end=self._float_to_time_str(prev_task.time_end), - start=self._float_to_time_str(task.time_start), - gap=gap_min, - travel=travel_min, - )) - - @api.onchange('technician_id', 'scheduled_date') - def _onchange_technician_date_autoset(self): - """Auto-set start/end time to the first available slot when tech+date change.""" - if not self.technician_id or not self.scheduled_date: - return - exclude_id = self._origin.id if self._origin else False - duration = self.duration_hours or 1.0 - s_open, _s_close = self._get_store_hours() - preferred = self.time_start or s_open - start, end = self._find_next_available_slot( - self.technician_id.id, - self.scheduled_date, - preferred_start=preferred, - duration=duration, - exclude_task_id=exclude_id, - dest_lat=self.address_lat or 0, - dest_lng=self.address_lng or 0, - ) - if start is not False: - self.time_start = start - self.time_end = end - self.duration_hours = end - start + def _create_vals_fill(self, vals): + """Fill address from sale order or purchase order during create.""" + if vals.get('sale_order_id') and not vals.get('address_street'): + order = self.env['sale.order'].browse(vals['sale_order_id']) + addr = order.partner_shipping_id or order.partner_id + if addr: + self._fill_address_vals(vals, addr) + if not vals.get('partner_id'): + vals['partner_id'] = order.partner_id.id + elif vals.get('purchase_order_id') and not vals.get('address_street'): + po = self.env['purchase.order'].browse(vals['purchase_order_id']) + addr = po.dest_address_id or po.partner_id + if addr: + self._fill_address_vals(vals, addr) + if not vals.get('partner_id'): + vals['partner_id'] = po.partner_id.id else: - return {'warning': { - 'title': _('Fully Booked'), - 'message': _( - '%s is fully booked on %s. No available slots.' - ) % (self.technician_id.name, - self.scheduled_date.strftime('%B %d, %Y')), - }} + super()._create_vals_fill(vals) - def _snap_if_overlap(self): - """Check if current time_start/time_end overlaps with another task. - If so, auto-snap to the next available slot and return a warning dict.""" - if not self.technician_id or not self.scheduled_date or not self.time_start: - return None - exclude_id = self._origin.id if self._origin else 0 - duration = max(self.duration_hours or 1.0, 0.25) - - all_tech_ids = (self.technician_id | self.additional_technician_ids).ids - overlapping = self.sudo().search([ - '|', - ('technician_id', 'in', all_tech_ids), - ('additional_technician_ids', 'in', all_tech_ids), - ('scheduled_date', '=', self.scheduled_date), - ('status', 'not in', ['cancelled']), - ('id', '!=', exclude_id), - ('time_start', '<', self.time_end), - ('time_end', '>', self.time_start), - ], limit=1) - if overlapping: - conflict_name = overlapping.name - conflict_start = self._float_to_time_str(overlapping.time_start) - conflict_end = self._float_to_time_str(overlapping.time_end) - start, end = self._find_next_available_slot( - self.technician_id.id, - self.scheduled_date, - preferred_start=self.time_start, - duration=duration, - exclude_task_id=exclude_id, - dest_lat=self.address_lat or 0, - dest_lng=self.address_lng or 0, - ) - if start is not False: - new_start_str = self._float_to_time_str(start) - new_end_str = self._float_to_time_str(end) - self.time_start = start - self.time_end = end - self.duration_hours = end - start - return {'warning': { - 'title': _('Moved to Available Slot'), - 'message': _( - 'The selected time conflicts with %s (%s - %s).\n' - 'Automatically moved to: %s - %s.' - ) % (conflict_name, conflict_start, conflict_end, - new_start_str, new_end_str), - }} - else: - return {'warning': { - 'title': _('No Available Slots'), - 'message': _( - 'The selected time conflicts with %s (%s - %s) ' - 'and no other slots are available on this day.' - ) % (conflict_name, conflict_start, conflict_end), - }} - return None - - # ------------------------------------------------------------------ - # DEFAULT_GET - Calendar pre-fill - # ------------------------------------------------------------------ - - def _snap_to_quarter(self, hour_float): - """Round a float hour to the nearest 15-minute slot and clamp to store hours.""" - s_open, s_close = self._get_store_hours() - snapped = round(hour_float * 4) / 4 - return max(s_open, min(s_close, snapped)) - - @api.model - def default_get(self, fields_list): - """Handle calendar time range selection: pre-fill date + times from context.""" - res = super().default_get(fields_list) - ctx = self.env.context - - # Set duration default based on task type from context - task_type = ctx.get('default_task_type', res.get('task_type', 'delivery')) - if 'duration_hours' not in res or not res.get('duration_hours'): - res['duration_hours'] = self.TASK_TYPE_DURATIONS.get(task_type, 1.0) - - # When user clicks a time range on the calendar, Odoo passes - # default_datetime_start/end in UTC - dt_start_utc = None - dt_end_utc = None - if ctx.get('default_datetime_start'): - try: - dt_start_utc = fields.Datetime.from_string(ctx['default_datetime_start']) - except (ValueError, TypeError): - pass - if ctx.get('default_datetime_end'): - try: - dt_end_utc = fields.Datetime.from_string(ctx['default_datetime_end']) - except (ValueError, TypeError): - pass - - if dt_start_utc or dt_end_utc: - import pytz - user_tz = pytz.timezone(self.env.user.tz or 'UTC') - - if dt_start_utc: - dt_start_local = pytz.utc.localize(dt_start_utc).astimezone(user_tz) - res['scheduled_date'] = dt_start_local.date() - start_float = self._snap_to_quarter( - dt_start_local.hour + dt_start_local.minute / 60.0) - res['time_start'] = start_float - - if dt_end_utc: - dt_end_local = pytz.utc.localize(dt_end_utc).astimezone(user_tz) - end_float = self._snap_to_quarter( - dt_end_local.hour + dt_end_local.minute / 60.0) - if 'time_start' in res and end_float <= res['time_start']: - end_float = res['time_start'] + 1.0 - res['time_end'] = end_float - # Compute duration from the calendar drag - if 'time_start' in res: - res['duration_hours'] = end_float - res['time_start'] - - # Always compute end from start + duration if not already set - if 'time_end' not in res and 'time_start' in res and 'duration_hours' in res: - _open, close = self._get_store_hours() - res['time_end'] = min( - res['time_start'] + res['duration_hours'], close) - - return res - - # ------------------------------------------------------------------ - # CRUD OVERRIDES - # ------------------------------------------------------------------ - - @api.model_create_multi - def create(self, vals_list): - for vals in vals_list: - if vals.get('name', _('New')) == _('New'): - vals['name'] = self.env['ir.sequence'].next_by_code('fusion.technician.task') or _('New') - if not vals.get('x_fc_sync_uuid') and not vals.get('x_fc_sync_source'): - vals['x_fc_sync_uuid'] = str(uuid.uuid4()) - # Auto-populate address from sale order if not provided - if vals.get('sale_order_id') and not vals.get('address_street'): - order = self.env['sale.order'].browse(vals['sale_order_id']) - addr = order.partner_shipping_id or order.partner_id - if addr: - self._fill_address_vals(vals, addr) - if not vals.get('partner_id'): - vals['partner_id'] = order.partner_id.id - # Auto-populate address from purchase order if not provided - elif vals.get('purchase_order_id') and not vals.get('address_street'): - po = self.env['purchase.order'].browse(vals['purchase_order_id']) - addr = po.dest_address_id or po.partner_id - if addr: - self._fill_address_vals(vals, addr) - if not vals.get('partner_id'): - vals['partner_id'] = po.partner_id.id - # Auto-populate address from partner if no order set - elif vals.get('partner_id') and not vals.get('address_street'): - partner = self.env['res.partner'].browse(vals['partner_id']) - if partner.street: - self._fill_address_vals(vals, partner) - records = super().create(vals_list) - # Post creation notice to linked order chatter - for rec in records: + def _on_create_post_actions(self): + """Post-create actions: chatter notices, delivery marking, ODSP.""" + for rec in self: rec._post_task_created_to_linked_order() - # If created from "Ready for Delivery" flow, mark the sale order if self.env.context.get('mark_ready_for_delivery'): - records._mark_sale_order_ready_for_delivery() + self._mark_sale_order_ready_for_delivery() if self.env.context.get('mark_odsp_ready_for_delivery'): - for rec in records: + for rec in self: order = rec.sale_order_id if order and order.x_fc_is_odsp_sale and order._get_odsp_status() != 'ready_delivery': order._odsp_advance_status('ready_delivery', "Order is ready for delivery. Delivery task scheduled.") - # Auto-calculate travel times for the full day chain - if not self.env.context.get('skip_travel_recalc'): - records._recalculate_day_travel_chains() - # Send "Appointment Scheduled" email - for rec in records: - rec._send_task_scheduled_email() - # Push new local tasks to remote instances - local_records = records.filtered(lambda r: not r.x_fc_sync_source) - if local_records and not self.env.context.get('skip_task_sync'): - self.env['fusion.task.sync.config']._push_tasks(local_records, 'create') - return records - def write(self, vals): - if self.env.context.get('skip_travel_recalc'): - return super().write(vals) + def _check_completion_requirements(self): + """Check rental inspection requirement before completing pickup tasks.""" + if self._is_rental_pickup_task() and not self.rental_inspection_completed: + raise UserError(_( + "Rental pickup tasks require a security inspection before " + "completion. Please complete the inspection from the " + "technician portal first." + )) - # Safety: ensure time_end is consistent when start/duration change - # but time_end wasn't sent (readonly field in view may not save) - if ('time_start' in vals or 'duration_hours' in vals) and 'time_end' not in vals: - _open, close = self._get_store_hours() - start = vals.get('time_start', self[:1].time_start if len(self) == 1 else 9.0) - dur = vals.get('duration_hours', self[:1].duration_hours if len(self) == 1 else 1.0) or 1.0 - vals['time_end'] = min(start + dur, close) + def _on_complete_extra(self): + """ODSP advancement and rental inspection on task completion.""" + if (self.task_type == 'delivery' + and self.sale_order_id + and self.sale_order_id.x_fc_is_odsp_sale + and self.sale_order_id._get_odsp_status() == 'ready_delivery'): + self.sale_order_id._odsp_advance_status( + 'delivered', + "Delivery task completed by technician. Order marked as delivered.", + ) + if self._is_rental_pickup_task(): + self._apply_rental_inspection_results() - # Detect reschedule mode: capture old values BEFORE write - reschedule_mode = self.env.context.get('reschedule_mode') - old_schedule = {} - schedule_fields = {'scheduled_date', 'time_start', 'time_end', - 'duration_hours', 'technician_id'} - schedule_changed = schedule_fields & set(vals.keys()) - if reschedule_mode and schedule_changed: - for task in self: - old_schedule[task.id] = { - 'date': task.scheduled_date, - 'time_start': task.time_start, - 'time_end': task.time_end, - } + def _on_cancel_extra(self): + """Revert sale order on delivery cancellation, send email otherwise.""" + if self.task_type == 'delivery': + self._revert_sale_order_on_cancel() + else: + self._send_task_cancelled_email() - # Capture old tech+date combos BEFORE write for travel recalc - travel_fields = {'address_street', 'address_city', 'address_zip', 'address_lat', 'address_lng', - 'scheduled_date', 'sequence', 'time_start', 'technician_id', - 'additional_technician_ids'} - needs_travel_recalc = travel_fields & set(vals.keys()) - old_combos = set() - if needs_travel_recalc: - for t in self: - old_combos.add((t.technician_id.id, t.scheduled_date)) - for tech in t.additional_technician_ids: - old_combos.add((tech.id, t.scheduled_date)) - res = super().write(vals) - if needs_travel_recalc: - new_combos = set() - for t in self: - new_combos.add((t.technician_id.id, t.scheduled_date)) - for tech in t.additional_technician_ids: - new_combos.add((tech.id, t.scheduled_date)) - all_combos = old_combos | new_combos - self._recalculate_combos_travel(all_combos) - - # After write: send reschedule email if schedule actually changed - if reschedule_mode and old_schedule: - for task in self: - old = old_schedule.get(task.id, {}) - if old and ( - old['date'] != task.scheduled_date - or abs(old['time_start'] - task.time_start) > 0.01 - or abs(old['time_end'] - task.time_end) > 0.01 - ): - task._post_status_message('rescheduled') - task._send_task_rescheduled_email( - old_date=old['date'], - old_start=old['time_start'], - old_end=old['time_end'], - ) - # Push updates to remote instances for local tasks - sync_fields = {'technician_id', 'additional_technician_ids', - 'scheduled_date', 'time_start', 'time_end', - 'duration_hours', 'status', 'task_type', 'address_street', - 'address_city', 'address_zip', 'address_lat', 'address_lng', - 'partner_id'} - if sync_fields & set(vals.keys()) and not self.env.context.get('skip_task_sync'): - local_records = self.filtered(lambda r: not r.x_fc_sync_source) - if local_records: - self.env['fusion.task.sync.config']._push_tasks(local_records, 'write') - return res - - @api.model - def _fill_address_vals(self, vals, partner): - """Helper to fill address vals dict from a partner record.""" - vals.update({ - 'address_partner_id': partner.id, - 'address_street': partner.street or '', - 'address_street2': partner.street2 or '', - 'address_city': partner.city or '', - 'address_state_id': partner.state_id.id if partner.state_id else False, - 'address_zip': partner.zip or '', - 'address_lat': partner.x_fc_latitude if hasattr(partner, 'x_fc_latitude') else 0, - 'address_lng': partner.x_fc_longitude if hasattr(partner, 'x_fc_longitude') else 0, - }) + # ------------------------------------------------------------------ + # ORDER LINKING METHODS + # ------------------------------------------------------------------ def _post_task_created_to_linked_order(self): """Post a brief task creation notice to the linked order's chatter.""" @@ -1491,26 +210,19 @@ class FusionTechnicianTask(models.Model): ) def _mark_sale_order_ready_for_delivery(self): - """Mark linked sale orders as Ready for Delivery. - - Called when a delivery task is created from the "Ready for Delivery" - button on the sale order. This replaces the old wizard workflow. - """ + """Mark linked sale orders as Ready for Delivery.""" for task in self: order = task.sale_order_id if not order: continue - # Only update if not already marked if order.x_fc_adp_application_status == 'ready_delivery': continue user_name = self.env.user.name tech_name = task.technician_id.name or '' - # Save current status so we can revert if task is cancelled previous_status = order.x_fc_adp_application_status - # Update the sale order status and delivery fields all_tech_ids = (task.technician_id | task.additional_technician_ids).ids order.with_context(skip_status_validation=True).write({ 'x_fc_adp_application_status': 'ready_delivery', @@ -1520,7 +232,6 @@ class FusionTechnicianTask(models.Model): 'x_fc_scheduled_delivery_datetime': task.datetime_start, }) - # Post chatter message early_badge = '' if order.x_fc_early_delivery: early_badge = ' Early Delivery' @@ -1553,7 +264,6 @@ class FusionTechnicianTask(models.Model): subtype_xmlid='mail.mt_note', ) - # Send email notifications try: order._send_ready_for_delivery_email( technicians=task.technician_id | task.additional_technician_ids, @@ -1563,162 +273,76 @@ class FusionTechnicianTask(models.Model): except Exception as e: _logger.warning("Ready for delivery email failed for %s: %s", order.name, e) - def _recalculate_day_travel_chains(self): - """Recalculate travel for all tech+date combos affected by these tasks. - - Includes combos for additional technicians so their schedules update too. - """ - combos = set() - for t in self: - if not t.scheduled_date: - continue - if t.technician_id: - combos.add((t.technician_id.id, t.scheduled_date)) - for tech in t.additional_technician_ids: - combos.add((tech.id, t.scheduled_date)) - self._recalculate_combos_travel(combos) - - def _get_technician_start_address(self, tech_id): - """Get the start address for a technician. - - Priority: - 1. Technician's personal x_fc_start_address (if set) - 2. Company default HQ address (fusion_claims.technician_start_address) - Returns the address string or ''. - """ - tech_user = self.env['res.users'].sudo().browse(tech_id) - if tech_user.exists() and tech_user.x_fc_start_address: - return tech_user.x_fc_start_address.strip() - # Fallback to company default - return (self.env['ir.config_parameter'].sudo() - .get_param('fusion_claims.technician_start_address', '') or '').strip() - - def _geocode_address_string(self, address, api_key): - """Geocode an address string and return (lat, lng) or (0.0, 0.0).""" - if not address or not api_key: - return 0.0, 0.0 - try: - url = 'https://maps.googleapis.com/maps/api/geocode/json' - params = {'address': address, 'key': api_key, 'region': 'ca'} - resp = requests.get(url, params=params, timeout=10) - data = resp.json() - if data.get('status') == 'OK' and data.get('results'): - loc = data['results'][0]['geometry']['location'] - return loc['lat'], loc['lng'] - except Exception as e: - _logger.warning("Address geocoding failed for '%s': %s", address, e) - return 0.0, 0.0 - - def _recalculate_combos_travel(self, combos): - """Recalculate travel for a set of (tech_id, date) combinations.""" - ICP = self.env['ir.config_parameter'].sudo() - enabled = ICP.get_param('fusion_claims.google_distance_matrix_enabled', False) - if not enabled: - return - api_key = self._get_google_maps_api_key() - - # Cache geocoded start addresses per technician to avoid repeated API calls - start_coords_cache = {} - - for tech_id, date in combos: - if not tech_id or not date: - continue - all_day_tasks = self.sudo().search([ - '|', - ('technician_id', '=', tech_id), - ('additional_technician_ids', 'in', [tech_id]), - ('scheduled_date', '=', date), - ('status', 'not in', ['cancelled']), - ], order='time_start, sequence, id') - if not all_day_tasks: - continue - - # Get this technician's start location (personal or company default) - if tech_id not in start_coords_cache: - addr = self._get_technician_start_address(tech_id) - start_coords_cache[tech_id] = self._geocode_address_string(addr, api_key) - - prev_lat, prev_lng = start_coords_cache[tech_id] - for i, task in enumerate(all_day_tasks): - if not (task.address_lat and task.address_lng): - task._geocode_address() - travel_vals = {} - if prev_lat and prev_lng and task.address_lat and task.address_lng: - task.with_context(skip_travel_recalc=True)._calculate_travel_time(prev_lat, prev_lng) - travel_vals['previous_task_id'] = all_day_tasks[i - 1].id if i > 0 else False - travel_vals['travel_origin'] = 'Start Location' if i == 0 else f'Task {all_day_tasks[i - 1].name}' - if travel_vals: - task.with_context(skip_travel_recalc=True).write(travel_vals) - prev_lat = task.address_lat or prev_lat - prev_lng = task.address_lng or prev_lng - - # ------------------------------------------------------------------ - # STATUS ACTIONS - # ------------------------------------------------------------------ - - def _check_previous_tasks_completed(self): - """Check that all earlier tasks for the same technician+date are completed. - - Considers tasks where the technician is either lead or additional. - """ + def _post_completion_to_linked_order(self): + """Post the completion notes to the linked order's chatter.""" self.ensure_one() - earlier_incomplete = self.sudo().search([ - '|', - ('technician_id', '=', self.technician_id.id), - ('additional_technician_ids', 'in', [self.technician_id.id]), - ('scheduled_date', '=', self.scheduled_date), - ('time_start', '<', self.time_start), - ('status', 'not in', ['completed', 'cancelled']), + order = self.sale_order_id or self.purchase_order_id + if not order or not self.completion_notes: + return + task_type_label = dict(self._fields['task_type'].selection).get(self.task_type, self.task_type) + body = Markup( + f'
' + f'
Technician Task Completed
' + f'
    ' + f'
  • Task: {self.name} ({task_type_label})
  • ' + f'
  • Technician(s): {self.all_technician_names or self.technician_id.name}
  • ' + f'
  • Completed: {self._utc_to_local(self.completion_datetime).strftime("%B %d, %Y at %I:%M %p") if self.completion_datetime else "N/A"}
  • ' + f'
' + f'
' + f'{self.completion_notes}' + f'
' + ) + order.message_post( + body=body, + message_type='notification', + subtype_xmlid='mail.mt_note', + ) + + def _revert_sale_order_on_cancel(self): + """When a delivery task is cancelled, revert the sale order status.""" + self.ensure_one() + if self.task_type != 'delivery' or not self.sale_order_id: + return + order = self.sale_order_id + if order.x_fc_adp_application_status != 'ready_delivery': + return + + other_delivery_tasks = self.sudo().search([ + ('sale_order_id', '=', order.id), + ('task_type', '=', 'delivery'), + ('status', 'not in', ['cancelled']), ('id', '!=', self.id), ], limit=1) - if earlier_incomplete: - raise UserError(_( - "Please complete previous task %s first before starting this one." - ) % earlier_incomplete.name) - - def action_start_en_route(self): - """Mark task as En Route.""" - for task in self: - if task.status != 'scheduled': - raise UserError(_("Only scheduled tasks can be marked as En Route.")) - task._check_previous_tasks_completed() - task.status = 'en_route' - task._post_status_message('en_route') - - def action_start_task(self): - """Mark task as In Progress.""" - for task in self: - if task.status not in ('scheduled', 'en_route'): - raise UserError(_("Task must be scheduled or en route to start.")) - task._check_previous_tasks_completed() - task.status = 'in_progress' - task._post_status_message('in_progress') - - def action_view_sale_order(self): - """Open the linked sale order / case.""" - self.ensure_one() - if not self.sale_order_id: + if other_delivery_tasks: return - return { - 'name': self.sale_order_id.name, - 'type': 'ir.actions.act_window', - 'res_model': 'sale.order', - 'view_mode': 'form', - 'res_id': self.sale_order_id.id, - } - def action_view_purchase_order(self): - """Open the linked purchase order.""" - self.ensure_one() - if not self.purchase_order_id: - return - return { - 'name': self.purchase_order_id.name, - 'type': 'ir.actions.act_window', - 'res_model': 'purchase.order', - 'view_mode': 'form', - 'res_id': self.purchase_order_id.id, - } + prev_status = order.x_fc_status_before_delivery or 'approved' + status_labels = dict(order._fields['x_fc_adp_application_status'].selection) + prev_label = status_labels.get(prev_status, prev_status) + + order.with_context( + skip_status_validation=True, + skip_status_emails=True, + ).write({ + 'x_fc_adp_application_status': prev_status, + 'x_fc_status_before_delivery': False, + }) + + body = Markup( + f'' + ) + order.message_post( + body=body, + message_type='notification', + subtype_xmlid='mail.mt_note', + ) + + self._send_task_cancelled_email() def _is_rental_pickup_task(self): """Check if this is a pickup task for a rental order.""" @@ -1729,40 +353,6 @@ class FusionTechnicianTask(models.Model): and self.sale_order_id.is_rental_order ) - def action_complete_task(self): - """Mark task as Completed.""" - for task in self: - if task.status not in ('in_progress', 'en_route', 'scheduled'): - raise UserError(_("Task must be in progress to complete.")) - - if task._is_rental_pickup_task() and not task.rental_inspection_completed: - raise UserError(_( - "Rental pickup tasks require a security inspection before " - "completion. Please complete the inspection from the " - "technician portal first." - )) - - task.with_context(skip_travel_recalc=True).write({ - 'status': 'completed', - 'completion_datetime': fields.Datetime.now(), - }) - task._post_status_message('completed') - if task.completion_notes and (task.sale_order_id or task.purchase_order_id): - task._post_completion_to_linked_order() - task._notify_scheduler_on_completion() - - if (task.task_type == 'delivery' - and task.sale_order_id - and task.sale_order_id.x_fc_is_odsp_sale - and task.sale_order_id._get_odsp_status() == 'ready_delivery'): - task.sale_order_id._odsp_advance_status( - 'delivered', - "Delivery task completed by technician. Order marked as delivered.", - ) - - if task._is_rental_pickup_task(): - task._apply_rental_inspection_results() - def _apply_rental_inspection_results(self): """Write inspection results from the task back to the rental order.""" self.ensure_one() @@ -1805,263 +395,88 @@ class FusionTechnicianTask(models.Model): order.name, e, ) - def action_cancel_task(self): - """Cancel the task. Sends cancellation email and reverts sale order if delivery.""" - for task in self: - if task.status == 'completed': - raise UserError(_("Cannot cancel a completed task.")) - task.status = 'cancelled' - task._post_status_message('cancelled') - # If this was a delivery task linked to a sale order that is - # currently in "Ready for Delivery" -- revert the order back. - # _revert_sale_order_on_cancel also sends the cancellation email - # for delivery tasks. - if task.task_type == 'delivery': - task._revert_sale_order_on_cancel() - else: - # Non-delivery tasks: still send a cancellation email - task._send_task_cancelled_email() + # ------------------------------------------------------------------ + # VIEW ACTIONS + # ------------------------------------------------------------------ - def _revert_sale_order_on_cancel(self): - """When a delivery task is cancelled, check if the linked sale order - should revert to its previous status. Only reverts if: - - Task is a delivery type - - Sale order is currently 'ready_delivery' - - No other active (non-cancelled) delivery tasks exist for this order - """ + def action_view_sale_order(self): + """Open the linked sale order / case.""" self.ensure_one() - if self.task_type != 'delivery' or not self.sale_order_id: + if not self.sale_order_id: return - order = self.sale_order_id - if order.x_fc_adp_application_status != 'ready_delivery': - return - - # Check if any other non-cancelled delivery tasks exist for this order - other_delivery_tasks = self.sudo().search([ - ('sale_order_id', '=', order.id), - ('task_type', '=', 'delivery'), - ('status', 'not in', ['cancelled']), - ('id', '!=', self.id), - ], limit=1) - if other_delivery_tasks: - return # Other active delivery tasks still exist, don't revert - - # Revert to the status saved before Ready for Delivery - prev_status = order.x_fc_status_before_delivery or 'approved' - status_labels = dict(order._fields['x_fc_adp_application_status'].selection) - prev_label = status_labels.get(prev_status, prev_status) - - # skip_status_emails prevents the "Approved" email from re-firing - order.with_context( - skip_status_validation=True, - skip_status_emails=True, - ).write({ - 'x_fc_adp_application_status': prev_status, - 'x_fc_status_before_delivery': False, - }) - - # Post chatter message about the revert - body = Markup( - f'' - ) - order.message_post( - body=body, - message_type='notification', - subtype_xmlid='mail.mt_note', - ) - - # Send a "Delivery Cancelled" email instead - self._send_task_cancelled_email() - - def action_reschedule(self): - """Open the reschedule form for this task. - Saves old schedule info, then opens the same task form for editing. - On save, the write() method detects the reschedule and sends emails.""" - self.ensure_one() return { + 'name': self.sale_order_id.name, 'type': 'ir.actions.act_window', - 'res_model': 'fusion.technician.task', - 'res_id': self.id, + 'res_model': 'sale.order', 'view_mode': 'form', - 'target': 'new', - 'context': { - 'reschedule_mode': True, - 'old_date': str(self.scheduled_date) if self.scheduled_date else '', - 'old_time_start': self.time_start, - 'old_time_end': self.time_end, - }, + 'res_id': self.sale_order_id.id, } - def action_reset_to_scheduled(self): - """Reset task back to scheduled.""" - for task in self: - task.status = 'scheduled' - - # ------------------------------------------------------------------ - # CHATTER / NOTIFICATIONS - # ------------------------------------------------------------------ - - def _post_status_message(self, new_status): - """Post a status change message to the task chatter.""" + def action_view_purchase_order(self): + """Open the linked purchase order.""" self.ensure_one() - status_labels = dict(self._fields['status'].selection) - label = status_labels.get(new_status, new_status) - icons = { - 'en_route': 'fa-road', - 'in_progress': 'fa-wrench', - 'completed': 'fa-check-circle', - 'cancelled': 'fa-times-circle', - 'rescheduled': 'fa-calendar', - } - icon = icons.get(new_status, 'fa-info-circle') - body = Markup( - f'

Task status changed to ' - f'{label} by {self.env.user.name}

' - ) - self.message_post(body=body, message_type='notification', subtype_xmlid='mail.mt_note') - - def _post_completion_to_linked_order(self): - """Post the completion notes to the linked order's chatter.""" - self.ensure_one() - order = self.sale_order_id or self.purchase_order_id - if not order or not self.completion_notes: + if not self.purchase_order_id: return - task_type_label = dict(self._fields['task_type'].selection).get(self.task_type, self.task_type) - body = Markup( - f'
' - f'
Technician Task Completed
' - f'
    ' - f'
  • Task: {self.name} ({task_type_label})
  • ' - f'
  • Technician(s): {self.all_technician_names or self.technician_id.name}
  • ' - f'
  • Completed: {self.completion_datetime.strftime("%B %d, %Y at %I:%M %p") if self.completion_datetime else "N/A"}
  • ' - f'
' - f'
' - f'{self.completion_notes}' - f'
' - ) - order.message_post( - body=body, - message_type='notification', - subtype_xmlid='mail.mt_note', - ) - - def _notify_scheduler_on_completion(self): - """Send an Odoo notification to whoever created/scheduled the task.""" - self.ensure_one() - # Notify the task creator (scheduler) if they're not the technician - if self.create_uid and self.create_uid not in self.all_technician_ids: - task_type_label = dict(self._fields['task_type'].selection).get(self.task_type, self.task_type) - task_url = f'/web#id={self.id}&model=fusion.technician.task&view_type=form' - client_name = self.partner_id.name or 'N/A' - order = self.sale_order_id or self.purchase_order_id - case_ref = order.name if order else '' - # Build address string - addr_parts = [p for p in [ - self.address_street, - self.address_street2, - self.address_city, - self.address_state_id.name if self.address_state_id else '', - self.address_zip, - ] if p] - address_str = ', '.join(addr_parts) or 'No address' - # Build subject - subject = f'Task Completed: {client_name}' - if case_ref: - subject += f' ({case_ref})' - body = Markup( - f'
' - f'

' - f'{task_type_label} Completed

' - f'' - f'' - f'' - f'' - f'' - f'' - f'' - f'' - f'' - f'' - f'' - f'
Client:{client_name}
Case:{case_ref or "N/A"}
Task:{self.name}
Technician(s):{self.all_technician_names or self.technician_id.name}
Location:{address_str}
' - f'

View Task

' - f'
' - ) - # Use Odoo's internal notification system - self.env['mail.thread'].sudo().message_notify( - partner_ids=[self.create_uid.partner_id.id], - body=body, - subject=subject, - ) + return { + 'name': self.purchase_order_id.name, + 'type': 'ir.actions.act_window', + 'res_model': 'purchase.order', + 'view_mode': 'form', + 'res_id': self.purchase_order_id.id, + } # ------------------------------------------------------------------ - # TASK EMAIL NOTIFICATIONS + # EMAIL OVERRIDES # ------------------------------------------------------------------ + def _get_email_builder(self): + """Prefer the linked sale order for email building.""" + if self.sale_order_id: + return self.sale_order_id + return super()._get_email_builder() + + def _is_email_notifications_enabled(self): + """Check linked sale order's notification settings.""" + if self.sale_order_id: + try: + return self.sale_order_id._is_email_notifications_enabled() + except Exception: + return True + return super()._is_email_notifications_enabled() + def _get_task_email_details(self): - """Build common detail rows for task emails.""" - self.ensure_one() - type_label = dict(self._fields['task_type'].selection).get( - self.task_type, self.task_type or '') - rows = [ - ('Task', f'{self.name} ({type_label})'), - ('Client', self.partner_id.name or 'N/A'), - ] + """Add SO/PO reference rows to email details.""" + rows = super()._get_task_email_details() + # Insert after Client row (index 1) + insert_idx = 2 if self.sale_order_id: - rows.append(('Case', self.sale_order_id.name)) + rows.insert(insert_idx, ('Case', self.sale_order_id.name)) + insert_idx += 1 if self.purchase_order_id: - rows.append(('Purchase Order', self.purchase_order_id.name)) - if self.scheduled_date: - date_str = self.scheduled_date.strftime('%B %d, %Y') - start_str = self._float_to_time_str(self.time_start) - end_str = self._float_to_time_str(self.time_end) - rows.append(('Scheduled', f'{date_str}, {start_str} - {end_str}')) - if self.technician_id: - rows.append(('Technician', self.all_technician_names or self.technician_id.name)) - if self.address_display: - rows.append(('Address', self.address_display)) + rows.insert(insert_idx, ('Purchase Order', self.purchase_order_id.name)) return rows def _get_task_email_recipients(self): - """Get email recipients for task notifications. - Returns dict with 'to' (client), 'cc' (technician, sales rep, office).""" - self.ensure_one() - to_emails = [] - cc_emails = [] - - # Client email - if self.partner_id and self.partner_id.email: - to_emails.append(self.partner_id.email) - - # Technician emails (lead + additional) - for tech in (self.technician_id | self.additional_technician_ids): - if tech.email: - cc_emails.append(tech.email) - - # Sales rep from the sale order + """Add sales rep and office CC from linked sale order.""" + result = super()._get_task_email_recipients() if self.sale_order_id and self.sale_order_id.user_id and \ self.sale_order_id.user_id.email: - cc_emails.append(self.sale_order_id.user_id.email) - - # Office notification recipients + result['cc'].append(self.sale_order_id.user_id.email) if self.sale_order_id: try: office_cc = self.sale_order_id._get_email_recipients( include_client=False).get('office_cc', []) - cc_emails.extend(office_cc) + result['cc'].extend(office_cc) except Exception: pass - - return {'to': to_emails, 'cc': list(set(cc_emails))} + result['cc'] = list(set(result['cc'])) + return result def _send_task_cancelled_email(self): - """Send cancellation email for a task/delivery/appointment.""" + """Send cancellation email using linked sale order's email builder.""" self.ensure_one() + if self.x_fc_sync_source: + return False order = self.sale_order_id if not order: return False @@ -2121,8 +536,10 @@ class FusionTechnicianTask(models.Model): return False def _send_task_scheduled_email(self): - """Send appointment scheduled email to client, technician, and sales rep.""" + """Send appointment scheduled email using linked sale order.""" self.ensure_one() + if self.x_fc_sync_source: + return False order = self.sale_order_id if not order: return False @@ -2184,9 +601,10 @@ class FusionTechnicianTask(models.Model): return False def _send_task_rescheduled_email(self, old_date=None, old_start=None, old_end=None): - """Send reschedule email to client, technician, and sales rep. - Shows old vs new schedule for clarity.""" + """Send reschedule email using linked sale order.""" self.ensure_one() + if self.x_fc_sync_source: + return False order = self.sale_order_id if not order: return False @@ -2209,7 +627,6 @@ class FusionTechnicianTask(models.Model): detail_rows = self._get_task_email_details() - # Show old schedule if provided if old_date or old_start is not None: old_parts = [] if old_date: @@ -2255,330 +672,3 @@ class FusionTechnicianTask(models.Model): except Exception as e: _logger.error("Failed to send rescheduled email for %s: %s", self.name, e) return False - - def get_next_task_for_technician(self): - """Get the next task in sequence for the same technician+date after this one. - - Considers tasks where the technician is either lead or additional. - """ - self.ensure_one() - return self.sudo().search([ - '|', - ('technician_id', '=', self.technician_id.id), - ('additional_technician_ids', 'in', [self.technician_id.id]), - ('scheduled_date', '=', self.scheduled_date), - ('time_start', '>=', self.time_start), - ('status', 'in', ['scheduled', 'en_route']), - ('id', '!=', self.id), - ], order='time_start, sequence, id', limit=1) - - # ------------------------------------------------------------------ - # GOOGLE MAPS INTEGRATION - # ------------------------------------------------------------------ - - def _get_google_maps_api_key(self): - """Get the Google Maps API key from config.""" - return self.env['ir.config_parameter'].sudo().get_param( - 'fusion_claims.google_maps_api_key', '' - ) - - @api.model - def get_map_data(self, domain=None): - """Return task data, technician locations, and Google Maps API key. - - Args: - domain: optional extra domain from the search bar filters. - """ - api_key = self.env['ir.config_parameter'].sudo().get_param( - 'fusion_claims.google_maps_api_key', '') - local_instance = self.env['ir.config_parameter'].sudo().get_param( - 'fusion_claims.sync_instance_id', '') - base_domain = [ - ('status', 'not in', ['cancelled']), - ] - if domain: - base_domain = expression.AND([base_domain, domain]) - tasks = self.sudo().search_read( - base_domain, - ['name', 'partner_id', 'technician_id', 'task_type', - 'address_lat', 'address_lng', 'address_display', - 'time_start', 'time_start_display', 'time_end_display', - 'status', 'scheduled_date', 'travel_time_minutes', - 'x_fc_sync_client_name', 'x_fc_is_shadow', 'x_fc_sync_source'], - order='scheduled_date asc NULLS LAST, time_start asc', - limit=500, - ) - locations = self.env['fusion.technician.location'].get_latest_locations() - return { - 'api_key': api_key, - 'tasks': tasks, - 'locations': locations, - 'local_instance_id': local_instance, - } - - def _geocode_address(self): - """Geocode the task address using Google Geocoding API.""" - self.ensure_one() - api_key = self._get_google_maps_api_key() - if not api_key or not self.address_display: - return False - - try: - url = 'https://maps.googleapis.com/maps/api/geocode/json' - params = { - 'address': self.address_display, - 'key': api_key, - 'region': 'ca', - } - resp = requests.get(url, params=params, timeout=10) - data = resp.json() - if data.get('status') == 'OK' and data.get('results'): - location = data['results'][0]['geometry']['location'] - self.write({ - 'address_lat': location['lat'], - 'address_lng': location['lng'], - }) - return True - except Exception as e: - _logger.warning(f"Geocoding failed for task {self.name}: {e}") - return False - - def _calculate_travel_time(self, origin_lat, origin_lng): - """Calculate travel time from origin to this task using Distance Matrix API.""" - self.ensure_one() - api_key = self._get_google_maps_api_key() - if not api_key: - return False - if not (origin_lat and origin_lng and self.address_lat and self.address_lng): - return False - - try: - url = 'https://maps.googleapis.com/maps/api/distancematrix/json' - params = { - 'origins': f'{origin_lat},{origin_lng}', - 'destinations': f'{self.address_lat},{self.address_lng}', - 'key': api_key, - 'mode': 'driving', - 'avoid': 'tolls', - 'traffic_model': 'best_guess', - 'departure_time': 'now', - } - resp = requests.get(url, params=params, timeout=10) - data = resp.json() - if data.get('status') == 'OK': - element = data['rows'][0]['elements'][0] - if element.get('status') == 'OK': - duration_seconds = element['duration_in_traffic']['value'] if 'duration_in_traffic' in element else element['duration']['value'] - distance_meters = element['distance']['value'] - self.write({ - 'travel_time_minutes': round(duration_seconds / 60), - 'travel_distance_km': round(distance_meters / 1000, 1), - }) - return True - except Exception as e: - _logger.warning(f"Travel time calculation failed for task {self.name}: {e}") - return False - - def action_calculate_travel_times(self): - """Calculate travel times for a day's schedule. Called from backend button or cron.""" - self._do_calculate_travel_times() - # Return False to stay on the current form without navigation - return False - - def _do_calculate_travel_times(self): - """Internal: calculate travel times for tasks. Does not return an action.""" - # Group tasks by technician and date - task_groups = {} - for task in self: - key = (task.technician_id.id, task.scheduled_date) - if key not in task_groups: - task_groups[key] = self.env['fusion.technician.task'] - task_groups[key] |= task - - api_key = self._get_google_maps_api_key() - start_coords_cache = {} - - for (tech_id, date), tasks in task_groups.items(): - sorted_tasks = tasks.sorted(lambda t: (t.sequence, t.time_start)) - - # Get this technician's start location (personal or company default) - if tech_id not in start_coords_cache: - addr = self._get_technician_start_address(tech_id) - start_coords_cache[tech_id] = self._geocode_address_string(addr, api_key) - - prev_lat, prev_lng = start_coords_cache[tech_id] - - for i, task in enumerate(sorted_tasks): - # Geocode task if needed - if not (task.address_lat and task.address_lng): - task._geocode_address() - - if prev_lat and prev_lng and task.address_lat and task.address_lng: - task._calculate_travel_time(prev_lat, prev_lng) - task.previous_task_id = sorted_tasks[i - 1].id if i > 0 else False - task.travel_origin = 'Start Location' if i == 0 else f'Task {sorted_tasks[i - 1].name}' - - prev_lat = task.address_lat - prev_lng = task.address_lng - - @api.model - def _cron_calculate_travel_times(self): - """Cron job: Calculate travel times for today and tomorrow.""" - today = fields.Date.context_today(self) - tomorrow = today + timedelta(days=1) - tasks = self.search([ - ('scheduled_date', 'in', [today, tomorrow]), - ('status', 'in', ['scheduled', 'en_route']), - ]) - if tasks: - tasks._do_calculate_travel_times() - _logger.info(f"Calculated travel times for {len(tasks)} tasks") - - # ------------------------------------------------------------------ - # PORTAL HELPERS - # ------------------------------------------------------------------ - - def get_technician_tasks_for_date(self, user_id, date): - """Get all tasks for a technician on a given date, ordered by sequence.""" - return self.sudo().search([ - ('technician_id', '=', user_id), - ('scheduled_date', '=', date), - ('status', '!=', 'cancelled'), - ], order='sequence, time_start, id') - - def get_next_task(self, user_id): - """Get the next upcoming task for a technician.""" - today = fields.Date.context_today(self) - return self.sudo().search([ - ('technician_id', '=', user_id), - ('scheduled_date', '>=', today), - ('status', 'in', ['scheduled', 'en_route']), - ], order='scheduled_date, sequence, time_start', limit=1) - - def get_current_task(self, user_id): - """Get the current in-progress task for a technician.""" - today = fields.Date.context_today(self) - return self.sudo().search([ - ('technician_id', '=', user_id), - ('scheduled_date', '=', today), - ('status', '=', 'in_progress'), - ], limit=1) - - # ------------------------------------------------------------------ - # PUSH NOTIFICATIONS - # ------------------------------------------------------------------ - - def _send_push_notification(self, title, body_text, url=None): - """Send a web push notification for this task.""" - self.ensure_one() - PushSub = self.env['fusion.push.subscription'].sudo() - subscriptions = PushSub.search([ - ('user_id', '=', self.technician_id.id), - ('active', '=', True), - ]) - if not subscriptions: - return - - ICP = self.env['ir.config_parameter'].sudo() - vapid_private = ICP.get_param('fusion_claims.vapid_private_key', '') - vapid_public = ICP.get_param('fusion_claims.vapid_public_key', '') - if not vapid_private or not vapid_public: - _logger.warning("VAPID keys not configured, cannot send push notification") - return - - try: - from pywebpush import webpush, WebPushException - except ImportError: - _logger.warning("pywebpush not installed, cannot send push notifications") - return - - payload = json.dumps({ - 'title': title, - 'body': body_text, - 'url': url or f'/my/technician/task/{self.id}', - 'task_id': self.id, - 'task_type': self.task_type, - }) - - for sub in subscriptions: - try: - webpush( - subscription_info={ - 'endpoint': sub.endpoint, - 'keys': { - 'p256dh': sub.p256dh_key, - 'auth': sub.auth_key, - }, - }, - data=payload, - vapid_private_key=vapid_private, - vapid_claims={'sub': 'mailto:support@nexasystems.ca'}, - ) - except Exception as e: - _logger.warning(f"Push notification failed for subscription {sub.id}: {e}") - # Deactivate invalid subscriptions - if 'gone' in str(e).lower() or '410' in str(e): - sub.active = False - - self.write({ - 'push_notified': True, - 'push_notified_datetime': fields.Datetime.now(), - }) - - @api.model - def _cron_send_push_notifications(self): - """Cron: Send push notifications for upcoming tasks.""" - ICP = self.env['ir.config_parameter'].sudo() - if not ICP.get_param('fusion_claims.push_enabled', False): - return - - advance_minutes = int(ICP.get_param('fusion_claims.push_advance_minutes', '30')) - now = fields.Datetime.now() - - # Find tasks starting within advance_minutes that haven't been notified - tasks = self.search([ - ('scheduled_date', '=', now.date()), - ('status', '=', 'scheduled'), - ('push_notified', '=', False), - ]) - - for task in tasks: - # Check if task is within the notification window - task_start_hour = int(task.time_start) - task_start_min = int((task.time_start % 1) * 60) - task_start_dt = now.replace(hour=task_start_hour, minute=task_start_min, second=0) - - minutes_until = (task_start_dt - now).total_seconds() / 60 - if 0 <= minutes_until <= advance_minutes: - task_type_label = dict(self._fields['task_type'].selection).get(task.task_type, task.task_type) - title = f'Upcoming: {task_type_label}' - body_text = f'{task.partner_id.name or "Task"} - {task.time_start_display}' - if task.travel_time_minutes: - body_text += f' ({task.travel_time_minutes} min drive)' - task._send_push_notification(title, body_text) - - # ------------------------------------------------------------------ - # HELPERS - # ------------------------------------------------------------------ - - @staticmethod - def _float_to_time_str(value): - """Convert float hours to time string like '9:30 AM'.""" - if not value and value != 0: - return '' - hours = int(value) - minutes = int(round((value % 1) * 60)) - period = 'AM' if hours < 12 else 'PM' - display_hour = hours % 12 or 12 - return f'{display_hour}:{minutes:02d} {period}' - - def get_google_maps_url(self): - """Get Google Maps navigation URL. Uses lat/lng coordinates to - navigate to the exact location (text addresses cause Google to - resolve to nearby business names instead).""" - self.ensure_one() - if self.address_lat and self.address_lng: - return f'https://www.google.com/maps/dir/?api=1&destination={self.address_lat},{self.address_lng}&travelmode=driving' - elif self.address_display: - return f'https://www.google.com/maps/dir/?api=1&destination={urllib.parse.quote(self.address_display)}&travelmode=driving' - return '' diff --git a/fusion_claims/report/report_actions.xml b/fusion_claims/report/report_actions.xml index ba103ca..b3e2abc 100644 --- a/fusion_claims/report/report_actions.xml +++ b/fusion_claims/report/report_actions.xml @@ -32,7 +32,7 @@ report - + Quotation / Order (Landscape - ADP) sale.order @@ -40,21 +40,6 @@ fusion_claims.report_saleorder_landscape fusion_claims.report_saleorder_landscape '%s - %s' % (object.name, object.partner_id.name) - - report - - - - - - - - LTC Repair Order / Quotation - sale.order - qweb-pdf - fusion_claims.report_saleorder_ltc_repair - fusion_claims.report_saleorder_ltc_repair - 'LTC Repair - %s - %s' % (object.name, object.partner_id.name) report @@ -127,19 +112,6 @@ report - - - - - Rental Agreement - sale.order - qweb-pdf - fusion_claims.report_rental_agreement - fusion_claims.report_rental_agreement - 'Rental Agreement - %s' % object.name - - report - @@ -169,6 +141,21 @@ report + + + + + Approved Items Report + sale.order + qweb-pdf + fusion_claims.report_approved_items + fusion_claims.report_approved_items + 'Approved Items - %s - %s' % (object.name, object.partner_id.name) + + report + + + diff --git a/fusion_claims/report/report_approved_items.xml b/fusion_claims/report/report_approved_items.xml new file mode 100644 index 0000000..3ef899a --- /dev/null +++ b/fusion_claims/report/report_approved_items.xml @@ -0,0 +1,162 @@ + + + + + diff --git a/fusion_claims/report/report_rental_agreement.xml b/fusion_claims/report/report_rental_agreement.xml deleted file mode 100644 index 4b424bd..0000000 --- a/fusion_claims/report/report_rental_agreement.xml +++ /dev/null @@ -1,365 +0,0 @@ - - - - - diff --git a/fusion_claims/report/sale_report_landscape.xml b/fusion_claims/report/sale_report_landscape.xml index 08c9680..8eaecb2 100644 --- a/fusion_claims/report/sale_report_landscape.xml +++ b/fusion_claims/report/sale_report_landscape.xml @@ -285,66 +285,14 @@
- -
-
- Terms of Acceptance + + +
+ Signature
+
+
-
- By signing this document, the undersigned ("Client") acknowledges and agrees: -
    -
  1. The Client has reviewed this quotation in its entirety and accepts all items, pricing, terms, and specifications as stated herein.
  2. -
  3. Upon signing, this quotation becomes a binding Sales Order between the Client and .
  4. -
  5. Any modifications to this order after acceptance must be submitted in writing and may result in revised pricing, terms, or delivery timelines.
  6. -
  7. Payment shall be made in accordance with the payment terms specified in this document.
  8. -
  9. For orders funded through the Ontario Assistive Devices Program (ADP), the Client authorizes to submit claims and documentation to ADP on their behalf.
  10. -
  11. Products are subject to the return and refund policy as outlined in 's standard terms of service.
  12. -
-
- - - - - - - -
-
Client Signature
-
- -
-
-
Printed Name
-
- -
-
-
Date & Time of Acceptance
-
- -
-
-
-
- - - - - - -
-
Client Signature
-
-
-
Printed Name
-
-
-
Date & Time
-
-
-
-
-
+
diff --git a/fusion_claims/report/sale_report_portrait.xml b/fusion_claims/report/sale_report_portrait.xml index d5fe26d..287ce4f 100644 --- a/fusion_claims/report/sale_report_portrait.xml +++ b/fusion_claims/report/sale_report_portrait.xml @@ -203,13 +203,13 @@ - + - + - + @@ -230,26 +230,26 @@ - + + + + + + + + + - - + + - - + + - - - - - - - -
Subtotal
Taxes
Total
ADP PortionTotal ADP Portion
Client PortionTotal Client Portion
Taxes
Grand Total
@@ -262,66 +262,14 @@
- -
-
- Terms of Acceptance + + +
+ Signature
+
+
-
- By signing this document, the undersigned ("Client") acknowledges and agrees: -
    -
  1. The Client has reviewed this quotation in its entirety and accepts all items, pricing, terms, and specifications as stated herein.
  2. -
  3. Upon signing, this quotation becomes a binding Sales Order between the Client and .
  4. -
  5. Any modifications to this order after acceptance must be submitted in writing and may result in revised pricing, terms, or delivery timelines.
  6. -
  7. Payment shall be made in accordance with the payment terms specified in this document.
  8. -
  9. For orders funded through the Ontario Assistive Devices Program (ADP), the Client authorizes to submit claims and documentation to ADP on their behalf.
  10. -
  11. Products are subject to the return and refund policy as outlined in 's standard terms of service.
  12. -
-
- - - - - - - -
-
Client Signature
-
- -
-
-
Printed Name
-
- -
-
-
Date & Time of Acceptance
-
- -
-
-
-
- - - - - - -
-
Client Signature
-
-
-
Printed Name
-
-
-
Date & Time
-
-
-
-
-
+
diff --git a/fusion_claims/security/ir.model.access.csv b/fusion_claims/security/ir.model.access.csv index 2db47b7..ff24424 100644 --- a/fusion_claims/security/ir.model.access.csv +++ b/fusion_claims/security/ir.model.access.csv @@ -36,15 +36,6 @@ access_fusion_client_chat_message_user,fusion.client.chat.message.user,model_fus access_fusion_client_chat_message_manager,fusion.client.chat.message.manager,model_fusion_client_chat_message,sales_team.group_sale_manager,1,1,1,1 access_fusion_xml_import_wizard,fusion.xml.import.wizard.user,model_fusion_xml_import_wizard,sales_team.group_sale_manager,1,1,1,1 access_fusion_claims_dashboard_user,fusion.claims.dashboard.user,model_fusion_claims_dashboard,sales_team.group_sale_salesman,1,1,1,1 -access_fusion_technician_task_user,fusion.technician.task.user,model_fusion_technician_task,sales_team.group_sale_salesman,1,1,1,0 -access_fusion_technician_task_manager,fusion.technician.task.manager,model_fusion_technician_task,sales_team.group_sale_manager,1,1,1,1 -access_fusion_technician_task_technician,fusion.technician.task.technician,model_fusion_technician_task,fusion_claims.group_field_technician,1,1,0,0 -access_fusion_technician_task_portal,fusion.technician.task.portal,model_fusion_technician_task,base.group_portal,1,0,0,0 -access_fusion_push_subscription_user,fusion.push.subscription.user,model_fusion_push_subscription,base.group_user,1,1,1,0 -access_fusion_push_subscription_portal,fusion.push.subscription.portal,model_fusion_push_subscription,base.group_portal,1,1,1,0 -access_fusion_technician_location_manager,fusion.technician.location.manager,model_fusion_technician_location,sales_team.group_sale_manager,1,1,1,1 -access_fusion_technician_location_user,fusion.technician.location.user,model_fusion_technician_location,sales_team.group_sale_salesman,1,0,0,0 -access_fusion_technician_location_portal,fusion.technician.location.portal,model_fusion_technician_location,base.group_portal,0,0,1,0 access_fusion_send_to_mod_wizard_user,fusion_claims.send.to.mod.wizard.user,model_fusion_claims_send_to_mod_wizard,sales_team.group_sale_salesman,1,1,1,0 access_fusion_send_to_mod_wizard_manager,fusion_claims.send.to.mod.wizard.manager,model_fusion_claims_send_to_mod_wizard,sales_team.group_sale_manager,1,1,1,1 access_fusion_mod_awaiting_wizard_user,fusion_claims.mod.awaiting.funding.wizard.user,model_fusion_claims_mod_awaiting_funding_wizard,sales_team.group_sale_salesman,1,1,1,0 @@ -71,23 +62,8 @@ access_fusion_odsp_ready_delivery_wizard_user,fusion_claims.odsp.ready.delivery. access_fusion_odsp_ready_delivery_wizard_manager,fusion_claims.odsp.ready.delivery.wizard.manager,model_fusion_claims_odsp_ready_delivery_wizard,sales_team.group_sale_manager,1,1,1,1 access_fusion_submit_to_odsp_wizard_user,fusion_claims.submit.to.odsp.wizard.user,model_fusion_claims_submit_to_odsp_wizard,sales_team.group_sale_salesman,1,1,1,0 access_fusion_submit_to_odsp_wizard_manager,fusion_claims.submit.to.odsp.wizard.manager,model_fusion_claims_submit_to_odsp_wizard,sales_team.group_sale_manager,1,1,1,1 -access_fusion_task_sync_config_manager,fusion.task.sync.config.manager,model_fusion_task_sync_config,sales_team.group_sale_manager,1,1,1,1 -access_fusion_task_sync_config_user,fusion.task.sync.config.user,model_fusion_task_sync_config,sales_team.group_sale_salesman,1,0,0,0 -access_fusion_ltc_facility_user,fusion.ltc.facility.user,model_fusion_ltc_facility,sales_team.group_sale_salesman,1,1,1,0 -access_fusion_ltc_facility_manager,fusion.ltc.facility.manager,model_fusion_ltc_facility,sales_team.group_sale_manager,1,1,1,1 -access_fusion_ltc_floor_user,fusion.ltc.floor.user,model_fusion_ltc_floor,sales_team.group_sale_salesman,1,1,1,0 -access_fusion_ltc_floor_manager,fusion.ltc.floor.manager,model_fusion_ltc_floor,sales_team.group_sale_manager,1,1,1,1 -access_fusion_ltc_station_user,fusion.ltc.station.user,model_fusion_ltc_station,sales_team.group_sale_salesman,1,1,1,0 -access_fusion_ltc_station_manager,fusion.ltc.station.manager,model_fusion_ltc_station,sales_team.group_sale_manager,1,1,1,1 -access_fusion_ltc_repair_user,fusion.ltc.repair.user,model_fusion_ltc_repair,sales_team.group_sale_salesman,1,1,1,0 -access_fusion_ltc_repair_manager,fusion.ltc.repair.manager,model_fusion_ltc_repair,sales_team.group_sale_manager,1,1,1,1 -access_fusion_ltc_repair_stage_user,fusion.ltc.repair.stage.user,model_fusion_ltc_repair_stage,sales_team.group_sale_salesman,1,0,0,0 -access_fusion_ltc_repair_stage_manager,fusion.ltc.repair.stage.manager,model_fusion_ltc_repair_stage,sales_team.group_sale_manager,1,1,1,1 -access_fusion_ltc_cleanup_user,fusion.ltc.cleanup.user,model_fusion_ltc_cleanup,sales_team.group_sale_salesman,1,1,1,0 -access_fusion_ltc_cleanup_manager,fusion.ltc.cleanup.manager,model_fusion_ltc_cleanup,sales_team.group_sale_manager,1,1,1,1 -access_fusion_ltc_family_contact_user,fusion.ltc.family.contact.user,model_fusion_ltc_family_contact,sales_team.group_sale_salesman,1,1,1,0 -access_fusion_ltc_family_contact_manager,fusion.ltc.family.contact.manager,model_fusion_ltc_family_contact,sales_team.group_sale_manager,1,1,1,1 -access_fusion_ltc_form_submission_user,fusion.ltc.form.submission.user,model_fusion_ltc_form_submission,sales_team.group_sale_salesman,1,1,0,0 -access_fusion_ltc_form_submission_manager,fusion.ltc.form.submission.manager,model_fusion_ltc_form_submission,sales_team.group_sale_manager,1,1,1,1 -access_fusion_ltc_repair_create_so_wizard_user,fusion.ltc.repair.create.so.wizard.user,model_fusion_ltc_repair_create_so_wizard,sales_team.group_sale_salesman,1,1,1,1 -access_fusion_ltc_repair_create_so_wizard_manager,fusion.ltc.repair.create.so.wizard.manager,model_fusion_ltc_repair_create_so_wizard,sales_team.group_sale_manager,1,1,1,1 \ No newline at end of file +access_fusion_page11_sign_request_user,fusion.page11.sign.request.user,model_fusion_page11_sign_request,sales_team.group_sale_salesman,1,1,1,0 +access_fusion_page11_sign_request_manager,fusion.page11.sign.request.manager,model_fusion_page11_sign_request,sales_team.group_sale_manager,1,1,1,1 +access_fusion_page11_sign_request_public,fusion.page11.sign.request.public,model_fusion_page11_sign_request,base.group_public,1,0,0,0 +access_fusion_send_page11_wizard_user,fusion_claims.send.page11.wizard.user,model_fusion_claims_send_page11_wizard,sales_team.group_sale_salesman,1,1,1,1 +access_fusion_send_page11_wizard_manager,fusion_claims.send.page11.wizard.manager,model_fusion_claims_send_page11_wizard,sales_team.group_sale_manager,1,1,1,1 \ No newline at end of file diff --git a/fusion_claims/security/security.xml b/fusion_claims/security/security.xml index 4b0801c..9351185 100644 --- a/fusion_claims/security/security.xml +++ b/fusion_claims/security/security.xml @@ -54,88 +54,5 @@ Temporary permission for editing locked documents on old/legacy cases. Requires the "Allow Document Lock Override" setting to be enabled in Fusion Claims Settings. Once all legacy cases are handled, disable the setting and remove this permission from users. - - - - - - - - - Field Technician - - - - - - - - - - Technician Task: Manager Full Access - - [(1, '=', 1)] - - - - - - - - - - Technician Task: Sales User Access - - [(1, '=', 1)] - - - - - - - - - - Technician Task: Technician Own Tasks - - [('technician_id', '=', user.id)] - - - - - - - - - - Technician Task: Portal Technician Access - - [('technician_id', '=', user.id)] - - - - - - - - - - - - - - Push Subscription: Own Only - - [('user_id', '=', user.id)] - - - - - - Push Subscription: Portal Own Only - - [('user_id', '=', user.id)] - - diff --git a/fusion_claims/static/src/css/fusion_task_map_view.scss b/fusion_claims/static/src/css/fusion_task_map_view.scss index 5b7318b..31c3d69 100644 --- a/fusion_claims/static/src/css/fusion_task_map_view.scss +++ b/fusion_claims/static/src/css/fusion_task_map_view.scss @@ -138,6 +138,75 @@ $transition-speed: .25s; font-weight: 500; } +// ── Technician filter chips ───────────────────────────────────────── +.fc_tech_filters { + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +.fc_tech_chip { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 3px 10px 3px 4px; + font-size: 11px; + font-weight: 600; + border: 1px solid $border-color; + border-radius: 14px; + background: transparent; + color: $text-muted; + cursor: pointer; + transition: all .15s; + line-height: 18px; + max-width: 100%; + overflow: hidden; + + &:hover { + border-color: rgba($primary, .35); + color: $body-color; + background: rgba($primary, .06); + } + + &--active { + background: $primary !important; + color: #fff !important; + border-color: $primary !important; + + .fc_tech_chip_avatar { + background: rgba(#fff, .25); + color: #fff; + } + } + + &--all { + padding: 3px 10px; + color: $body-color; + font-weight: 500; + &:hover { background: rgba($primary, .1); } + } +} + +.fc_tech_chip_avatar { + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 50%; + background: rgba($secondary, .15); + color: $body-color; + font-size: 9px; + font-weight: 700; + flex-shrink: 0; +} + +.fc_tech_chip_name { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + // Collapsed toggle button (floating) .fc_sidebar_toggle_btn { position: absolute; @@ -320,6 +389,25 @@ $transition-speed: .25s; .fa { opacity: .8; } } +.fc_task_edit_btn { + display: inline-flex; + align-items: center; + font-size: 10px; + font-weight: 600; + color: var(--btn-primary-color, #fff); + background: var(--btn-primary-bg, #{$primary}); + padding: 2px 10px; + border-radius: 4px; + cursor: pointer; + margin-left: auto; + transition: all .15s; + + &:hover { + opacity: .85; + filter: brightness(1.15); + } +} + // ── Map area ──────────────────────────────────────────────────────── .fc_map_area { flex: 1 1 auto; @@ -341,15 +429,21 @@ $transition-speed: .25s; min-height: 400px; } -// ── Google Maps InfoWindow override (always light bg) ─────────────── -// InfoWindow is rendered by Google outside our DOM; we style via -// the .gm-style-iw container that Google injects. +// ── Google Maps InfoWindow override ────────────────────────────────── .gm-style-iw-d { overflow: auto !important; } .gm-style .gm-style-iw-c { padding: 0 !important; border-radius: 10px !important; + overflow: hidden !important; + box-shadow: 0 4px 20px rgba(0,0,0,.15) !important; +} +.gm-style .gm-style-iw-tc { + display: none !important; +} +.gm-style .gm-ui-hover-effect { + display: none !important; } // ── Responsive ────────────────────────────────────────────────────── diff --git a/fusion_claims/static/src/js/debug_required_fields.js b/fusion_claims/static/src/js/debug_required_fields.js new file mode 100644 index 0000000..37cf3cc --- /dev/null +++ b/fusion_claims/static/src/js/debug_required_fields.js @@ -0,0 +1,20 @@ +/** @odoo-module **/ + +import { Record } from "@web/model/relational_model/record"; +import { patch } from "@web/core/utils/patch"; + +patch(Record.prototype, { + _displayInvalidFieldNotification() { + const fieldNames = []; + for (const fieldName of this._invalidFields) { + const fieldDef = this.fields[fieldName]; + const label = fieldDef?.string || fieldName; + fieldNames.push(`${label} (${fieldName})`); + } + const message = fieldNames.length + ? `Missing required fields:\n${fieldNames.join(", ")}` + : "Missing required fields (unknown)"; + console.error("FUSION DEBUG:", message, Array.from(this._invalidFields)); + return this.model.notification.add(message, { type: "danger" }); + }, +}); diff --git a/fusion_claims/static/src/js/fusion_task_map_view.js b/fusion_claims/static/src/js/fusion_task_map_view.js index 8ee5923..59ab79e 100644 --- a/fusion_claims/static/src/js/fusion_task_map_view.js +++ b/fusion_claims/static/src/js/fusion_task_map_view.js @@ -180,9 +180,22 @@ const SOURCE_COLORS = { mobility: "#198754", }; +/** Extract unique technicians from task data, sorted by name */ +function extractTechnicians(tasksData) { + const map = {}; + for (const t of tasksData) { + if (t.technician_id) { + const [id, name] = t.technician_id; + if (!map[id]) { + map[id] = { id, name, initials: initialsOf(name) }; + } + } + } + return Object.values(map).sort((a, b) => a.name.localeCompare(b.name)); +} + /** Group + sort tasks, returning { groupKey: { label, tasks[], count } } */ -function groupTasks(tasksData, localInstanceId) { - // Sort by date ASC, time ASC +function groupTasks(tasksData, localInstanceId, visibleTechIds) { const sorted = [...tasksData].sort((a, b) => { const da = a.scheduled_date || ""; const db = b.scheduled_date || ""; @@ -190,6 +203,8 @@ function groupTasks(tasksData, localInstanceId) { return (a.time_start || 0) - (b.time_start || 0); }); + const hasTechFilter = visibleTechIds && Object.keys(visibleTechIds).length > 0; + const groups = {}; const order = [GROUP_PENDING, GROUP_YESTERDAY, GROUP_TODAY, GROUP_TOMORROW, GROUP_THIS_WEEK, GROUP_LATER]; for (const key of order) { @@ -203,13 +218,17 @@ function groupTasks(tasksData, localInstanceId) { }; } - let globalIdx = 0; + const dayCounters = {}; for (const task of sorted) { - globalIdx++; + const techId = task.technician_id ? task.technician_id[0] : 0; + if (hasTechFilter && !visibleTechIds[techId]) continue; + const g = classifyTask(task); - task._scheduleNum = globalIdx; + const dayKey = task.scheduled_date || "none"; + dayCounters[dayKey] = (dayCounters[dayKey] || 0) + 1; + task._scheduleNum = dayCounters[dayKey]; task._group = g; - task._dayColor = DAY_COLORS[g] || "#6b7280"; // Pin colour by day + task._dayColor = DAY_COLORS[g] || "#6b7280"; task._statusColor = STATUS_COLORS[task.status] || "#6b7280"; task._statusLabel = STATUS_LABELS[task.status] || task.status || ""; task._statusIcon = STATUS_ICONS[task.status] || "fa-circle"; @@ -227,7 +246,6 @@ function groupTasks(tasksData, localInstanceId) { groups[g].count++; } - // Return only non-empty groups in order return order.map((k) => groups[k]).filter((g) => g.count > 0); } @@ -255,21 +273,22 @@ export class FusionTaskMapController extends Component { showTasks: true, showTechnicians: true, showTraffic: true, + showRoute: true, taskCount: 0, techCount: 0, - // Sidebar sidebarOpen: true, - groups: [], // [{key, label, tasks[], count}] - collapsedGroups: {}, // {groupKey: true} - activeTaskId: null, // Highlighted task - // Day filters for map pins (which groups show on map) + groups: [], + collapsedGroups: {}, + activeTaskId: null, visibleGroups: { - [GROUP_YESTERDAY]: false, // hidden by default + [GROUP_YESTERDAY]: false, [GROUP_TODAY]: true, - [GROUP_TOMORROW]: true, - [GROUP_THIS_WEEK]: false, // hidden by default - [GROUP_LATER]: false, // hidden by default + [GROUP_TOMORROW]: false, + [GROUP_THIS_WEEK]: false, + [GROUP_LATER]: false, }, + allTechnicians: [], + visibleTechIds: {}, }); // Yesterday collapsed by default in sidebar list @@ -280,7 +299,11 @@ export class FusionTaskMapController extends Component { this.taskMarkers = []; this.taskMarkerMap = {}; // id → marker this.techMarkers = []; + this.routeLines = []; // route polylines + this.routeLabels = []; // travel time overlay labels + this.routeAnimFrameId = null; this.infoWindow = null; + this.techStartLocations = {}; this.apiKey = ""; this.tasksData = []; this.locationsData = []; @@ -312,6 +335,7 @@ export class FusionTaskMapController extends Component { }); onWillUnmount(() => { this._clearMarkers(); + this._clearRoute(); window.__fusionMapOpenTask = () => {}; }); } @@ -327,17 +351,30 @@ export class FusionTaskMapController extends Component { } // ── Data ───────────────────────────────────────────────────────── + _storeResult(result) { + this.localInstanceId = result.local_instance_id || this.localInstanceId || ""; + this.tasksData = result.tasks || []; + this.locationsData = result.locations || []; + this.techStartLocations = result.tech_start_locations || {}; + this.state.allTechnicians = extractTechnicians(this.tasksData); + this._rebuildGroups(); + } + + _rebuildGroups() { + this.state.groups = groupTasks( + this.tasksData, this.localInstanceId, this.state.visibleTechIds, + ); + const filteredCount = this.state.groups.reduce((s, g) => s + g.count, 0); + this.state.taskCount = filteredCount; + this.state.techCount = this.locationsData.length; + } + async _loadAndRender() { try { const domain = this._getDomain(); const result = await this.orm.call("fusion.technician.task", "get_map_data", [domain]); this.apiKey = result.api_key; - this.localInstanceId = result.local_instance_id || ""; - this.tasksData = result.tasks || []; - this.locationsData = result.locations || []; - this.state.taskCount = this.tasksData.length; - this.state.techCount = this.locationsData.length; - this.state.groups = groupTasks(this.tasksData, this.localInstanceId); + this._storeResult(result); if (!this.apiKey) { this.state.error = _t("Google Maps API key not configured. Go to Settings > Fusion Claims."); @@ -345,7 +382,11 @@ export class FusionTaskMapController extends Component { return; } await loadGoogleMaps(this.apiKey); - if (this.mapRef.el) this._initMap(); + if (this.map) { + this._renderMarkers(); + } else if (this.mapRef.el) { + this._initMap(); + } this.state.loading = false; } catch (e) { console.error("FusionTaskMap load error:", e); @@ -354,17 +395,33 @@ export class FusionTaskMapController extends Component { } } + async _softRefresh() { + if (!this.map) return; + try { + const center = this.map.getCenter(); + const zoom = this.map.getZoom(); + + const domain = this._getDomain(); + const result = await this.orm.call("fusion.technician.task", "get_map_data", [domain]); + this._storeResult(result); + + this._placeMarkers(); + + if (center && zoom != null) { + this.map.setCenter(center); + this.map.setZoom(zoom); + } + } catch (e) { + console.error("FusionTaskMap soft refresh error:", e); + } + } + async _onModelUpdate() { if (!this.map) return; try { const domain = this._getDomain(); const result = await this.orm.call("fusion.technician.task", "get_map_data", [domain]); - this.localInstanceId = result.local_instance_id || this.localInstanceId || ""; - this.tasksData = result.tasks || []; - this.locationsData = result.locations || []; - this.state.taskCount = this.tasksData.length; - this.state.techCount = this.locationsData.length; - this.state.groups = groupTasks(this.tasksData, this.localInstanceId); + this._storeResult(result); this._renderMarkers(); } catch (e) { console.error("FusionTaskMap update error:", e); @@ -407,12 +464,27 @@ export class FusionTaskMapController extends Component { this.techMarkers = []; } - _renderMarkers() { - this._clearMarkers(); + _clearRoute() { + if (this.routeAnimFrameId) { + cancelAnimationFrame(this.routeAnimFrameId); + this.routeAnimFrameId = null; + } + for (const l of this.routeLines) l.setMap(null); + this.routeLines = []; + for (const lb of this.routeLabels) lb.setMap(null); + this.routeLabels = []; + } + + _placeMarkers() { + for (const m of this.taskMarkers) m.setMap(null); + for (const m of this.techMarkers) m.setMap(null); + this.taskMarkers = []; + this.taskMarkerMap = {}; + this.techMarkers = []; + const bounds = new google.maps.LatLngBounds(); let hasBounds = false; - // Task pins: only show groups that are enabled in the day filter if (this.state.showTasks) { for (const group of this.state.groups) { const groupVisible = this.state.visibleGroups[group.key] !== false; @@ -444,21 +516,26 @@ export class FusionTaskMapController extends Component { } } - // Technician markers if (this.state.showTechnicians) { for (const loc of this.locationsData) { if (!loc.latitude || !loc.longitude) continue; const pos = { lat: loc.latitude, lng: loc.longitude }; const initials = initialsOf(loc.name); + const src = loc.sync_instance || this.localInstanceId || ""; + const isRemote = src && src !== this.localInstanceId; + const pinColor = isRemote + ? (SOURCE_COLORS[src] || "#6c757d") + : "#1d4ed8"; + const srcLabel = src ? src.charAt(0).toUpperCase() + src.slice(1) : ""; const svg = `` + - `` + + `` + `${initials}` + ``; const marker = new google.maps.Marker({ position: pos, map: this.map, - title: loc.name, + title: loc.name + (isRemote ? ` [${srcLabel}]` : ""), icon: { url: "data:image/svg+xml;charset=UTF-8," + encodeURIComponent(svg), scaledSize: new google.maps.Size(44, 44), @@ -469,8 +546,9 @@ export class FusionTaskMapController extends Component { marker.addListener("click", () => { this.infoWindow.setContent(`
-
+
${loc.name} + ${srcLabel ? `${srcLabel}` : ""}
Last seen: ${loc.logged_at || "Unknown"}
@@ -485,45 +563,410 @@ export class FusionTaskMapController extends Component { } } + const starts = this.techStartLocations || {}; + for (const uid of Object.keys(starts)) { + const sl = starts[uid]; + if (sl && sl.lat && sl.lng) { + bounds.extend({ lat: sl.lat, lng: sl.lng }); + hasBounds = true; + } + } + + return { bounds, hasBounds }; + } + + _renderMarkers() { + this._clearRoute(); + const { bounds, hasBounds } = this._placeMarkers(); + + if (this.state.showRoute && this.state.showTasks) { + this._renderRoute(); + } + if (hasBounds) { - this.map.fitBounds(bounds); - if (this.taskMarkers.length + this.techMarkers.length === 1) { - this.map.setZoom(14); + try { + this.map.fitBounds(bounds); + if (this.taskMarkers.length + this.techMarkers.length === 1) { + this.map.setZoom(14); + } + } catch (_e) { + // bounds not ready yet } } } + _renderRoute() { + this._clearRoute(); + + const routeSegments = {}; + for (const group of this.state.groups) { + if (this.state.visibleGroups[group.key] === false) continue; + for (const task of group.tasks) { + if (!task._hasCoords) continue; + const techId = task.technician_id ? task.technician_id[0] : 0; + if (!techId) continue; + const dayKey = task.scheduled_date || "none"; + const segKey = `${techId}_${dayKey}`; + if (!routeSegments[segKey]) { + routeSegments[segKey] = { + name: task._techName, day: dayKey, + techId, tasks: [], + }; + } + routeSegments[segKey].tasks.push(task); + } + } + + const LEG_COLORS = [ + "#3b82f6", "#f59e0b", "#8b5cf6", "#ec4899", + "#f97316", "#0ea5e9", "#d946ef", "#06b6d4", + "#a855f7", "#6366f1", "#eab308", "#0284c7", + "#c026d3", "#7c3aed", "#2563eb", "#db2777", + "#9333ea", "#0891b2", "#4f46e5", "#be185d", + ]; + let globalLegIdx = 0; + + if (!this._directionsService) { + this._directionsService = new google.maps.DirectionsService(); + } + + const allAnimLines = []; + const starts = this.techStartLocations || {}; + + for (const segKey of Object.keys(routeSegments)) { + const seg = routeSegments[segKey]; + const tasks = seg.tasks; + tasks.sort((a, b) => (a.time_start || 0) - (b.time_start || 0)); + + const startLoc = starts[seg.techId]; + const hasStart = startLoc && startLoc.lat && startLoc.lng; + + if (tasks.length < 2 && !hasStart) continue; + if (tasks.length < 1) continue; + + const segBaseColor = LEG_COLORS[globalLegIdx % LEG_COLORS.length]; + + let origin, destination, waypoints, hasStartLeg; + + if (hasStart) { + origin = { lat: startLoc.lat, lng: startLoc.lng }; + destination = { + lat: tasks[tasks.length - 1].address_lat, + lng: tasks[tasks.length - 1].address_lng, + }; + waypoints = tasks.slice(0, -1).map(t => ({ + location: { lat: t.address_lat, lng: t.address_lng }, + stopover: true, + })); + hasStartLeg = true; + } else { + origin = { lat: tasks[0].address_lat, lng: tasks[0].address_lng }; + destination = { + lat: tasks[tasks.length - 1].address_lat, + lng: tasks[tasks.length - 1].address_lng, + }; + waypoints = tasks.slice(1, -1).map(t => ({ + location: { lat: t.address_lat, lng: t.address_lng }, + stopover: true, + })); + hasStartLeg = false; + } + + if (hasStart) { + const startSvg = + `` + + `` + + `` + + ``; + const startMarker = new google.maps.Marker({ + position: origin, + map: this.map, + title: `${seg.name} - Start`, + icon: { + url: "data:image/svg+xml;charset=UTF-8," + encodeURIComponent(startSvg), + scaledSize: new google.maps.Size(32, 32), + anchor: new google.maps.Point(16, 16), + }, + zIndex: 5, + }); + startMarker.addListener("click", () => { + this.infoWindow.setContent(` +
+
+ ${seg.name} - Start +
+
+ ${startLoc.address || 'Start location'} +
${startLoc.source === 'clock_in' ? 'Clock-in location' : startLoc.source === 'start_address' ? 'Home address' : 'Company HQ'}
+
+
`); + this.infoWindow.open(this.map, startMarker); + }); + this.routeLines.push(startMarker); + } + + this._directionsService.route({ + origin, + destination, + waypoints, + optimizeWaypoints: false, + travelMode: google.maps.TravelMode.DRIVING, + avoidTolls: true, + drivingOptions: { + departureTime: new Date(), + trafficModel: "bestguess", + }, + }, (result, status) => { + if (status !== "OK" || !result.routes || !result.routes[0]) return; + + const route = result.routes[0]; + + for (let li = 0; li < route.legs.length; li++) { + const leg = route.legs[li]; + const legColor = LEG_COLORS[globalLegIdx % LEG_COLORS.length]; + globalLegIdx++; + + const legPath = []; + for (const step of leg.steps) { + for (const pt of step.path) legPath.push(pt); + } + if (legPath.length < 2) continue; + + const baseLine = new google.maps.Polyline({ + path: legPath, map: this.map, + strokeColor: legColor, strokeOpacity: 0.25, strokeWeight: 6, + zIndex: 1, + }); + this.routeLines.push(baseLine); + + const animLine = new google.maps.Polyline({ + path: legPath, map: this.map, + strokeOpacity: 0, strokeWeight: 0, zIndex: 2, + icons: [{ + icon: { + path: "M 0,-0.5 0,0.5", + strokeOpacity: 0.8, strokeColor: legColor, + strokeWeight: 3, scale: 4, + }, + offset: "0%", repeat: "16px", + }], + }); + this.routeLines.push(animLine); + allAnimLines.push(animLine); + + const arrowLine = new google.maps.Polyline({ + path: legPath, map: this.map, + strokeOpacity: 0, strokeWeight: 0, zIndex: 3, + icons: [{ + icon: { + path: google.maps.SymbolPath.FORWARD_OPEN_ARROW, + scale: 3, strokeColor: legColor, + strokeOpacity: 0.9, strokeWeight: 2.5, + }, + offset: "0%", repeat: "80px", + }], + }); + this.routeLines.push(arrowLine); + allAnimLines.push(arrowLine); + + const dur = leg.duration_in_traffic || leg.duration; + const dist = leg.distance; + if (dur) { + const totalMins = Math.round(dur.value / 60); + const totalKm = dist ? (dist.value / 1000).toFixed(1) : null; + + const destIdx = hasStartLeg ? li : li + 1; + const destTask = destIdx < tasks.length ? tasks[destIdx] : tasks[tasks.length - 1]; + const etaFloat = destTask.time_start || 0; + const etaStr = etaFloat ? floatToTime12(etaFloat) : ""; + + const techName = seg.name; + this.routeLabels.push(this._createTravelLabel( + legPath, totalMins, totalKm, legColor, techName, etaStr, + )); + } + } + + if (!this.routeAnimFrameId) { + this._startRouteAnimation(allAnimLines); + } + }); + } + } + + _pointAlongLeg(leg, fraction) { + const points = []; + for (const step of leg.steps) { + for (const pt of step.path) { + points.push(pt); + } + } + if (points.length < 2) return leg.start_location; + + const segDists = []; + let totalDist = 0; + for (let i = 1; i < points.length; i++) { + const d = google.maps.geometry + ? google.maps.geometry.spherical.computeDistanceBetween(points[i - 1], points[i]) + : this._haversine(points[i - 1], points[i]); + segDists.push(d); + totalDist += d; + } + + const target = totalDist * fraction; + let acc = 0; + for (let i = 0; i < segDists.length; i++) { + if (acc + segDists[i] >= target) { + const remain = target - acc; + const ratio = segDists[i] > 0 ? remain / segDists[i] : 0; + return new google.maps.LatLng( + points[i].lat() + (points[i + 1].lat() - points[i].lat()) * ratio, + points[i].lng() + (points[i + 1].lng() - points[i].lng()) * ratio, + ); + } + acc += segDists[i]; + } + return points[points.length - 1]; + } + + _haversine(a, b) { + const R = 6371000; + const dLat = (b.lat() - a.lat()) * Math.PI / 180; + const dLng = (b.lng() - a.lng()) * Math.PI / 180; + const s = Math.sin(dLat / 2) ** 2 + + Math.cos(a.lat() * Math.PI / 180) * Math.cos(b.lat() * Math.PI / 180) * + Math.sin(dLng / 2) ** 2; + return R * 2 * Math.atan2(Math.sqrt(s), Math.sqrt(1 - s)); + } + + _createTravelLabel(legPath, mins, km, color, techName, eta) { + if (!this._TravelLabel) { + this._TravelLabel = class extends google.maps.OverlayView { + constructor(path, html) { + super(); + this._path = path; + this._html = html; + this._div = null; + } + onAdd() { + this._div = document.createElement("div"); + this._div.style.position = "absolute"; + this._div.style.whiteSpace = "nowrap"; + this._div.style.pointerEvents = "none"; + this._div.style.zIndex = "50"; + this._div.style.transition = "left .3s ease, top .3s ease"; + this._div.innerHTML = this._html; + this.getPanes().floatPane.appendChild(this._div); + } + draw() { + const proj = this.getProjection(); + if (!proj || !this._div) return; + const map = this.getMap(); + if (!map) return; + const bounds = map.getBounds(); + if (!bounds) return; + + const visible = this._path.filter(p => bounds.contains(p)); + if (visible.length === 0) { + this._div.style.display = "none"; + return; + } + this._div.style.display = ""; + + const anchor = visible[Math.floor(visible.length / 2)]; + + const px = proj.fromLatLngToDivPixel(anchor); + if (px) { + this._div.style.left = (px.x - this._div.offsetWidth / 2) + "px"; + this._div.style.top = (px.y - this._div.offsetHeight - 8) + "px"; + } + } + onRemove() { + if (this._div && this._div.parentNode) { + this._div.parentNode.removeChild(this._div); + } + this._div = null; + } + }; + } + + const timeStr = mins < 60 + ? `${mins} min` + : `${Math.floor(mins / 60)}h ${mins % 60}m`; + const distStr = km ? `${km} km` : ""; + + const firstName = techName ? techName.split(" ")[0] : ""; + const html = `
${firstName ? `${firstName}|` : ""}🚗${timeStr}${distStr ? `· ${distStr}` : ""}${eta ? `|ETA ${eta}` : ""}
`; + + const label = new this._TravelLabel(legPath, html); + label.setMap(this.map); + return label; + } + + _startRouteAnimation(animLines) { + let off = 0; + let last = 0; + const animate = (ts) => { + this.routeAnimFrameId = requestAnimationFrame(animate); + if (ts - last < 50) return; + last = ts; + off = (off + 0.08) % 100; + const pct = off + "%"; + for (const line of animLines) { + const icons = line.get("icons"); + if (icons && icons.length > 0) { + icons[0].offset = pct; + line.set("icons", icons); + } + } + }; + this.routeAnimFrameId = requestAnimationFrame(animate); + } + _openTaskPopup(task, marker) { const c = task._dayColor; + const sc = task._statusColor; + const navDest = task.address_lat && task.address_lng + ? `${task.address_lat},${task.address_lng}` + : encodeURIComponent(task.address_display || ""); const html = ` -
-
- #${task._scheduleNum}  ${task.name} -
- ${task._statusLabel} - +
+
+
+ #${task._scheduleNum} ${task.name} + ${task._statusLabel}
+
${task._clientName}
-
-
Client: ${task._clientName}
-
Type: ${task._typeLbl}
-
Technician: ${task._techName}
-
Date: ${task.scheduled_date || ""}
-
Time: ${task._timeRange}
- ${task.address_display ? `
Address: ${task.address_display}
` : ""} - ${task.travel_time_minutes ? `
Travel: ${task.travel_time_minutes} min
` : ""} +
+ + ${task._typeLbl} + + + ${task._timeRange} + + ${task.travel_time_minutes ? `${task.travel_time_minutes} min` : ""}
-
+
+
👤${task._techName}
+
📅${task.scheduled_date || "No date"}
+ ${task.address_display ? `
📍${task.address_display}
` : ""} +
+
- - Navigate → + + Navigate →
`; @@ -590,6 +1033,28 @@ export class FusionTaskMapController extends Component { this._renderMarkers(); } + // ── Technician filter ───────────────────────────────────────────── + toggleTechFilter(techId) { + if (this.state.visibleTechIds[techId]) { + delete this.state.visibleTechIds[techId]; + } else { + this.state.visibleTechIds[techId] = true; + } + this._rebuildGroups(); + this._renderMarkers(); + } + + isTechVisible(techId) { + const hasFilter = Object.keys(this.state.visibleTechIds).length > 0; + return !hasFilter || !!this.state.visibleTechIds[techId]; + } + + showAllTechs() { + this.state.visibleTechIds = {}; + this._rebuildGroups(); + this._renderMarkers(); + } + // ── Top bar actions ───────────────────────────────────────────── toggleTraffic() { this.state.showTraffic = !this.state.showTraffic; @@ -605,26 +1070,69 @@ export class FusionTaskMapController extends Component { this.state.showTechnicians = !this.state.showTechnicians; this._renderMarkers(); } + toggleRoute() { + this.state.showRoute = !this.state.showRoute; + if (this.state.showRoute) { + this._renderRoute(); + } else { + this._clearRoute(); + } + } onRefresh() { this.state.loading = true; this._loadAndRender(); } - openTask(taskId) { - this.actionService.switchView("form", { resId: taskId }); + async openTask(taskId) { + if (!taskId) return; + try { + await this.actionService.doAction( + { + type: "ir.actions.act_window", + res_model: "fusion.technician.task", + res_id: taskId, + view_mode: "form", + views: [[false, "form"]], + target: "new", + context: { dialog_size: "extra-large" }, + }, + { onClose: () => this._softRefresh() }, + ); + } catch (e) { + console.error("[FusionMap] openTask failed:", e); + this.actionService.doAction({ + type: "ir.actions.act_window", + res_model: "fusion.technician.task", + res_id: taskId, + view_mode: "form", + views: [[false, "form"]], + target: "current", + }); + } } - createNewTask() { - this.actionService.doAction({ - type: "ir.actions.act_window", - res_model: "fusion.technician.task", - views: [[false, "form"]], - target: "new", - context: { default_task_type: "delivery", dialog_size: "extra-large" }, - }, { - onClose: () => { - // Refresh map data after dialog closes (task may have been created) - this.onRefresh(); - }, - }); + async createNewTask() { + try { + await this.actionService.doAction( + { + type: "ir.actions.act_window", + res_model: "fusion.technician.task", + view_mode: "form", + views: [[false, "form"]], + target: "new", + context: { default_task_type: "delivery", dialog_size: "extra-large" }, + }, + { onClose: () => this._softRefresh() }, + ); + } catch (e) { + console.error("[FusionMap] createNewTask failed:", e); + this.actionService.doAction({ + type: "ir.actions.act_window", + res_model: "fusion.technician.task", + view_mode: "form", + views: [[false, "form"]], + target: "current", + context: { default_task_type: "delivery" }, + }); + } } } diff --git a/fusion_claims/static/src/js/google_address_autocomplete.js b/fusion_claims/static/src/js/google_address_autocomplete.js index 178c4a0..9466be8 100644 --- a/fusion_claims/static/src/js/google_address_autocomplete.js +++ b/fusion_claims/static/src/js/google_address_autocomplete.js @@ -1048,331 +1048,7 @@ async function setupSimpleAddressFields(el, orm) { } } -/** - * Setup autocomplete for LTC Facility form. - * Attaches establishment search on the name field and address search on street. - */ -async function setupFacilityAutocomplete(el, model, orm) { - globalOrm = orm; - - const apiKey = await getGoogleMapsApiKey(orm); - if (!apiKey) return; - try { await loadGoogleMapsApi(apiKey); } catch (e) { return; } - - // --- Name field: establishment autocomplete --- - const nameSelectors = [ - '.oe_title [name="name"] input', - 'div[name="name"] input', - '.o_field_widget[name="name"] input', - '[name="name"] input', - ]; - - let nameInput = null; - for (const sel of nameSelectors) { - nameInput = el.querySelector(sel); - if (nameInput) break; - } - - if (nameInput && !autocompleteInstances.has('facility_name_' + (nameInput.id || 'default'))) { - _attachFacilityNameAutocomplete(nameInput, el, model); - } - - // --- Street field: address autocomplete --- - const streetSelectors = [ - 'div[name="street"] input', - '.o_field_widget[name="street"] input', - '[name="street"] input', - ]; - - let streetInput = null; - for (const sel of streetSelectors) { - streetInput = el.querySelector(sel); - if (streetInput) break; - } - - if (streetInput && !autocompleteInstances.has(streetInput)) { - _attachFacilityAddressAutocomplete(streetInput, el, model); - } -} - -/** - * Attach establishment (business) autocomplete on facility name field. - * Selecting a business fills name, address, phone, email, and website. - */ -function _attachFacilityNameAutocomplete(input, el, model) { - if (!input || !window.google?.maps?.places) return; - - const instanceKey = 'facility_name_' + (input.id || 'default'); - if (autocompleteInstances.has(instanceKey)) return; - - const autocomplete = new google.maps.places.Autocomplete(input, { - componentRestrictions: { country: 'ca' }, - types: ['establishment'], - fields: [ - 'place_id', 'name', 'address_components', 'formatted_address', - 'formatted_phone_number', 'international_phone_number', 'website', - ], - }); - - autocomplete.addListener('place_changed', async () => { - let place = autocomplete.getPlace(); - if (!place.name && !place.place_id) return; - - if (place.place_id && !place.formatted_phone_number && !place.website) { - try { - const service = new google.maps.places.PlacesService(document.createElement('div')); - const details = await new Promise((resolve, reject) => { - service.getDetails( - { - placeId: place.place_id, - fields: ['formatted_phone_number', 'international_phone_number', 'website'], - }, - (result, status) => { - if (status === google.maps.places.PlacesServiceStatus.OK) resolve(result); - else reject(new Error(status)); - } - ); - }); - if (details.formatted_phone_number) place.formatted_phone_number = details.formatted_phone_number; - if (details.international_phone_number) place.international_phone_number = details.international_phone_number; - if (details.website) place.website = details.website; - } catch (_) { /* ignore */ } - } - - let streetNumber = '', streetName = '', unitNumber = ''; - let city = '', province = '', postalCode = '', countryCode = ''; - - if (place.address_components) { - for (const c of place.address_components) { - const t = c.types; - if (t.includes('street_number')) streetNumber = c.long_name; - else if (t.includes('route')) streetName = c.long_name; - else if (t.includes('subpremise')) unitNumber = c.long_name; - else if (t.includes('floor') && !unitNumber) unitNumber = 'Floor ' + c.long_name; - else if (t.includes('locality')) city = c.long_name; - else if (t.includes('sublocality_level_1') && !city) city = c.long_name; - else if (t.includes('administrative_area_level_1')) province = c.short_name; - else if (t.includes('postal_code')) postalCode = c.long_name; - else if (t.includes('country')) countryCode = c.short_name; - } - } - - const street = streetNumber ? `${streetNumber} ${streetName}` : streetName; - const phone = place.formatted_phone_number || place.international_phone_number || ''; - - if (!model?.root) return; - const record = model.root; - - let countryId = null, stateId = null; - if (globalOrm && countryCode) { - try { - const [countries, states] = await Promise.all([ - globalOrm.searchRead('res.country', [['code', '=', countryCode]], ['id'], { limit: 1 }), - province - ? globalOrm.searchRead('res.country.state', [['code', '=', province], ['country_id.code', '=', countryCode]], ['id'], { limit: 1 }) - : Promise.resolve([]), - ]); - if (countries.length) countryId = countries[0].id; - if (states.length) stateId = states[0].id; - } catch (_) { /* ignore */ } - } - - if (record.resId && globalOrm) { - try { - const firstWrite = {}; - if (place.name) firstWrite.name = place.name; - if (street) firstWrite.street = street; - if (unitNumber) firstWrite.street2 = unitNumber; - if (city) firstWrite.city = city; - if (postalCode) firstWrite.zip = postalCode; - if (phone) firstWrite.phone = phone; - if (place.website) firstWrite.website = place.website; - if (countryId) firstWrite.country_id = countryId; - - await globalOrm.write('fusion.ltc.facility', [record.resId], firstWrite); - - if (stateId) { - await new Promise(r => setTimeout(r, 100)); - await globalOrm.write('fusion.ltc.facility', [record.resId], { state_id: stateId }); - } - - await record.load(); - } catch (err) { - console.error('[GooglePlaces Facility] Name autocomplete ORM write failed:', err); - } - } else { - try { - const textUpdate = {}; - if (place.name) textUpdate.name = place.name; - if (street) textUpdate.street = street; - if (unitNumber) textUpdate.street2 = unitNumber; - if (city) textUpdate.city = city; - if (postalCode) textUpdate.zip = postalCode; - if (phone) textUpdate.phone = phone; - if (place.website) textUpdate.website = place.website; - - await record.update(textUpdate); - - if (countryId && globalOrm) { - const formEl = input.closest('.o_form_view') || input.closest('.o_content') || document.body; - const [countryData, stateData] = await Promise.all([ - globalOrm.read('res.country', [countryId], ['display_name']), - stateId ? globalOrm.read('res.country.state', [stateId], ['display_name']) : Promise.resolve([]), - ]); - - await simulateMany2OneSelection(formEl, 'country_id', countryId, countryData[0]?.display_name || 'Canada'); - await new Promise(r => setTimeout(r, 300)); - - if (stateId && stateData.length) { - await simulateMany2OneSelection(formEl, 'state_id', stateId, stateData[0]?.display_name || province); - } - } - } catch (err) { - console.error('[GooglePlaces Facility] Name autocomplete update failed:', err); - } - } - }); - - autocompleteInstances.set(instanceKey, autocomplete); - - input.style.backgroundImage = 'url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 24 24\' fill=\'%232196F3\'%3E%3Cpath d=\'M12 7V3H2v18h20V7H12zM6 19H4v-2h2v2zm0-4H4v-2h2v2zm0-4H4V9h2v2zm0-4H4V5h2v2zm4 12H8v-2h2v2zm0-4H8v-2h2v2zm0-4H8V9h2v2zm0-4H8V5h2v2zm10 12h-8v-2h2v-2h-2v-2h2v-2h-2V9h8v10zm-2-8h-2v2h2v-2zm0 4h-2v2h2v-2z\'/%3E%3C/svg%3E")'; - input.style.backgroundRepeat = 'no-repeat'; - input.style.backgroundPosition = 'right 8px center'; - input.style.backgroundSize = '20px'; - input.style.paddingRight = '35px'; -} - -/** - * Attach address autocomplete on facility street field. - * Fills street, street2, city, state, zip, and country. - */ -function _attachFacilityAddressAutocomplete(input, el, model) { - if (!input || !window.google?.maps?.places) return; - if (autocompleteInstances.has(input)) return; - - const autocomplete = new google.maps.places.Autocomplete(input, { - componentRestrictions: { country: 'ca' }, - types: ['address'], - fields: ['address_components', 'formatted_address'], - }); - - autocomplete.addListener('place_changed', async () => { - const place = autocomplete.getPlace(); - if (!place.address_components) return; - - let streetNumber = '', streetName = '', unitNumber = ''; - let city = '', province = '', postalCode = '', countryCode = ''; - - for (const c of place.address_components) { - const t = c.types; - if (t.includes('street_number')) streetNumber = c.long_name; - else if (t.includes('route')) streetName = c.long_name; - else if (t.includes('subpremise')) unitNumber = c.long_name; - else if (t.includes('floor') && !unitNumber) unitNumber = 'Floor ' + c.long_name; - else if (t.includes('locality')) city = c.long_name; - else if (t.includes('sublocality_level_1') && !city) city = c.long_name; - else if (t.includes('administrative_area_level_1')) province = c.short_name; - else if (t.includes('postal_code')) postalCode = c.long_name; - else if (t.includes('country')) countryCode = c.short_name; - } - - const street = streetNumber ? `${streetNumber} ${streetName}` : streetName; - - if (!model?.root) return; - const record = model.root; - - let countryId = null, stateId = null; - if (globalOrm && countryCode) { - try { - const [countries, states] = await Promise.all([ - globalOrm.searchRead('res.country', [['code', '=', countryCode]], ['id'], { limit: 1 }), - province - ? globalOrm.searchRead('res.country.state', [['code', '=', province], ['country_id.code', '=', countryCode]], ['id'], { limit: 1 }) - : Promise.resolve([]), - ]); - if (countries.length) countryId = countries[0].id; - if (states.length) stateId = states[0].id; - } catch (_) { /* ignore */ } - } - - if (record.resId && globalOrm) { - try { - const firstWrite = {}; - if (street) firstWrite.street = street; - if (unitNumber) firstWrite.street2 = unitNumber; - if (city) firstWrite.city = city; - if (postalCode) firstWrite.zip = postalCode; - if (countryId) firstWrite.country_id = countryId; - - await globalOrm.write('fusion.ltc.facility', [record.resId], firstWrite); - - if (stateId) { - await new Promise(r => setTimeout(r, 100)); - await globalOrm.write('fusion.ltc.facility', [record.resId], { state_id: stateId }); - } - - await record.load(); - } catch (err) { - console.error('[GooglePlaces Facility] Address ORM write failed:', err); - } - } else { - try { - const textUpdate = {}; - if (street) textUpdate.street = street; - if (unitNumber) textUpdate.street2 = unitNumber; - if (city) textUpdate.city = city; - if (postalCode) textUpdate.zip = postalCode; - - await record.update(textUpdate); - - if (countryId && globalOrm) { - const formEl = input.closest('.o_form_view') || input.closest('.o_content') || document.body; - const [countryData, stateData] = await Promise.all([ - globalOrm.read('res.country', [countryId], ['display_name']), - stateId ? globalOrm.read('res.country.state', [stateId], ['display_name']) : Promise.resolve([]), - ]); - - await simulateMany2OneSelection(formEl, 'country_id', countryId, countryData[0]?.display_name || 'Canada'); - await new Promise(r => setTimeout(r, 300)); - - if (stateId && stateData.length) { - await simulateMany2OneSelection(formEl, 'state_id', stateId, stateData[0]?.display_name || province); - } - } - } catch (err) { - console.error('[GooglePlaces Facility] Address autocomplete update failed:', err); - } - } - - setTimeout(() => { _reattachFacilityAutocomplete(el, model); }, 400); - }); - - autocompleteInstances.set(input, autocomplete); - - input.style.backgroundImage = 'url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 24 24\' fill=\'%234CAF50\'%3E%3Cpath d=\'M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z\'/%3E%3C/svg%3E")'; - input.style.backgroundRepeat = 'no-repeat'; - input.style.backgroundPosition = 'right 8px center'; - input.style.backgroundSize = '20px'; - input.style.paddingRight = '35px'; -} - -/** - * Re-attach facility autocomplete after OWL re-renders inputs. - */ -function _reattachFacilityAutocomplete(el, model) { - const streetSelectors = [ - 'div[name="street"] input', - '.o_field_widget[name="street"] input', - '[name="street"] input', - ]; - for (const sel of streetSelectors) { - const inp = el.querySelector(sel); - if (inp && !autocompleteInstances.has(inp)) { - _attachFacilityAddressAutocomplete(inp, el, model); - break; - } - } -} +/** REMOVED: LTC Facility autocomplete functions moved to fusion_ltc_management */ /** * Patch FormController to add Google autocomplete for partner forms and dialog detection @@ -1490,35 +1166,6 @@ patch(FormController.prototype, { } } - // LTC Facility form - if (this.props.resModel === 'fusion.ltc.facility') { - setTimeout(() => { - if (this.rootRef && this.rootRef.el) { - setupFacilityAutocomplete(this.rootRef.el, this.model, this.orm); - } - }, 800); - - if (this.rootRef && this.rootRef.el) { - this._facilityAddrObserver = new MutationObserver((mutations) => { - const hasNewInputs = mutations.some(m => - m.addedNodes.length > 0 && - Array.from(m.addedNodes).some(n => - n.nodeType === 1 && (n.tagName === 'INPUT' || n.querySelector?.('input')) - ) - ); - if (hasNewInputs) { - setTimeout(() => { - setupFacilityAutocomplete(this.rootRef.el, this.model, this.orm); - }, 300); - } - }); - this._facilityAddrObserver.observe(this.rootRef.el, { - childList: true, - subtree: true, - }); - } - } - // Simple address autocomplete: res.partner, res.users, res.config.settings if (this.props.resModel === 'res.partner' || this.props.resModel === 'res.users' || this.props.resModel === 'res.config.settings') { setTimeout(() => { @@ -1556,9 +1203,6 @@ patch(FormController.prototype, { if (this._taskAddressObserver) { this._taskAddressObserver.disconnect(); } - if (this._facilityAddrObserver) { - this._facilityAddrObserver.disconnect(); - } if (this._simpleAddrObserver) { this._simpleAddrObserver.disconnect(); } diff --git a/fusion_claims/static/src/scss/fusion_claims.scss b/fusion_claims/static/src/scss/fusion_claims.scss index 08ae70d..6c56a18 100644 --- a/fusion_claims/static/src/scss/fusion_claims.scss +++ b/fusion_claims/static/src/scss/fusion_claims.scss @@ -928,99 +928,3 @@ html.dark, .o_dark { } - -// ======================================================================== -// AI CHAT: Table and response styling for Fusion Claims Intelligence -// ======================================================================== -.o-mail-Message-body, -.o-mail-Message-textContent, -.o_mail_body_content { - table { - width: 100%; - border-collapse: collapse; - margin: 8px 0; - font-size: 12px; - line-height: 1.4; - - th, td { - border: 1px solid rgba(150, 150, 150, 0.4); - padding: 5px 8px; - text-align: left; - vertical-align: top; - } - - th { - background-color: rgba(100, 100, 100, 0.15); - font-weight: 600; - font-size: 11px; - text-transform: uppercase; - letter-spacing: 0.3px; - white-space: nowrap; - } - - td { - white-space: nowrap; - } - - tr:nth-child(even) td { - background-color: rgba(100, 100, 100, 0.05); - } - - tr:hover td { - background-color: rgba(100, 100, 100, 0.1); - } - } - - h3 { - font-size: 14px; - font-weight: 700; - margin: 12px 0 6px 0; - padding-bottom: 4px; - border-bottom: 1px solid rgba(150, 150, 150, 0.3); - } - - h4 { - font-size: 13px; - font-weight: 600; - margin: 10px 0 4px 0; - } - - strong { - font-weight: 600; - } - - code { - background-color: rgba(100, 100, 100, 0.1); - padding: 1px 4px; - border-radius: 3px; - font-size: 11px; - } - - ul, ol { - margin: 4px 0; - padding-left: 20px; - } - - li { - margin-bottom: 2px; - } -} - -html.dark .o-mail-Message-body, -html.dark .o-mail-Message-textContent, -html.dark .o_mail_body_content, -.o_dark .o-mail-Message-body, -.o_dark .o-mail-Message-textContent, -.o_dark .o_mail_body_content { - table { - th, td { - border-color: rgba(200, 200, 200, 0.2); - } - th { - background-color: rgba(200, 200, 200, 0.1); - } - tr:nth-child(even) td { - background-color: rgba(200, 200, 200, 0.04); - } - } -} diff --git a/fusion_claims/static/src/xml/fusion_task_map_view.xml b/fusion_claims/static/src/xml/fusion_task_map_view.xml index f41cd92..cd031da 100644 --- a/fusion_claims/static/src/xml/fusion_task_map_view.xml +++ b/fusion_claims/static/src/xml/fusion_task_map_view.xml @@ -52,6 +52,22 @@
+ + + +
+ + + + +
+
@@ -113,6 +129,11 @@ + + Edit +
@@ -170,6 +191,11 @@ Upcoming Yesterday +
-

External APIs

- -
- -
-
- Google Maps API -
- API key for Google Maps Places autocomplete in address fields (accessibility assessments, etc.) -
-
- -
- -
-
-
-

AI Client Intelligence

@@ -256,117 +236,6 @@
-

Technician Management

- -
- -
-
- Store / Scheduling Hours -
- Operating hours for technician task scheduling. Tasks can only be booked - within these hours. Calendar view is also restricted to this range. -
-
- - to - -
-
-
- -
-
- -
-
-
-
- -
-
- Default HQ / Fallback Address -
- Company default start location used when a technician has no personal - start address set. Each technician can set their own start location - in their user profile or from the portal. -
-
- -
-
-
- -
-
- Location History Retention -
- How many days to keep technician GPS location history before automatic cleanup. -
-
- - days -
-
- Leave empty = 30 days. Enter 0 = delete at end of each day. 1+ = keep that many days. -
-
-
-
- -

Push Notifications

- -
- -
-
- -
-
-
-
- -
-
- Notification Advance Time -
- Send push notification this many minutes before a scheduled task. -
-
- minutes -
-
-
- -
-
- VAPID Public Key -
- -
-
-
- -
-
- VAPID Private Key -
- -
-
-
-
-

March of Dimes

@@ -501,25 +370,6 @@
- -

Portal Forms

-
-
-
- LTC Form Access Password -
- Set a password to protect the public LTC repair form. - Share this with facility staff so they can submit repair requests. - Minimum 4 characters. Leave empty to allow unrestricted access. -
-
- -
-
-
-
-

Portal Branding

diff --git a/fusion_claims/views/res_partner_views.xml b/fusion_claims/views/res_partner_views.xml index 44f74af..824238d 100644 --- a/fusion_claims/views/res_partner_views.xml +++ b/fusion_claims/views/res_partner_views.xml @@ -16,32 +16,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/fusion_claims/views/sale_order_views.xml b/fusion_claims/views/sale_order_views.xml index 0ce3ba4..bcde3d2 100644 --- a/fusion_claims/views/sale_order_views.xml +++ b/fusion_claims/views/sale_order_views.xml @@ -2467,28 +2467,6 @@ sale.order - - - - - - - - - - - - - - - - - diff --git a/fusion_claims/views/sale_portal_templates.xml b/fusion_claims/views/sale_portal_templates.xml deleted file mode 100644 index e1e6be9..0000000 --- a/fusion_claims/views/sale_portal_templates.xml +++ /dev/null @@ -1,416 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/fusion_claims/views/task_sync_views.xml b/fusion_claims/views/task_sync_views.xml deleted file mode 100644 index 5d8862f..0000000 --- a/fusion_claims/views/task_sync_views.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - fusion.task.sync.config.form - fusion.task.sync.config - -
-
-
- -
-

-
- - - - - - - - - - - - - - -
- - Technicians are matched across instances by their - Tech Sync ID field (Settings > Users). - Set the same ID (e.g. "gordy") on both instances for each shared technician. -
-
- -
-
- - - - - - fusion.task.sync.config.list - fusion.task.sync.config - - - - - - - - - - - - - - - - - Task Sync Instances - fusion.task.sync.config - list,form - - - - -
diff --git a/fusion_claims/views/technician_location_views.xml b/fusion_claims/views/technician_location_views.xml deleted file mode 100644 index 9d0e24e..0000000 --- a/fusion_claims/views/technician_location_views.xml +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - fusion.technician.location.list - fusion.technician.location - - - - - - - - - - - - - - - - - fusion.technician.location.form - fusion.technician.location - -
- - - - - - - - - - - - - - -
-
-
- - - - - - fusion.technician.location.search - fusion.technician.location - - - - - - - - - - - - - - - - - - - - Location History - fusion.technician.location - list,form - - { - 'search_default_filter_today': 1, - 'search_default_group_user': 1, - } - -

- No location data logged yet. -

-

Technician locations are automatically logged when they use the portal.

-
-
- - - - - - Technician Map - /my/technician/admin/map - self - - - - - - - - - - - - Cleanup Old Technician Locations - - code - model._cron_cleanup_old_locations() - 1 - days - True - - -
diff --git a/fusion_claims/views/technician_task_views.xml b/fusion_claims/views/technician_task_views.xml index b2261e6..c9cb4ee 100644 --- a/fusion_claims/views/technician_task_views.xml +++ b/fusion_claims/views/technician_task_views.xml @@ -1,540 +1,97 @@ + - + - - Technician Task - fusion.technician.task - TASK- - 5 - 1 - - - - - - - res.users.form.field.staff - res.users - + + fusion.technician.task.search.claims + fusion.technician.task + - - - - + + - + - - fusion.technician.task.search + + fusion.technician.task.form.claims fusion.technician.task + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - fusion.technician.task.form + + fusion.technician.task.list.claims fusion.technician.task + -
- - -
-
- - - -
- - -
- - - -
-

- -

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - - - - - fusion.technician.task.list - fusion.technician.task - - - - - - - - - - - - - - - + - - +
- - - - - fusion.technician.task.kanban - fusion.technician.task - - - - - - - - - - - - - - - - - - -
-
-
-
- - - -
- -
-
- - - -
-
- - - - - - - -
-
- - - min - -
-
- - + technician(s) -
-
-
- -
-
- -
-
-
-
-
-
-
-
-
- - - - - - fusion.technician.task.calendar - fusion.technician.task - - - - - - - - - - - - - - - - - - - - - - - - fusion.technician.task.map - fusion.technician.task - - - - - - - - - - - - - - - - - - - - Technician Tasks - fusion.technician.task - list,kanban,form,calendar,map - - {'search_default_filter_active': 1} - -

- Create your first technician task -

-

Schedule deliveries, repairs, and other field tasks for your technicians.

-
-
- - - - Schedule - fusion.technician.task - map,calendar,list,kanban,form - - {'search_default_filter_active': 1} - - - - - Delivery Map - fusion.technician.task - map,list,kanban,form,calendar - - {'search_default_filter_active': 1} - - - - - Today's Tasks - fusion.technician.task - kanban,list,form,map - - {'search_default_filter_today': 1, 'search_default_filter_active': 1} - - - - - My Tasks - fusion.technician.task - list,kanban,form,calendar,map - - {'search_default_filter_my_tasks': 1, 'search_default_filter_active': 1} - - - - - Pending Tasks - fusion.technician.task - list,kanban,form - - {'search_default_filter_pending': 1} - - - - - - - - - - - - - - - - - - - - - - + diff --git a/fusion_claims/wizard/__init__.py b/fusion_claims/wizard/__init__.py index b5f91b1..90d36dd 100644 --- a/fusion_claims/wizard/__init__.py +++ b/fusion_claims/wizard/__init__.py @@ -30,4 +30,4 @@ from . import odsp_discretionary_wizard from . import odsp_pre_approved_wizard from . import odsp_ready_delivery_wizard from . import odsp_submit_to_odsp_wizard -from . import ltc_repair_create_so_wizard \ No newline at end of file +from . import send_page11_wizard \ No newline at end of file diff --git a/fusion_claims/wizard/application_received_wizard.py b/fusion_claims/wizard/application_received_wizard.py index 5cffd36..610e5f7 100644 --- a/fusion_claims/wizard/application_received_wizard.py +++ b/fusion_claims/wizard/application_received_wizard.py @@ -34,18 +34,42 @@ class ApplicationReceivedWizard(models.TransientModel): signed_pages_11_12 = fields.Binary( string='Signed Pages 11 & 12', - required=True, - help='Upload the signed pages 11 and 12 from the application', + help='Upload the signed pages 11 and 12 from the application. ' + 'Not required if a remote signing request has been sent.', ) signed_pages_filename = fields.Char( string='Pages Filename', ) + + has_pending_page11_request = fields.Boolean( + compute='_compute_has_pending_page11_request', + ) + has_signed_page11 = fields.Boolean( + compute='_compute_has_pending_page11_request', + ) notes = fields.Text( string='Notes', help='Any notes about the received application', ) + @api.depends('sale_order_id') + def _compute_has_pending_page11_request(self): + for wiz in self: + order = wiz.sale_order_id + if order: + requests = order.page11_sign_request_ids + wiz.has_pending_page11_request = bool( + requests.filtered(lambda r: r.state in ('draft', 'sent')) + ) + wiz.has_signed_page11 = bool( + order.x_fc_signed_pages_11_12 + or requests.filtered(lambda r: r.state == 'signed') + ) + else: + wiz.has_pending_page11_request = False + wiz.has_signed_page11 = False + @api.model def default_get(self, fields_list): res = super().default_get(fields_list) @@ -53,7 +77,6 @@ class ApplicationReceivedWizard(models.TransientModel): if active_id: order = self.env['sale.order'].browse(active_id) res['sale_order_id'] = order.id - # Pre-fill if documents already exist if order.x_fc_original_application: res['original_application'] = order.x_fc_original_application res['original_application_filename'] = order.x_fc_original_application_filename @@ -91,20 +114,33 @@ class ApplicationReceivedWizard(models.TransientModel): if order.x_fc_adp_application_status not in ('assessment_completed', 'waiting_for_application'): raise UserError("Can only receive application from 'Waiting for Application' status.") - # Validate files are uploaded if not self.original_application: raise UserError("Please upload the Original ADP Application.") - if not self.signed_pages_11_12: - raise UserError("Please upload the Signed Pages 11 & 12.") - - # Update sale order with documents - order.with_context(skip_status_validation=True).write({ + + page11_covered = bool( + self.signed_pages_11_12 + or order.x_fc_signed_pages_11_12 + or order.page11_sign_request_ids.filtered( + lambda r: r.state in ('sent', 'signed') + ) + ) + if not page11_covered: + raise UserError( + "Signed Pages 11 & 12 are required.\n\n" + "You can either upload the file here, or use the " + "'Request Page 11 Signature' button on the sale order " + "to send it for remote signing before confirming." + ) + + vals = { 'x_fc_adp_application_status': 'application_received', 'x_fc_original_application': self.original_application, 'x_fc_original_application_filename': self.original_application_filename, - 'x_fc_signed_pages_11_12': self.signed_pages_11_12, - 'x_fc_signed_pages_filename': self.signed_pages_filename, - }) + } + if self.signed_pages_11_12: + vals['x_fc_signed_pages_11_12'] = self.signed_pages_11_12 + vals['x_fc_signed_pages_filename'] = self.signed_pages_filename + order.with_context(skip_status_validation=True).write(vals) # Post to chatter from datetime import date @@ -128,3 +164,15 @@ class ApplicationReceivedWizard(models.TransientModel): ) return {'type': 'ir.actions.act_window_close'} + + def action_request_page11_signature(self): + """Open the Page 11 remote signing wizard from within the Application Received wizard.""" + self.ensure_one() + return { + 'type': 'ir.actions.act_window', + 'name': 'Request Page 11 Signature', + 'res_model': 'fusion_claims.send.page11.wizard', + 'view_mode': 'form', + 'target': 'new', + 'context': {'default_sale_order_id': self.sale_order_id.id}, + } diff --git a/fusion_claims/wizard/application_received_wizard_views.xml b/fusion_claims/wizard/application_received_wizard_views.xml index 063362b..5eb8980 100644 --- a/fusion_claims/wizard/application_received_wizard_views.xml +++ b/fusion_claims/wizard/application_received_wizard_views.xml @@ -10,10 +10,12 @@ Upload Required Documents

Please upload the ADP application documents received from the client.

- + + + + + - - @@ -24,6 +26,28 @@ + +
+ Don't have signed pages? +
+ +
+
+ A remote signing request has been sent. + You can proceed without uploading signed pages -- they will be auto-filled when signed. +
+
+ +
+
+ Page 11 has been signed remotely. +
+
diff --git a/fusion_claims/wizard/assessment_completed_wizard.py b/fusion_claims/wizard/assessment_completed_wizard.py index 5d7fd82..14dd29d 100644 --- a/fusion_claims/wizard/assessment_completed_wizard.py +++ b/fusion_claims/wizard/assessment_completed_wizard.py @@ -11,7 +11,6 @@ _logger = logging.getLogger(__name__) class AssessmentCompletedWizard(models.TransientModel): - """Wizard to record assessment completion date.""" _name = 'fusion_claims.assessment.completed.wizard' _description = 'Assessment Completed Wizard' @@ -21,18 +20,49 @@ class AssessmentCompletedWizard(models.TransientModel): required=True, readonly=True, ) - + + is_override = fields.Boolean( + string='Scheduling Override', + compute='_compute_is_override', + store=False, + ) + + assessment_start_date = fields.Date( + string='Assessment Start Date', + required=True, + help='Date the assessment was conducted', + ) + completion_date = fields.Date( string='Assessment Completion Date', required=True, default=fields.Date.context_today, ) - + notes = fields.Text( - string='Assessment Notes', - help='Any notes from the assessment', + string='Notes', + help='Notes from the assessment', ) + override_reason = fields.Text( + string='Override Reason', + help='Mandatory when skipping the scheduling step. Explain why the assessment was completed without scheduling through the system.', + ) + + notify_authorizer = fields.Boolean( + string='Notify Authorizer', + default=True, + help='Send email to the authorizer about assessment completion', + ) + + @api.depends('sale_order_id') + def _compute_is_override(self): + for rec in self: + rec.is_override = ( + rec.sale_order_id + and rec.sale_order_id.x_fc_adp_application_status == 'quotation' + ) + @api.model def default_get(self, fields_list): res = super().default_get(fields_list) @@ -40,43 +70,174 @@ class AssessmentCompletedWizard(models.TransientModel): if active_id: order = self.env['sale.order'].browse(active_id) res['sale_order_id'] = order.id + if order.x_fc_assessment_start_date: + res['assessment_start_date'] = order.x_fc_assessment_start_date + else: + res['assessment_start_date'] = fields.Date.context_today(self) return res def action_complete(self): """Mark assessment as completed.""" self.ensure_one() - + order = self.sale_order_id - - if order.x_fc_adp_application_status != 'assessment_scheduled': - raise UserError("Can only complete assessment from 'Assessment Scheduled' status.") - - # Validate completion date is not before start date - if order.x_fc_assessment_start_date and self.completion_date < order.x_fc_assessment_start_date: + current_status = order.x_fc_adp_application_status + is_override = current_status == 'quotation' + + if current_status not in ('quotation', 'assessment_scheduled'): raise UserError( - f"Completion date ({self.completion_date}) cannot be before " - f"assessment start date ({order.x_fc_assessment_start_date})." + _("Can only complete assessment from 'Quotation' or 'Assessment Scheduled' status.") ) - - # Update sale order - order.with_context(skip_status_validation=True).write({ + + if is_override and not (self.override_reason or '').strip(): + raise UserError( + _("Override Reason is mandatory when skipping the assessment scheduling step. " + "Please explain why this assessment was completed without being scheduled through the system.") + ) + + if self.completion_date < self.assessment_start_date: + raise UserError( + _("Completion date (%s) cannot be before assessment start date (%s).") + % (self.completion_date, self.assessment_start_date) + ) + + write_vals = { 'x_fc_adp_application_status': 'assessment_completed', 'x_fc_assessment_end_date': self.completion_date, - }) - - # Post to chatter - notes_html = f'

Notes: {self.notes}

' if self.notes else '' - - order.message_post( - body=Markup( - '
' - '

Assessment Completed

' - f'

Completion Date: {self.completion_date.strftime("%B %d, %Y")}

' - f'{notes_html}' + } + if is_override or not order.x_fc_assessment_start_date: + write_vals['x_fc_assessment_start_date'] = self.assessment_start_date + + order.with_context(skip_status_validation=True).write(write_vals) + + if is_override: + override_html = Markup( + '
' + '

' + ' Assessment Scheduling Override

' + '

Override by: %s

' + '

Reason: %s

' + '

Assessment Date: %s to %s

' + '%s' '
' - ), - message_type='notification', - subtype_xmlid='mail.mt_note', - ) - + ) % ( + self.env.user.name, + self.override_reason.strip(), + self.assessment_start_date.strftime("%B %d, %Y"), + self.completion_date.strftime("%B %d, %Y"), + Markup('

Notes: %s

') % self.notes if self.notes else Markup(''), + ) + order.message_post( + body=override_html, + message_type='notification', + subtype_xmlid='mail.mt_note', + ) + else: + notes_html = ( + Markup('

Notes: %s

') % self.notes + ) if self.notes else Markup('') + + order.message_post( + body=Markup( + '
' + '

' + ' Assessment Completed

' + '

Completion Date: %s

' + '%s' + '
' + ) % (self.completion_date.strftime("%B %d, %Y"), notes_html), + message_type='notification', + subtype_xmlid='mail.mt_note', + ) + + if self.notify_authorizer: + self._send_backend_completion_email(order, is_override) + return {'type': 'ir.actions.act_window_close'} + + def _send_backend_completion_email(self, order, is_override): + """Send assessment completion email when done from backend.""" + self.ensure_one() + + if not order._email_is_enabled(): + return + + authorizer = order.x_fc_authorizer_id + if not authorizer or not authorizer.email: + _logger.info("No authorizer email for %s, skipping notification", order.name) + return + + to_email = authorizer.email + cc_emails = [] + if order.user_id and order.user_id.email: + cc_emails.append(order.user_id.email) + company = self.env.company + office_partners = company.sudo().x_fc_office_notification_ids + cc_emails.extend([p.email for p in office_partners if p.email]) + + client_name = order.partner_id.name or 'Client' + override_note = '' + if is_override: + override_note = ( + '
' + 'Note: This assessment was completed without being scheduled ' + 'through the system. ' + f'Reason: {self.override_reason.strip()}' + '
' + ) + + sections = [ + ('Assessment Details', [ + ('Client', client_name), + ('Case', order.name), + ('Assessment Date', f"{self.assessment_start_date.strftime('%B %d, %Y')} to {self.completion_date.strftime('%B %d, %Y')}"), + ('Completed by', self.env.user.name), + ]), + ] + if self.notes: + sections.append(('Notes', [('', self.notes)])) + + summary = ( + f'The assessment for {client_name} ({order.name}) ' + f'has been completed on {self.completion_date.strftime("%B %d, %Y")}.' + ) + if is_override: + summary += f' {override_note}' + + email_body = order._email_build( + title='Assessment Completed', + summary=summary, + email_type='success', + sections=sections, + note='Next step: Please submit the ADP application ' + '(including pages 11-12 signed by the client) so we can proceed.', + button_url=f'{order.get_base_url()}/web#id={order.id}&model=sale.order&view_type=form', + button_text='View Case', + sender_name=order.user_id.name if order.user_id else 'The Team', + ) + + try: + self.env['mail.mail'].sudo().create({ + 'subject': f'Assessment Completed - {client_name} - {order.name}', + 'body_html': email_body, + 'email_to': to_email, + 'email_cc': ', '.join(cc_emails) if cc_emails else False, + 'model': 'sale.order', + 'res_id': order.id, + 'auto_delete': True, + }).send() + + order.message_post( + body=Markup( + '' + ) % (to_email, ', '.join(cc_emails) or 'None'), + message_type='notification', + subtype_xmlid='mail.mt_note', + ) + _logger.info("Sent backend assessment completed email for %s", order.name) + except Exception as e: + _logger.error("Failed to send assessment completed email for %s: %s", order.name, e) diff --git a/fusion_claims/wizard/assessment_completed_wizard_views.xml b/fusion_claims/wizard/assessment_completed_wizard_views.xml index 6b86a6f..cd5e074 100644 --- a/fusion_claims/wizard/assessment_completed_wizard_views.xml +++ b/fusion_claims/wizard/assessment_completed_wizard_views.xml @@ -1,18 +1,41 @@ - fusion_claims.assessment.completed.wizard.form fusion_claims.assessment.completed.wizard
+ + + + + - - - + + + + + + + + + + + + +
-
+ +
+
+ + + Request Page 11 Signature + fusion_claims.send.page11.wizard + form + new + {'default_sale_order_id': active_id} + +
diff --git a/fusion_claims/wizard/status_change_reason_wizard.py b/fusion_claims/wizard/status_change_reason_wizard.py index 50d3a55..21397b8 100644 --- a/fusion_claims/wizard/status_change_reason_wizard.py +++ b/fusion_claims/wizard/status_change_reason_wizard.py @@ -61,6 +61,18 @@ class StatusChangeReasonWizard(models.TransientModel): help='Select the reason ADP denied the funding', ) + # ========================================================================== + # WITHDRAWAL INTENT (for 'withdrawn' status) + # ========================================================================== + withdrawal_intent = fields.Selection( + selection=[ + ('cancel', 'Cancel Application'), + ('resubmit', 'Withdraw for Correction & Resubmission'), + ], + string='What would you like to do after withdrawal?', + default='resubmit', + ) + reason = fields.Text( string='Reason / Additional Details', help='Please provide additional details for this status change.', @@ -181,8 +193,10 @@ class StatusChangeReasonWizard(models.TransientModel): } header_color, bg_color, border_color = status_colors.get(new_status, ('#17a2b8', '#f0f9ff', '#bee5eb')) - # For on_hold, also store the previous status and hold date + # Build initial update vals update_vals = {'x_fc_adp_application_status': new_status} + if new_status == 'withdrawn': + update_vals['x_fc_previous_status_before_withdrawal'] = self.previous_status # ================================================================= # REJECTED: ADP rejected submission (within 24 hours) @@ -261,7 +275,7 @@ class StatusChangeReasonWizard(models.TransientModel): # Don't post message here - _send_on_hold_email() will post the message message_body = None elif new_status == 'withdrawn': - # Don't post message here - _send_withdrawal_email() will post the message + # Handled entirely below based on withdrawal_intent message_body = None elif new_status == 'cancelled': # Cancelled has its own detailed message posted later @@ -302,10 +316,129 @@ class StatusChangeReasonWizard(models.TransientModel): order._send_correction_needed_email(reason=reason) # ================================================================= - # WITHDRAWN: Send email notification to all parties + # WITHDRAWN: Branch based on withdrawal intent # ================================================================= if new_status == 'withdrawn': - order._send_withdrawal_email(reason=reason) + intent = self.withdrawal_intent + + if intent == 'cancel': + # --------------------------------------------------------- + # WITHDRAW & CANCEL: Cancel invoices + SO + # --------------------------------------------------------- + cancelled_invoices = [] + cancelled_so = False + + # Cancel related invoices first + invoices = order.invoice_ids.filtered(lambda inv: inv.state != 'cancel') + for invoice in invoices: + try: + inv_msg = Markup(f''' + + ''') + invoice.message_post( + body=inv_msg, + message_type='notification', + subtype_xmlid='mail.mt_note', + ) + if invoice.state == 'posted': + invoice.button_draft() + invoice.button_cancel() + cancelled_invoices.append(invoice.name) + except Exception as e: + warn_msg = Markup(f''' + + ''') + order.message_post( + body=warn_msg, + message_type='notification', + subtype_xmlid='mail.mt_note', + ) + + # Cancel the sale order itself + if order.state not in ('cancel', 'done'): + try: + order._action_cancel() + cancelled_so = True + except Exception as e: + warn_msg = Markup(f''' + + ''') + order.message_post( + body=warn_msg, + message_type='notification', + subtype_xmlid='mail.mt_note', + ) + + # Build cancellation summary + invoice_list_html = '' + if cancelled_invoices: + invoice_items = ''.join([f'
  • {inv}
  • ' for inv in cancelled_invoices]) + invoice_list_html = f'
  • Invoices Cancelled:
      {invoice_items}
  • ' + + so_status = 'Cancelled' if cancelled_so else 'Not applicable' + summary_msg = Markup(f''' + + ''') + order.message_post( + body=summary_msg, + message_type='notification', + subtype_xmlid='mail.mt_note', + ) + order._send_withdrawal_email(reason=reason, intent='cancel') + + else: + # --------------------------------------------------------- + # WITHDRAW & RESUBMIT: Return to ready_submission + # --------------------------------------------------------- + order.with_context(skip_status_validation=True).write({ + 'x_fc_adp_application_status': 'ready_submission', + 'x_fc_previous_status_before_withdrawal': self.previous_status, + }) + + resubmit_msg = Markup(f''' + + ''') + order.message_post( + body=resubmit_msg, + message_type='notification', + subtype_xmlid='mail.mt_note', + ) + order._send_withdrawal_email(reason=reason, intent='resubmit') # ================================================================= # ON HOLD: Send email notification to all parties diff --git a/fusion_claims/wizard/status_change_reason_wizard_views.xml b/fusion_claims/wizard/status_change_reason_wizard_views.xml index d903cc2..8cd2b83 100644 --- a/fusion_claims/wizard/status_change_reason_wizard_views.xml +++ b/fusion_claims/wizard/status_change_reason_wizard_views.xml @@ -26,7 +26,7 @@ + + +
    +
    +
    +
    +
    + + + + + + diff --git a/fusion_shipping/views/res_partner_views.xml b/fusion_shipping/views/res_partner_views.xml new file mode 100644 index 0000000..ee35afa --- /dev/null +++ b/fusion_shipping/views/res_partner_views.xml @@ -0,0 +1,15 @@ + + + + res.partner.form.inherit.fusion.shipping + res.partner + + + + + + + + diff --git a/fusion_shipping/views/sale_order_views.xml b/fusion_shipping/views/sale_order_views.xml new file mode 100644 index 0000000..88ff3f2 --- /dev/null +++ b/fusion_shipping/views/sale_order_views.xml @@ -0,0 +1,49 @@ + + + + sale.order.form.fusion.shipping + sale.order + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fusion_shipping/views/stock_picking_views.xml b/fusion_shipping/views/stock_picking_views.xml new file mode 100644 index 0000000..eb9f173 --- /dev/null +++ b/fusion_shipping/views/stock_picking_views.xml @@ -0,0 +1,18 @@ + + + + stock.picking.form.fusion.shipping + stock.picking + + + + + + + + diff --git a/fusion_shipping/wizard/__init__.py b/fusion_shipping/wizard/__init__.py new file mode 100644 index 0000000..d412c17 --- /dev/null +++ b/fusion_shipping/wizard/__init__.py @@ -0,0 +1 @@ +from . import choose_delivery_fusion_rate diff --git a/fusion_shipping/wizard/choose_delivery_fusion_rate.py b/fusion_shipping/wizard/choose_delivery_fusion_rate.py new file mode 100644 index 0000000..dba0557 --- /dev/null +++ b/fusion_shipping/wizard/choose_delivery_fusion_rate.py @@ -0,0 +1,446 @@ +import json + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + + +class ChooseDeliveryFusionPackage(models.TransientModel): + """One package row in the Add Shipping wizard.""" + + _name = 'choose.delivery.fusion.package' + _description = 'Shipping Package (Wizard)' + _order = 'sequence, id' + + wizard_id = fields.Many2one( + 'choose.delivery.carrier', + string='Wizard', + ondelete='cascade', + ) + sequence = fields.Integer(default=10) + package_type_id = fields.Many2one( + 'stock.package.type', + string='Box Type', + domain="[('package_carrier_type', '=', 'fusion_canada_post')]", + ) + package_length = fields.Float(string='Length') + package_width = fields.Float(string='Width') + package_height = fields.Float(string='Height') + weight = fields.Float(string='Weight') + + # Per-package cost for the selected service (updated on service select) + selected_price = fields.Float( + string='Cost', digits='Product Price', readonly=True) + currency_id = fields.Many2one( + 'res.currency', + related='wizard_id.currency_id', + ) + + @api.onchange('package_type_id') + def _onchange_package_type_id(self): + """Pre-fill dimensions from selected box type.""" + if self.package_type_id: + self.package_length = self.package_type_id.packaging_length + self.package_width = self.package_type_id.width + self.package_height = self.package_type_id.height + + +class ChooseDeliveryFusionRate(models.TransientModel): + _name = 'choose.delivery.fusion.rate' + _description = 'Shipping Rate Option' + + wizard_id = fields.Many2one( + 'choose.delivery.carrier', + string='Wizard', + ondelete='cascade', + ) + service_code = fields.Char(string='Service Code') + service_name = fields.Char(string='Service') + price = fields.Float(string='Shipping Cost', digits='Product Price') + expected_delivery = fields.Char(string='Expected Delivery Date') + is_selected = fields.Boolean(string='Selected', default=False) + currency_id = fields.Many2one( + 'res.currency', + related='wizard_id.currency_id', + ) + # JSON: [{"pkg_id": , "price": }, ...] + per_package_prices = fields.Text(string='Per-Package Prices') + + def action_select(self): + """Select this rate and deselect others. Update per-package costs.""" + self.ensure_one() + # Deselect all, then select this one + self.wizard_id.fusion_rate_ids.write({'is_selected': False}) + self.is_selected = True + + # Update per-package costs from stored JSON + if self.per_package_prices: + try: + pkg_prices = json.loads(self.per_package_prices) + for pp in pkg_prices: + pkg = self.env['choose.delivery.fusion.package'].browse( + pp['pkg_id']) + if pkg.exists(): + pkg.selected_price = pp['price'] + except (json.JSONDecodeError, KeyError): + pass + + # Apply margin from the carrier + carrier = self.wizard_id.carrier_id + price = self.price + if carrier: + price = carrier._apply_margins(price, self.wizard_id.order_id) + # Check free_over + if carrier.free_over: + order = self.wizard_id.order_id + amount = order.currency_id._convert( + order.amount_untaxed, + order.company_id.currency_id, + order.company_id, + fields.Date.today(), + ) + if amount >= carrier.amount: + price = 0.0 + + self.wizard_id.write({ + 'delivery_price': price, + 'display_price': price, + 'fusion_selected_service': self.service_code, + 'fusion_selected_service_name': self.service_name, + 'fusion_selected_expected_delivery': self.expected_delivery, + }) + # Re-open the wizard to show updated selection + return { + 'name': _('Add a shipping method'), + 'type': 'ir.actions.act_window', + 'res_model': 'choose.delivery.carrier', + 'res_id': self.wizard_id.id, + 'view_mode': 'form', + 'target': 'new', + } + + +class ChooseDeliveryCarrier(models.TransientModel): + _inherit = 'choose.delivery.carrier' + + fusion_rate_ids = fields.One2many( + 'choose.delivery.fusion.rate', + 'wizard_id', + string='Available Services', + ) + fusion_selected_service = fields.Char( + string='Selected Service Code', + ) + fusion_selected_service_name = fields.Char( + string='Selected Service Name', + ) + fusion_selected_expected_delivery = fields.Char( + string='Selected Expected Delivery', + ) + + # -- Package list -- + fusion_package_ids = fields.One2many( + 'choose.delivery.fusion.package', + 'wizard_id', + string='Packages', + ) + + # -- Unit labels -- + fusion_dimension_unit_label = fields.Char( + string='Dimension Unit', + compute='_compute_fusion_dimension_unit_label', + ) + fusion_weight_unit_label = fields.Char( + string='Weight Unit', + compute='_compute_fusion_weight_unit_label', + ) + + @api.depends('carrier_id') + def _compute_fusion_dimension_unit_label(self): + for rec in self: + if (rec.carrier_id + and rec.carrier_id.delivery_type == 'fusion_canada_post'): + rec.fusion_dimension_unit_label = ( + rec.carrier_id.fusion_cp_dimension_unit or 'cm') + else: + rec.fusion_dimension_unit_label = '' + + @api.depends('carrier_id') + def _compute_fusion_weight_unit_label(self): + for rec in self: + if (rec.carrier_id + and rec.carrier_id.delivery_type == 'fusion_canada_post'): + uom = (rec.order_id.company_id + .weight_unit_of_measurement_id) + rec.fusion_weight_unit_label = uom.name if uom else 'kg' + else: + rec.fusion_weight_unit_label = '' + + @api.onchange('carrier_id') + def _onchange_carrier_id_fusion_packages(self): + """When a CP carrier is selected, create one default package.""" + if (self.carrier_id + and self.carrier_id.delivery_type == 'fusion_canada_post' + and not self.fusion_package_ids): + vals = { + 'sequence': 10, + 'weight': self.total_weight or 0.0, + } + # Pre-fill from default package type if set on carrier + if self.carrier_id.product_packaging_id: + pkg = self.carrier_id.product_packaging_id + vals['package_type_id'] = pkg.id + vals['package_length'] = pkg.packaging_length + vals['package_width'] = pkg.width + vals['package_height'] = pkg.height + self.fusion_package_ids = [(5, 0, 0), (0, 0, vals)] + + # -- Rate fetching -- + + def update_price(self): + """Override: for Canada Post, fetch all rates for all packages.""" + if self.carrier_id.delivery_type == 'fusion_canada_post': + return self._update_fusion_rates() + return super().update_price() + + def _get_fusion_package_info_for_pkg(self, pkg): + """Build package_info dict for a single package, converted to cm.""" + carrier = self.carrier_id + return { + 'length': round(carrier._fusion_cp_convert_dimension_to_cm( + pkg.package_length), 1), + 'width': round(carrier._fusion_cp_convert_dimension_to_cm( + pkg.package_width), 1), + 'height': round(carrier._fusion_cp_convert_dimension_to_cm( + pkg.package_height), 1), + } + + def _update_fusion_rates(self): + """Fetch shipping service rates for every package and aggregate.""" + carrier = self.carrier_id + packages = self.fusion_package_ids + + if not packages: + raise UserError(_( + "Please add at least one package with dimensions.")) + + from_unit = (self.order_id.company_id + .weight_unit_of_measurement_id) + + # -- Validate every package -- + for pkg in packages: + if not (pkg.package_length and pkg.package_width + and pkg.package_height): + raise UserError(_( + "Please enter dimensions (L x W x H) " + "for all packages.")) + if not pkg.weight: + raise UserError(_( + "Please enter weight for all packages.")) + package_info = self._get_fusion_package_info_for_pkg(pkg) + weight_kg = pkg.weight + if from_unit: + weight_kg = round(carrier.convert_weight( + from_unit, carrier.weight_uom_id, weight_kg), 2) + carrier._fusion_cp_validate_package(weight_kg, package_info) + + # -- Clear old rates -- + self.fusion_rate_ids.unlink() + + # -- Fetch rates per package -- + # {service_code: {service_name, packages: [{pkg_id, price, exp}]}} + all_service_rates = {} + + for pkg in packages: + package_info = self._get_fusion_package_info_for_pkg(pkg) + carrier_ctx = carrier.with_context( + order_weight=pkg.weight, # raw, in company UOM + cp_package_info=package_info, + ) + rates = carrier_ctx.fusion_canada_post_rate_shipment_all( + self.order_id) + + if isinstance(rates, dict) and rates.get('error_message'): + raise UserError(rates['error_message']) + + if not rates: + raise UserError(_( + "No shipping prices available for this order.")) + + for rate in rates: + code = rate['service_code'] + if code not in all_service_rates: + all_service_rates[code] = { + 'service_name': rate['service_name'], + 'packages': [], + } + all_service_rates[code]['packages'].append({ + 'pkg_id': pkg.id, + 'price': rate['price'], + 'expected_delivery': rate.get( + 'expected_delivery', ''), + }) + + # -- Keep only services available for ALL packages -- + num_packages = len(packages) + available = { + code: data + for code, data in all_service_rates.items() + if len(data['packages']) == num_packages + } + + if not available: + raise UserError(_( + "No single shipping service covers all packages. " + "Try adjusting package dimensions or weight.")) + + # -- Find cheapest service -- + cheapest_code = min( + available.keys(), + key=lambda c: sum( + p['price'] for p in available[c]['packages'])) + + # -- Create combined rate lines -- + vals_list = [] + for code, data in available.items(): + total_price = sum(p['price'] for p in data['packages']) + expected_dates = [ + p['expected_delivery'] for p in data['packages'] + if p['expected_delivery'] + ] + expected = max(expected_dates) if expected_dates else '' + is_sel = (code == cheapest_code) + vals_list.append({ + 'wizard_id': self.id, + 'service_code': code, + 'service_name': data['service_name'], + 'price': total_price, + 'expected_delivery': expected, + 'is_selected': is_sel, + 'per_package_prices': json.dumps(data['packages']), + }) + + self.env['choose.delivery.fusion.rate'].create(vals_list) + + # -- Auto-select cheapest: update packages and wizard -- + cheapest_data = available[cheapest_code] + selected_price = sum( + p['price'] for p in cheapest_data['packages']) + + for pkg_data in cheapest_data['packages']: + pkg_rec = packages.filtered( + lambda p, pid=pkg_data['pkg_id']: p.id == pid) + if pkg_rec: + pkg_rec.selected_price = pkg_data['price'] + + # Apply carrier margins + price = selected_price + if self.carrier_id: + price = self.carrier_id._apply_margins( + price, self.order_id) + if self.carrier_id.free_over: + amount = self.order_id.currency_id._convert( + self.order_id.amount_untaxed, + self.order_id.company_id.currency_id, + self.order_id.company_id, + fields.Date.today(), + ) + if amount >= self.carrier_id.amount: + price = 0.0 + + expected_dates = [ + p['expected_delivery'] for p in cheapest_data['packages'] + if p['expected_delivery'] + ] + + self.write({ + 'delivery_price': price, + 'display_price': price, + 'fusion_selected_service': cheapest_code, + 'fusion_selected_service_name': ( + cheapest_data['service_name']), + 'fusion_selected_expected_delivery': ( + max(expected_dates) if expected_dates else ''), + }) + + return { + 'name': _('Add a shipping method'), + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'res_model': 'choose.delivery.carrier', + 'res_id': self.id, + 'target': 'new', + } + + # -- Confirm -- + + def button_confirm(self): + """Override: store per-package info on the sale order and enhance + the delivery line description.""" + if self.carrier_id.delivery_type == 'fusion_canada_post': + order = self.order_id + + # Clear previous package records + order.fusion_package_ids.unlink() + + selected_code = self.fusion_selected_service or '' + selected_name = self.fusion_selected_service_name or '' + selected_delivery = ( + self.fusion_selected_expected_delivery or '') + + pkg_vals = [] + for idx, pkg in enumerate( + self.fusion_package_ids.sorted('sequence')): + pkg_vals.append((0, 0, { + 'sequence': (idx + 1) * 10, + 'package_type_id': ( + pkg.package_type_id.id + if pkg.package_type_id else False), + 'package_length': pkg.package_length, + 'package_width': pkg.package_width, + 'package_height': pkg.package_height, + 'weight': pkg.weight, + 'service_code': selected_code, + 'service_name': selected_name, + 'price': pkg.selected_price, + 'expected_delivery': selected_delivery, + })) + + write_vals = { + 'fusion_package_ids': pkg_vals, + 'fusion_cp_service_code': selected_code, + } + + # Backward compat: first-package dims in legacy fields + first_pkg = self.fusion_package_ids.sorted('sequence')[:1] + if first_pkg: + write_vals['fusion_cp_package_length'] = ( + first_pkg.package_length) + write_vals['fusion_cp_package_width'] = ( + first_pkg.package_width) + write_vals['fusion_cp_package_height'] = ( + first_pkg.package_height) + + order.write(write_vals) + + res = super().button_confirm() + + # Enhance delivery line description with service details + if (self.carrier_id.delivery_type == 'fusion_canada_post' + and self.fusion_selected_service_name): + delivery_line = self.order_id.order_line.filtered( + 'is_delivery') + if delivery_line: + line = delivery_line[-1] + parts = [line.name] + parts.append( + "Service: %s" + % self.fusion_selected_service_name) + num_pkgs = len(self.fusion_package_ids) + if num_pkgs > 1: + parts.append("Packages: %d" % num_pkgs) + if self.fusion_selected_expected_delivery: + parts.append( + "Expected Delivery: %s" + % self.fusion_selected_expected_delivery) + line.name = '\n'.join(parts) + return res diff --git a/fusion_tasks/static/description/icon.png b/fusion_tasks/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5738185dcb6a3cd61de15da3dcc51b9d4cb5a49c GIT binary patch literal 43989 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 Z9Xa0@WCXV>@0 { const cb = "_fc_gmap_" + Date.now(); @@ -720,10 +717,7 @@ export class FusionTaskMapController extends Component { trafficModel: "bestguess", }, }, (result, status) => { - if (status !== "OK" || !result.routes || !result.routes[0]) { - console.warn("[FusionMap] Directions API returned:", status, "for", seg.name); - return; - } + if (status !== "OK" || !result.routes || !result.routes[0]) return; const route = result.routes[0]; diff --git a/fusion_tasks/views/technician_task_views.xml b/fusion_tasks/views/technician_task_views.xml index 054333a..0406458 100644 --- a/fusion_tasks/views/technician_task_views.xml +++ b/fusion_tasks/views/technician_task_views.xml @@ -473,7 +473,15 @@ groups="fusion_tasks.group_field_technician" sequence="45"/> - + + + + - - -

    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 diff --git a/Fusion Accounting/models/__pycache__/account_tax.cpython-310.pyc b/Fusion Accounting/models/__pycache__/account_tax.cpython-310.pyc deleted file mode 100644 index 76c71b646d43903243bd078ac6d5493d229c2163..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9373 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 U%g^RI!4DOtFDOrG8mEl^0e{D?3jhEB diff --git a/Fusion Accounting/models/__pycache__/balance_sheet.cpython-310.pyc b/Fusion Accounting/models/__pycache__/balance_sheet.cpython-310.pyc deleted file mode 100644 index d673e33fbf03af829c0c9687ddaf83c9d4ffdb10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 867 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 C09+IR diff --git a/Fusion Accounting/models/__pycache__/bank_rec_widget_line.cpython-310.pyc b/Fusion Accounting/models/__pycache__/bank_rec_widget_line.cpython-310.pyc deleted file mode 100644 index 6a3eb6fb3f15e859020664683b1a3f32549035a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12691 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 diff --git a/Fusion Accounting/models/__pycache__/bank_reconciliation_report.cpython-310.pyc b/Fusion Accounting/models/__pycache__/bank_reconciliation_report.cpython-310.pyc deleted file mode 100644 index 1617dc04cd6204470a074e50dd03e2a89be82026..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23220 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%0DRFr0#cy)anu;Xt|gnXXebAIrnomYqtXn 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 B2i5=p diff --git a/Fusion Accounting/models/__pycache__/digest.cpython-310.pyc b/Fusion Accounting/models/__pycache__/digest.cpython-310.pyc deleted file mode 100644 index 39b7c550c7c731f9e556ba88d104b3f81873c735..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1643 zcma)6PmkO*6t`n1lliyXqV0;(N(3PagQ^{Y5Q1vEf_4$7-3zw}WVx|tCbRihJ6W+& z=EAJh3w(!}8~sYYa^f4Hf)LM{nX*DX;3&_2_Osu6zxT8A;jo9GnTLPOesvN0%_=t= z1?4OF_2(c25;#FA4sq&)PD(<8?VOX))D2yXzD0rvdW{5?&fHs)up`{>(8&88_M#Cn z-XxZZ(8f!1Aroyd8)4J`I*O#$-zb%<$Qs^kWUJ^C7)TUifkH>%kVv|6g|o(?C+WOn z-Ts0@U%0}%Mq&2_nqbip{6p-a4=4LU_X>@VELop+1l)?;tsHFVW)yftoIHhBJ-KmbPn zynbh!*3neuRq;B>N=sEgI?aPDFM}DslmXa5fgT3>A})e*7VALpk_VGq1=jdO&|5qM znesTP2fK4qfYMN~m9=y7_!T}Lk$QigS1JP(V{ueo71B6yHgXKLcp7SKj*5;sq!AM!kntN<2%eHvCLKa-{RF+a{(`QO@ zvDsrt42+~Aj!Ly>cecd@_lCd;=HAWv|F`lTL$ypM#?>WPrHx6L?k4&eNMuvz##1uN zl~9Lp_)DbTh3?gt<0#215hazj0gZE!=VLBJp6M|!ccqB4>3DNf##_P`SIW}L#;F54 zzyrMJ^so<~gL~Nd%XjIYzUMo8cn{OR>rYNMO#lCMj&@fLj)$9rkacM1^rs+7R03O< z&dOAMAFvbCWpZStLgtC`46Ar96n#+_{;vhU3c^;5A-VSVpRS zYeSTr00YH{s=J`KHBz^>EBYliUB;q>Yt2|&I2Bl{_d(b)YU}4Md>>O>-+u$pubqwt z#%C-8`Y~pDj6qSW#Eu7yJ+FAOUGZ6*&7_J;<-ozF^CZs`$usqy)%>$OlR&d8^){$2 z4cb7=a2sMIlVlgBjZS@JMPP-E(>_Id&<-f>IRorF2e;dm^O0|;Wnk-8*;;Sz(7v5K ql0QbWXt8UrmvPVRU6L2YnC!fo)O^-c0&XM?=rad(@+GbBniKwA4xnL79~_d+pv}bDxFX}ex*U3 zC?!?y-fWVO$`P`@ivmbW1xYZ+Yzoz9Q5rH*0ID$u%=jgDo3>KbLN2bl4ID;BajG-zxc>eyUB ze0Z*8*T!ed!?=LVbPA@HiOhfng$84Ts;o<6 zqqmuAFpW+JWTGsJPekn@nD5VLc_llMSDiMX^h^pVXPLmd^(-@dJtlOX4T~}R)hfPj z6(3qPZI|}J`pk2ThSfoHUN*r0c4=I_Zo)c35z&a=^CtA__}|+y{jaczKWx)H32el9 zlhwevop6o?bd@`AbN;%^s`X98%jN=9Y1BSq>ASKa?l_yJ0Kn`tBd$C-P(`@CegS}fs#I5}h zIPy!ra^epVC&rruazKnU;~nqYw{M>9hQmXkh%bIFN(S&fHTxl`Iin26R1lC^ShA8a zcnioedjQM~yXE&;nW4CZaqkD=V9esd(xI_Ir*=O$HD{FJITZsXLnt{i`ka>niR1yw z9`=apARfK*Q{7aq4pXgNTQ?VG<$QDQKGxPLjK`IiY6GCPLrC92ByKV5;>zenU1??W zPEkI0KHq|?`X=At^Bq2Da^X*;=Op+IWf)QU47cRLGx);3() zpis?jL{eRB%q{-TKUsb9kBB4jgd};y*C+qCZ}KlmGqSxeWv!M*%6KRx{i!9vG7hBt z&?>uq)9WC#i6&@Ytuk)DAJrhiH^hRb!a>} d`99sA4zb6ThVzLq_D?suUZoQXiF&}E^54U(z%Bp) diff --git a/Fusion Accounting/models/__pycache__/ir_ui_menu.cpython-310.pyc b/Fusion Accounting/models/__pycache__/ir_ui_menu.cpython-310.pyc deleted file mode 100644 index 1947d6fe9005ec1d84329999a4d11536218f0da8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1099 zcmZ`&&1xGl5SFyNVV&4%lRzjvgwoqWyY2&|A(WERLvjhdEEKU>$+paTF4|eF?!i1hvn> z2qI`nMl_?8yd#2%;EISq(jmJHvOt8VB#C}P97(8O8)+d+TcCSi5D=vSlq92qWK2-F z9$k|j6##S9%ED_Bk}LwiH6On>AJnm|++!X6raT*(eh3p%(hp?#0CApzf zdP`^QGa0gBa1#pf>|P)8{zhlP>alnlyd{@(7EakTd_qpilwlnwlfAqsw5#g0Z?qfp zg~yG|g|5nrB!Gx|xvi>xx{RpvKF1s<&mNxR-ZGvypp!TMjHs2aR)I#2wW;~Ym9;9I zF|sNyAc^z5bU6kV6zW|HlClQ618P$+12QEacV-kwAVVMoTlj?;BxOO+4Z9`7;4Axt zWwRq_-L}bxA}9iG+EUzVg)EQP`Aa?~eR_-7QM( zL{U0hOJloQXx+^PP-eRiitWw^q^k^fs^KdgU-;I(m35&<#M5=Y2>b)5iTs$TQdX8_P!k zKYP4rD|EPd)Mk$cN(fo4Qh@4PoEK$oE$3zffXpVEEif%mi`Z?t8N_ruifKqEPyb&} zLXU9fIj{1OvHwlay7tunAe0{Z>~Ta1LGXTd;?0N2n!Sufg?}SrSGbLz)>Km@ZY1TCjzM6v-d9ISgrE%ir>DF zh0^6kTqZY3xvoYTPtqCh%r7Bh5~s=}`SpYgk(7zbc{(Y$=9!sd8<{QG3O8hV<`b-4wwDqU^}em9yxnQ>*_@GA#WrG zfQ4@8_E-!zo*0Sak0b%ium{(llgsK6FBxxGg#FJPZD2PnvT|KWR`Oe>Yj#u=SoXvq%q)_p4h(3 z%?dm!m-h6K0YK%+{Kk4(E^P3NRj%`12F$Sp989uh|`rRr8aXVl$yiw0EXrV8_89|otJHlD8T)O;(>4;aB!4A!Qnm#CY*entfJuMll%rrzO`Pe@d=JEgph86P xkPJ!fy_^P?I)dtA+Fe)L-HXF&GB<@?ThFz6$&kBjfcIY#I+6uTBdF$TA15Cjrfp%Q1fBpJcz76~Hg zD-u+aC4He;B%&!wI$to065_iLg;c`&ZmES-HV^QwQ7FdX_5^}N85Srb0%uf4%Z?y( zEa)4~y6sYbe4XdIuH1~T@8wDxcb&W9sc@UwW+k78B`?&7Z=sO`3>fqsUo+E8KlkCo z(>71AA6Tg*OzVnqAtp)B$Bb3Hl#Ka4W5BppA>U{0vF2*qiCIxCq$!*sK-uq2|CabT z=T6SG*}Nk&fL;@RZqi)oTI8y>P8yqPp>+!X)0IuRW4u*VRP(ewQ5sYL+zMks#b~uL z;T3w|whJLf5gw8Tn-*=oCj=SDVcKBPzV7c#qv A$p8QV diff --git a/Fusion Accounting/models/__pycache__/res_company.cpython-310.pyc b/Fusion Accounting/models/__pycache__/res_company.cpython-310.pyc deleted file mode 100644 index 8bdde8b2349832852440cc6175417b4e73b82f5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21903 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 bRISXfqJpWEg@I0+pJ>mvgtmYIdf@y5vh`w8 diff --git a/Fusion Accounting/models/__pycache__/res_partner.cpython-310.pyc b/Fusion Accounting/models/__pycache__/res_partner.cpython-310.pyc deleted file mode 100644 index 25cd61795aef705246f99694b540a16fcccc4ab8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2442 zcmai0TW{P%6rQoim-Su}(rW;<5h|4oB`YcfJX94DRe54TZJ#XA$n{J%4!(89n>4E} zFNxGAenC@d^Vq*MJo40kfEEcj$Gh8Zs#f)Cj>mKHob#RUeBQF#4G3I+-uQd>_ZA_4 z;lt|1;Nd%HdK-)&f@UPA5zVd0%I(O`S;Q#$fe1_3$3)oD-gA!Z$QA5937wa)hlI@7 zag`c(CzYAd#?4D1Gi@kOaC-H^!|>9-fRQAkf<%^}kqwPWXX@fHr&i<%CY)mu`N9<* z+*`sIEw~4=J?)6#ls+XpRJ29s*gv9CSFDSkSUaXs?X_pv?idh zpo0Mkx=E%~Sf}&}g=ed_(H>D@|7HnxVhKmMC)R`t?-rRr@S#5#Z=`BFPU;lATHePu z&qTHuUdD<@m#SU{$ulX2QoTfU0m=3vc1VCW!Ki@91&_1r0nB-2)tqB`>98o3C`iex^g-s%V1Z#f1dw_HRNHgZl?jLf_MQ>Jp4;D$X53YuFUH+H@63!3PtJ2| zVqF4~^46oCfVYP3KwXDZ4Vlrc8ND&Hx4zg4gE?Wmq%3OrxHdMRWE?%Jq%s#cYQ(6P zT%@|n;zM4iY>oa~@333IKIRUAh77Jp9;fytRVCtO5)p{@i93Yc0`{E&P>*GCF??`0bkqiTqt1abol&7AfS;tG0#Ro-6>=#PQ5&kB z1JzY3^|*_rQP**@o8!=DxVg0hyXHx8AErgD4o#;SYO-0>#n#dPK4 zM$+n%!!dJCxrvf7NIsruoW@Oc4f-Hba~Yb;pu&7dYp-MD7{Z(7dA$+!Wo@l|vQS80 z??n=6#Ks5O^ekpEAQ2WX{``LeFQ6NXF&9SI%>8ft>PjR~8<1Hk8fwENegG5- zu=)tir)cn_W!8iQr51XbkEV3gU7YGJDl3sdokew_j;XKFpv)^53<_#fXFfrII+kzw zw(t8vk8ZS^6@$<-3_sP)k&W953reAa8Rtj*gVQWrNmO%C^C|If&C$4x`mb@{W&5K2 EKeOkawg3PC diff --git a/Fusion Accounting/wizard/__pycache__/__init__.cpython-310.pyc b/Fusion Accounting/wizard/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 442ffb67f697dbadd70c386835864290a0409928..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 658 zcmZvaJ5B>J5Qe>ZZn7JM1VT_yP)0)R0YHco(9pTDjO~Dh*N*%E!U;GAEmulQMM;OK zm@x?;gjf6Pvu9`K_u6hU850<9hws-<1tBju`8_%?IfFMmf=D6@L)wBC1#$UgkbljbbF|UNPLL0^I^aJJ(2d?10pyF8)57#U~z4xT*jE diff --git a/Fusion Accounting/wizard/__pycache__/account_auto_reconcile_wizard.cpython-310.pyc b/Fusion Accounting/wizard/__pycache__/account_auto_reconcile_wizard.cpython-310.pyc deleted file mode 100644 index 5c3c7d2000f4496aa18b3b0e1bcd0e41fcd006f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7168 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 diff --git a/Fusion Accounting/wizard/__pycache__/account_change_lock_date.cpython-310.pyc b/Fusion Accounting/wizard/__pycache__/account_change_lock_date.cpython-310.pyc deleted file mode 100644 index d10720513071fe70201aa372dabd1b81503b2ba0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15249 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 X(}5$v3vKAn8+mx%(#)SXx7hy|Bv_#` diff --git a/Fusion Accounting/wizard/__pycache__/account_report_file_download_error_wizard.cpython-310.pyc b/Fusion Accounting/wizard/__pycache__/account_report_file_download_error_wizard.cpython-310.pyc deleted file mode 100644 index 5e59199371a83aab8a415387e7131adafcd49307..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1034 zcma)5OKaRP5SC=waW=c@gOalkrC=y_3#GmEoj#zD9GXDE6og`F69=!fm2A?O-kN{W z7LsHClCC|JUV1Kt(vIvbEImaajXe4^GvBANo=ipv#?PC-4!#qFzWI;kLE+;uOnUSDt3WCu~B!~!3kO(9>3_b=06?6xs;Sa!~l(=YG$y!(!&9soUExmhrK*AyT z9oR@zV1WuEa1jVnP#K;^XQ)^K8-ra1I|92V0zpquF%}`Dirn=lWvN@$yq0rqnio|q zpXno2Yc8G}qs^PD<3@BTA5fX;Vc8ULnK%b#(j&8@0eW|fE50w2=0GMMmh4L<4R0!~ zk{)Q2snN4!h?L0pKCjJ6yu{4!Bbb{o?JZb7pi^{)FxW$EHUt&~cH|Q%=RIKQmr#Tv z`W)v>;Sk~G~@QtLht&vPC=~nyeiqE7Yl}NFpR@T$6$c%yA@3%SJX+>GLwvoo> zT4+IZp^C3L{BTl&9NIVzk=}`dI6V~iTC-x&iBv25>ZTXOky14 zG3n2?)6h{ba$kW70P5Cv4Og~;zS#1ATaR0c>)*-v;4Z#DP<-gS)cRDH$wu%G=;1B* diff --git a/Fusion Accounting/wizard/__pycache__/account_report_send.cpython-310.pyc b/Fusion Accounting/wizard/__pycache__/account_report_send.cpython-310.pyc deleted file mode 100644 index f988372ea53ee5a00aaaaaaae27304224c13fee0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10408 zcmbVSOKcoRdhYJ&>1hs!!c(A!!^Z|EJP zVRQwHul+Me)8SioV$zyH{wGg|({HO1}mzt!5^= zn-sjQAhFg1zb&G~>V(2?M>Rbe`yle=btyxM7kk5x-L}`=Xi~rYUnzZwW(-a9wT3RV zh9UGuK^P6wFSaaEpcyEKS<+o`ugD;bKo)WYCJe{kb!Sk#*A?C!qIQgK^IKl6y#A)#uD9mBJ zDU3Id+LPjO@q~C1wWsh#Gkr>&!AN7mc~ESe<`z$jXSn7p=K2_F9vA2EuIj#rR!^XA zN}T657cknB{!^_pKhxHAu^?*q?R$E|p&Ie5c<#Q2x2MHLap}I+ct+I4BJO9!^Wp{E z&vC2E;ze%tJ^!iw^Wyslx_C*v%rzId<`vYuDt?P=7Pw|fydr*ZP!LzdYg}6!^n6|X zkZYdhnm5GN14Fziu5sOSxw^N+b*{U}b<5(0cpLQI5$|&CrL^{K^!}0fG1t|3t~bRB zzb%Se;yv*@_w~l};sf!%SjE!|KhsRDaT${KVKRNS*$lV4@%#Q(DC1SXD|)k@s;Wz_ zt4Ca!wsTZNhgyC%F8!#!<;l3~%jAUH3_DxfvG1leZXlwXkyKDkV(WIjj-Qm$M)+Vw zL3g9=_oi0)4$947EAYE9z7;#$?KtSo-lW@B+aG?6l8M1UM0fcX6lE;(+kP_+!mg}9 z-MUno03>+A28)%Tl2@T0m;l@LOf@(3)#Y$H{}7J zqvZ5x+x*5AhlF{da)#zKO>-)(Zm+e#?&Pu2?vbi?*Fv#dGZims$Xr+ZTG)-T#Zj%O z)>n(`}!SCp2gj`qsz1X0=~gI;+xqw`-U*CX#E22McfVO zjx6562T~$PB$y74@5GSHVMRXQXOp$`7K71j&K?M#*H_Hk*RHfWTpna1#T18PQRE;UUqTBd4 zjoy=258IP^Zc$CzknGRG18vTaa7A?#eIqi3wu*ZTtQPtMy`_WoddpZh0Q9R3|AizZ2^=)O#b6FQS_&zE1?^T=Ko#n%lW|y!N)=z^7;)g=v_&p_{t- zwQZPR8MrK5y$eT~bS#}F$!+#W&%~~4xFWI*eQjSONyc688wdJHFy)cH0_%C6sloa* z{mrmVeBc@8wMTfdmz*7rnl0Tu+z1Lrtdc*V;tCaqSa6PsWSe|sAf={j=orz4vzUC{ zd-)qU@Ev5DI!I)Hr0671%n+IxG2}qgac}8+kd%FdIvY^#Xcje)n!YY{D6iH+IDxW+ z%SN5?(1bJQ0iGLDj%b$OX)$HkOqAZnQ0Z zH-vYjDtVEL>r}i-1x1Wxff0jALPp!fJf*n}cnV$oIl^1;3`P^BDU1W-=h`m{dxn5n#1jlB z1(U*hXm04>XvupCTLjM?Z_94vcE44RuD5=4%&#}1x0 zd~6n+w#bo_Y5_~mRfmkO6RtZV;9$d(yG+W5Q_9bA*!fu}5ZMH>Wg`4oa%eUS>)vg@ zh89GUSToeUi)vz+QP=m05kVKTB$Ed7OfT`J+x~8T9Ez?+M;eSnpNpTnoeYk~7@WSZ@2Yom>};_9nfpt#;7#sQDs3Blu$#N=%L}?QFKt<)QUMtnm&+S*}oV9!2svS?$c$q~3Tv zX!}G&sc+8eCHCOcsOgh$5#)wF0``B&Tf|xhp z9(>ttLrw^ztHb{4F1CPTr<%F)YjTouf(i=IYK5c#$>tFflM;_dsh6>oA2{S>?>`rvnIUP52!Qgm#y`# zearF&etjhUgUk$sN6~}qH$W|!6F~$e-&NX8ObUV3q|rn!!eFz4INTS>%y5h>am~W6 z-vi0UV`(=^dSW=qG~y&7KD0b}677G1DD__1?m8@H6rmswi|W0}4+DS488+{ozD5CKuEt?30F(e9kdcIP zmOAL`ZTQK!$3X?M5afK8dP(Lyl~1{yAR@fPDib?fAN(bU^R#oaL+$5|6|TdaJcXve#Kles$&5aQtj{tmNLTqw+cN*FclKK( zEE|1fGc(`Dk_2p3Nz#d-g^-hw97qu4q40?jo6vGd3u1SECu{e}SYhh`evs~|;QA$c zdX0(_ilpFo?<8iEjMPU|c}PT_MBU%wN)@Jz2ztsekB^-*VPU^Qi6=)B955VN*&tPW z3dC7Q%4%fvAH8WEP~uOYvC+1X_i^y z)B$NDd@}+=Tp401$wcU!i5=MSN5(PEYv<)N6x&-9D8 z;4Dna(vku?CPjdGkq_|DY=uCz3LS4NDIjfQa3KQ{#&dH!WciY*)Hw{mjDI24lRg>d zJOKz__MSP0@oE8sPAemr9^u0Sjl2SP-8UoXPD|?+`sTiVpzRg=MWLV8_AJ~D+zExj zUYyo|Dij5fJ;V)m-;@i4B-}^;mbrCqgEO7Iv98&-3BB1H6QwL#*e{7OLV5PeOsUM2 z%AhjdFGS1z@qJs2AHZuM8ljOY7_-tJM=X6~Z=yc|Z(@isL>&|RV|{|rjJ;~#lAj`4 z`4qEgWBjt1K&0J9@A17!l$E`y?mR3CdiQNmsEX==$zO&wlVVDgU`M7O6!)gkX0kuk zp9Fk7)~~j#lNyf2a4gs_$-nAX`qfu3qfc)3EAkwPEV5&=V&mvcyma33)p5 zkhqG32zSQ$#K>#_Xi$v;!#}!^Gv*gQt_>2-bfAHE%O4vp4Um?!GeDAvG%0p=0^msK zP)2cf_QU`=|6IdIAM!&+Pt%c3O*Ia!3WTtXbvAoE%eZ0@bCCIQfm^ zLn10G-V=A_(xPyZ15Bn?rcAct!7sX0#I+;6G{v^TuatlRbt;ui)xzTRcOW~pn zlde;P6vtNKcXmFJ9jfFe$FbiIQMpUS8&psf2=A(5YnR-z{0XXR(+Xj=lsvP4PUemk zLdm(;_z*0O8g?A20K74dr_4~Z1T-dx6N)ILQVF-63UJgol{NR)+J4@)Gqm2ARujs7 zkj&Db(qdj56by@;%p#0KoBXe67Lfy_jr z-I|zuKt}{q-Yl8pYsk@KWqU-}+~(6rM%PH*$s~0M=Ooia?@b)Gg+LW^0QEcT&W;KY zG8@CE-E_j=pRJQ$<%sFuVh+Q*Y_LwhwF`hLg_X;jGv) zXXhux6K2pcYpwTg+}Z>Z!a-AWTLS5h+qZ3j)HH=T>dnr-^wL2@F)Zov3pJ;j+5Gk}%s&8(&lVYo-V zi>tZ)!u;2KnkUVWF?xu=n%BI&A@K_fQI~%RW#~Pg8Nf!6_{VbKE8=#zBm; z@J+M_unjl>Xb^Qy3bD75STHGGN7)x-jemv@Y{ulD(-Rr3+Kd9c*)G0KwdA9uMFn{d zb>MWKp8lAMp-C=Ce;p^!_<_X6hA(sZ=Bw210Tn)q6h%m;kBC%bBJ)4IA!=fW%oi{X z0woR^uW9I3L|)J?&I7k9v&kX^i!dyMj1;=-Y-^_6MzVV64aI_6;Y z*s*YSjKkyQ+KX5#|1QOKld|gq$=rr*c9V+hezNVg={NvyMfKB_3Qi27rVKc)k!0qQ zv726Z_i_h?vi4)EgWsVL?~~G-I0f;&E`AQxWwW380N#PdR9Z{o$Z(+wEd+Hn-;MBV z7D8wk;7M#Aip~;wFM0D8xZ$-;Pb$2Fwnma4Ow6~DjVf=*-nBrLMgG+ZAKxn$O}k>w;%Nq#2?uH6UM=h85-xhjRkq<2 z>D`{WENKNRweiHHpHL{3A`Y`q$ReJSN%~Uv?>2oE2D9>|wK0Sussqq#Duk+fOM2ai i4A4!WRBfi7V;pM_`+fV0lF-+oDk@VurICKj7ybur#uOs} diff --git a/Fusion Accounting/wizard/__pycache__/asset_modify.cpython-310.pyc b/Fusion Accounting/wizard/__pycache__/asset_modify.cpython-310.pyc deleted file mode 100644 index dbc71332c9e2c4511a4739a29f4f97f626c8e9b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14648 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 diff --git a/Fusion Accounting/wizard/__pycache__/fiscal_year.cpython-310.pyc b/Fusion Accounting/wizard/__pycache__/fiscal_year.cpython-310.pyc deleted file mode 100644 index 9e7702b8241e67f67b033e816af03473f83d0742..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 984 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 diff --git a/Fusion Accounting/wizard/__pycache__/setup_wizards.cpython-310.pyc b/Fusion Accounting/wizard/__pycache__/setup_wizards.cpython-310.pyc deleted file mode 100644 index a686bf1ccf72fd54f6c394d8b1a6d0a81e71e610..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 970 zcmZ`&Piqu06wjZTby{1cT8e^&@l;lH_X|iVRq&)B6@4e)=QCqdxV$y5T}u->alh+ z(`q-cF=B8W{DYe5mZ?G6#g%vkI50*LJBr;8${~l((FM68*XT>I#+2g;u@8ckj8je~ z#OwyU$LV8qb>|wHU7kcAq~|!Yp1sdsi;A_%GymFpjj;d{5n9G z);9DHegoiBn3{zr1F1*CNY{b+%>{u{C{i@%no~Y*ZOa@HXkr-UY-Y uax`)HOq@IuKbgPx!7y!Qn3K2h$8^Cfl{<`B*=H~3P436RurYU=?9jgsRum2Z diff --git a/fusion_authorizer_portal/__init__.py b/fusion_authorizer_portal/__init__.py index c3d410e..d95dc36 100644 --- a/fusion_authorizer_portal/__init__.py +++ b/fusion_authorizer_portal/__init__.py @@ -2,3 +2,29 @@ from . import models from . import controllers + + +def _reactivate_views(env): + """Ensure all module views are active after install/update. + + Odoo silently deactivates inherited views when an xpath fails + validation (e.g. parent view structure changed between versions). + Once deactivated, subsequent -u runs never reactivate them. + This hook prevents that from silently breaking the portal. + """ + views = env['ir.ui.view'].sudo().search([ + ('key', 'like', 'fusion_authorizer_portal.%'), + ('active', '=', False), + ]) + if views: + views.write({'active': True}) + env.cr.execute(""" + SELECT key FROM ir_ui_view + WHERE key LIKE 'fusion_authorizer_portal.%%' + AND id = ANY(%s) + """, [views.ids]) + keys = [r[0] for r in env.cr.fetchall()] + import logging + logging.getLogger(__name__).warning( + "Reactivated %d deactivated views: %s", len(keys), keys + ) diff --git a/fusion_authorizer_portal/__manifest__.py b/fusion_authorizer_portal/__manifest__.py index b977607..0dc4352 100644 --- a/fusion_authorizer_portal/__manifest__.py +++ b/fusion_authorizer_portal/__manifest__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- { 'name': 'Fusion Authorizer & Sales Portal', - 'version': '19.0.2.2.0', + 'version': '19.0.2.5.0', 'category': 'Sales/Portal', 'summary': 'Portal for Authorizers (OTs) and Sales Reps with Assessment Forms', 'description': """ @@ -50,8 +50,10 @@ This module provides external portal access for: 'website', 'mail', 'calendar', + 'appointment', 'knowledge', 'fusion_claims', + 'fusion_tasks', ], 'data': [ # Security @@ -62,6 +64,7 @@ This module provides external portal access for: 'data/portal_menu_data.xml', 'data/ir_actions_server_data.xml', 'data/welcome_articles.xml', + 'data/appointment_invite_data.xml', # Views 'views/res_partner_views.xml', 'views/sale_order_views.xml', @@ -76,7 +79,8 @@ This module provides external portal access for: 'views/portal_accessibility_forms.xml', 'views/portal_technician_templates.xml', 'views/portal_book_assessment.xml', - 'views/portal_repair_form.xml', + 'views/portal_schedule.xml', + 'views/portal_page11_sign_templates.xml', ], 'assets': { 'web.assets_backend': [ @@ -93,9 +97,11 @@ This module provides external portal access for: 'fusion_authorizer_portal/static/src/js/pdf_field_editor.js', 'fusion_authorizer_portal/static/src/js/technician_push.js', 'fusion_authorizer_portal/static/src/js/technician_location.js', + 'fusion_authorizer_portal/static/src/js/portal_schedule_booking.js', ], }, 'images': ['static/description/icon.png'], + 'post_init_hook': '_reactivate_views', 'installable': True, 'application': False, 'auto_install': False, diff --git a/fusion_authorizer_portal/controllers/__init__.py b/fusion_authorizer_portal/controllers/__init__.py index 25941d4..ce12d22 100644 --- a/fusion_authorizer_portal/controllers/__init__.py +++ b/fusion_authorizer_portal/controllers/__init__.py @@ -3,4 +3,5 @@ from . import portal_main from . import portal_assessment from . import pdf_editor -from . import portal_repair \ No newline at end of file +from . import portal_schedule +from . import portal_page11_sign \ No newline at end of file diff --git a/fusion_authorizer_portal/controllers/portal_main.py b/fusion_authorizer_portal/controllers/portal_main.py index 8b60fec..cb6c927 100644 --- a/fusion_authorizer_portal/controllers/portal_main.py +++ b/fusion_authorizer_portal/controllers/portal_main.py @@ -26,6 +26,7 @@ class AuthorizerPortal(CustomerPortal): if hasattr(response, 'qcontext') and (partner.is_authorizer or partner.is_sales_rep_portal or partner.is_client_portal or partner.is_technician_portal): posting_info = self._get_adp_posting_info() response.qcontext.update(posting_info) + response.qcontext.update(self._get_clock_status_data()) # Add signature count (documents to sign) - only if Sign module is installed sign_count = 0 @@ -724,7 +725,7 @@ class AuthorizerPortal(CustomerPortal): 'sale_type_filter': sale_type, 'status_filter': status, } - + values.update(self._get_clock_status_data()) return request.render('fusion_authorizer_portal.portal_sales_dashboard', values) @http.route(['/my/sales/cases', '/my/sales/cases/page/'], type='http', auth='user', website=True) @@ -1090,14 +1091,60 @@ class AuthorizerPortal(CustomerPortal): _logger.error(f"Error downloading proof of delivery: {e}") return request.redirect('/my/funding-claims') + # ==================== CLOCK STATUS HELPER ==================== + + def _get_clock_status_data(self): + """Get clock in/out status for the current portal user.""" + try: + user = request.env.user + Employee = request.env['hr.employee'].sudo() + employee = Employee.search([('user_id', '=', user.id)], limit=1) + if not employee: + employee = Employee.search([ + ('name', '=', user.partner_id.name), + ('user_id', '=', False), + ], limit=1) + if not employee or not getattr(employee, 'x_fclk_enable_clock', False): + return {'clock_enabled': False} + + is_checked_in = employee.attendance_state == 'checked_in' + check_in_time = '' + location_name = '' + if is_checked_in: + att = request.env['hr.attendance'].sudo().search([ + ('employee_id', '=', employee.id), + ('check_out', '=', False), + ], limit=1) + if att: + check_in_time = att.check_in.isoformat() if att.check_in else '' + location_name = att.x_fclk_location_id.name if att.x_fclk_location_id else '' + + return { + 'clock_enabled': True, + 'clock_checked_in': is_checked_in, + 'clock_check_in_time': check_in_time, + 'clock_location_name': location_name, + } + except Exception as e: + _logger.warning("Clock status check failed: %s", e) + return {'clock_enabled': False} + # ==================== TECHNICIAN PORTAL ==================== def _check_technician_access(self): """Check if current user is a technician portal user.""" partner = request.env.user.partner_id - if not partner.is_technician_portal: - return False - return True + if partner.is_technician_portal: + return True + has_tasks = request.env['fusion.technician.task'].sudo().search_count([ + '|', + ('technician_id', '=', request.env.user.id), + ('additional_technician_ids', 'in', [request.env.user.id]), + ], limit=1) + if has_tasks: + partner.sudo().write({'is_technician_portal': True}) + return True + return False @http.route(['/my/technician', '/my/technician/dashboard'], type='http', auth='user', website=True) def technician_dashboard(self, **kw): @@ -1159,6 +1206,8 @@ class AuthorizerPortal(CustomerPortal): ICP = request.env['ir.config_parameter'].sudo() google_maps_api_key = ICP.get_param('fusion_claims.google_maps_api_key', '') + clock_data = self._get_clock_status_data() + values = { 'today_tasks': today_tasks, 'current_task': current_task, @@ -1174,6 +1223,7 @@ class AuthorizerPortal(CustomerPortal): 'google_maps_api_key': google_maps_api_key, 'page_name': 'technician_dashboard', } + values.update(clock_data) return request.render('fusion_authorizer_portal.portal_technician_dashboard', values) @http.route(['/my/technician/tasks', '/my/technician/tasks/page/'], type='http', auth='user', website=True) @@ -1423,11 +1473,17 @@ class AuthorizerPortal(CustomerPortal): return {'success': False, 'error': str(e)} @http.route('/my/technician/task//action', type='json', auth='user', website=True) - def technician_task_action(self, task_id, action, **kw): - """Handle task status changes (start, complete, en_route, cancel).""" + def technician_task_action(self, task_id, action, latitude=None, longitude=None, accuracy=None, **kw): + """Handle task status changes (start, complete, en_route, cancel). + Location is mandatory -- the client must send GPS coordinates.""" if not self._check_technician_access(): return {'success': False, 'error': 'Access denied'} + if not latitude or not longitude: + return {'success': False, 'error': 'Location is required. Please enable GPS and try again.'} + if not (-90 <= latitude <= 90 and -180 <= longitude <= 180): + return {'success': False, 'error': 'Invalid GPS coordinates.'} + user = request.env.user Task = request.env['fusion.technician.task'].sudo() @@ -1439,21 +1495,39 @@ class AuthorizerPortal(CustomerPortal): ): return {'success': False, 'error': 'Task not found or not assigned to you'} + request.env['fusion.technician.location'].sudo().log_location( + latitude=latitude, + longitude=longitude, + accuracy=accuracy, + ) + + # Push location to remote instances for cross-instance visibility + try: + request.env['fusion.task.sync.config'].sudo()._push_technician_location( + user.id, latitude, longitude, accuracy or 0) + except Exception: + pass # Non-blocking: sync failure should not block task action + + location_ctx = { + 'action_latitude': latitude, + 'action_longitude': longitude, + 'action_accuracy': accuracy or 0, + } + if action == 'en_route': - task.action_start_en_route() + task.with_context(**location_ctx).action_start_en_route() elif action == 'start': - task.action_start_task() + task.with_context(**location_ctx).action_start_task() elif action == 'complete': completion_notes = kw.get('completion_notes', '') if completion_notes: task.completion_notes = completion_notes - task.action_complete_task() + task.with_context(**location_ctx).action_complete_task() elif action == 'cancel': - task.action_cancel_task() + task.with_context(**location_ctx).action_cancel_task() else: return {'success': False, 'error': f'Unknown action: {action}'} - # For completion, also return next task info result = { 'success': True, 'status': task.status, @@ -1600,10 +1674,14 @@ class AuthorizerPortal(CustomerPortal): return {'success': False, 'error': str(e)} @http.route('/my/technician/task//voice-complete', type='json', auth='user', website=True) - def technician_voice_complete(self, task_id, transcription, **kw): + def technician_voice_complete(self, task_id, transcription, latitude=None, longitude=None, accuracy=None, **kw): """Format transcription with GPT and complete the task.""" if not self._check_technician_access(): return {'success': False, 'error': 'Access denied'} + if not latitude or not longitude: + return {'success': False, 'error': 'Location is required. Please enable GPS and try again.'} + if not (-90 <= latitude <= 90 and -180 <= longitude <= 180): + return {'success': False, 'error': 'Invalid GPS coordinates.'} user = request.env.user Task = request.env['fusion.technician.task'].sudo() @@ -1675,7 +1753,18 @@ class AuthorizerPortal(CustomerPortal): 'completion_notes': completion_html, 'voice_note_transcription': transcription, }) - task.action_complete_task() + + request.env['fusion.technician.location'].sudo().log_location( + latitude=latitude, + longitude=longitude, + accuracy=accuracy, + ) + location_ctx = { + 'action_latitude': latitude, + 'action_longitude': longitude, + 'action_accuracy': accuracy or 0, + } + task.with_context(**location_ctx).action_complete_task() return { 'success': True, @@ -1788,6 +1877,25 @@ class AuthorizerPortal(CustomerPortal): _logger.warning(f"Location log error: {e}") return {'success': False} + @http.route('/my/technician/clock-status', type='json', auth='user', website=True) + def technician_clock_status(self, **kw): + """Check if the current technician is clocked in. + + Returns {clocked_in: bool} so the JS background logger can decide + whether to track location. Replaces the fixed 9-6 hour window. + """ + if not self._check_technician_access(): + return {'clocked_in': False} + try: + emp = request.env['hr.employee'].sudo().search([ + ('user_id', '=', request.env.user.id), + ], limit=1) + if emp and emp.attendance_state == 'checked_in': + return {'clocked_in': True} + except Exception: + pass + return {'clocked_in': False} + @http.route('/my/technician/settings/start-location', type='json', auth='user', website=True) def technician_save_start_location(self, address='', **kw): """Save the technician's personal start location.""" @@ -2055,6 +2163,94 @@ class AuthorizerPortal(CustomerPortal): _logger.error(f"Error saving POD signature: {e}") return {'success': False, 'error': str(e)} + # ==================== TASK-LEVEL POD SIGNATURE ==================== + + @http.route('/my/technician/task//pod', type='http', auth='user', website=True) + def task_pod_signature_page(self, task_id, **kw): + """Task-level POD signature capture page (works for all tasks including shadow).""" + if not self._check_technician_access(): + return request.redirect('/my') + + user = request.env.user + Task = request.env['fusion.technician.task'].sudo() + + try: + task = Task.browse(task_id) + if not task.exists() or ( + task.technician_id.id != user.id + and user.id not in task.additional_technician_ids.ids + ): + raise AccessError(_('You do not have access to this task.')) + except (AccessError, MissingError): + return request.redirect('/my/technician/tasks') + + values = { + 'task': task, + 'has_existing_signature': bool(task.pod_signature), + 'page_name': 'task_pod_signature', + } + return request.render('fusion_authorizer_portal.portal_task_pod_signature', values) + + @http.route('/my/technician/task//pod/sign', type='json', auth='user', methods=['POST']) + def task_pod_save_signature(self, task_id, client_name, signature_data, signature_date=None, **kw): + """Save POD signature directly on a task.""" + if not self._check_technician_access(): + return {'success': False, 'error': 'Access denied'} + + user = request.env.user + Task = request.env['fusion.technician.task'].sudo() + + try: + task = Task.browse(task_id) + if not task.exists() or ( + task.technician_id.id != user.id + and user.id not in task.additional_technician_ids.ids + ): + return {'success': False, 'error': 'Task not found'} + + if not client_name or not client_name.strip(): + return {'success': False, 'error': 'Client name is required'} + if not signature_data: + return {'success': False, 'error': 'Signature is required'} + + if ',' in signature_data: + signature_data = signature_data.split(',')[1] + + from datetime import datetime as dt_datetime + sig_date = None + if signature_date: + try: + sig_date = dt_datetime.strptime(signature_date, '%Y-%m-%d').date() + except ValueError: + pass + + task.write({ + 'pod_signature': signature_data, + 'pod_client_name': client_name.strip(), + 'pod_signature_date': sig_date, + 'pod_signed_by_user_id': user.id, + 'pod_signed_datetime': fields.Datetime.now(), + }) + + if task.sale_order_id: + task.sale_order_id.write({ + 'x_fc_pod_signature': signature_data, + 'x_fc_pod_client_name': client_name.strip(), + 'x_fc_pod_signature_date': sig_date, + 'x_fc_pod_signed_by_user_id': user.id, + 'x_fc_pod_signed_datetime': fields.Datetime.now(), + }) + + return { + 'success': True, + 'message': 'Signature saved successfully', + 'redirect_url': f'/my/technician/task/{task_id}', + } + + except Exception as e: + _logger.error(f"Error saving task POD signature: {e}") + return {'success': False, 'error': str(e)} + def _generate_signed_pod_pdf(self, order, save_to_field=True): """Generate a signed POD PDF with the signature embedded. diff --git a/fusion_authorizer_portal/controllers/portal_page11_sign.py b/fusion_authorizer_portal/controllers/portal_page11_sign.py new file mode 100644 index 0000000..ab1dedd --- /dev/null +++ b/fusion_authorizer_portal/controllers/portal_page11_sign.py @@ -0,0 +1,206 @@ +# -*- coding: utf-8 -*- +# Copyright 2024-2026 Nexa Systems Inc. +# License OPL-1 (Odoo Proprietary License v1.0) + +import base64 +import json +import logging + +from odoo import http, fields, _ +from odoo.http import request + +_logger = logging.getLogger(__name__) + + +class Page11PublicSignController(http.Controller): + + def _get_sign_request(self, token): + """Look up and validate a signing request by token.""" + req = request.env['fusion.page11.sign.request'].sudo().search([ + ('access_token', '=', token), + ], limit=1) + if not req: + return None, 'not_found' + if req.state == 'signed': + return req, 'already_signed' + if req.state == 'cancelled': + return req, 'cancelled' + if req.state == 'expired' or ( + req.expiry_date and req.expiry_date < fields.Datetime.now() + ): + if req.state != 'expired': + req.state = 'expired' + return req, 'expired' + return req, 'ok' + + @http.route('/page11/sign/', type='http', auth='public', + website=True, sitemap=False) + def page11_sign_form(self, token, **kw): + """Display the Page 11 signing form.""" + sign_req, status = self._get_sign_request(token) + + if status == 'not_found': + return request.render( + 'fusion_authorizer_portal.portal_page11_sign_invalid', {} + ) + + if status in ('expired', 'cancelled'): + return request.render( + 'fusion_authorizer_portal.portal_page11_sign_expired', + {'sign_request': sign_req}, + ) + + if status == 'already_signed': + return request.render( + 'fusion_authorizer_portal.portal_page11_sign_success', + {'sign_request': sign_req, 'token': token}, + ) + + order = sign_req.sale_order_id + partner = order.partner_id + + assessment = request.env['fusion.assessment'].sudo().search([ + ('sale_order_id', '=', order.id), + ], limit=1, order='create_date desc') + + ICP = request.env['ir.config_parameter'].sudo() + google_maps_api_key = ICP.get_param('fusion_claims.google_maps_api_key', '') + + client_first_name = '' + client_last_name = '' + client_middle_name = '' + client_health_card = '' + client_health_card_version = '' + + if assessment: + client_first_name = assessment.client_first_name or '' + client_last_name = assessment.client_last_name or '' + client_middle_name = assessment.client_middle_name or '' + client_health_card = assessment.client_health_card or '' + client_health_card_version = assessment.client_health_card_version or '' + else: + first, last = order._get_client_name_parts() + client_first_name = first + client_last_name = last + + values = { + 'sign_request': sign_req, + 'order': order, + 'partner': partner, + 'assessment': assessment, + 'company': order.company_id, + 'token': token, + 'signer_type': sign_req.signer_type, + 'is_agent': sign_req.signer_type != 'client', + 'google_maps_api_key': google_maps_api_key, + 'client_first_name': client_first_name, + 'client_last_name': client_last_name, + 'client_middle_name': client_middle_name, + 'client_health_card': client_health_card, + 'client_health_card_version': client_health_card_version, + } + return request.render( + 'fusion_authorizer_portal.portal_page11_public_sign', values, + ) + + @http.route('/page11/sign//submit', type='http', + auth='public', methods=['POST'], website=True, + csrf=True, sitemap=False) + def page11_sign_submit(self, token, **post): + """Process the submitted Page 11 signature.""" + sign_req, status = self._get_sign_request(token) + + if status != 'ok': + return request.redirect(f'/page11/sign/{token}') + + signature_data = post.get('signature_data', '') + if not signature_data: + return request.redirect(f'/page11/sign/{token}?error=no_signature') + + if signature_data.startswith('data:image'): + signature_data = signature_data.split(',', 1)[1] + + consent_accepted = post.get('consent_declaration', '') == 'on' + if not consent_accepted: + return request.redirect(f'/page11/sign/{token}?error=no_consent') + + signer_name = post.get('signer_name', sign_req.signer_name or '') + chosen_signer_type = post.get('signer_type', sign_req.signer_type or 'client') + consent_signed_by = 'applicant' if chosen_signer_type == 'client' else 'agent' + + signer_type_labels = { + 'spouse': 'Spouse', 'parent': 'Parent', + 'legal_guardian': 'Legal Guardian', + 'poa': 'Power of Attorney', + 'public_trustee': 'Public Trustee', + } + + vals = { + 'signature_data': signature_data, + 'signer_name': signer_name, + 'signer_type': chosen_signer_type, + 'consent_declaration_accepted': True, + 'consent_signed_by': consent_signed_by, + 'signed_date': fields.Datetime.now(), + 'state': 'signed', + 'client_first_name': post.get('client_first_name', ''), + 'client_last_name': post.get('client_last_name', ''), + 'client_health_card': post.get('client_health_card', ''), + 'client_health_card_version': post.get('client_health_card_version', ''), + } + + if consent_signed_by == 'agent': + vals.update({ + 'agent_first_name': post.get('agent_first_name', ''), + 'agent_last_name': post.get('agent_last_name', ''), + 'agent_middle_initial': post.get('agent_middle_initial', ''), + 'agent_phone': post.get('agent_phone', ''), + 'agent_unit': post.get('agent_unit', ''), + 'agent_street_number': post.get('agent_street_number', ''), + 'agent_street': post.get('agent_street', ''), + 'agent_city': post.get('agent_city', ''), + 'agent_province': post.get('agent_province', 'Ontario'), + 'agent_postal_code': post.get('agent_postal_code', ''), + 'signer_relationship': signer_type_labels.get(chosen_signer_type, chosen_signer_type), + }) + + sign_req.sudo().write(vals) + + try: + sign_req.sudo()._generate_signed_pdf() + except Exception as e: + _logger.error("PDF generation failed for sign request %s: %s", sign_req.id, e) + + try: + sign_req.sudo()._update_sale_order() + except Exception as e: + _logger.error("Sale order update failed for sign request %s: %s", sign_req.id, e) + + return request.render( + 'fusion_authorizer_portal.portal_page11_sign_success', + {'sign_request': sign_req, 'token': token}, + ) + + @http.route('/page11/sign//download', type='http', + auth='public', website=True, sitemap=False) + def page11_download_pdf(self, token, **kw): + """Download the signed Page 11 PDF.""" + sign_req = request.env['fusion.page11.sign.request'].sudo().search([ + ('access_token', '=', token), + ('state', '=', 'signed'), + ], limit=1) + + if not sign_req or not sign_req.signed_pdf: + return request.redirect(f'/page11/sign/{token}') + + pdf_content = base64.b64decode(sign_req.signed_pdf) + filename = sign_req.signed_pdf_filename or 'Page11_Signed.pdf' + + return request.make_response( + pdf_content, + headers=[ + ('Content-Type', 'application/pdf'), + ('Content-Disposition', f'attachment; filename="{filename}"'), + ('Content-Length', str(len(pdf_content))), + ], + ) diff --git a/fusion_authorizer_portal/controllers/portal_schedule.py b/fusion_authorizer_portal/controllers/portal_schedule.py new file mode 100644 index 0000000..49f793e --- /dev/null +++ b/fusion_authorizer_portal/controllers/portal_schedule.py @@ -0,0 +1,327 @@ +# -*- coding: utf-8 -*- + +from odoo import http, _, fields +from odoo.http import request +from odoo.addons.portal.controllers.portal import CustomerPortal +from odoo.exceptions import AccessError, ValidationError +from datetime import datetime, timedelta +import json +import logging +import pytz + +_logger = logging.getLogger(__name__) + + +class PortalSchedule(CustomerPortal): + """Portal controller for appointment scheduling and calendar management.""" + + def _get_schedule_values(self): + """Common values for schedule pages.""" + ICP = request.env['ir.config_parameter'].sudo() + g_start = ICP.get_param('fusion_claims.portal_gradient_start', '#5ba848') + g_mid = ICP.get_param('fusion_claims.portal_gradient_mid', '#3a8fb7') + g_end = ICP.get_param('fusion_claims.portal_gradient_end', '#2e7aad') + gradient = 'linear-gradient(135deg, %s 0%%, %s 60%%, %s 100%%)' % (g_start, g_mid, g_end) + google_maps_api_key = ICP.get_param('fusion_claims.google_maps_api_key', '') + + return { + 'portal_gradient': gradient, + 'google_maps_api_key': google_maps_api_key, + } + + def _get_user_timezone(self): + tz_name = request.env.user.tz or 'America/Toronto' + try: + return pytz.timezone(tz_name) + except pytz.exceptions.UnknownTimeZoneError: + return pytz.timezone('America/Toronto') + + def _get_appointment_types(self): + """Get appointment types available to the current user.""" + return request.env['appointment.type'].sudo().search([ + ('staff_user_ids', 'in', [request.env.user.id]), + ]) + + @http.route(['/my/schedule'], type='http', auth='user', website=True) + def schedule_page(self, **kw): + """Schedule overview: upcoming appointments and shareable link.""" + partner = request.env.user.partner_id + user = request.env.user + now = fields.Datetime.now() + + upcoming_events = request.env['calendar.event'].sudo().search([ + ('partner_ids', 'in', [partner.id]), + ('start', '>=', now), + ], order='start asc', limit=20) + + today_events = request.env['calendar.event'].sudo().search([ + ('partner_ids', 'in', [partner.id]), + ('start', '>=', now.replace(hour=0, minute=0, second=0)), + ('start', '<', (now + timedelta(days=1)).replace(hour=0, minute=0, second=0)), + ], order='start asc') + + invite = request.env['appointment.invite'].sudo().search([ + ('staff_user_ids', 'in', [user.id]), + ], limit=1) + share_url = invite.book_url if invite else '' + + appointment_types = self._get_appointment_types() + tz = self._get_user_timezone() + + values = self._get_schedule_values() + values.update({ + 'page_name': 'schedule', + 'upcoming_events': upcoming_events, + 'today_events': today_events, + 'share_url': share_url, + 'appointment_types': appointment_types, + 'user_tz': tz, + 'now': now, + }) + return request.render('fusion_authorizer_portal.portal_schedule_page', values) + + @http.route(['/my/schedule/book'], type='http', auth='user', website=True) + def schedule_book(self, appointment_type_id=None, **kw): + """Booking form for a new appointment.""" + appointment_types = self._get_appointment_types() + if not appointment_types: + return request.redirect('/my/schedule') + + if appointment_type_id: + selected_type = request.env['appointment.type'].sudo().browse(int(appointment_type_id)) + if not selected_type.exists(): + selected_type = appointment_types[0] + else: + selected_type = appointment_types[0] + + values = self._get_schedule_values() + values.update({ + 'page_name': 'schedule_book', + 'appointment_types': appointment_types, + 'selected_type': selected_type, + 'now': fields.Datetime.now(), + 'error': kw.get('error'), + 'success': kw.get('success'), + }) + return request.render('fusion_authorizer_portal.portal_schedule_book', values) + + @http.route('/my/schedule/available-slots', type='json', auth='user', website=True) + def schedule_available_slots(self, appointment_type_id, selected_date=None, **kw): + """JSON-RPC endpoint: return available time slots for a date.""" + appointment_type = request.env['appointment.type'].sudo().browse(int(appointment_type_id)) + if not appointment_type.exists(): + return {'error': 'Appointment type not found', 'slots': []} + + user = request.env.user + tz_name = user.tz or 'America/Toronto' + tz = self._get_user_timezone() + + ref_date = fields.Datetime.now() + slot_data = appointment_type._get_appointment_slots( + timezone=tz_name, + filter_users=request.env['res.users'].sudo().browse(user.id), + asked_capacity=1, + reference_date=ref_date, + ) + + filtered_slots = [] + target_date = None + if selected_date: + try: + target_date = datetime.strptime(selected_date, '%Y-%m-%d').date() + except ValueError: + return {'error': 'Invalid date format', 'slots': []} + + for month_data in slot_data: + for week in month_data.get('weeks', []): + for day_info in week: + if not day_info: + continue + day = day_info.get('day') + if target_date and day != target_date: + continue + for slot in day_info.get('slots', []): + slot_dt_str = slot.get('datetime') + if not slot_dt_str: + continue + filtered_slots.append({ + 'datetime': slot_dt_str, + 'start_hour': slot.get('start_hour', ''), + 'end_hour': slot.get('end_hour', ''), + 'duration': slot.get('slot_duration', str(appointment_type.appointment_duration)), + 'staff_user_id': slot.get('staff_user_id', user.id), + }) + + available_dates = [] + if not target_date: + seen = set() + for month_data in slot_data: + for week in month_data.get('weeks', []): + for day_info in week: + if not day_info: + continue + day = day_info.get('day') + if day and day_info.get('slots') and str(day) not in seen: + seen.add(str(day)) + available_dates.append(str(day)) + + return { + 'slots': filtered_slots, + 'available_dates': sorted(available_dates), + 'duration': appointment_type.appointment_duration, + 'timezone': tz_name, + } + + @http.route('/my/schedule/week-events', type='json', auth='user', website=True) + def schedule_week_events(self, selected_date, **kw): + """Return the user's calendar events for the Mon-Sun week containing selected_date.""" + try: + target = datetime.strptime(selected_date, '%Y-%m-%d').date() + except (ValueError, TypeError): + return {'error': 'Invalid date format', 'events': [], 'week_days': []} + + monday = target - timedelta(days=target.weekday()) + sunday = monday + timedelta(days=6) + + partner = request.env.user.partner_id + tz = self._get_user_timezone() + + monday_start_local = tz.localize(datetime.combine(monday, datetime.min.time())) + sunday_end_local = tz.localize(datetime.combine(sunday, datetime.max.time())) + monday_start_utc = monday_start_local.astimezone(pytz.utc).replace(tzinfo=None) + sunday_end_utc = sunday_end_local.astimezone(pytz.utc).replace(tzinfo=None) + + events = request.env['calendar.event'].sudo().search([ + ('partner_ids', 'in', [partner.id]), + ('start', '>=', monday_start_utc), + ('start', '<=', sunday_end_utc), + ], order='start asc') + + event_list = [] + for ev in events: + start_utc = ev.start + stop_utc = ev.stop + start_local = pytz.utc.localize(start_utc).astimezone(tz) + stop_local = pytz.utc.localize(stop_utc).astimezone(tz) + event_list.append({ + 'name': ev.name or '', + 'start': start_local.strftime('%Y-%m-%d %H:%M'), + 'end': stop_local.strftime('%Y-%m-%d %H:%M'), + 'start_time': start_local.strftime('%I:%M %p'), + 'end_time': stop_local.strftime('%I:%M %p'), + 'day_of_week': start_local.weekday(), + 'date': start_local.strftime('%Y-%m-%d'), + 'location': ev.location or '', + 'duration': ev.duration, + }) + + day_labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + week_days = [] + for i in range(7): + day = monday + timedelta(days=i) + week_days.append({ + 'label': day_labels[i], + 'date': day.strftime('%Y-%m-%d'), + 'day_num': day.day, + 'is_selected': day == target, + }) + + return { + 'events': event_list, + 'week_days': week_days, + 'selected_date': selected_date, + } + + @http.route('/my/schedule/book/submit', type='http', auth='user', website=True, methods=['POST']) + def schedule_book_submit(self, **post): + """Process the booking form submission.""" + appointment_type_id = int(post.get('appointment_type_id', 0)) + appointment_type = request.env['appointment.type'].sudo().browse(appointment_type_id) + if not appointment_type.exists(): + return request.redirect('/my/schedule/book?error=Invalid+appointment+type') + + client_name = (post.get('client_name') or '').strip() + client_street = (post.get('client_street') or '').strip() + client_city = (post.get('client_city') or '').strip() + client_province = (post.get('client_province') or '').strip() + client_postal = (post.get('client_postal') or '').strip() + notes = (post.get('notes') or '').strip() + slot_datetime = (post.get('slot_datetime') or '').strip() + slot_duration = post.get('slot_duration', str(appointment_type.appointment_duration)) + + if not client_name or not slot_datetime: + return request.redirect('/my/schedule/book?error=Client+name+and+time+slot+are+required') + + user = request.env.user + tz = self._get_user_timezone() + + try: + start_dt_naive = datetime.strptime(slot_datetime, '%Y-%m-%d %H:%M:%S') + start_dt_local = tz.localize(start_dt_naive) + start_dt_utc = start_dt_local.astimezone(pytz.utc).replace(tzinfo=None) + except (ValueError, Exception) as e: + _logger.error("Failed to parse slot datetime %s: %s", slot_datetime, e) + return request.redirect('/my/schedule/book?error=Invalid+time+slot') + + duration = float(slot_duration) + stop_dt_utc = start_dt_utc + timedelta(hours=duration) + + is_valid = appointment_type._check_appointment_is_valid_slot( + staff_user=user, + resources=request.env['appointment.resource'], + asked_capacity=1, + timezone=str(tz), + start_dt=start_dt_utc, + duration=duration, + allday=False, + ) + if not is_valid: + return request.redirect('/my/schedule/book?error=This+slot+is+no+longer+available.+Please+choose+another+time.') + + address_parts = [p for p in [client_street, client_city, client_province, client_postal] if p] + location = ', '.join(address_parts) + + description_lines = [] + if client_name: + description_lines.append(f"Client: {client_name}") + if location: + description_lines.append(f"Address: {location}") + if notes: + description_lines.append(f"Notes: {notes}") + description = '\n'.join(description_lines) + + event_name = f"{client_name} - {appointment_type.name}" + + booking_line_values = [{ + 'appointment_user_id': user.id, + 'capacity_reserved': 1, + 'capacity_used': 1, + }] + + try: + event_vals = appointment_type._prepare_calendar_event_values( + asked_capacity=1, + booking_line_values=booking_line_values, + description=description, + duration=duration, + allday=False, + appointment_invite=request.env['appointment.invite'], + guests=request.env['res.partner'], + name=event_name, + customer=user.partner_id, + staff_user=user, + start=start_dt_utc, + stop=stop_dt_utc, + ) + event_vals['location'] = location + event = request.env['calendar.event'].sudo().create(event_vals) + + _logger.info( + "Appointment booked: %s at %s (event ID: %s)", + event_name, start_dt_utc, event.id, + ) + except Exception as e: + _logger.error("Failed to create appointment: %s", e) + return request.redirect('/my/schedule/book?error=Failed+to+create+appointment.+Please+try+again.') + + return request.redirect('/my/schedule?success=Appointment+booked+successfully') diff --git a/fusion_authorizer_portal/data/appointment_invite_data.xml b/fusion_authorizer_portal/data/appointment_invite_data.xml new file mode 100644 index 0000000..5d3be76 --- /dev/null +++ b/fusion_authorizer_portal/data/appointment_invite_data.xml @@ -0,0 +1,13 @@ + + + + + + + book-appointment + + + + diff --git a/fusion_authorizer_portal/migrations/19.0.2.3.0/end-migrate.py b/fusion_authorizer_portal/migrations/19.0.2.3.0/end-migrate.py new file mode 100644 index 0000000..8146169 --- /dev/null +++ b/fusion_authorizer_portal/migrations/19.0.2.3.0/end-migrate.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +"""Reactivate any views that Odoo silently deactivated. + +Odoo deactivates inherited views when xpath validation fails (e.g. parent +view structure changed between versions). Once deactivated, subsequent +``-u`` runs never reactivate them. This end-migration script catches +that scenario on every version bump. +""" +import logging + +_logger = logging.getLogger(__name__) + +MODULE = 'fusion_authorizer_portal' + + +def migrate(cr, version): + if not version: + return + + cr.execute(""" + UPDATE ir_ui_view v + SET active = true + FROM ir_model_data d + WHERE d.res_id = v.id + AND d.model = 'ir.ui.view' + AND d.module = %s + AND v.active = false + RETURNING v.id, v.name, v.key + """, [MODULE]) + + rows = cr.fetchall() + if rows: + _logger.warning( + "Reactivated %d deactivated views for %s: %s", + len(rows), MODULE, [r[2] or r[1] for r in rows], + ) diff --git a/fusion_authorizer_portal/migrations/19.0.2.4.0/end-migrate.py b/fusion_authorizer_portal/migrations/19.0.2.4.0/end-migrate.py new file mode 100644 index 0000000..8146169 --- /dev/null +++ b/fusion_authorizer_portal/migrations/19.0.2.4.0/end-migrate.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +"""Reactivate any views that Odoo silently deactivated. + +Odoo deactivates inherited views when xpath validation fails (e.g. parent +view structure changed between versions). Once deactivated, subsequent +``-u`` runs never reactivate them. This end-migration script catches +that scenario on every version bump. +""" +import logging + +_logger = logging.getLogger(__name__) + +MODULE = 'fusion_authorizer_portal' + + +def migrate(cr, version): + if not version: + return + + cr.execute(""" + UPDATE ir_ui_view v + SET active = true + FROM ir_model_data d + WHERE d.res_id = v.id + AND d.model = 'ir.ui.view' + AND d.module = %s + AND v.active = false + RETURNING v.id, v.name, v.key + """, [MODULE]) + + rows = cr.fetchall() + if rows: + _logger.warning( + "Reactivated %d deactivated views for %s: %s", + len(rows), MODULE, [r[2] or r[1] for r in rows], + ) diff --git a/fusion_authorizer_portal/migrations/19.0.2.5.0/end-migrate.py b/fusion_authorizer_portal/migrations/19.0.2.5.0/end-migrate.py new file mode 100644 index 0000000..8146169 --- /dev/null +++ b/fusion_authorizer_portal/migrations/19.0.2.5.0/end-migrate.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +"""Reactivate any views that Odoo silently deactivated. + +Odoo deactivates inherited views when xpath validation fails (e.g. parent +view structure changed between versions). Once deactivated, subsequent +``-u`` runs never reactivate them. This end-migration script catches +that scenario on every version bump. +""" +import logging + +_logger = logging.getLogger(__name__) + +MODULE = 'fusion_authorizer_portal' + + +def migrate(cr, version): + if not version: + return + + cr.execute(""" + UPDATE ir_ui_view v + SET active = true + FROM ir_model_data d + WHERE d.res_id = v.id + AND d.model = 'ir.ui.view' + AND d.module = %s + AND v.active = false + RETURNING v.id, v.name, v.key + """, [MODULE]) + + rows = cr.fetchall() + if rows: + _logger.warning( + "Reactivated %d deactivated views for %s: %s", + len(rows), MODULE, [r[2] or r[1] for r in rows], + ) diff --git a/fusion_authorizer_portal/models/assessment.py b/fusion_authorizer_portal/models/assessment.py index be76e66..4f82749 100644 --- a/fusion_authorizer_portal/models/assessment.py +++ b/fusion_authorizer_portal/models/assessment.py @@ -499,6 +499,7 @@ class FusionAssessment(models.Model): 'res_model': 'sale.order', 'res_id': sale_order.id, 'view_mode': 'form', + 'views': [(False, 'form')], 'target': 'current', } @@ -1482,6 +1483,7 @@ class FusionAssessment(models.Model): 'name': _('Documents'), 'res_model': 'fusion.adp.document', 'view_mode': 'list,form', + 'views': [(False, 'list'), (False, 'form')], 'domain': [('assessment_id', '=', self.id)], 'context': {'default_assessment_id': self.id}, } @@ -1497,6 +1499,7 @@ class FusionAssessment(models.Model): 'res_model': 'sale.order', 'res_id': self.sale_order_id.id, 'view_mode': 'form', + 'views': [(False, 'form')], 'target': 'current', } diff --git a/fusion_authorizer_portal/models/loaner_checkout.py b/fusion_authorizer_portal/models/loaner_checkout.py index 6330b6b..2f3e0ee 100644 --- a/fusion_authorizer_portal/models/loaner_checkout.py +++ b/fusion_authorizer_portal/models/loaner_checkout.py @@ -23,5 +23,6 @@ class FusionLoanerCheckoutAssessment(models.Model): 'type': 'ir.actions.act_window', 'res_model': 'fusion.assessment', 'view_mode': 'form', + 'views': [(False, 'form')], 'res_id': self.assessment_id.id, } diff --git a/fusion_authorizer_portal/models/res_partner.py b/fusion_authorizer_portal/models/res_partner.py index 129a5a8..82342f5 100644 --- a/fusion_authorizer_portal/models/res_partner.py +++ b/fusion_authorizer_portal/models/res_partner.py @@ -160,7 +160,7 @@ class ResPartner(models.Model): if self.is_technician_portal: # Add Field Technician group - g = self.env.ref('fusion_claims.group_field_technician', raise_if_not_found=False) + g = self.env.ref('fusion_tasks.group_field_technician', raise_if_not_found=False) if g and g not in internal_user.group_ids: internal_user.sudo().write({'group_ids': [(4, g.id)]}) added.append('Field Technician') @@ -596,6 +596,7 @@ class ResPartner(models.Model): 'name': _('Assigned Cases'), 'res_model': 'sale.order', 'view_mode': 'list,form', + 'views': [(False, 'list'), (False, 'form')], 'domain': [('x_fc_authorizer_id', '=', self.id)], 'context': {'default_x_fc_authorizer_id': self.id}, } @@ -614,6 +615,7 @@ class ResPartner(models.Model): 'name': _('Assessments'), 'res_model': 'fusion.assessment', 'view_mode': 'list,form', + 'views': [(False, 'list'), (False, 'form')], 'domain': domain, } @@ -697,6 +699,7 @@ class ResPartner(models.Model): 'name': _('Assigned Deliveries'), 'res_model': 'sale.order', 'view_mode': 'list,form', + 'views': [(False, 'list'), (False, 'form')], 'domain': [('x_fc_delivery_technician_ids', 'in', [self.authorizer_portal_user_id.id])], } diff --git a/fusion_authorizer_portal/models/sale_order.py b/fusion_authorizer_portal/models/sale_order.py index a216c6a..38f2c0c 100644 --- a/fusion_authorizer_portal/models/sale_order.py +++ b/fusion_authorizer_portal/models/sale_order.py @@ -101,6 +101,7 @@ class SaleOrder(models.Model): 'name': 'Message Authorizer', 'res_model': 'mail.compose.message', 'view_mode': 'form', + 'views': [(False, 'form')], 'target': 'new', 'context': { 'default_model': 'sale.order', @@ -137,6 +138,7 @@ class SaleOrder(models.Model): 'name': _('Portal Comments'), 'res_model': 'fusion.authorizer.comment', 'view_mode': 'list,form', + 'views': [(False, 'list'), (False, 'form')], 'domain': [('sale_order_id', '=', self.id)], 'context': {'default_sale_order_id': self.id}, } @@ -149,6 +151,7 @@ class SaleOrder(models.Model): 'name': _('Portal Documents'), 'res_model': 'fusion.adp.document', 'view_mode': 'list,form', + 'views': [(False, 'list'), (False, 'form')], 'domain': [('sale_order_id', '=', self.id)], 'context': {'default_sale_order_id': self.id}, } diff --git a/fusion_authorizer_portal/static/src/css/technician_portal.css b/fusion_authorizer_portal/static/src/css/technician_portal.css index 18139f2..a4e3873 100644 --- a/fusion_authorizer_portal/static/src/css/technician_portal.css +++ b/fusion_authorizer_portal/static/src/css/technician_portal.css @@ -14,16 +14,12 @@ .tech-stats-bar { display: flex; gap: 0.5rem; - overflow-x: auto; - padding-bottom: 0.5rem; - scrollbar-width: none; } -.tech-stats-bar::-webkit-scrollbar { display: none; } .tech-stat-card { - flex: 0 0 auto; - min-width: 100px; - padding: 0.75rem 1rem; + flex: 1 1 0; + min-width: 0; + padding: 0.75rem 0.5rem; border-radius: 12px; text-align: center; color: #fff; @@ -42,7 +38,145 @@ .tech-stat-total { background: linear-gradient(135deg, #5ba848, #3a8fb7); } .tech-stat-remaining { background: linear-gradient(135deg, #3498db, #2980b9); } .tech-stat-completed { background: linear-gradient(135deg, #27ae60, #219a52); } -.tech-stat-travel { background: linear-gradient(135deg, #8e44ad, #7d3c98); } + +/* ---- Clock In/Out Card ---- */ +.tech-clock-card { + background: var(--o-main-card-bg, #fff); + border: 1px solid var(--o-main-border-color, #e9ecef); + border-radius: 14px; + padding: 0.875rem 1rem; +} +.tech-clock-dot { + width: 10px; + height: 10px; + border-radius: 50%; + background: #adb5bd; + flex-shrink: 0; +} +.tech-clock-dot--active { + background: #10b981; + box-shadow: 0 0 6px rgba(16, 185, 129, 0.5); + animation: tech-clock-pulse 2s ease-in-out infinite; +} +@keyframes tech-clock-pulse { + 0%, 100% { box-shadow: 0 0 6px rgba(16, 185, 129, 0.5); } + 50% { box-shadow: 0 0 12px rgba(16, 185, 129, 0.8); } +} +.tech-clock-status { + font-size: 0.85rem; + font-weight: 600; + color: var(--o-main-text-color, #212529); + line-height: 1.2; +} +.tech-clock-timer { + font-size: 0.75rem; + font-weight: 600; + font-variant-numeric: tabular-nums; + color: #6c757d; +} +.tech-clock-btn { + display: inline-flex; + align-items: center; + gap: 0.4rem; + padding: 0.5rem 1rem; + border-radius: 10px; + border: none; + font-weight: 600; + font-size: 0.85rem; + cursor: pointer; + transition: all 0.15s; + white-space: nowrap; +} +.tech-clock-btn:active { transform: scale(0.96); } +.tech-clock-btn--in { + background: #10b981; + color: #fff; +} +.tech-clock-btn--in:hover { background: #059669; } +.tech-clock-btn--out { + background: #ef4444; + color: #fff; +} +.tech-clock-btn--out:hover { background: #dc2626; } +.tech-clock-btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} +.tech-clock-error { + display: flex; + align-items: center; + gap: 0.4rem; + margin-top: 0.5rem; + padding: 0.4rem 0.75rem; + border-radius: 8px; + background: #fef2f2; + color: #dc2626; + font-size: 0.8rem; + font-weight: 500; +} + +/* ---- Quick Links (All Tasks / Tomorrow / Repair Form) ---- */ +.tech-quick-links { + display: flex; + gap: 0.5rem; +} + +.tech-quick-link { + flex: 1 1 0; + min-width: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.35rem; + padding: 0.875rem 0.5rem; + border-radius: 12px; + border: 1.5px solid; + text-decoration: none !important; + font-weight: 600; + font-size: 0.8rem; + transition: all 0.15s; + position: relative; +} +.tech-quick-link:active { transform: scale(0.97); } +.tech-quick-link i { font-size: 1.1rem; } + +.tech-quick-link-primary { + border-color: #3498db; + color: #3498db !important; + background: rgba(52, 152, 219, 0.04); +} +.tech-quick-link-primary:hover { background: rgba(52, 152, 219, 0.1); } + +.tech-quick-link-secondary { + border-color: #6c757d; + color: #6c757d !important; + background: rgba(108, 117, 125, 0.04); +} +.tech-quick-link-secondary:hover { background: rgba(108, 117, 125, 0.1); } + +.tech-quick-link-warning { + border-color: #e67e22; + color: #e67e22 !important; + background: rgba(230, 126, 34, 0.04); +} +.tech-quick-link-warning:hover { background: rgba(230, 126, 34, 0.1); } + +.tech-quick-link-badge { + position: absolute; + top: -6px; + right: -6px; + background: #3498db; + color: #fff; + font-size: 0.65rem; + font-weight: 700; + min-width: 18px; + height: 18px; + line-height: 18px; + text-align: center; + border-radius: 9px; + padding: 0 4px; +} /* ---- Hero Card (Dashboard Current Task) ---- */ .tech-hero-card { @@ -475,12 +609,18 @@ gap: 1rem; } .tech-stat-card { - min-width: 130px; padding: 1rem 1.5rem; } .tech-stat-card .stat-number { font-size: 2rem; } + .tech-quick-links { + gap: 1rem; + } + .tech-quick-link { + padding: 1rem 0.75rem; + font-size: 0.85rem; + } .tech-bottom-bar { position: static; box-shadow: none; diff --git a/fusion_authorizer_portal/static/src/js/chatter_message_authorizer.js b/fusion_authorizer_portal/static/src/js/chatter_message_authorizer.js index 6802395..8626a1b 100644 --- a/fusion_authorizer_portal/static/src/js/chatter_message_authorizer.js +++ b/fusion_authorizer_portal/static/src/js/chatter_message_authorizer.js @@ -28,6 +28,9 @@ patch(Chatter.prototype, { [thread.id], ); if (result && result.type === "ir.actions.act_window") { + if (!result.views && result.view_mode) { + result.views = result.view_mode.split(",").map(v => [false, v.trim()]); + } this._fapActionService.doAction(result); } } catch (e) { diff --git a/fusion_authorizer_portal/static/src/js/portal_schedule_booking.js b/fusion_authorizer_portal/static/src/js/portal_schedule_booking.js new file mode 100644 index 0000000..40e0c1c --- /dev/null +++ b/fusion_authorizer_portal/static/src/js/portal_schedule_booking.js @@ -0,0 +1,343 @@ +(function () { + 'use strict'; + + var dateInput = document.getElementById('bookingDate'); + var slotsContainer = document.getElementById('slotsContainer'); + var slotsGrid = document.getElementById('slotsGrid'); + var slotsLoading = document.getElementById('slotsLoading'); + var noSlots = document.getElementById('noSlots'); + var slotDatetimeInput = document.getElementById('slotDatetime'); + var slotDurationInput = document.getElementById('slotDuration'); + var submitBtn = document.getElementById('btnSubmitBooking'); + var typeSelect = document.getElementById('appointmentTypeSelect'); + var selectedSlotBtn = null; + + var weekContainer = document.getElementById('weekCalendarContainer'); + var weekLoading = document.getElementById('weekCalendarLoading'); + var weekGrid = document.getElementById('weekCalendarGrid'); + var weekHeader = document.getElementById('weekCalendarHeader'); + var weekBody = document.getElementById('weekCalendarBody'); + var weekEmpty = document.getElementById('weekCalendarEmpty'); + + function getAppointmentTypeId() { + if (typeSelect) return typeSelect.value; + var hidden = document.querySelector('input[name="appointment_type_id"]'); + return hidden ? hidden.value : null; + } + + function escapeHtml(str) { + var div = document.createElement('div'); + div.textContent = str; + return div.innerHTML; + } + + function truncate(str, max) { + if (!str) return ''; + return str.length > max ? str.substring(0, max) + '...' : str; + } + + function fetchWeekEvents(date) { + if (!weekContainer || !date) return; + + weekContainer.style.display = 'block'; + weekLoading.style.display = 'block'; + weekGrid.style.display = 'none'; + weekEmpty.style.display = 'none'; + + fetch('/my/schedule/week-events', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + method: 'call', + params: { selected_date: date }, + }), + }) + .then(function (resp) { return resp.json(); }) + .then(function (data) { + weekLoading.style.display = 'none'; + var result = data.result || {}; + var events = result.events || []; + var weekDays = result.week_days || []; + + if (result.error || !weekDays.length) { + weekEmpty.style.display = 'block'; + return; + } + + renderWeekCalendar(weekDays, events, date); + }) + .catch(function () { + weekLoading.style.display = 'none'; + weekEmpty.textContent = 'Failed to load calendar. Please try again.'; + weekEmpty.style.display = 'block'; + }); + } + + function renderWeekCalendar(weekDays, events, selectedDate) { + weekHeader.innerHTML = ''; + weekBody.innerHTML = ''; + + var eventsByDate = {}; + events.forEach(function (ev) { + if (!eventsByDate[ev.date]) eventsByDate[ev.date] = []; + eventsByDate[ev.date].push(ev); + }); + + var hasAnyEvents = events.length > 0; + + weekDays.forEach(function (day) { + var isSelected = day.date === selectedDate; + var isWeekend = day.label === 'Sat' || day.label === 'Sun'; + var dayEvents = eventsByDate[day.date] || []; + + var headerCell = document.createElement('div'); + headerCell.className = 'text-center py-2 flex-fill'; + headerCell.style.cssText = 'min-width: 0; font-size: 12px; border-right: 1px solid #dee2e6;'; + if (isSelected) { + headerCell.style.backgroundColor = '#e8f4fd'; + } + if (isWeekend) { + headerCell.style.opacity = '0.6'; + } + + var labelEl = document.createElement('div'); + labelEl.className = 'fw-semibold text-muted'; + labelEl.textContent = day.label; + + var numEl = document.createElement('div'); + numEl.className = isSelected ? 'fw-bold text-primary' : 'fw-semibold'; + numEl.style.fontSize = '14px'; + numEl.textContent = day.day_num; + + headerCell.appendChild(labelEl); + headerCell.appendChild(numEl); + weekHeader.appendChild(headerCell); + + var bodyCell = document.createElement('div'); + bodyCell.className = 'flex-fill p-1'; + bodyCell.style.cssText = 'min-width: 0; min-height: 70px; border-right: 1px solid #dee2e6; overflow: hidden;'; + if (isSelected) { + bodyCell.style.backgroundColor = '#f0f8ff'; + } + + if (dayEvents.length) { + dayEvents.forEach(function (ev) { + var card = document.createElement('div'); + card.className = 'mb-1 px-1 py-1 rounded'; + card.style.cssText = 'font-size: 11px; background: #eef6ff; border-left: 3px solid #3a8fb7; overflow: hidden; cursor: default;'; + card.title = ev.start_time + ' - ' + ev.end_time + '\n' + ev.name + (ev.location ? '\n' + ev.location : ''); + + var timeEl = document.createElement('div'); + timeEl.className = 'fw-semibold text-primary'; + timeEl.style.fontSize = '10px'; + timeEl.textContent = ev.start_time; + + var nameEl = document.createElement('div'); + nameEl.className = 'text-truncate'; + nameEl.style.fontSize = '10px'; + nameEl.textContent = truncate(ev.name, 18); + + card.appendChild(timeEl); + card.appendChild(nameEl); + bodyCell.appendChild(card); + }); + } + + weekBody.appendChild(bodyCell); + }); + + if (hasAnyEvents) { + weekGrid.style.display = 'block'; + weekEmpty.style.display = 'none'; + } else { + weekGrid.style.display = 'none'; + weekEmpty.style.display = 'block'; + } + } + + function fetchSlots(date) { + var typeId = getAppointmentTypeId(); + if (!typeId || !date) return; + + slotsContainer.style.display = 'block'; + slotsLoading.style.display = 'block'; + slotsGrid.innerHTML = ''; + noSlots.style.display = 'none'; + slotDatetimeInput.value = ''; + if (submitBtn) submitBtn.disabled = true; + selectedSlotBtn = null; + + fetch('/my/schedule/available-slots', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + jsonrpc: '2.0', + method: 'call', + params: { + appointment_type_id: parseInt(typeId), + selected_date: date, + }, + }), + }) + .then(function (resp) { return resp.json(); }) + .then(function (data) { + slotsLoading.style.display = 'none'; + var result = data.result || {}; + var slots = result.slots || []; + + if (result.error) { + noSlots.textContent = result.error; + noSlots.style.display = 'block'; + return; + } + + if (!slots.length) { + noSlots.style.display = 'block'; + return; + } + + var morningSlots = []; + var afternoonSlots = []; + slots.forEach(function (slot) { + var hour = parseInt(slot.start_hour); + if (isNaN(hour)) { + var match = slot.start_hour.match(/(\d+)/); + hour = match ? parseInt(match[1]) : 0; + if (slot.start_hour.toLowerCase().indexOf('pm') > -1 && hour !== 12) hour += 12; + if (slot.start_hour.toLowerCase().indexOf('am') > -1 && hour === 12) hour = 0; + } + if (hour < 12) { + morningSlots.push(slot); + } else { + afternoonSlots.push(slot); + } + }); + + function renderGroup(label, icon, groupSlots) { + if (!groupSlots.length) return; + var header = document.createElement('div'); + header.className = 'w-100 mt-2 mb-1'; + header.innerHTML = '' + label + ''; + slotsGrid.appendChild(header); + + groupSlots.forEach(function (slot) { + var btn = document.createElement('button'); + btn.type = 'button'; + btn.className = 'btn btn-outline-primary btn-sm slot-btn'; + btn.style.cssText = 'min-width: 100px; border-radius: 8px; padding: 8px 14px;'; + btn.textContent = slot.start_hour; + btn.dataset.datetime = slot.datetime; + btn.dataset.duration = slot.duration; + btn.addEventListener('click', function () { + if (selectedSlotBtn) { + selectedSlotBtn.classList.remove('btn-primary'); + selectedSlotBtn.classList.add('btn-outline-primary'); + } + btn.classList.remove('btn-outline-primary'); + btn.classList.add('btn-primary'); + selectedSlotBtn = btn; + slotDatetimeInput.value = slot.datetime; + slotDurationInput.value = slot.duration; + if (submitBtn) submitBtn.disabled = false; + }); + slotsGrid.appendChild(btn); + }); + } + + renderGroup('Morning', 'fa-sun-o', morningSlots); + renderGroup('Afternoon', 'fa-cloud', afternoonSlots); + }) + .catch(function (err) { + slotsLoading.style.display = 'none'; + noSlots.textContent = 'Failed to load slots. Please try again.'; + noSlots.style.display = 'block'; + }); + } + + if (dateInput) { + dateInput.addEventListener('change', function () { + var val = this.value; + fetchWeekEvents(val); + fetchSlots(val); + }); + } + + if (typeSelect) { + typeSelect.addEventListener('change', function () { + if (dateInput && dateInput.value) { + fetchSlots(dateInput.value); + } + }); + } + + var bookingForm = document.getElementById('bookingForm'); + if (bookingForm) { + bookingForm.addEventListener('submit', function (e) { + if (!slotDatetimeInput || !slotDatetimeInput.value) { + e.preventDefault(); + alert('Please select a time slot before booking.'); + return false; + } + var clientName = bookingForm.querySelector('input[name="client_name"]'); + if (!clientName || !clientName.value.trim()) { + e.preventDefault(); + alert('Please enter the client name.'); + return false; + } + if (submitBtn) { + submitBtn.disabled = true; + submitBtn.innerHTML = ' Booking...'; + } + }); + } + + window.initScheduleAddressAutocomplete = function () { + var streetInput = document.getElementById('clientStreet'); + if (!streetInput) return; + + var autocomplete = new google.maps.places.Autocomplete(streetInput, { + componentRestrictions: { country: 'ca' }, + types: ['address'], + }); + + autocomplete.addListener('place_changed', function () { + var place = autocomplete.getPlace(); + if (!place.address_components) return; + + var streetNumber = ''; + var streetName = ''; + var city = ''; + var province = ''; + var postalCode = ''; + + for (var i = 0; i < place.address_components.length; i++) { + var component = place.address_components[i]; + var types = component.types; + + if (types.indexOf('street_number') > -1) { + streetNumber = component.long_name; + } else if (types.indexOf('route') > -1) { + streetName = component.long_name; + } else if (types.indexOf('locality') > -1) { + city = component.long_name; + } else if (types.indexOf('administrative_area_level_1') > -1) { + province = component.long_name; + } else if (types.indexOf('postal_code') > -1) { + postalCode = component.long_name; + } + } + + streetInput.value = (streetNumber + ' ' + streetName).trim(); + var cityInput = document.getElementById('clientCity'); + if (cityInput) cityInput.value = city; + var provInput = document.getElementById('clientProvince'); + if (provInput) provInput.value = province; + var postalInput = document.getElementById('clientPostal'); + if (postalInput) postalInput.value = postalCode; + }); + }; + +})(); diff --git a/fusion_authorizer_portal/static/src/js/technician_location.js b/fusion_authorizer_portal/static/src/js/technician_location.js index 25b6e82..648c2be 100644 --- a/fusion_authorizer_portal/static/src/js/technician_location.js +++ b/fusion_authorizer_portal/static/src/js/technician_location.js @@ -1,94 +1,234 @@ /** - * Technician Location Logger - * Logs GPS location every 5 minutes during working hours (9 AM - 6 PM) - * Only logs while the browser tab is visible. + * Technician Location Services + * + * 1. Background logger -- logs GPS every 5 minutes while the tech is clocked in. + * 2. getLocation() -- returns a Promise that resolves to {latitude, longitude, accuracy}. + * If the user denies permission or the request times out a blocking modal is shown + * and the promise is rejected. + * 3. Blocking modal -- cannot be dismissed; forces the technician to grant permission. */ (function () { 'use strict'; - var INTERVAL_MS = 5 * 60 * 1000; // 5 minutes - var STORE_OPEN_HOUR = 9; - var STORE_CLOSE_HOUR = 18; + var INTERVAL_MS = 5 * 60 * 1000; + var CLOCK_CHECK_MS = 60 * 1000; // check clock status every 60s var locationTimer = null; + var clockCheckTimer = null; + var isClockedIn = false; + var permissionDenied = false; - function isWorkingHours() { - var now = new Date(); - var hour = now.getHours(); - return hour >= STORE_OPEN_HOUR && hour < STORE_CLOSE_HOUR; + // ===================================================================== + // BLOCKING MODAL + // ===================================================================== + + var modalEl = null; + + function ensureModal() { + if (modalEl) return; + var div = document.createElement('div'); + div.id = 'fusionLocationModal'; + div.innerHTML = + '