# -*- 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 logging from odoo import models, fields, api _logger = logging.getLogger(__name__) class ResConfigSettings(models.TransientModel): _inherit = 'res.config.settings' # ========================================================================= # COMPANY SETTINGS (Related to res.company) # ========================================================================= fc_store_address_1 = fields.Char( related='company_id.x_fc_store_address_1', readonly=False, string='Store Address Line 1', ) fc_store_address_2 = fields.Char( related='company_id.x_fc_store_address_2', readonly=False, string='Store Address Line 2', ) fc_company_tagline = fields.Char( related='company_id.x_fc_company_tagline', readonly=False, string='Company Tagline', ) fc_etransfer_email = fields.Char( related='company_id.x_fc_etransfer_email', readonly=False, string='E-Transfer Email', ) fc_cheque_payable_to = fields.Char( related='company_id.x_fc_cheque_payable_to', readonly=False, string='Cheque Payable To', ) fc_payment_terms_html = fields.Html( related='company_id.x_fc_payment_terms_html', readonly=False, string='Payment Terms', ) fc_include_refund_page = fields.Boolean( related='company_id.x_fc_include_refund_page', readonly=False, string='Include Refund Policy Page', ) fc_refund_policy_html = fields.Html( related='company_id.x_fc_refund_policy_html', readonly=False, string='Refund Policy', ) # ========================================================================= # ADP BILLING SETTINGS # ========================================================================= fc_vendor_code = fields.Char( string='ADP Vendor Code', config_parameter='fusion_claims.vendor_code', help='Your ADP vendor/location code for claim submissions', ) # ========================================================================= # FIELD MAPPINGS # ========================================================================= fc_field_sale_type = fields.Char( string='Sale Type Field', config_parameter='fusion_claims.field_sale_type', help='Field name for sale type on sale.order', ) fc_field_so_client_type = fields.Char( string='SO Client Type Field', config_parameter='fusion_claims.field_so_client_type', help='Field name for client type on sale.order', ) fc_field_so_authorizer = fields.Char( string='SO Authorizer Field', config_parameter='fusion_claims.field_so_authorizer', help='Field name for authorizer on sale.order', ) fc_field_invoice_type = fields.Char( string='Invoice Type Field', config_parameter='fusion_claims.field_invoice_type', help='Field name for invoice type on account.move', ) fc_field_inv_client_type = fields.Char( string='Invoice Client Type Field', config_parameter='fusion_claims.field_inv_client_type', help='Field name for client type on account.move', ) fc_field_inv_authorizer = fields.Char( string='Invoice Authorizer Field', config_parameter='fusion_claims.field_inv_authorizer', help='Field name for authorizer on account.move', ) fc_field_product_code = fields.Char( string='Product ADP Code Field', config_parameter='fusion_claims.field_product_code', help='Field name for ADP device code on product.template', ) fc_field_sol_serial = fields.Char( string='SO Line Serial Field', config_parameter='fusion_claims.field_sol_serial', help='Field name for serial number on sale.order.line', ) fc_field_aml_serial = fields.Char( string='Invoice Line Serial Field', config_parameter='fusion_claims.field_aml_serial', help='Field name for serial number on account.move.line', ) # ========================================================================= # ADDITIONAL SALE ORDER FIELD MAPPINGS # ========================================================================= fc_field_so_claim_number = fields.Char( string='SO Claim Number Field', config_parameter='fusion_claims.field_so_claim_number', help='Field name for claim number on sale.order', ) fc_field_so_client_ref_1 = fields.Char( string='SO Client Ref 1 Field', config_parameter='fusion_claims.field_so_client_ref_1', help='Field name for client reference 1 on sale.order', ) fc_field_so_client_ref_2 = fields.Char( string='SO Client Ref 2 Field', config_parameter='fusion_claims.field_so_client_ref_2', help='Field name for client reference 2 on sale.order', ) fc_field_so_delivery_date = fields.Char( string='SO Delivery Date Field', config_parameter='fusion_claims.field_so_delivery_date', help='Field name for ADP delivery date on sale.order', ) fc_field_so_adp_status = fields.Char( string='SO ADP Status Field', config_parameter='fusion_claims.field_so_adp_status', help='Field name for ADP status on sale.order', ) fc_field_so_service_start = fields.Char( string='SO Service Start Date Field', config_parameter='fusion_claims.field_so_service_start', help='Field name for service start date on sale.order', ) fc_field_so_service_end = fields.Char( string='SO Service End Date Field', config_parameter='fusion_claims.field_so_service_end', help='Field name for service end date on sale.order', ) # ========================================================================= # ADDITIONAL INVOICE FIELD MAPPINGS # ========================================================================= fc_field_inv_claim_number = fields.Char( string='Invoice Claim Number Field', config_parameter='fusion_claims.field_inv_claim_number', help='Field name for claim number on account.move', ) fc_field_inv_client_ref_1 = fields.Char( string='Invoice Client Ref 1 Field', config_parameter='fusion_claims.field_inv_client_ref_1', help='Field name for client reference 1 on account.move', ) fc_field_inv_client_ref_2 = fields.Char( string='Invoice Client Ref 2 Field', config_parameter='fusion_claims.field_inv_client_ref_2', help='Field name for client reference 2 on account.move', ) fc_field_inv_delivery_date = fields.Char( string='Invoice Delivery Date Field', config_parameter='fusion_claims.field_inv_delivery_date', help='Field name for ADP delivery date on account.move', ) fc_field_inv_service_start = fields.Char( string='Invoice Service Start Date Field', config_parameter='fusion_claims.field_inv_service_start', help='Field name for service start date on account.move', ) fc_field_inv_service_end = fields.Char( string='Invoice Service End Date Field', config_parameter='fusion_claims.field_inv_service_end', help='Field name for service end date on account.move', ) # ========================================================================= # SALE ORDER LINE FIELD MAPPINGS # ========================================================================= fc_field_sol_placement = fields.Char( string='SO Line Placement Field', config_parameter='fusion_claims.field_sol_placement', help='Field name for device placement on sale.order.line', ) # ========================================================================= # INVOICE LINE FIELD MAPPINGS # ========================================================================= fc_field_aml_placement = fields.Char( string='Invoice Line Placement Field', config_parameter='fusion_claims.field_aml_placement', help='Field name for device placement on account.move.line', ) # ========================================================================= # PRODUCT FIELD MAPPINGS # ========================================================================= fc_field_product_adp_price = fields.Char( string='Product ADP Price Field', config_parameter='fusion_claims.field_product_adp_price', help='Field name for ADP price on product.template', ) # ========================================================================= # HEADER-LEVEL SERIAL NUMBER MAPPINGS # ========================================================================= fc_field_so_primary_serial = fields.Char( string='SO Primary Serial Field', config_parameter='fusion_claims.field_so_primary_serial', help='Field name for primary serial number on sale.order (header level)', ) fc_field_inv_primary_serial = fields.Char( string='Invoice Primary Serial Field', config_parameter='fusion_claims.field_inv_primary_serial', help='Field name for primary serial number on account.move (header level)', ) # ========================================================================= # ADP POSTING SCHEDULE SETTINGS # ========================================================================= fc_adp_posting_base_date = fields.Char( string='ADP Posting Base Date', config_parameter='fusion_claims.adp_posting_base_date', help='Reference date for calculating bi-weekly posting schedule (a known posting day). Format: YYYY-MM-DD', ) fc_adp_posting_frequency_days = fields.Integer( string='Posting Frequency (Days)', config_parameter='fusion_claims.adp_posting_frequency_days', help='Number of days between ADP posting cycles (typically 14 days)', ) fc_adp_billing_reminder_user_id = fields.Many2one( 'res.users', string='Billing Deadline Reminder Person', # NOTE: stored manually via get_values/set_values (not config_parameter) # because Many2one + config_parameter causes double-write conflicts help='Person to remind on Monday to complete ADP billing by Wednesday 6 PM', ) fc_adp_correction_reminder_user_ids = fields.Many2many( 'res.users', 'fc_config_correction_reminder_users_rel', 'config_id', 'user_id', string='Correction Alert Recipients', help='People to notify when an ADP invoice needs correction/resubmission', ) # ========================================================================= # EMAIL NOTIFICATION SETTINGS # ========================================================================= fc_enable_email_notifications = fields.Boolean( string='Enable Automated Email Notifications', config_parameter='fusion_claims.enable_email_notifications', help='Enable/disable automated email notifications for ADP workflow events', ) fc_office_notification_ids = fields.Many2many( related='company_id.x_fc_office_notification_ids', readonly=False, string='Office Notification Recipients', ) fc_application_reminder_days = fields.Integer( string='First Reminder Days', config_parameter='fusion_claims.application_reminder_days', help='Number of days after assessment completion to send first application reminder to therapist', ) fc_application_reminder_2_days = fields.Integer( string='Second Reminder Days (After First)', config_parameter='fusion_claims.application_reminder_2_days', help='Number of days after first reminder to send second application reminder to therapist', ) # ========================================================================= # WORKFLOW LOCK SETTINGS # ========================================================================= fc_allow_sale_type_override = fields.Boolean( string='Allow Sale Type Override', config_parameter='fusion_claims.allow_sale_type_override', help='If enabled, allows changing Sale Type even after application is submitted (for cases where additional benefits are discovered)', ) fc_allow_document_lock_override = fields.Boolean( string='Allow Document Lock Override', config_parameter='fusion_claims.allow_document_lock_override', help='When enabled, users in the "Document Lock Override" group can edit locked documents on old cases. ' 'Disable this once all legacy cases have been processed to enforce strict workflow.', ) fc_designated_vendor_signer = fields.Many2one( 'res.users', string='Designated Vendor Signer', 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 # ------------------------------------------------------------------ fc_ai_api_key = fields.Char( string='AI API Key', config_parameter='fusion_claims.ai_api_key', help='OpenAI API key for Client Intelligence chat', ) fc_ai_model = fields.Selection([ ('gpt-4o-mini', 'GPT-4o Mini (Fast, Lower Cost)'), ('gpt-4o', 'GPT-4o (Best Quality)'), ('gpt-4.1-mini', 'GPT-4.1 Mini'), ('gpt-4.1', 'GPT-4.1'), ], string='AI Model', config_parameter='fusion_claims.ai_model', ) fc_auto_parse_xml = fields.Boolean( string='Auto-Parse XML Files', config_parameter='fusion_claims.auto_parse_xml', 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 # ------------------------------------------------------------------ fc_twilio_enabled = fields.Boolean( string='Enable Twilio SMS', config_parameter='fusion_claims.twilio_enabled', help='Enable SMS notifications via Twilio for assessment bookings and key status updates', ) fc_twilio_account_sid = fields.Char( string='Twilio Account SID', config_parameter='fusion_claims.twilio_account_sid', groups='fusion_claims.group_fusion_claims_manager', ) fc_twilio_auth_token = fields.Char( string='Twilio Auth Token', config_parameter='fusion_claims.twilio_auth_token', groups='fusion_claims.group_fusion_claims_manager', ) fc_twilio_phone_number = fields.Char( string='Twilio Phone Number', config_parameter='fusion_claims.twilio_phone_number', help='Your Twilio phone number for sending SMS (e.g. +1234567890)', ) # ------------------------------------------------------------------ # MARCH OF DIMES SETTINGS # ------------------------------------------------------------------ fc_mod_default_email = fields.Char( string='MOD Default Email', config_parameter='fusion_claims.mod_default_email', help='Default email for sending quotations and documents to March of Dimes (e.g. hvmp@marchofdimes.ca)', ) fc_mod_vendor_code = fields.Char( string='March of Dimes Vendor Code', config_parameter='fusion_claims.mod_vendor_code', help='Your vendor code assigned by March of Dimes (e.g. TRD0001234)', ) # ------------------------------------------------------------------ # MOD FOLLOW-UP SETTINGS # ------------------------------------------------------------------ fc_mod_followup_interval_days = fields.Integer( string='Follow-up Interval (Days)', config_parameter='fusion_claims.mod_followup_interval_days', help='Number of days between follow-up reminders for MOD cases awaiting funding (default: 14)', ) fc_mod_followup_escalation_days = fields.Integer( string='Escalation Delay (Days)', config_parameter='fusion_claims.mod_followup_escalation_days', help='Days after a follow-up activity is due before auto-sending email to client (default: 3)', ) # ------------------------------------------------------------------ # ODSP CONFIGURATION # ------------------------------------------------------------------ fc_sa_mobility_email = fields.Char( string='SA Mobility Email', config_parameter='fusion_claims.sa_mobility_email', help='Email address for SA Mobility submissions (default: samobility@ontario.ca)', ) fc_sa_mobility_phone = fields.Char( string='SA Mobility Phone', config_parameter='fusion_claims.sa_mobility_phone', help='SA Mobility phone number (default: 1-888-222-5099)', ) fc_odsp_default_office_id = fields.Many2one( 'res.partner', string='Default ODSP Office', domain="[('x_fc_contact_type', '=', 'odsp_office')]", help='Default ODSP office contact for new ODSP cases', ) @api.model def get_values(self): res = super().get_values() ICP = self.env['ir.config_parameter'].sudo() # Get billing reminder user billing_user_id = ICP.get_param('fusion_claims.adp_billing_reminder_user_id', False) if billing_user_id: try: res['fc_adp_billing_reminder_user_id'] = int(billing_user_id) except (ValueError, TypeError): res['fc_adp_billing_reminder_user_id'] = False # Get correction reminder users (stored as comma-separated IDs) correction_user_ids = ICP.get_param('fusion_claims.adp_correction_reminder_user_ids', '') if correction_user_ids: try: user_ids = [int(x.strip()) for x in correction_user_ids.split(',') if x.strip()] res['fc_adp_correction_reminder_user_ids'] = [(6, 0, user_ids)] except (ValueError, TypeError): res['fc_adp_correction_reminder_user_ids'] = [(6, 0, [])] # Get designated vendor signer vendor_signer_id = ICP.get_param('fusion_claims.designated_vendor_signer', False) if vendor_signer_id: try: res['fc_designated_vendor_signer'] = int(vendor_signer_id) except (ValueError, TypeError): res['fc_designated_vendor_signer'] = False # Get default ODSP office odsp_office_id = ICP.get_param('fusion_claims.odsp_default_office_id', False) if odsp_office_id: try: res['fc_odsp_default_office_id'] = int(odsp_office_id) except (ValueError, TypeError): res['fc_odsp_default_office_id'] = False return res def set_values(self): ICP = self.env['ir.config_parameter'].sudo() # --- Protect sensitive config_parameter fields from accidental blanking --- # These are keys where a blank/default value should NOT overwrite # 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 = {} for key in _protected_keys: val = ICP.get_param(key, '') if val: _existing[key] = val super().set_values() # Restore any protected values that were blanked by the save for key, old_val in _existing.items(): new_val = ICP.get_param(key, '') if not new_val and old_val: ICP.set_param(key, old_val) _logger.warning( "Settings protection: restored %s (was blanked during save)", key ) # Store billing reminder user (Many2one - manual handling) if self.fc_adp_billing_reminder_user_id: ICP.set_param('fusion_claims.adp_billing_reminder_user_id', str(self.fc_adp_billing_reminder_user_id.id)) # Only clear if explicitly set to empty AND there was no existing value elif not ICP.get_param('fusion_claims.adp_billing_reminder_user_id', ''): ICP.set_param('fusion_claims.adp_billing_reminder_user_id', '') # Store correction reminder users as comma-separated IDs if self.fc_adp_correction_reminder_user_ids: user_ids = ','.join(str(u.id) for u in self.fc_adp_correction_reminder_user_ids) ICP.set_param('fusion_claims.adp_correction_reminder_user_ids', user_ids) # Only clear if explicitly empty AND no existing value elif not ICP.get_param('fusion_claims.adp_correction_reminder_user_ids', ''): ICP.set_param('fusion_claims.adp_correction_reminder_user_ids', '') # Office notification recipients are stored via related field on res.company # No need to store in ir.config_parameter # Store designated vendor signer (Many2one - manual handling) if self.fc_designated_vendor_signer: ICP.set_param('fusion_claims.designated_vendor_signer', str(self.fc_designated_vendor_signer.id)) elif not ICP.get_param('fusion_claims.designated_vendor_signer', ''): ICP.set_param('fusion_claims.designated_vendor_signer', '') # Store default ODSP office (Many2one - manual handling) if self.fc_odsp_default_office_id: ICP.set_param('fusion_claims.odsp_default_office_id', str(self.fc_odsp_default_office_id.id)) elif not ICP.get_param('fusion_claims.odsp_default_office_id', ''): ICP.set_param('fusion_claims.odsp_default_office_id', '') # ========================================================================= # ACTION METHODS # ========================================================================= def action_open_field_mapping_wizard(self): """Open the field mapping configuration wizard.""" return { 'type': 'ir.actions.act_window', 'name': 'Field Mapping Configuration', 'res_model': 'fusion_claims.field_mapping_config', 'view_mode': 'form', 'target': 'new', 'context': {}, }