Reworks the March of Dimes workflow to match reality: the OT does their own disability assessment and provides the VOD letter; our accessibility specialist then visits to produce the proposal/drawings/quote; and the application can be submitted by us (internal), the client, or the authorizer themselves. The old workflow flattened all this into one assessment state with a dead-end funding_denied and no document tracking. Data model (13 new sale.order fields): - 5 new document binaries + filenames: VOD letter, Application Form (filled), Notice of Assessment, Property Tax, Proposal Document - x_fc_mod_submitted_by Selection (internal/client/authorizer) - x_fc_mod_handoff_date, x_fc_mod_vod_requested_date - x_fc_mod_accessibility_specialist_id (m2o res.partner — internal or external) - x_fc_mod_previous_status_before_hold (for proper resume) - x_fc_mod_funding_denial_reason (captured via wizard) Settings (4 res.company fields + res_config_settings mirrors): - x_fc_mod_application_form (blank) + filename - x_fc_mod_vod_form (blank) + filename - x_fc_mod_followup_assignee_mode (office_contact / sales_rep) - x_fc_mod_followup_office_contact_id res.partner: added 'accessibility_specialist' to x_fc_contact_type. State machine: - New state handoff_to_client between quote_submitted and awaiting_funding, used for paths B/C (client or authorizer submits themselves) - Fixed action_mod_on_hold to save x_fc_mod_previous_status_before_hold - Fixed action_mod_resume to restore previous status (was hardcoded to in_production, losing context for cases held earlier) 4 new wizards: - mod_submission_path_wizard — chooses submitted_by, auto-fires VOD request email on first switch to 'internal' - mod_funding_denied_wizard — captures denial category + reason - mod_resubmit_wizard — revises + resubmits denied cases (with optional doc clearing) - mod_submission_confirmed_wizard — records client/authorizer confirmed submission, advances to awaiting_funding 8 new action methods: - action_mod_set_submission_path, action_mod_request_vod, action_mod_handoff_to_client (validates docs, fires handoff email), action_mod_confirmed_submission, action_mod_resubmit_from_denied, action_mod_cancel_from_denied, action_mod_reopen_cancelled - action_mod_funding_denied now opens the denial wizard 3 new email methods + 2 existing fixes: - _send_mod_vod_request_email — auto-attaches blank VOD form from company settings, sent to authorizer when we are handling submission - _send_mod_handoff_email — two templates (client vs authorizer), attaches proposal + drawing + blank MOD Application Form - _mod_company_attachment helper for building attachments from company Binary - Fixed _send_mod_assessment_completed_email to include authorizer - Fixed _send_mod_pod_submitted_email to include client New cron: - _cron_mod_handoff_followup (daily 09:00) — creates mail.activity for office to confirm MOD submission. Assignee via company setting (office contact or sales rep). Uses existing rolling-window cap (2/month per order). Views: - sale_order form: new status-bar buttons (set path, request VOD, handoff, confirm, resubmit, cancel, reopen), new document section in MOD Documents tab with submission-path tracking, denial details, hold history - res_config_settings: new MOD blank forms upload + assignee config Deployed to odoo-westin (westin-v19) and odoo-mobility (mobility). Pre-deploy FK cleanup from earlier session means mobility updated cleanly without workaround. HTTP 200 on both, cron verified active, all new fields present.
680 lines
28 KiB
Python
680 lines
28 KiB
Python
# -*- 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
|
|
from odoo.exceptions import ValidationError
|
|
|
|
_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',
|
|
)
|
|
|
|
# =========================================================================
|
|
# MARCH OF DIMES SETTINGS (added 2026-04 MOD workflow update)
|
|
# =========================================================================
|
|
fc_mod_application_form = fields.Binary(
|
|
related='company_id.x_fc_mod_application_form',
|
|
readonly=False,
|
|
string='MOD Application Form (blank)',
|
|
)
|
|
fc_mod_application_form_filename = fields.Char(
|
|
related='company_id.x_fc_mod_application_form_filename',
|
|
readonly=False,
|
|
)
|
|
fc_mod_vod_form = fields.Binary(
|
|
related='company_id.x_fc_mod_vod_form',
|
|
readonly=False,
|
|
string='Verification of Disability Form (blank)',
|
|
)
|
|
fc_mod_vod_form_filename = fields.Char(
|
|
related='company_id.x_fc_mod_vod_form_filename',
|
|
readonly=False,
|
|
)
|
|
fc_mod_followup_assignee_mode = fields.Selection(
|
|
related='company_id.x_fc_mod_followup_assignee_mode',
|
|
readonly=False,
|
|
string='MOD Follow-up Assignee',
|
|
)
|
|
fc_mod_followup_office_contact_id = fields.Many2one(
|
|
related='company_id.x_fc_mod_followup_office_contact_id',
|
|
readonly=False,
|
|
string='Office Follow-up Contact',
|
|
)
|
|
|
|
# =========================================================================
|
|
# 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',
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
# 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',
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
# 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',
|
|
)
|
|
|
|
# =========================================================================
|
|
# PORTAL BRANDING
|
|
# =========================================================================
|
|
|
|
fc_portal_gradient_preset = fields.Selection([
|
|
('green_teal', 'Green to Teal (Default)'),
|
|
('blue_purple', 'Blue to Purple'),
|
|
('orange_red', 'Sunset Orange'),
|
|
('dark_slate', 'Dark Slate'),
|
|
('custom', 'Custom'),
|
|
],
|
|
string='Portal Gradient Theme',
|
|
config_parameter='fusion_claims.portal_gradient_preset',
|
|
default='green_teal',
|
|
)
|
|
fc_portal_gradient_start = fields.Char(
|
|
string='Gradient Start Color',
|
|
config_parameter='fusion_claims.portal_gradient_start',
|
|
default='#5ba848',
|
|
)
|
|
fc_portal_gradient_mid = fields.Char(
|
|
string='Gradient Mid Color',
|
|
config_parameter='fusion_claims.portal_gradient_mid',
|
|
default='#3a8fb7',
|
|
)
|
|
fc_portal_gradient_end = fields.Char(
|
|
string='Gradient End Color',
|
|
config_parameter='fusion_claims.portal_gradient_end',
|
|
default='#2e7aad',
|
|
)
|
|
fc_portal_gradient_preview = fields.Html(
|
|
string='Gradient Preview',
|
|
compute='_compute_portal_gradient_preview',
|
|
sanitize=False,
|
|
)
|
|
|
|
@api.onchange('fc_portal_gradient_preset')
|
|
def _onchange_portal_gradient_preset(self):
|
|
preset = self.fc_portal_gradient_preset
|
|
if preset and preset != 'custom' and preset in self.GRADIENT_PRESETS:
|
|
start, mid, end = self.GRADIENT_PRESETS[preset]
|
|
self.fc_portal_gradient_start = start
|
|
self.fc_portal_gradient_mid = mid
|
|
self.fc_portal_gradient_end = end
|
|
|
|
@api.depends(
|
|
'fc_portal_gradient_preset',
|
|
'fc_portal_gradient_start',
|
|
'fc_portal_gradient_mid',
|
|
'fc_portal_gradient_end',
|
|
)
|
|
def _compute_portal_gradient_preview(self):
|
|
for rec in self:
|
|
start = rec.fc_portal_gradient_start or '#5ba848'
|
|
mid = rec.fc_portal_gradient_mid or '#3a8fb7'
|
|
end = rec.fc_portal_gradient_end or '#2e7aad'
|
|
gradient = 'linear-gradient(135deg, %s 0%%, %s 60%%, %s 100%%)' % (start, mid, end)
|
|
rec.fc_portal_gradient_preview = (
|
|
'<div style="background: %s; border-radius: 12px; padding: 24px; min-height: 80px;">'
|
|
'<div style="display: flex; align-items: center; color: #fff;">'
|
|
'<div style="flex: 1;">'
|
|
'<h5 style="color: #fff; margin-bottom: 4px;">'
|
|
'<i class="fa fa-hand-peace-o" style="margin-right: 8px;"></i>'
|
|
'Welcome back, Preview User!</h5>'
|
|
'<small style="color: rgba(255,255,255,0.9);">'
|
|
'This is how your portal dashboard header will look.</small>'
|
|
'</div>'
|
|
'<div style="text-align: right;">'
|
|
'<div style="background: rgba(255,255,255,0.2); border-radius: 8px; padding: 8px 12px;">'
|
|
'<small style="display: block; color: rgba(255,255,255,0.8);">Preview</small>'
|
|
'<strong style="color: #fff;">Live</strong>'
|
|
'</div></div></div></div>'
|
|
) % gradient
|
|
|
|
@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.vendor_code',
|
|
'fusion_claims.ai_model',
|
|
'fusion_claims.adp_posting_base_date',
|
|
'fusion_claims.application_reminder_days',
|
|
'fusion_claims.application_reminder_2_days',
|
|
]
|
|
# 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', '')
|
|
|
|
# Apply gradient preset colors to ICP so the portal reads them
|
|
preset = self.fc_portal_gradient_preset
|
|
if preset and preset != 'custom' and preset in self.GRADIENT_PRESETS:
|
|
start, mid, end = self.GRADIENT_PRESETS[preset]
|
|
ICP.set_param('fusion_claims.portal_gradient_start', start)
|
|
ICP.set_param('fusion_claims.portal_gradient_mid', mid)
|
|
ICP.set_param('fusion_claims.portal_gradient_end', end)
|
|
|
|
# =========================================================================
|
|
# 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': {},
|
|
}
|
|
|
|
# -- Portal Gradient Presets -----------------------------------------------
|
|
|
|
GRADIENT_PRESETS = {
|
|
'green_teal': ('#5ba848', '#3a8fb7', '#2e7aad'),
|
|
'blue_purple': ('#4a90d9', '#7b68ee', '#6a5acd'),
|
|
'orange_red': ('#f7971e', '#e44d26', '#c0392b'),
|
|
'dark_slate': ('#2c3e50', '#4a6274', '#34495e'),
|
|
}
|
|
|
|
def _apply_gradient_preset(self, preset_key):
|
|
start, mid, end = self.GRADIENT_PRESETS[preset_key]
|
|
self.fc_portal_gradient_preset = preset_key
|
|
self.fc_portal_gradient_start = start
|
|
self.fc_portal_gradient_mid = mid
|
|
self.fc_portal_gradient_end = end
|
|
|
|
def action_set_gradient_green_teal(self):
|
|
self._apply_gradient_preset('green_teal')
|
|
|
|
def action_set_gradient_blue_purple(self):
|
|
self._apply_gradient_preset('blue_purple')
|
|
|
|
def action_set_gradient_orange_red(self):
|
|
self._apply_gradient_preset('orange_red')
|
|
|
|
def action_set_gradient_dark_slate(self):
|
|
self._apply_gradient_preset('dark_slate')
|
|
|
|
def action_migrate_adp_export_files(self):
|
|
"""Migrate ADP export files from Documents app to the new Export Files menu."""
|
|
return self.env['fusion_claims.adp.export.record'].migrate_from_documents()
|
|
|