# -*- coding: utf-8 -*- from odoo import api, fields, models, _ from odoo.exceptions import UserError, ValidationError from markupsafe import Markup from datetime import datetime, timedelta import base64 import json import logging _logger = logging.getLogger(__name__) class FusionAssessment(models.Model): _name = 'fusion.assessment' _description = 'ADP Assessment' _inherit = ['mail.thread', 'mail.activity.mixin', 'fusion.email.builder.mixin'] _order = 'assessment_date desc, id desc' _rec_name = 'display_name' # Status state = fields.Selection([ ('draft', 'In Progress'), ('pending_signature', 'Pending Signatures'), ('completed', 'Completed'), ('cancelled', 'Cancelled'), ], string='Status', default='draft', tracking=True, index=True) display_name = fields.Char( string='Display Name', compute='_compute_display_name', store=True, ) # Reference reference = fields.Char( string='Reference', readonly=True, copy=False, default=lambda self: _('New'), ) # ===== EXPRESS FORM: EQUIPMENT TYPE ===== equipment_type = fields.Selection([ ('rollator', 'Rollator'), ('wheelchair', 'Wheelchair'), ('powerchair', 'Powerchair'), ], string='Equipment Type', tracking=True, index=True) # Rollator Types rollator_type = fields.Selection([ ('type_1', 'Type 1'), ('type_2', 'Type 2'), ('type_3', 'Type 3'), ], string='Rollator Type') # Wheelchair Types (Adult Manual) wheelchair_type = fields.Selection([ ('type_1', 'Type 1 - Adult Manual Standard Wheelchair'), ('type_2', 'Type 2 - Adult Manual Lightweight Wheelchair'), ('type_3', 'Type 3 - Adult Manual Ultra Lightweight Wheelchair'), ('type_4', 'Type 4 - Adult Manual Tilt Wheelchair'), ('type_5', 'Type 5 - Adult Manual Dynamic Tilt Wheelchair'), ], string='Wheelchair Type') # Powerchair Types powerchair_type = fields.Selection([ ('type_1', 'Adult Power Base Type 1'), ('type_2', 'Adult Power Base Type 2'), ('type_3', 'Adult Power Base Type 3'), ], string='Powerchair Type') # ===== EXPRESS FORM: ROLLATOR MEASUREMENTS ===== rollator_handle_height = fields.Float(string='Handle Height (inches)', digits=(10, 2)) rollator_seat_height = fields.Float(string='Seat Height (inches)', digits=(10, 2)) # ===== EXPRESS FORM: ROLLATOR ADDONS ===== rollator_addons = fields.Text( string='Rollator Addons', help='Comma-separated list of selected rollator addons', ) # ===== EXPRESS FORM: WHEELCHAIR/POWERCHAIR MEASUREMENTS ===== legrest_length = fields.Float(string='Legrest Length (inches)', digits=(10, 2)) cane_height = fields.Float(string='Cane Height (inches)', digits=(10, 2), help='Ground to Canes') # ===== EXPRESS FORM: WHEELCHAIR OPTIONS ===== frame_options = fields.Text( string='Frame Options', help='Comma-separated list: Recliner Option, Dynamic Tilt Frame, Titanium Frame', ) wheel_options = fields.Text( string='Wheel Options/Addons', help='Comma-separated list of wheel options', ) legrest_options = fields.Text( string='Legrest Accessories', help='Comma-separated list of legrest accessories', ) additional_adp_options = fields.Text( string='Additional ADP Funded Options', help='Comma-separated list of additional ADP options', ) seatbelt_type = fields.Selection([ ('standard', 'Standard Belt - No Padding'), ('padded', 'Padded Belt - Positioning Belts (Modular)'), ('4point', '4 Point Seat Belt - Positioning Belts (Modular)'), ('chest_harness', 'Chest Harness - Positioning Belts (Modular)'), ('additional_pads', 'Additional Pads - Positioning Belts (Custom)'), ], string='Seat Belt Type') # ===== EXPRESS FORM: POWERCHAIR OPTIONS ===== powerchair_options = fields.Text( string='Powerchair Additional Options', help='Comma-separated list of powerchair additional ADP options', ) specialty_controls = fields.Text( string='Specialty Components', help='Comma-separated list of specialty controls (rationale required)', ) # ===== EXPRESS FORM: ADDITIONAL INFO ===== additional_customization = fields.Text( string='Additional Information/Customization', help='Free-form notes for customization requirements', ) cushion_info = fields.Char( string='Cushion', help='Cushion details for wheelchair/powerchair', ) backrest_info = fields.Char( string='Backrest', help='Backrest details for wheelchair/powerchair', ) # ===== EXPRESS FORM: KEY DATES (map to SO x_fc_ fields) ===== assessment_start_date = fields.Date( string='Assessment Start Date', tracking=True, ) assessment_end_date = fields.Date( string='Assessment End Date', tracking=True, ) claim_authorization_date = fields.Date( string='Claim Authorization Date', tracking=True, ) # ===== EXPRESS FORM: CLIENT TYPE ===== client_type = fields.Selection([ ('reg', 'REG - Regular ADP'), ('ods', 'ODS - ODSP'), ('acs', 'ACS - ACSD'), ('owp', 'OWP - Ontario Works'), ], string='Client Type', default='reg', help='REG = ADP only, ODS/ACS/OWP = ADP + ODSP') # ===== EXPRESS FORM: REASON FOR APPLICATION ===== reason_for_application = fields.Selection([ ('first_access', 'First Time Access - NO previous ADP'), ('additions', 'Additions'), ('mod_non_adp', 'Modification/Upgrade - Original NOT through ADP'), ('mod_adp', 'Modification/Upgrade - Original through ADP'), ('replace_status', 'Replacement - Change in Status'), ('replace_size', 'Replacement - Change in Body Size'), ('replace_worn', 'Replacement - Worn out (past useful life)'), ('replace_lost', 'Replacement - Lost'), ('replace_stolen', 'Replacement - Stolen'), ('replace_damaged', 'Replacement - Damaged beyond repair'), ('replace_no_longer_meets', 'Replacement - No longer meets needs'), ('growth', 'Growth/Change in condition'), ], string='Reason for Application') previous_funding_date = fields.Date( string='Previous Funding Date', help='Date of previous ADP funding (for replacements)', ) # ===== EXPRESS FORM: CLIENT BIOGRAPHIC (enhanced) ===== client_middle_name = fields.Char(string='Middle Name') client_health_card_version = fields.Char(string='Health Card Version', help='Version code on health card') # ===== CLIENT INFORMATION ===== client_name = fields.Char( string='Client Name', required=True, tracking=True, ) client_first_name = fields.Char(string='First Name') client_last_name = fields.Char(string='Last Name') client_address = fields.Text(string='Address') client_street = fields.Char(string='Street') client_unit = fields.Char(string='Unit/Apt/Suite') client_city = fields.Char(string='City') client_state = fields.Char(string='Province/State', default='Ontario') client_postal_code = fields.Char(string='Postal Code') client_country_id = fields.Many2one('res.country', string='Country', default=lambda self: self.env.ref('base.ca', raise_if_not_found=False)) client_phone = fields.Char(string='Phone') client_mobile = fields.Char(string='Mobile') client_email = fields.Char(string='Email') client_dob = fields.Date(string='Date of Birth') client_health_card = fields.Char(string='Health Card Number') # Link to existing partner or create new partner_id = fields.Many2one( 'res.partner', string='Client Partner', help='Link to existing customer record', ) create_new_partner = fields.Boolean( string='Create New Client', default=True, help='If checked, a new customer record will be created on completion', ) # Client References (for searchability) client_reference_1 = fields.Char(string='Client Reference 1') client_reference_2 = fields.Char(string='Client Reference 2') # ===== ASSESSMENT PARTICIPANTS ===== sales_rep_id = fields.Many2one( 'res.users', string='Sales Rep', default=lambda self: self.env.user, tracking=True, ) authorizer_id = fields.Many2one( 'res.partner', string='Authorizer/OT', tracking=True, help='The Occupational Therapist or Authorizer conducting the assessment', ) # ===== ASSESSMENT DATE/LOCATION ===== assessment_date = fields.Datetime( string='Assessment Date', default=fields.Datetime.now, required=True, tracking=True, ) assessment_location = fields.Selection([ ('home', 'Client Home'), ('clinic', 'Clinic/Office'), ('hospital', 'Hospital'), ('ltc', 'Long Term Care'), ('other', 'Other'), ], string='Location Type', default='home') assessment_location_notes = fields.Text(string='Location Notes') # ===== WHEELCHAIR SPECIFICATIONS ===== # Measurements (in inches) seat_width = fields.Float(string='Seat Width (inches)', digits=(10, 2)) seat_depth = fields.Float(string='Seat Depth (inches)', digits=(10, 2)) seat_to_floor_height = fields.Float(string='Seat to Floor Height (inches)', digits=(10, 2)) back_height = fields.Float(string='Back Height (inches)', digits=(10, 2)) armrest_height = fields.Float(string='Armrest Height (inches)', digits=(10, 2)) footrest_length = fields.Float(string='Footrest Length (inches)', digits=(10, 2)) # Additional Measurements overall_width = fields.Float(string='Overall Width (inches)', digits=(10, 2)) overall_length = fields.Float(string='Overall Length (inches)', digits=(10, 2)) overall_height = fields.Float(string='Overall Height (inches)', digits=(10, 2)) seat_angle = fields.Float(string='Seat Angle (degrees)', digits=(10, 1)) back_angle = fields.Float(string='Back Angle (degrees)', digits=(10, 1)) # Client Measurements client_weight = fields.Float(string='Client Weight (lbs)', digits=(10, 1)) client_height = fields.Float(string='Client Height (inches)', digits=(10, 1)) # ===== PRODUCT TYPES ===== cushion_type = fields.Selection([ ('foam', 'Foam'), ('gel', 'Gel'), ('air', 'Air'), ('hybrid', 'Hybrid'), ('custom', 'Custom Molded'), ('other', 'Other'), ], string='Cushion Type') cushion_notes = fields.Text(string='Cushion Notes') backrest_type = fields.Selection([ ('standard', 'Standard'), ('adjustable', 'Adjustable Tension'), ('solid', 'Solid Back'), ('contoured', 'Contoured'), ('custom', 'Custom'), ('other', 'Other'), ], string='Backrest Type') backrest_notes = fields.Text(string='Backrest Notes') frame_type = fields.Selection([ ('folding', 'Folding'), ('rigid', 'Rigid'), ('tilt', 'Tilt-in-Space'), ('reclining', 'Reclining'), ('standing', 'Standing'), ('power', 'Power'), ('other', 'Other'), ], string='Frame Type') frame_notes = fields.Text(string='Frame Notes') wheel_type = fields.Selection([ ('pneumatic', 'Pneumatic'), ('solid', 'Solid'), ('flat_free', 'Flat-Free'), ('mag', 'Mag Wheels'), ('spoke', 'Spoke Wheels'), ('other', 'Other'), ], string='Wheel Type') wheel_notes = fields.Text(string='Wheel Notes') # ===== ACCESSIBILITY/MOBILITY NEEDS ===== mobility_notes = fields.Text( string='Mobility Needs', help='Document the client mobility needs and challenges', ) accessibility_notes = fields.Text( string='Accessibility Notes', help='Document accessibility requirements and home environment', ) special_requirements = fields.Text( string='Special Requirements', help='Any special requirements or customizations needed', ) diagnosis = fields.Text( string='Diagnosis/Condition', help='Relevant medical diagnosis or conditions', ) # ===== PAGE 11: CONSENT & DECLARATION ===== consent_signed_by = fields.Selection([ ('applicant', 'Applicant'), ('agent', 'Agent'), ], string='Consent Signed By', default='applicant', help='Who signed the ADP consent declaration') consent_declaration_accepted = fields.Boolean( string='Consent Declaration Accepted', help='Whether the consent declaration checkbox was checked', ) consent_date = fields.Date( string='Consent Date', help='Date the consent was signed', ) # Agent info (only if consent_signed_by == 'agent') agent_relationship = fields.Selection([ ('spouse', 'Spouse'), ('parent', 'Parent(s)'), ('child', 'Child'), ('power_of_attorney', 'Power of Attorney'), ('public_guardian', 'Public Guardian/Trustee'), ], string='Agent Relationship') agent_first_name = fields.Char(string='Agent First Name') agent_last_name = fields.Char(string='Agent Last Name') agent_middle_initial = fields.Char(string='Agent Middle Initial') agent_unit = fields.Char(string='Agent Unit') agent_street_number = fields.Char(string='Agent Street Number') agent_street_name = fields.Char(string='Agent Street Name') agent_city = fields.Char(string='Agent City') agent_province = fields.Char(string='Agent Province', default='Ontario') agent_postal_code = fields.Char(string='Agent Postal Code') agent_home_phone = fields.Char(string='Agent Home Phone') agent_business_phone = fields.Char(string='Agent Business Phone') agent_phone_ext = fields.Char(string='Agent Phone Ext') # Generated filled PDF from template engine signed_page_11_pdf = fields.Binary(string='Signed Page 11 PDF') signed_page_11_pdf_filename = fields.Char(string='Signed Page 11 Filename') # ===== SIGNATURES ===== signature_page_11 = fields.Binary( string='Page 11 Signature (Authorizer)', help='Digital signature for ADP Application Page 11', ) signature_page_11_name = fields.Char(string='Page 11 Signer Name') signature_page_11_date = fields.Datetime(string='Page 11 Signed Date') signature_page_12 = fields.Binary( string='Page 12 Signature (Client)', help='Digital signature for ADP Application Page 12', ) signature_page_12_name = fields.Char(string='Page 12 Signer Name') signature_page_12_date = fields.Datetime(string='Page 12 Signed Date') signatures_complete = fields.Boolean( string='Signatures Complete', compute='_compute_signatures_complete', store=True, ) # ===== RELATIONSHIPS ===== document_ids = fields.One2many( 'fusion.adp.document', 'assessment_id', string='Documents', ) comment_ids = fields.One2many( 'fusion.authorizer.comment', 'assessment_id', string='Comments', ) sale_order_id = fields.Many2one( 'sale.order', string='Created Sale Order', readonly=True, copy=False, ) # ===== COMPUTED FIELDS ===== document_count = fields.Integer( string='Document Count', compute='_compute_document_count', ) @api.depends('client_name', 'reference', 'equipment_type') def _compute_display_name(self): equipment_labels = {'rollator': 'Rollator', 'wheelchair': 'Wheelchair', 'powerchair': 'Powerchair'} for assessment in self: if assessment.reference and assessment.reference != 'New': equip = equipment_labels.get(assessment.equipment_type, '') equip_suffix = f' ({equip})' if equip else '' assessment.display_name = f"{assessment.reference} - {assessment.client_name or 'Unknown'}{equip_suffix}" else: assessment.display_name = assessment.client_name or _('New Assessment') @api.depends('signature_page_11', 'signature_page_12') def _compute_signatures_complete(self): for assessment in self: assessment.signatures_complete = bool(assessment.signature_page_11 and assessment.signature_page_12) @api.depends('document_ids') def _compute_document_count(self): for assessment in self: assessment.document_count = len(assessment.document_ids) @api.model_create_multi def create(self, vals_list): """Override create to generate reference number""" for vals in vals_list: if vals.get('reference', 'New') == 'New': vals['reference'] = self.env['ir.sequence'].next_by_code('fusion.assessment') or 'New' return super().create(vals_list) def action_mark_pending_signature(self): """Move to pending signature state""" self.ensure_one() self.state = 'pending_signature' return True def action_complete(self): """Complete the assessment and create draft sale order""" self.ensure_one() if not self.signatures_complete: raise UserError(_('Please ensure both Page 11 and Page 12 signatures are captured before completing.')) # Create or link partner partner = self._ensure_partner() # Create draft sale order sale_order = self._create_draft_sale_order(partner) # Generate signed documents self._generate_signed_documents() # Update state self.write({ 'state': 'completed', 'sale_order_id': sale_order.id, 'partner_id': partner.id, }) # Send notifications self._send_completion_notifications() return { 'type': 'ir.actions.act_window', 'name': _('Created Sale Order'), 'res_model': 'sale.order', 'res_id': sale_order.id, 'view_mode': 'form', 'views': [(False, 'form')], 'target': 'current', } def action_complete_express(self): """Complete express assessment and create draft sale order (no signatures required)""" self.ensure_one() # Validate required fields if not self.equipment_type: raise UserError(_('Please select an equipment type.')) if not self.authorizer_id: raise UserError(_('Please select an authorizer.')) if not self.client_name: raise UserError(_('Please enter the client name.')) # Create or link partner partner = self._ensure_partner() # Create draft sale order sale_order = self._create_draft_sale_order(partner) # Update state self.write({ 'state': 'completed', 'sale_order_id': sale_order.id, 'partner_id': partner.id, }) _logger.info(f"Completed express assessment {self.reference}, created SO {sale_order.name}") return sale_order def action_cancel(self): """Cancel the assessment""" self.ensure_one() self.state = 'cancelled' return True def action_reset_draft(self): """Reset to draft state""" self.ensure_one() self.state = 'draft' return True def _ensure_partner(self): """Ensure a partner exists for the client, create if needed""" self.ensure_one() if self.partner_id: return self.partner_id if not self.create_new_partner: raise UserError(_('Please select an existing client or check "Create New Client".')) # Build address address_parts = [] if self.client_street: address_parts.append(self.client_street) if self.client_city: address_parts.append(self.client_city) if self.client_state: address_parts.append(self.client_state) if self.client_postal_code: address_parts.append(self.client_postal_code) partner_vals = { 'name': self.client_name, 'street': self.client_street or '', 'street2': self.client_unit or False, 'city': self.client_city or '', 'zip': self.client_postal_code or '', 'phone': self.client_phone or '', 'email': self.client_email or '', 'customer_rank': 1, } if self.client_country_id: partner_vals['country_id'] = self.client_country_id.id partner = self.env['res.partner'].sudo().create(partner_vals) _logger.info(f"Created new partner {partner.name} (ID: {partner.id}) from assessment {self.reference}") return partner def _create_draft_sale_order(self, partner): """Create a draft sale order from the assessment""" self.ensure_one() SaleOrder = self.env['sale.order'].sudo() CrmTag = self.env['crm.tag'].sudo() # Determine sale type based on client type # REG = ADP only, ODS/ACS/OWP = ADP + ODSP sale_type = 'adp' if self.client_type in ['ods', 'acs', 'owp']: sale_type = 'adp_odsp' # Determine the correct workflow status for the SO. # The write() auto-transition (assessment_completed -> waiting_for_application) # does NOT fire during create(), so we set the final target status directly. if self.assessment_start_date and self.assessment_end_date: # Both dates filled = assessment completed -> advance to waiting_for_application target_status = 'waiting_for_application' elif self.assessment_start_date: # Only start date = assessment is scheduled target_status = 'assessment_scheduled' else: target_status = 'quotation' # Prepare values so_vals = { 'partner_id': partner.id, 'user_id': self.sales_rep_id.id if self.sales_rep_id else self.env.user.id, 'state': 'draft', 'origin': f'Assessment: {self.reference}', 'x_fc_sale_type': sale_type, # Issue 2 fix: Set assessment back-link for traceability 'assessment_id': self.id, # Set the correct workflow status directly 'x_fc_adp_application_status': target_status, } # Set authorizer if available if self.authorizer_id: so_vals['x_fc_authorizer_id'] = self.authorizer_id.id # Set client references if available if self.client_reference_1: so_vals['x_fc_client_ref_1'] = self.client_reference_1 if self.client_reference_2: so_vals['x_fc_client_ref_2'] = self.client_reference_2 # Map assessment dates to SO x_fc_ fields if self.assessment_start_date: so_vals['x_fc_assessment_start_date'] = self.assessment_start_date if self.assessment_end_date: so_vals['x_fc_assessment_end_date'] = self.assessment_end_date if self.claim_authorization_date: so_vals['x_fc_claim_authorization_date'] = self.claim_authorization_date # Map reason for application if self.reason_for_application: so_vals['x_fc_reason_for_application'] = self.reason_for_application if self.previous_funding_date: so_vals['x_fc_previous_funding_date'] = self.previous_funding_date # Map client type (assessment uses lowercase, sale.order uses uppercase) if self.client_type: client_type_map = { 'reg': 'REG', 'ods': 'ODS', 'acs': 'ACS', 'owp': 'OWP', } so_vals['x_fc_client_type'] = client_type_map.get(self.client_type, self.client_type.upper()) # ===================================================================== # Issue 3 & 4 fix: Map Page 11 consent & signature tracking to SO # ===================================================================== if self.consent_signed_by: # Map consent_signed_by to x_fc_page11_signer_type if self.consent_signed_by == 'applicant': so_vals['x_fc_page11_signer_type'] = 'client' so_vals['x_fc_page11_signer_name'] = self.client_name or '' elif self.consent_signed_by == 'agent': # Map agent_relationship to signer_type agent_type_map = { 'spouse': 'spouse', 'parent': 'parent', 'child': 'legal_guardian', 'power_of_attorney': 'poa', 'public_guardian': 'public_trustee', } so_vals['x_fc_page11_signer_type'] = agent_type_map.get( self.agent_relationship, 'legal_guardian' ) agent_name = f"{self.agent_first_name or ''} {self.agent_last_name or ''}".strip() so_vals['x_fc_page11_signer_name'] = agent_name or self.client_name or '' if self.agent_relationship: relationship_labels = dict(self._fields['agent_relationship'].selection) so_vals['x_fc_page11_signer_relationship'] = relationship_labels.get( self.agent_relationship, self.agent_relationship ) if self.consent_date: so_vals['x_fc_page11_signed_date'] = self.consent_date # Build tags list tag_ids = [] # Always add ADP tag adp_tag = CrmTag.search([('name', 'ilike', 'ADP')], limit=1) if adp_tag: tag_ids.append(adp_tag.id) # Add equipment type tag based on selection equipment_tag_name = None if self.equipment_type == 'wheelchair' and self.wheelchair_type: type_map = { 'type_1': 'TYPE 1 WHEELCHAIR', 'type_2': 'TYPE 2 WHEELCHAIR', 'type_3': 'TYPE 3 WHEELCHAIR', 'type_4': 'TYPE 4 WHEELCHAIR', 'type_5': 'TYPE 5 WHEELCHAIR', } equipment_tag_name = type_map.get(self.wheelchair_type) elif self.equipment_type == 'rollator' and self.rollator_type: type_map = { 'type_1': 'TYPE 1 ROLLATOR', 'type_2': 'TYPE 2 ROLLATOR', 'type_3': 'TYPE 3 ROLLATOR', } equipment_tag_name = type_map.get(self.rollator_type) elif self.equipment_type == 'powerchair' and self.powerchair_type: type_map = { 'type_1': 'TYPE 1 POWERCHAIR', 'type_2': 'TYPE 2 POWERCHAIR', 'type_3': 'TYPE 3 POWERCHAIR', } equipment_tag_name = type_map.get(self.powerchair_type) # Find or create the equipment tag if equipment_tag_name: equipment_tag = CrmTag.search([('name', 'ilike', equipment_tag_name)], limit=1) if not equipment_tag: # Create the tag if it doesn't exist equipment_tag = CrmTag.create({'name': equipment_tag_name}) _logger.info(f"Created new CRM tag: {equipment_tag_name}") tag_ids.append(equipment_tag.id) # Add tags to sale order values if tag_ids: so_vals['tag_ids'] = [(6, 0, tag_ids)] sale_order = SaleOrder.create(so_vals) _logger.info(f"Created draft sale order {sale_order.name} from assessment {self.reference} " f"with sale_type={sale_type}, status={target_status}") # ===================================================================== # Issue 6 fix: Post workflow-consistent chatter messages # ===================================================================== # Post a workflow status message matching what backend wizards would generate equipment_labels = { 'rollator': 'Rollator', 'wheelchair': 'Wheelchair', 'powerchair': 'Powerchair', } equipment_label = equipment_labels.get(self.equipment_type, 'Equipment') sales_rep_name = self.sales_rep_id.name if self.sales_rep_id else self.env.user.name authorizer_name = self.authorizer_id.name if self.authorizer_id else 'N/A' workflow_msg = Markup( '' ) sale_order.message_post( body=workflow_msg, message_type='notification', subtype_xmlid='mail.mt_note', ) # Post assessment details to chatter as HTML table assessment_html = self._format_assessment_html_table() sale_order.message_post( body=Markup(assessment_html), message_type='comment', subtype_xmlid='mail.mt_note', ) # ===================================================================== # Issue 4 fix: Post consent & agent details to chatter # ===================================================================== if self.consent_signed_by: consent_label = 'Applicant' if self.consent_signed_by == 'applicant' else 'Agent' consent_parts = [ f'
  • Signed by: {consent_label}
  • ', ] if self.consent_date: consent_parts.append(f'
  • Consent date: {self.consent_date}
  • ') if self.consent_declaration_accepted: consent_parts.append('
  • Declaration accepted: Yes
  • ') # Add agent details if signed by agent if self.consent_signed_by == 'agent': agent_name = f"{self.agent_first_name or ''} {self.agent_last_name or ''}".strip() if agent_name: consent_parts.append(f'
  • Agent name: {agent_name}
  • ') if self.agent_relationship: rel_labels = dict(self._fields['agent_relationship'].selection) consent_parts.append( f'
  • Relationship: ' f'{rel_labels.get(self.agent_relationship, self.agent_relationship)}
  • ' ) agent_addr_parts = [] if self.agent_unit: agent_addr_parts.append(f'Unit {self.agent_unit}') if self.agent_street_number: agent_addr_parts.append(self.agent_street_number) if self.agent_street_name: agent_addr_parts.append(self.agent_street_name) if self.agent_city: agent_addr_parts.append(self.agent_city) if self.agent_province: agent_addr_parts.append(self.agent_province) if self.agent_postal_code: agent_addr_parts.append(self.agent_postal_code) if agent_addr_parts: consent_parts.append( f'
  • Agent address: {", ".join(agent_addr_parts)}
  • ' ) phones = [] if self.agent_home_phone: phones.append(f'Home: {self.agent_home_phone}') if self.agent_business_phone: ext = f' ext {self.agent_phone_ext}' if self.agent_phone_ext else '' phones.append(f'Business: {self.agent_business_phone}{ext}') if phones: consent_parts.append( f'
  • Agent phone: {", ".join(phones)}
  • ' ) consent_msg = Markup( '' ) sale_order.message_post( body=consent_msg, message_type='notification', subtype_xmlid='mail.mt_note', ) # Send email notification to sales person and authorizer self._send_assessment_completed_email(sale_order) # Schedule follow-up activity for sales rep self._schedule_followup_activity(sale_order) return sale_order def _schedule_followup_activity(self, sale_order): """Schedule a follow-up activity for the sales rep""" self.ensure_one() sales_rep = self.sales_rep_id or self.env.user if not sales_rep: return # Get the "To Do" activity type activity_type = self.env.ref('mail.mail_activity_data_todo', raise_if_not_found=False) if not activity_type: _logger.warning("Could not find 'To Do' activity type") return # Get equipment type label equipment_labels = { 'wheelchair': 'Wheelchair', 'rollator': 'Rollator', 'powerchair': 'Powerchair', } equipment_label = equipment_labels.get(self.equipment_type, 'Equipment') # Schedule activity for tomorrow due_date = fields.Date.today() + timedelta(days=1) try: sale_order.activity_schedule( activity_type_id=activity_type.id, date_deadline=due_date, user_id=sales_rep.id, summary=f'Follow up on {equipment_label} Assessment', note=f'Assessment {self.reference} for {self.client_name} has been completed. Please follow up with the client and authorizer.', ) _logger.info(f"Scheduled follow-up activity for {sales_rep.name} on SO {sale_order.name}") except Exception as e: _logger.error(f"Failed to schedule follow-up activity: {e}") def _send_assessment_completed_email(self, sale_order): """Send email notification to sales person, authorizer, and office about completed assessment. Includes the full assessment report so recipients can review without logging in.""" self.ensure_one() if not self._email_is_enabled(): return to_emails = [] cc_emails = [] if self.authorizer_id and self.authorizer_id.email: to_emails.append(self.authorizer_id.email) if self.sales_rep_id and self.sales_rep_id.email: cc_emails.append(self.sales_rep_id.email) company = self.env.company office_partners = company.sudo().x_fc_office_notification_ids cc_emails.extend([p.email for p in office_partners if p.email]) if not to_emails and not cc_emails: return sales_rep_name = self.sales_rep_id.name if self.sales_rep_id else 'The Sales Team' assessment_date = self.assessment_end_date.strftime('%B %d, %Y') if self.assessment_end_date else 'Today' equipment_labels = {'rollator': 'Rollator', 'wheelchair': 'Wheelchair', 'powerchair': 'Powerchair'} equipment_label = equipment_labels.get(self.equipment_type, 'Equipment') # Build the detailed assessment report sections for the email report_sections = self._build_assessment_email_sections(sale_order, equipment_label, assessment_date) email_body = self._email_build( title='Assessment Completed', summary=f'The ADP assessment for {self.client_name} has been completed ' f'on {assessment_date}. A draft sales order has been created. ' f'The full assessment report is included below.', email_type='success', sections=report_sections, note='Next steps: Please submit the ADP application ' '(including pages 11-12 signed by the client) so we can proceed with the claim submission.', button_url=f'{sale_order.get_base_url()}/web#id={sale_order.id}&model=sale.order&view_type=form', button_text='View Sale Order', sender_name=sales_rep_name, ) email_to = ', '.join(to_emails) if to_emails else ', '.join(cc_emails[:1]) email_cc = ', '.join(cc_emails) if to_emails else ', '.join(cc_emails[1:]) try: self.env['mail.mail'].sudo().create({ 'subject': f'Assessment Completed - {self.client_name} ({equipment_label}) - {sale_order.name}', 'body_html': email_body, 'email_to': email_to, 'email_cc': email_cc, 'model': 'sale.order', 'res_id': sale_order.id, 'auto_delete': True, }).send() chatter_body = Markup( '' ) sale_order.message_post(body=chatter_body, message_type='notification', subtype_xmlid='mail.mt_note') _logger.info(f"Sent assessment completed email for {self.reference}") except Exception as e: _logger.error(f"Failed to send assessment completed email: {e}") def _build_assessment_email_sections(self, sale_order, equipment_label, assessment_date): """Build comprehensive email sections with all assessment details. Returns a list of (heading, rows) tuples for _email_build().""" self.ensure_one() sections = [] # --- Section 1: Overview --- overview_rows = [ ('Reference', self.reference), ('Sales Order', sale_order.name), ('Client', self.client_name), ('Equipment', equipment_label), ('Assessment Date', assessment_date), ('Authorizer/OT', self.authorizer_id.name if self.authorizer_id else None), ('Sales Rep', self.sales_rep_id.name if self.sales_rep_id else None), ] if self.client_type: ct_labels = { 'reg': 'REG - Regular ADP', 'ods': 'ODS - ODSP', 'acs': 'ACS - ACSD', 'owp': 'OWP - Ontario Works', } overview_rows.append(('Client Type', ct_labels.get(self.client_type, self.client_type))) if self.reason_for_application: reason_labels = dict(self._fields['reason_for_application'].selection) overview_rows.append(('Reason for Application', reason_labels.get( self.reason_for_application, self.reason_for_application))) if self.previous_funding_date: overview_rows.append(('Previous Funding Date', str(self.previous_funding_date))) sections.append(('Assessment Overview', overview_rows)) # --- Section 2: Client Information --- client_rows = [ ('Name', self.client_name), ] if self.client_first_name or self.client_last_name: full = f"{self.client_first_name or ''} {self.client_middle_name or ''} {self.client_last_name or ''}".strip() client_rows.append(('Full Name', full)) if self.client_weight: client_rows.append(('Weight', f'{int(self.client_weight)} lbs')) if self.client_dob: client_rows.append(('Date of Birth', str(self.client_dob))) if self.client_health_card: version = f' ({self.client_health_card_version})' if self.client_health_card_version else '' client_rows.append(('Health Card', f'{self.client_health_card}{version}')) # Address addr_parts = [p for p in [ self.client_street, self.client_unit, self.client_city, self.client_state, self.client_postal_code, ] if p] if addr_parts: client_rows.append(('Address', ', '.join(addr_parts))) if self.client_phone: client_rows.append(('Phone', self.client_phone)) if self.client_mobile: client_rows.append(('Mobile', self.client_mobile)) if self.client_email: client_rows.append(('Email', self.client_email)) sections.append(('Client Information', client_rows)) # --- Section 3: Equipment Type & Subtype --- equip_rows = [('Equipment Type', equipment_label)] if self.equipment_type == 'rollator': if self.rollator_type: type_labels = dict(self._fields['rollator_type'].selection) equip_rows.append(('Rollator Type', type_labels.get(self.rollator_type, self.rollator_type))) elif self.equipment_type == 'wheelchair': if self.wheelchair_type: type_labels = dict(self._fields['wheelchair_type'].selection) equip_rows.append(('Wheelchair Type', type_labels.get(self.wheelchair_type, self.wheelchair_type))) elif self.equipment_type == 'powerchair': if self.powerchair_type: type_labels = dict(self._fields['powerchair_type'].selection) equip_rows.append(('Powerchair Type', type_labels.get(self.powerchair_type, self.powerchair_type))) sections.append(('Equipment Selection', equip_rows)) # --- Section 4: Measurements --- meas_rows = [] if self.equipment_type == 'rollator': if self.rollator_handle_height: meas_rows.append(('Handle Height', f'{self.rollator_handle_height}"')) if self.rollator_seat_height: meas_rows.append(('Seat Height', f'{self.rollator_seat_height}"')) else: # Wheelchair / Powerchair measurements if self.seat_width: meas_rows.append(('Seat Width', f'{self.seat_width}"')) if self.seat_depth: meas_rows.append(('Seat Depth', f'{self.seat_depth}"')) if self.legrest_length: meas_rows.append(('Legrest Length', f'{self.legrest_length}"')) if self.seat_to_floor_height: sfh_label = f'{self.seat_to_floor_height}" (Including Cushion)' if self.equipment_type == 'wheelchair' else f'{self.seat_to_floor_height}"' meas_rows.append(('Seat to Floor Height', sfh_label)) if self.cane_height: meas_rows.append(('Cane Height', f'{self.cane_height}" (Ground to Canes)')) if self.back_height: meas_rows.append(('Back Height', f'{self.back_height}"')) if self.armrest_height: meas_rows.append(('Armrest Height', f'{self.armrest_height}"')) if self.footrest_length: meas_rows.append(('Footrest Length', f'{self.footrest_length}"')) if meas_rows: sections.append(('Measurements', meas_rows)) # --- Section 5: Options & Addons --- options_rows = [] if self.equipment_type == 'rollator' and self.rollator_addons: for addon in self.rollator_addons.split(','): addon = addon.strip() if addon: options_rows.append((addon, 'Yes')) elif self.equipment_type == 'wheelchair': if self.frame_options: for opt in self.frame_options.split(','): opt = opt.strip() if opt: options_rows.append((f'Frame: {opt}', 'Yes')) if self.wheel_options: for opt in self.wheel_options.split(','): opt = opt.strip() if opt: options_rows.append((f'Wheel: {opt}', 'Yes')) if self.legrest_options: for opt in self.legrest_options.split(','): opt = opt.strip() if opt: options_rows.append((f'Legrest: {opt}', 'Yes')) if self.additional_adp_options: for opt in self.additional_adp_options.split(','): opt = opt.strip() if opt: options_rows.append((f'ADP Option: {opt}', 'Yes')) elif self.equipment_type == 'powerchair': if self.powerchair_options: for opt in self.powerchair_options.split(','): opt = opt.strip() if opt: options_rows.append((f'Powerchair: {opt}', 'Yes')) if self.specialty_controls: for opt in self.specialty_controls.split(','): opt = opt.strip() if opt: options_rows.append((f'Specialty: {opt}', 'Yes')) # Seatbelt (wheelchair & powerchair) if self.seatbelt_type and self.equipment_type in ('wheelchair', 'powerchair'): belt_labels = dict(self._fields['seatbelt_type'].selection) options_rows.append(('Seat Belt', belt_labels.get(self.seatbelt_type, self.seatbelt_type))) # Cushion & Backrest if self.cushion_info: options_rows.append(('Cushion', self.cushion_info)) if self.backrest_info: options_rows.append(('Backrest', self.backrest_info)) if options_rows: sections.append(('Options & Accessories', options_rows)) # --- Section 6: Additional Notes --- notes_rows = [] if self.additional_customization: notes_rows.append(('Customization', self.additional_customization)) if self.mobility_notes: notes_rows.append(('Mobility Needs', self.mobility_notes)) if self.accessibility_notes: notes_rows.append(('Accessibility', self.accessibility_notes)) if self.special_requirements: notes_rows.append(('Special Requirements', self.special_requirements)) if self.diagnosis: notes_rows.append(('Diagnosis/Condition', self.diagnosis)) if notes_rows: sections.append(('Additional Notes', notes_rows)) # --- Section 7: Key Dates --- date_rows = [] if self.assessment_start_date: date_rows.append(('Assessment Start', self.assessment_start_date.strftime('%B %d, %Y'))) if self.assessment_end_date: date_rows.append(('Assessment End', self.assessment_end_date.strftime('%B %d, %Y'))) if self.claim_authorization_date: date_rows.append(('Claim Authorization', self.claim_authorization_date.strftime('%B %d, %Y'))) if date_rows: sections.append(('Key Dates', date_rows)) # --- Section 8: Consent Information --- if self.consent_signed_by: consent_rows = [] consent_label = 'Applicant' if self.consent_signed_by == 'applicant' else 'Agent' consent_rows.append(('Signed By', consent_label)) if self.consent_date: consent_rows.append(('Consent Date', str(self.consent_date))) if self.consent_declaration_accepted: consent_rows.append(('Declaration Accepted', 'Yes')) if self.consent_signed_by == 'agent': agent_name = f"{self.agent_first_name or ''} {self.agent_last_name or ''}".strip() if agent_name: consent_rows.append(('Agent Name', agent_name)) if self.agent_relationship: rel_labels = dict(self._fields['agent_relationship'].selection) consent_rows.append(('Relationship', rel_labels.get( self.agent_relationship, self.agent_relationship))) sections.append(('Consent & Declaration (Page 11)', consent_rows)) return sections def _format_assessment_html_table(self): """Format assessment data as HTML table for chatter""" self.ensure_one() # Get equipment type display name equipment_labels = { 'rollator': 'Rollator', 'wheelchair': 'Wheelchair', 'powerchair': 'Powerchair', } equipment_display = equipment_labels.get(self.equipment_type, 'Unknown') # Start building HTML html = f'''

    ADP Assessment - {equipment_display}

    ''' row_num = 0 def add_row(label, value, is_header=False): nonlocal html, row_num if is_header: html += f''' ''' else: bg = '#f9f9f9' if row_num % 2 == 0 else '#ffffff' html += f''' ''' row_num += 1 # Client Type if self.client_type: client_type_labels = { 'reg': 'REG - Regular ADP', 'ods': 'ODS - ODSP', 'acs': 'ACS - ACSD', 'owp': 'OWP - Ontario Works', } add_row('Client Type', client_type_labels.get(self.client_type, self.client_type)) # Equipment Type add_row('Equipment Type', equipment_display) # Client Weight - shown prominently at the top if self.client_weight: add_row('Client Weight', f'{int(self.client_weight)} lbs') # Type-specific info if self.equipment_type == 'rollator': if self.rollator_type: type_labels = dict(self._fields['rollator_type'].selection) add_row('Rollator Type', type_labels.get(self.rollator_type, self.rollator_type)) add_row('Measurements', '', is_header=True) if self.rollator_handle_height: add_row('Handle Height', f'{self.rollator_handle_height}"') if self.rollator_seat_height: add_row('Seat Height', f'{self.rollator_seat_height}"') if self.rollator_addons: add_row('Addons', '', is_header=True) for addon in self.rollator_addons.split(','): addon = addon.strip() if addon: add_row(addon, 'Yes') elif self.equipment_type == 'wheelchair': if self.wheelchair_type: type_labels = dict(self._fields['wheelchair_type'].selection) add_row('Wheelchair Type', type_labels.get(self.wheelchair_type, self.wheelchair_type)) add_row('Measurements', '', is_header=True) if self.seat_width: add_row('Seat Width', f'{self.seat_width}"') if self.seat_depth: add_row('Seat Depth', f'{self.seat_depth}"') if self.legrest_length: add_row('Legrest Length', f'{self.legrest_length}"') if self.seat_to_floor_height: add_row('Seat to Floor Height', f'{self.seat_to_floor_height}" (Including Cushion)') if self.cane_height: add_row('Cane Height', f'{self.cane_height}" (Ground to Canes)') if self.back_height: add_row('Back Height', f'{self.back_height}"') if self.frame_options: add_row('Frame Options - ADP Funded', '', is_header=True) for opt in self.frame_options.split(','): opt = opt.strip() if opt: add_row(opt, 'Yes') if self.wheel_options: add_row('Wheel Options/Addons - ADP Funded', '', is_header=True) for opt in self.wheel_options.split(','): opt = opt.strip() if opt: add_row(opt, 'Yes') if self.legrest_options: add_row('Legrest Accessories - ADP Funded', '', is_header=True) for opt in self.legrest_options.split(','): opt = opt.strip() if opt: add_row(opt, 'Yes') if self.additional_adp_options: add_row('Additional ADP Funded Options', '', is_header=True) for opt in self.additional_adp_options.split(','): opt = opt.strip() if opt: add_row(opt, 'Yes') if self.seatbelt_type: belt_labels = dict(self._fields['seatbelt_type'].selection) add_row('Seat Belt Type', belt_labels.get(self.seatbelt_type, self.seatbelt_type)) elif self.equipment_type == 'powerchair': if self.powerchair_type: type_labels = dict(self._fields['powerchair_type'].selection) add_row('Powerchair Type', type_labels.get(self.powerchair_type, self.powerchair_type)) add_row('Measurements', '', is_header=True) if self.seat_width: add_row('Seat Width', f'{self.seat_width}"') if self.seat_depth: add_row('Seat Depth', f'{self.seat_depth}"') if self.legrest_length: add_row('Legrest Length', f'{self.legrest_length}"') if self.seat_to_floor_height: add_row('Seat to Floor Height', f'{self.seat_to_floor_height}"') if self.cane_height: add_row('Cane Height', f'{self.cane_height}" (Ground to Canes)') if self.back_height: add_row('Back Height', f'{self.back_height}"') if self.armrest_height: add_row('Armrest Height', f'{self.armrest_height}"') if self.footrest_length: add_row('Footrest Length', f'{self.footrest_length}"') if self.seatbelt_type: belt_labels = dict(self._fields['seatbelt_type'].selection) add_row('Seat Belt Type', belt_labels.get(self.seatbelt_type, self.seatbelt_type)) if self.powerchair_options: add_row('Powerchair - Additional ADP Funded Options', '', is_header=True) for opt in self.powerchair_options.split(','): opt = opt.strip() if opt: add_row(opt, 'Yes') if self.specialty_controls: add_row('Specialty Components (Rationale Required)', '', is_header=True) for opt in self.specialty_controls.split(','): opt = opt.strip() if opt: add_row(opt, 'Yes') # Cushion & Backrest (wheelchair/powerchair) if self.cushion_info: add_row('Cushion', self.cushion_info) if self.backrest_info: add_row('Backrest', self.backrest_info) # Additional customization notes if self.additional_customization: add_row('Additional Information/Customization', self.additional_customization) # Client info summary add_row('Client Information', '', is_header=True) add_row('Client Name', self.client_name or '') if self.client_weight: add_row('Client Weight', f'{int(self.client_weight)} lbs') if self.client_health_card: version = f' ({self.client_health_card_version})' if self.client_health_card_version else '' add_row('Health Card', f'{self.client_health_card}{version}') if self.client_phone: add_row('Phone', self.client_phone) if self.client_email: add_row('Email', self.client_email) # Dates add_row('Key Dates', '', is_header=True) if self.assessment_start_date: add_row('Assessment Start Date', str(self.assessment_start_date)) if self.assessment_end_date: add_row('Assessment End Date', str(self.assessment_end_date)) if self.claim_authorization_date: add_row('Claim Authorization Date', str(self.claim_authorization_date)) if self.reason_for_application: reason_labels = dict(self._fields['reason_for_application'].selection) add_row('Reason for Application', reason_labels.get(self.reason_for_application, self.reason_for_application)) html += '
    Field Value
    {label}
    {label} {value}
    ' return html def _format_specifications_for_order(self): """Format wheelchair specifications for the sale order notes (legacy)""" self.ensure_one() lines = [ f"=== Assessment Specifications ({self.reference}) ===", "", "MEASUREMENTS:", ] if self.seat_width: lines.append(f" Seat Width: {self.seat_width}\"") if self.seat_depth: lines.append(f" Seat Depth: {self.seat_depth}\"") if self.seat_to_floor_height: lines.append(f" Seat to Floor: {self.seat_to_floor_height}\"") if self.back_height: lines.append(f" Back Height: {self.back_height}\"") if self.armrest_height: lines.append(f" Armrest Height: {self.armrest_height}\"") if self.footrest_length: lines.append(f" Footrest Length: {self.footrest_length}\"") lines.append("") lines.append("PRODUCT SELECTIONS:") if self.cushion_type: lines.append(f" Cushion: {dict(self._fields['cushion_type'].selection).get(self.cushion_type, self.cushion_type)}") if self.backrest_type: lines.append(f" Backrest: {dict(self._fields['backrest_type'].selection).get(self.backrest_type, self.backrest_type)}") if self.frame_type: lines.append(f" Frame: {dict(self._fields['frame_type'].selection).get(self.frame_type, self.frame_type)}") if self.wheel_type: lines.append(f" Wheels: {dict(self._fields['wheel_type'].selection).get(self.wheel_type, self.wheel_type)}") if self.mobility_notes: lines.append("") lines.append("MOBILITY NEEDS:") lines.append(f" {self.mobility_notes}") if self.accessibility_notes: lines.append("") lines.append("ACCESSIBILITY NOTES:") lines.append(f" {self.accessibility_notes}") if self.special_requirements: lines.append("") lines.append("SPECIAL REQUIREMENTS:") lines.append(f" {self.special_requirements}") return "\n".join(lines) def _generate_signed_documents(self): """Generate document records for signed pages""" self.ensure_one() ADPDocument = self.env['fusion.adp.document'].sudo() # Create Page 11 document if signature exists if self.signature_page_11: ADPDocument.create({ 'assessment_id': self.id, 'sale_order_id': self.sale_order_id.id if self.sale_order_id else False, 'document_type': 'page_11', 'file': self.signature_page_11, 'filename': f'page_11_signature_{self.reference}.png', 'mimetype': 'image/png', 'source': 'assessment', }) # Create Page 12 document if signature exists if self.signature_page_12: ADPDocument.create({ 'assessment_id': self.id, 'sale_order_id': self.sale_order_id.id if self.sale_order_id else False, 'document_type': 'page_12', 'file': self.signature_page_12, 'filename': f'page_12_signature_{self.reference}.png', 'mimetype': 'image/png', 'source': 'assessment', }) def _send_completion_notifications(self): """Send email notifications when assessment is completed""" self.ensure_one() # Send to authorizer if self.authorizer_id and self.authorizer_id.email: try: template = self.env.ref('fusion_authorizer_portal.mail_template_assessment_complete_authorizer', raise_if_not_found=False) if template: template.send_mail(self.id, force_send=True) _logger.info(f"Sent assessment completion email to authorizer {self.authorizer_id.email}") except Exception as e: _logger.error(f"Failed to send authorizer notification: {e}") # Send to client if self.client_email: try: template = self.env.ref('fusion_authorizer_portal.mail_template_assessment_complete_client', raise_if_not_found=False) if template: template.send_mail(self.id, force_send=True) _logger.info(f"Sent assessment completion email to client {self.client_email}") except Exception as e: _logger.error(f"Failed to send client notification: {e}") def action_view_documents(self): """View related documents""" self.ensure_one() return { 'type': 'ir.actions.act_window', 'name': _('Documents'), 'res_model': 'fusion.adp.document', 'view_mode': 'list,form', 'views': [(False, 'list'), (False, 'form')], 'domain': [('assessment_id', '=', self.id)], 'context': {'default_assessment_id': self.id}, } def action_view_sale_order(self): """View the created sale order""" self.ensure_one() if not self.sale_order_id: raise UserError(_('No sale order has been created yet.')) return { 'type': 'ir.actions.act_window', 'name': _('Sale Order'), 'res_model': 'sale.order', 'res_id': self.sale_order_id.id, 'view_mode': 'form', 'views': [(False, 'form')], 'target': 'current', } # ===== PDF TEMPLATE ENGINE INTEGRATION ===== def _get_pdf_context(self): """Return a flat dict of all assessment data for PDF template filling. This is the data source for the generic PDF template engine. Each key can be mapped to a field on any PDF template via field_key. """ self.ensure_one() ctx = { # Client biographical 'client_last_name': self.client_last_name or '', 'client_first_name': self.client_first_name or '', 'client_middle_name': self.client_middle_name or '', 'client_name': self.client_name or '', 'client_health_card': self.client_health_card or '', 'client_health_card_version': self.client_health_card_version or '', 'client_street': self.client_street or '', 'client_unit': self.client_unit or '', 'client_city': self.client_city or '', 'client_state': self.client_state or '', 'client_postal_code': self.client_postal_code or '', 'client_phone': self.client_phone or '', 'client_email': self.client_email or '', 'client_weight': str(int(self.client_weight)) if self.client_weight else '', # Client type 'client_type': self.client_type or '', 'client_type_reg': self.client_type == 'reg', 'client_type_ods': self.client_type == 'ods', 'client_type_acs': self.client_type == 'acs', 'client_type_owp': self.client_type == 'owp', # Consent & Declaration (Page 11) 'consent_signed_by': self.consent_signed_by or '', 'consent_applicant': self.consent_signed_by == 'applicant', 'consent_agent': self.consent_signed_by == 'agent', 'consent_declaration_accepted': self.consent_declaration_accepted, 'consent_date': str(self.consent_date) if self.consent_date else '', # Agent info 'agent_relationship': self.agent_relationship or '', 'agent_rel_spouse': self.agent_relationship == 'spouse', 'agent_rel_parent': self.agent_relationship == 'parent', 'agent_rel_child': self.agent_relationship == 'child', 'agent_rel_poa': self.agent_relationship == 'power_of_attorney', 'agent_rel_guardian': self.agent_relationship == 'public_guardian', 'agent_first_name': self.agent_first_name or '', 'agent_last_name': self.agent_last_name or '', 'agent_middle_initial': self.agent_middle_initial or '', 'agent_unit': self.agent_unit or '', 'agent_street_number': self.agent_street_number or '', 'agent_street_name': self.agent_street_name or '', 'agent_city': self.agent_city or '', 'agent_province': self.agent_province or '', 'agent_postal_code': self.agent_postal_code or '', 'agent_home_phone': self.agent_home_phone or '', 'agent_business_phone': self.agent_business_phone or '', 'agent_phone_ext': self.agent_phone_ext or '', # Equipment 'equipment_type': self.equipment_type or '', 'seat_width': str(self.seat_width) if self.seat_width else '', 'seat_depth': str(self.seat_depth) if self.seat_depth else '', 'seat_to_floor_height': str(self.seat_to_floor_height) if self.seat_to_floor_height else '', 'back_height': str(self.back_height) if self.back_height else '', 'legrest_length': str(self.legrest_length) if self.legrest_length else '', 'cane_height': str(self.cane_height) if self.cane_height else '', # Dates 'assessment_start_date': str(self.assessment_start_date) if self.assessment_start_date else '', 'assessment_end_date': str(self.assessment_end_date) if self.assessment_end_date else '', 'claim_authorization_date': str(self.claim_authorization_date) if self.claim_authorization_date else '', # Reason 'reason_for_application': self.reason_for_application or '', # Reference 'reference': self.reference or '', } # Authorizer info if self.authorizer_id: ctx['authorizer_name'] = self.authorizer_id.name or '' ctx['authorizer_phone'] = self.authorizer_id.phone or '' ctx['authorizer_email'] = self.authorizer_id.email or '' return ctx def _get_pdf_signatures(self): """Return a dict of signature binaries for PDF template filling.""" self.ensure_one() sigs = {} if self.signature_page_11: sigs['signature_page_11'] = base64.b64decode(self.signature_page_11) if self.signature_page_12: sigs['signature_page_12'] = base64.b64decode(self.signature_page_12) return sigs def generate_template_pdf(self, template_name='adp_page_11'): """Generate a filled PDF using the named template. Args: template_name: the name field of the fusion.pdf.template record Returns: bytes of the filled PDF, or None if template not found """ self.ensure_one() template = self.env['fusion.pdf.template'].search([ ('state', '=', 'active'), ('name', 'ilike', template_name), ], limit=1) if not template: _logger.warning("No active PDF template found matching '%s'", template_name) return None context_data = self._get_pdf_context() signatures = self._get_pdf_signatures() try: pdf_bytes = template.generate_filled_pdf(context_data, signatures) _logger.info("Generated filled PDF from template '%s' for assessment %s", template.name, self.reference) return pdf_bytes except Exception as e: _logger.error("Failed to generate PDF for assessment %s: %s", self.reference, e) return None