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
This commit is contained in:
2026-03-11 16:19:52 +00:00
parent 1f79cdcaaf
commit 431052920e
274 changed files with 52782 additions and 7302 deletions

View File

@@ -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'''
<div class="alert alert-danger" role="alert">
<h5 class="alert-heading"><i class="fa fa-ban"></i> Invoice Cancelled (Withdrawal)</h5>
<ul>
<li><strong>Related Order:</strong> {order.name}</li>
<li><strong>Cancelled By:</strong> {user_name}</li>
<li><strong>Date:</strong> {change_date}</li>
</ul>
<hr>
<p class="mb-0"><strong>Reason:</strong> {reason}</p>
</div>
''')
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'''
<div class="alert alert-warning" role="alert">
<p class="mb-0"><i class="fa fa-exclamation-triangle"></i> <strong>Warning:</strong> Could not cancel invoice {invoice.name}</p>
<p class="mb-0 small">{str(e)}</p>
</div>
''')
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'''
<div class="alert alert-warning" role="alert">
<p class="mb-0"><i class="fa fa-exclamation-triangle"></i> <strong>Warning:</strong> Could not cancel sale order</p>
<p class="mb-0 small">{str(e)}</p>
</div>
''')
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'<li>{inv}</li>' for inv in cancelled_invoices])
invoice_list_html = f'<li><strong>Invoices Cancelled:</strong><ul>{invoice_items}</ul></li>'
so_status = 'Cancelled' if cancelled_so else 'Not applicable'
summary_msg = Markup(f'''
<div class="alert alert-danger" role="alert">
<h5 class="alert-heading"><i class="fa fa-ban"></i> Application Withdrawn &amp; Cancelled</h5>
<ul>
<li><strong>Withdrawn By:</strong> {user_name}</li>
<li><strong>Date:</strong> {change_date}</li>
<li><strong>Sale Order:</strong> {so_status}</li>
{invoice_list_html}
</ul>
<hr>
<p class="mb-0"><strong>Reason:</strong> {reason}</p>
</div>
''')
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'''
<div class="alert alert-info" role="alert">
<h5 class="alert-heading"><i class="fa fa-undo"></i> Application Withdrawn for Correction</h5>
<ul>
<li><strong>Withdrawn By:</strong> {user_name}</li>
<li><strong>Date:</strong> {change_date}</li>
<li><strong>Status Returned To:</strong> Ready for Submission</li>
</ul>
<hr>
<p class="mb-0"><strong>Reason:</strong> {reason}</p>
<p class="mb-0 mt-2"><i class="fa fa-info-circle"></i> Make corrections and click <strong>Submit Application</strong> to resubmit.</p>
</div>
''')
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