# -*- 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, api, _ from odoo.exceptions import UserError from markupsafe import Markup import logging _logger = logging.getLogger(__name__) class CaseCloseVerificationWizard(models.TransientModel): """Wizard to verify audit trail documents before closing an ADP case. This wizard checks: - Signed Pages 11 & 12 uploaded - Final Application uploaded - Proof of Delivery uploaded - Vendor bills linked """ _name = 'fusion_claims.case.close.verification.wizard' _description = 'Case Close Verification Wizard' # ========================================================================== # MAIN FIELDS # ========================================================================== sale_order_id = fields.Many2one( 'sale.order', string='Sale Order', required=True, readonly=True, ) # Document verification fields (computed) has_signed_pages = fields.Boolean( string='Signed Pages 11 & 12', compute='_compute_document_status', ) has_final_application = fields.Boolean( string='Final Application', compute='_compute_document_status', ) has_proof_of_delivery = fields.Boolean( string='Proof of Delivery', compute='_compute_document_status', ) has_vendor_bills = fields.Boolean( string='Vendor Bills Linked', compute='_compute_document_status', ) vendor_bill_count = fields.Integer( string='Vendor Bills Count', compute='_compute_document_status', ) # Overall status all_verified = fields.Boolean( string='All Verified', compute='_compute_all_verified', ) missing_items = fields.Text( string='Missing Items', compute='_compute_all_verified', ) # User notes (optional) closing_notes = fields.Text( string='Closing Notes', help='Optional notes to record when closing the case.', ) # ========================================================================== # COMPUTED METHODS # ========================================================================== @api.depends('sale_order_id') def _compute_document_status(self): for wizard in self: order = wizard.sale_order_id wizard.has_signed_pages = bool(order.x_fc_signed_pages_11_12) wizard.has_final_application = bool(order.x_fc_final_submitted_application) wizard.has_proof_of_delivery = bool(order.x_fc_proof_of_delivery) wizard.has_vendor_bills = len(order.x_fc_vendor_bill_ids) > 0 wizard.vendor_bill_count = len(order.x_fc_vendor_bill_ids) @api.depends('has_signed_pages', 'has_final_application', 'has_proof_of_delivery', 'has_vendor_bills') def _compute_all_verified(self): for wizard in self: missing = [] if not wizard.has_signed_pages: missing.append('Signed Pages 11 & 12') if not wizard.has_final_application: missing.append('Final Application') if not wizard.has_proof_of_delivery: missing.append('Proof of Delivery') if not wizard.has_vendor_bills: missing.append('Vendor Bills (at least one)') wizard.all_verified = len(missing) == 0 wizard.missing_items = '\n'.join(f'• {item}' for item in missing) if missing else '' # ========================================================================== # DEFAULT GET # ========================================================================== @api.model def default_get(self, fields_list): res = super().default_get(fields_list) active_id = self._context.get('active_id') if not active_id: return res res['sale_order_id'] = active_id return res # ========================================================================== # ACTIONS # ========================================================================== def action_close_case(self): """Close the case after verification.""" self.ensure_one() order = self.sale_order_id # Check if everything is verified if not self.all_verified: raise UserError( "Cannot close case - the following items are missing:\n\n" f"{self.missing_items}\n\n" "Please upload all required documents and link vendor bills before closing." ) # Close the case order.with_context(skip_status_validation=True).write({ 'x_fc_adp_application_status': 'case_closed', }) # Build summary message with consistent card style from datetime import date vendor_bills_list = ', '.join(order.x_fc_vendor_bill_ids.mapped('name')) if order.x_fc_vendor_bill_ids else 'N/A' message_body = f"""

Case Closed

Date: {date.today().strftime("%B %d, %Y")}

Audit Trail Verified:

{f'

Notes: {self.closing_notes}

' if self.closing_notes else ''}

Closed by {self.env.user.name}

""" order.message_post( body=Markup(message_body), message_type='notification', subtype_xmlid='mail.mt_note', ) return { 'type': 'ir.actions.act_window_close', } def action_close_anyway(self): """Close the case even if some items are missing (with warning).""" self.ensure_one() order = self.sale_order_id # Close the case order.with_context(skip_status_validation=True).write({ 'x_fc_adp_application_status': 'case_closed', }) # Build warning message with consistent card style from datetime import date missing_items_html = self.missing_items.replace('\n', '
') if self.missing_items else 'None' message_body = f"""

Case Closed (Incomplete)

Date: {date.today().strftime("%B %d, %Y")}

Warning: Case closed without complete audit trail.

Missing Items:
{missing_items_html}

{f'

Notes: {self.closing_notes}

' if self.closing_notes else ''}

Closed by {self.env.user.name}

""" order.message_post( body=Markup(message_body), message_type='notification', subtype_xmlid='mail.mt_note', ) return { 'type': 'ir.actions.act_window_close', }