# -*- coding: utf-8 -*- from markupsafe import Markup from odoo import api, fields, models class StatusChangeReasonWizard(models.TransientModel): """Wizard to capture reason when changing to specific statuses.""" _name = 'fusion.status.change.reason.wizard' _description = 'Status Change Reason Wizard' sale_order_id = fields.Many2one( 'sale.order', string='Sale Order', required=True, ondelete='cascade', ) new_status = fields.Selection( selection=[ ('rejected', 'Rejected by ADP'), # New: Initial rejection (within 24 hours) ('denied', 'Application Denied'), # Funding denied (after 2-3 weeks) ('withdrawn', 'Application Withdrawn'), ('on_hold', 'On Hold'), ('cancelled', 'Cancelled'), ('needs_correction', 'Application Needs Correction'), ], string='New Status', required=True, ) # ========================================================================== # REJECTION REASON FIELDS (for 'rejected' status - initial submission rejection) # ========================================================================== rejection_reason = fields.Selection( selection=[ ('name_correction', 'Name Correction Needed'), ('healthcard_correction', 'Health Card Correction Needed'), ('duplicate_claim', 'Duplicate Claim Exists'), ('xml_format_error', 'XML Format/Validation Error'), ('missing_info', 'Missing Required Information'), ('other', 'Other'), ], string='Rejection Reason', help='Select the reason ADP rejected the submission', ) # ========================================================================== # DENIAL REASON FIELDS (for 'denied' status - funding denial after review) # ========================================================================== denial_reason = fields.Selection( selection=[ ('eligibility', 'Client Eligibility Issues'), ('recent_funding', 'Previous Funding Within 5 Years'), ('medical_justification', 'Insufficient Medical Justification'), ('equipment_not_covered', 'Equipment Not Covered by ADP'), ('documentation_incomplete', 'Documentation Incomplete'), ('other', 'Other'), ], string='Denial Reason', help='Select the reason ADP denied the funding', ) reason = fields.Text( string='Reason / Additional Details', help='Please provide additional details for this status change.', ) # For on_hold: track the previous status previous_status = fields.Char( string='Previous Status', readonly=True, ) # Computed field to determine if reason is required reason_required = fields.Boolean( compute='_compute_reason_required', ) @api.depends('new_status', 'rejection_reason', 'denial_reason') def _compute_reason_required(self): """Reason text is required for 'other' selections or non-rejection/denial statuses.""" for wizard in self: if wizard.new_status == 'rejected': # Reason required if rejection_reason is 'other' wizard.reason_required = wizard.rejection_reason == 'other' elif wizard.new_status == 'denied': # Reason required if denial_reason is 'other' wizard.reason_required = wizard.denial_reason == 'other' else: # For other statuses (on_hold, cancelled, etc.), reason is always required wizard.reason_required = True @api.model def default_get(self, fields_list): """Set defaults from context.""" res = super().default_get(fields_list) if self.env.context.get('active_model') == 'sale.order': order_id = self.env.context.get('active_id') if order_id: order = self.env['sale.order'].browse(order_id) res['sale_order_id'] = order_id res['previous_status'] = order.x_fc_adp_application_status if self.env.context.get('default_new_status'): res['new_status'] = self.env.context.get('default_new_status') return res def _get_status_label(self, status): """Get human-readable label for status.""" labels = { 'rejected': 'Rejected by ADP', 'denied': 'Application Denied', 'withdrawn': 'Application Withdrawn', 'on_hold': 'On Hold', 'cancelled': 'Cancelled', 'needs_correction': 'Application Needs Correction', } return labels.get(status, status) def _get_status_icon(self, status): """Get FontAwesome icon for status.""" icons = { 'rejected': 'fa-times', 'denied': 'fa-times-circle', 'withdrawn': 'fa-undo', 'on_hold': 'fa-pause-circle', 'cancelled': 'fa-ban', 'needs_correction': 'fa-exclamation-triangle', } return icons.get(status, 'fa-info-circle') def _get_rejection_reason_label(self, reason): """Get human-readable label for rejection reason.""" labels = { 'name_correction': 'Name Correction Needed', 'healthcard_correction': 'Health Card Correction Needed', 'duplicate_claim': 'Duplicate Claim Exists', 'xml_format_error': 'XML Format/Validation Error', 'missing_info': 'Missing Required Information', 'other': 'Other', } return labels.get(reason, reason) def _get_denial_reason_label(self, reason): """Get human-readable label for denial reason.""" labels = { 'eligibility': 'Client Eligibility Issues', 'recent_funding': 'Previous Funding Within 5 Years', 'medical_justification': 'Insufficient Medical Justification', 'equipment_not_covered': 'Equipment Not Covered by ADP', 'documentation_incomplete': 'Documentation Incomplete', 'other': 'Other', } return labels.get(reason, reason) def action_confirm(self): """Confirm status change and post reason to chatter.""" self.ensure_one() order = self.sale_order_id new_status = self.new_status reason = self.reason or '' # Build chatter message status_label = self._get_status_label(new_status) icon = self._get_status_icon(new_status) user_name = self.env.user.name change_date = fields.Date.today().strftime('%B %d, %Y') # Color scheme for different status types status_colors = { 'rejected': ('#e74c3c', '#fff5f5', '#f5c6cb'), # Red (lighter) 'denied': ('#dc3545', '#fff5f5', '#f5c6cb'), # Red 'withdrawn': ('#6c757d', '#f8f9fa', '#dee2e6'), # Gray 'on_hold': ('#fd7e14', '#fff8f0', '#ffecd0'), # Orange 'cancelled': ('#dc3545', '#fff5f5', '#f5c6cb'), # Red 'needs_correction': ('#ffc107', '#fffbf0', '#ffeeba'), # Yellow } 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 update_vals = {'x_fc_adp_application_status': new_status} # ================================================================= # REJECTED: ADP rejected submission (within 24 hours) # ================================================================= if new_status == 'rejected': rejection_reason = self.rejection_reason rejection_label = self._get_rejection_reason_label(rejection_reason) # Store rejection details in sale order current_count = order.x_fc_rejection_count or 0 update_vals.update({ 'x_fc_rejection_reason': rejection_reason, 'x_fc_rejection_reason_other': reason if rejection_reason == 'other' else False, 'x_fc_rejection_date': fields.Date.today(), 'x_fc_rejection_count': current_count + 1, }) # Build rejection message details_html = '' if rejection_reason == 'other' and reason: details_html = f'
Details: {reason}
' message_body = f'''Next Step: Correct the issue and resubmit the application.
Details: {reason}
' message_body = f'''Reason: {reason}
Reason: {reason}
Warning: Could not cancel invoice {invoice.name}
{str(e)}
Warning: Could not cancel sale order
{str(e)}
Reason for Cancellation: {reason}