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.
125 lines
5.0 KiB
Python
125 lines
5.0 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.
|
|
|
|
from odoo import models, fields
|
|
|
|
|
|
class ResCompany(models.Model):
|
|
_inherit = 'res.company'
|
|
|
|
# Store/Location Information
|
|
x_fc_store_address_1 = fields.Char(
|
|
string='Store Address Line 1',
|
|
help='First store/location address for reports (e.g., "Main Store - 123 Street, City, Province, Postal")',
|
|
)
|
|
x_fc_store_address_2 = fields.Char(
|
|
string='Store Address Line 2',
|
|
help='Second store/location address for reports (optional)',
|
|
)
|
|
x_fc_company_tagline = fields.Char(
|
|
string='Company Tagline',
|
|
help='Company tagline/slogan for reports (e.g., "Enhancing Accessibility, Improving Lives.")',
|
|
)
|
|
|
|
# Payment Information
|
|
x_fc_etransfer_email = fields.Char(
|
|
string='E-Transfer Email',
|
|
help='Email address for Interac e-Transfers',
|
|
)
|
|
x_fc_cheque_payable_to = fields.Char(
|
|
string='Cheque Payable To',
|
|
help='Name for cheque payments (defaults to company name if empty)',
|
|
)
|
|
x_fc_payment_terms_html = fields.Html(
|
|
string='Payment Terms',
|
|
help='Payment terms and conditions displayed on reports (supports HTML formatting)',
|
|
sanitize=True,
|
|
sanitize_overridable=True,
|
|
)
|
|
|
|
# Refund Policy
|
|
x_fc_include_refund_page = fields.Boolean(
|
|
string='Include Refund Policy Page',
|
|
default=True,
|
|
help='Include a separate refund policy page at the end of reports',
|
|
)
|
|
x_fc_refund_policy_html = fields.Html(
|
|
string='Refund Policy',
|
|
help='Full refund policy displayed on a separate page (supports HTML formatting)',
|
|
sanitize=True,
|
|
sanitize_overridable=True,
|
|
)
|
|
|
|
# Office Notification Recipients
|
|
x_fc_office_notification_ids = fields.Many2many(
|
|
'res.partner',
|
|
'fc_company_office_notification_partners_rel',
|
|
'company_id',
|
|
'partner_id',
|
|
string='Office Notification Recipients',
|
|
help='Contacts who will receive a copy (CC) of all automated ADP notifications',
|
|
)
|
|
|
|
# ==========================================================================
|
|
# MARCH OF DIMES SETTINGS (shared blank forms + follow-up assignment)
|
|
# Added 2026-04 MOD update. Latest revision of the MOD-issued blank forms
|
|
# lives here so every case can auto-attach them to outgoing emails.
|
|
# ==========================================================================
|
|
x_fc_mod_application_form = fields.Binary(
|
|
string='MOD Application Form (blank)',
|
|
attachment=True,
|
|
help='Blank March of Dimes Application Form (latest MOD revision). '
|
|
'Auto-attached to the handoff email sent to the client or authorizer '
|
|
'when they will be submitting the application themselves.',
|
|
)
|
|
x_fc_mod_application_form_filename = fields.Char(
|
|
string='MOD Application Form Filename',
|
|
)
|
|
x_fc_mod_vod_form = fields.Binary(
|
|
string='Verification of Disability Form (blank)',
|
|
attachment=True,
|
|
help='Blank March of Dimes Verification of Disability form. '
|
|
'Auto-attached to the VOD request email sent to the authorizer when '
|
|
'our office is handling the MOD submission internally.',
|
|
)
|
|
x_fc_mod_vod_form_filename = fields.Char(
|
|
string='VOD Form Filename',
|
|
)
|
|
x_fc_mod_followup_assignee_mode = fields.Selection(
|
|
selection=[
|
|
('office_contact', 'Designated Office Contact'),
|
|
('sales_rep', 'Order Sales Rep'),
|
|
],
|
|
string='MOD Follow-up Assignee',
|
|
default='office_contact',
|
|
help='Who gets the follow-up activity when a client or authorizer is '
|
|
'handling the MOD submission themselves. Office contact = one '
|
|
'designated person handles all follow-ups. Sales rep = the rep '
|
|
'on each order is responsible for their own follow-ups.',
|
|
)
|
|
x_fc_mod_followup_office_contact_id = fields.Many2one(
|
|
'res.users',
|
|
string='MOD Follow-up Office Contact',
|
|
help='The office user who receives MOD handoff follow-up activities '
|
|
'when assignee mode is set to Office Contact. Only used when '
|
|
'x_fc_mod_followup_assignee_mode == office_contact.',
|
|
)
|
|
|
|
def _get_cheque_payable_name(self):
|
|
"""Get the name for cheque payments, defaulting to company name."""
|
|
self.ensure_one()
|
|
return self.x_fc_cheque_payable_to or self.name
|
|
|
|
def _get_mod_followup_assignee(self, sale_order):
|
|
"""Return the res.users who should receive the MOD handoff follow-up
|
|
activity for the given order, based on this company's configuration.
|
|
Falls back to the order's sales rep when the office contact is missing.
|
|
"""
|
|
self.ensure_one()
|
|
if self.x_fc_mod_followup_assignee_mode == 'office_contact' and self.x_fc_mod_followup_office_contact_id:
|
|
return self.x_fc_mod_followup_office_contact_id
|
|
return sale_order.user_id or self.env.user
|
|
|