# -*- coding: utf-8 -*- from odoo import api, fields, models, _ import logging _logger = logging.getLogger(__name__) class SaleOrder(models.Model): _inherit = 'sale.order' # Comments from portal users portal_comment_ids = fields.One2many( 'fusion.authorizer.comment', 'sale_order_id', string='Portal Comments', ) portal_comment_count = fields.Integer( string='Comment Count', compute='_compute_portal_comment_count', ) # Documents uploaded via portal portal_document_ids = fields.One2many( 'fusion.adp.document', 'sale_order_id', string='Portal Documents', ) portal_document_count = fields.Integer( string='Document Count', compute='_compute_portal_document_count', ) # Link to assessment assessment_id = fields.Many2one( 'fusion.assessment', string='Source Assessment', readonly=True, help='The assessment that created this sale order', ) # Authorizer helper field (consolidates multiple possible fields) portal_authorizer_id = fields.Many2one( 'res.partner', string='Authorizer (Portal)', compute='_compute_portal_authorizer_id', store=True, help='Consolidated authorizer field for portal access', ) @api.depends('portal_comment_ids') def _compute_portal_comment_count(self): for order in self: order.portal_comment_count = len(order.portal_comment_ids) @api.depends('portal_document_ids') def _compute_portal_document_count(self): for order in self: order.portal_document_count = len(order.portal_document_ids) @api.depends('x_fc_authorizer_id') def _compute_portal_authorizer_id(self): """Get authorizer from x_fc_authorizer_id field""" for order in self: order.portal_authorizer_id = order.x_fc_authorizer_id def write(self, vals): """Override write to send notification when authorizer is assigned.""" old_authorizers = { order.id: order.x_fc_authorizer_id.id if order.x_fc_authorizer_id else False for order in self } result = super().write(vals) # Check for authorizer changes if 'x_fc_authorizer_id' in vals: for order in self: old_auth = old_authorizers.get(order.id) new_auth = vals.get('x_fc_authorizer_id') if new_auth and new_auth != old_auth: order._send_authorizer_assignment_notification() # NOTE: Generic status change notifications removed. # Each status transition already sends its own detailed email # from fusion_claims (approval, denial, submission, billed, etc.) # A generic "status changed" email on top was redundant and lacked detail. return result def action_message_authorizer(self): """Open composer to send message to authorizer only""" self.ensure_one() if not self.x_fc_authorizer_id: return {'type': 'ir.actions.act_window_close'} return { 'type': 'ir.actions.act_window', 'name': 'Message Authorizer', 'res_model': 'mail.compose.message', 'view_mode': 'form', 'target': 'new', 'context': { 'default_model': 'sale.order', 'default_res_ids': [self.id], 'default_partner_ids': [self.x_fc_authorizer_id.id], 'default_composition_mode': 'comment', 'default_subtype_xmlid': 'mail.mt_note', }, } def _send_authorizer_assignment_notification(self): """Send email when an authorizer is assigned to the order""" self.ensure_one() if not self.x_fc_authorizer_id or not self.x_fc_authorizer_id.email: return try: template = self.env.ref('fusion_authorizer_portal.mail_template_case_assigned', raise_if_not_found=False) if template: template.send_mail(self.id, force_send=False) _logger.info(f"Sent case assignment notification to {self.x_fc_authorizer_id.email} for {self.name}") except Exception as e: _logger.error(f"Failed to send authorizer assignment notification: {e}") # _send_status_change_notification removed -- redundant. # Each workflow transition in fusion_claims sends its own detailed email. def action_view_portal_comments(self): """View portal comments""" self.ensure_one() return { 'type': 'ir.actions.act_window', 'name': _('Portal Comments'), 'res_model': 'fusion.authorizer.comment', 'view_mode': 'list,form', 'domain': [('sale_order_id', '=', self.id)], 'context': {'default_sale_order_id': self.id}, } def action_view_portal_documents(self): """View portal documents""" self.ensure_one() return { 'type': 'ir.actions.act_window', 'name': _('Portal Documents'), 'res_model': 'fusion.adp.document', 'view_mode': 'list,form', 'domain': [('sale_order_id', '=', self.id)], 'context': {'default_sale_order_id': self.id}, } def get_portal_display_data(self): """Get data for portal display, excluding sensitive information""" self.ensure_one() return { 'id': self.id, 'name': self.name, 'date_order': self.date_order, 'state': self.state, 'state_display': dict(self._fields['state'].selection).get(self.state, self.state), 'partner_name': self.partner_id.name if self.partner_id else '', 'partner_address': self._get_partner_address_display(), 'client_reference_1': self.x_fc_client_ref_1 or '', 'client_reference_2': self.x_fc_client_ref_2 or '', 'claim_number': self.x_fc_claim_number or '', 'authorizer_name': self.x_fc_authorizer_id.name if self.x_fc_authorizer_id else '', 'sales_rep_name': self.user_id.name if self.user_id else '', 'product_lines': self._get_product_lines_for_portal(), 'comment_count': self.portal_comment_count, 'document_count': self.portal_document_count, } def _get_partner_address_display(self): """Get formatted partner address for display""" if not self.partner_id: return '' parts = [] if self.partner_id.street: parts.append(self.partner_id.street) if self.partner_id.city: city_part = self.partner_id.city if self.partner_id.state_id: city_part += f", {self.partner_id.state_id.name}" if self.partner_id.zip: city_part += f" {self.partner_id.zip}" parts.append(city_part) return ', '.join(parts) def _get_product_lines_for_portal(self): """Get product lines for portal display (excluding costs)""" lines = [] for line in self.order_line: lines.append({ 'id': line.id, 'product_name': line.product_id.name if line.product_id else line.name, 'quantity': line.product_uom_qty, 'uom': line.product_uom_id.name if line.product_uom_id else '', 'adp_code': line.x_fc_adp_device_code or '' if hasattr(line, 'x_fc_adp_device_code') else '', 'device_type': '', 'serial_number': line.x_fc_serial_number or '' if hasattr(line, 'x_fc_serial_number') else '', }) return lines @api.model def get_authorizer_portal_cases(self, partner_id, search_query=None, limit=100, offset=0): """Get cases for authorizer portal with optional search""" domain = [('x_fc_authorizer_id', '=', partner_id)] # Add search if provided if search_query: search_domain = self._build_search_domain(search_query) domain = ['&'] + domain + search_domain orders = self.sudo().search(domain, limit=limit, offset=offset, order='date_order desc, id desc') return orders @api.model def get_sales_rep_portal_cases(self, user_id, search_query=None, limit=100, offset=0): """Get cases for sales rep portal with optional search""" domain = [('user_id', '=', user_id)] # Add search if provided if search_query: search_domain = self._build_search_domain(search_query) domain = domain + search_domain orders = self.sudo().search(domain, limit=limit, offset=offset, order='date_order desc, id desc') return orders def _build_search_domain(self, query): """Build search domain for portal search""" if not query or len(query) < 2: return [] search_domain = [ '|', '|', '|', '|', ('partner_id.name', 'ilike', query), ('x_fc_claim_number', 'ilike', query), ('x_fc_client_ref_1', 'ilike', query), ('x_fc_client_ref_2', 'ilike', query), ] return search_domain