Files
Odoo-Modules/fusion_claims/models/client_profile.py
2026-02-22 01:22:18 -05:00

299 lines
13 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2024-2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
from odoo import api, fields, models
class FusionClientProfile(models.Model):
_name = 'fusion.client.profile'
_description = 'Client Profile'
_order = 'last_name, first_name'
_rec_name = 'display_name'
_inherit = ['mail.thread', 'mail.activity.mixin']
# ------------------------------------------------------------------
# PERSONAL INFORMATION (from ADP XML Section 1)
# ------------------------------------------------------------------
partner_id = fields.Many2one(
'res.partner', string='Odoo Contact',
help='Linked contact record in Odoo',
)
first_name = fields.Char(string='First Name', tracking=True)
last_name = fields.Char(string='Last Name', tracking=True)
middle_initial = fields.Char(string='Middle Initial')
display_name = fields.Char(
string='Name', compute='_compute_display_name', store=True,
)
health_card_number = fields.Char(
string='Health Card Number', index=True, tracking=True,
help='Ontario Health Card Number (10 digits)',
)
health_card_version = fields.Char(string='Health Card Version')
date_of_birth = fields.Date(string='Date of Birth', tracking=True)
ltch_name = fields.Char(
string='Long-Term Care Home',
help='Name of LTCH if applicable',
)
# ------------------------------------------------------------------
# ADDRESS
# ------------------------------------------------------------------
unit_number = fields.Char(string='Unit Number')
street_number = fields.Char(string='Street Number')
street_name = fields.Char(string='Street Name')
rural_route = fields.Char(string='Lot/Concession/Rural Route')
city = fields.Char(string='City', index=True, tracking=True)
province = fields.Char(string='Province', default='ON')
postal_code = fields.Char(string='Postal Code')
# ------------------------------------------------------------------
# CONTACT
# ------------------------------------------------------------------
home_phone = fields.Char(string='Home Phone')
business_phone = fields.Char(string='Business Phone')
phone_extension = fields.Char(string='Phone Extension')
# ------------------------------------------------------------------
# BENEFITS ELIGIBILITY (from XML confirmationOfBenefit)
# ------------------------------------------------------------------
receives_social_assistance = fields.Boolean(
string='Receives Social Assistance', tracking=True,
)
benefit_type = fields.Selection([
('owp', 'Ontario Works Program (OWP)'),
('odsp', 'Ontario Disability Support Program (ODSP)'),
('acsd', 'Assistance to Children with Severe Disabilities (ACSD)'),
], string='Benefit Type', tracking=True)
wsib_eligible = fields.Boolean(string='WSIB Eligible', tracking=True)
vac_eligible = fields.Boolean(
string='Veterans Affairs Canada Eligible', tracking=True,
)
# ------------------------------------------------------------------
# CURRENT MEDICAL STATUS (updated from latest XML)
# ------------------------------------------------------------------
medical_condition = fields.Text(
string='Medical Condition/Diagnosis', tracking=True,
help='Current presenting medical condition from latest ADP application',
)
mobility_status = fields.Text(
string='Functional Mobility Status', tracking=True,
help='Current functional mobility status from latest ADP application',
)
last_assessment_date = fields.Date(
string='Last Assessment Date', tracking=True,
)
# ------------------------------------------------------------------
# RELATIONSHIPS
# ------------------------------------------------------------------
application_data_ids = fields.One2many(
'fusion.adp.application.data', 'profile_id',
string='ADP Applications',
)
# Chat is handled via Odoo's native AI agent (discuss.channel with ai_chat type)
# ------------------------------------------------------------------
# COMPUTED FIELDS
# ------------------------------------------------------------------
claim_count = fields.Integer(
string='Claims', compute='_compute_claim_stats', store=True,
)
total_adp_funded = fields.Monetary(
string='Total ADP Funded', compute='_compute_claim_stats', store=True,
currency_field='currency_id',
)
total_client_portion = fields.Monetary(
string='Total Client Portion', compute='_compute_claim_stats', store=True,
currency_field='currency_id',
)
total_amount = fields.Monetary(
string='Total Amount', compute='_compute_claim_stats', store=True,
currency_field='currency_id',
)
currency_id = fields.Many2one(
'res.currency', string='Currency',
default=lambda self: self.env.company.currency_id,
)
application_count = fields.Integer(
string='Applications', compute='_compute_application_count',
)
# ------------------------------------------------------------------
# AI ANALYSIS (auto-computed from application data)
# ------------------------------------------------------------------
ai_summary = fields.Text(
string='Summary',
compute='_compute_ai_analysis',
)
ai_risk_flags = fields.Text(
string='Risk Flags',
compute='_compute_ai_analysis',
)
ai_last_analyzed = fields.Datetime(string='Last AI Analysis')
# ------------------------------------------------------------------
# COMPUTED METHODS
# ------------------------------------------------------------------
@api.depends('first_name', 'last_name')
def _compute_display_name(self):
for profile in self:
parts = [profile.last_name or '', profile.first_name or '']
profile.display_name = ', '.join(p for p in parts if p) or 'New Profile'
@api.depends('partner_id', 'partner_id.sale_order_ids',
'partner_id.sale_order_ids.x_fc_adp_portion_total',
'partner_id.sale_order_ids.x_fc_client_portion_total',
'partner_id.sale_order_ids.amount_total')
def _compute_claim_stats(self):
for profile in self:
if profile.partner_id:
orders = self.env['sale.order'].search([
('partner_id', '=', profile.partner_id.id),
('x_fc_sale_type', '!=', False),
])
profile.claim_count = len(orders)
profile.total_adp_funded = sum(orders.mapped('x_fc_adp_portion_total'))
profile.total_client_portion = sum(orders.mapped('x_fc_client_portion_total'))
profile.total_amount = sum(orders.mapped('amount_total'))
else:
profile.claim_count = 0
profile.total_adp_funded = 0
profile.total_client_portion = 0
profile.total_amount = 0
def _compute_application_count(self):
for profile in self:
profile.application_count = len(profile.application_data_ids)
@api.depends('application_data_ids', 'application_data_ids.application_date',
'application_data_ids.base_device', 'application_data_ids.reason_for_application')
def _compute_ai_analysis(self):
for profile in self:
apps = profile.application_data_ids.sorted('application_date', reverse=True)
# --- SUMMARY ---
summary_lines = []
# Number of applications
app_count = len(apps)
summary_lines.append(f"Total Applications: {app_count}")
# Last funding history
if apps:
latest = apps[0]
date_str = latest.application_date.strftime('%B %d, %Y') if latest.application_date else 'Unknown date'
device = latest.base_device or 'Not specified'
category = dict(latest._fields['device_category'].selection).get(
latest.device_category, latest.device_category or 'N/A'
) if latest.device_category else 'N/A'
summary_lines.append(f"Last Application: {date_str}")
summary_lines.append(f"Last Device: {device} ({category})")
# Reason for last application
reason = latest.reason_for_application or 'Not specified'
summary_lines.append(f"Reason: {reason}")
# Authorizer
auth_name = f"{latest.authorizer_first_name or ''} {latest.authorizer_last_name or ''}".strip()
if auth_name:
summary_lines.append(f"Authorizer: {auth_name}")
# All devices received (unique)
devices = set()
for a in apps:
if a.base_device:
devices.add(a.base_device)
if len(devices) > 1:
summary_lines.append(f"All Devices: {', '.join(sorted(devices))}")
else:
summary_lines.append("No applications on file.")
profile.ai_summary = '\n'.join(summary_lines)
# --- RISK FLAGS ---
risk_lines = []
if app_count >= 2:
# Calculate frequency
dated_apps = [a for a in apps if a.application_date]
if len(dated_apps) >= 2:
dates = sorted([a.application_date for a in dated_apps])
total_span = (dates[-1] - dates[0]).days
if total_span > 0:
avg_days = total_span / (len(dates) - 1)
if avg_days < 365:
risk_lines.append(
f"High Frequency: {app_count} applications over "
f"{total_span} days (avg {avg_days:.0f} days apart)"
)
elif avg_days < 730:
risk_lines.append(
f"Moderate Frequency: {app_count} applications over "
f"{total_span // 365} year(s) (avg {avg_days:.0f} days apart)"
)
else:
risk_lines.append(
f"Normal Frequency: {app_count} applications over "
f"{total_span // 365} year(s) (avg {avg_days:.0f} days apart)"
)
# Check for multiple replacements
replacements = [a for a in apps if a.reason_for_application and 'replacement' in a.reason_for_application.lower()]
if len(replacements) >= 2:
risk_lines.append(f"Multiple Replacements: {len(replacements)} replacement applications")
if not risk_lines:
risk_lines.append("No flags identified.")
profile.ai_risk_flags = '\n'.join(risk_lines)
# ------------------------------------------------------------------
# ACTIONS
# ------------------------------------------------------------------
def action_view_claims(self):
"""Open sale orders for this client."""
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': f'Claims - {self.display_name}',
'res_model': 'sale.order',
'view_mode': 'list,form',
'domain': [('partner_id', '=', self.partner_id.id), ('x_fc_sale_type', '!=', False)],
}
def action_view_applications(self):
"""Open parsed ADP application data for this client."""
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': f'Applications - {self.display_name}',
'res_model': 'fusion.adp.application.data',
'view_mode': 'list,form',
'domain': [('profile_id', '=', self.id)],
}
def action_open_ai_chat(self):
"""Open AI chat about this client using Odoo's native AI agent."""
self.ensure_one()
agent = self.env.ref('fusion_claims.ai_agent_fusion_claims', raise_if_not_found=False)
if agent:
# Create channel with client context so the AI knows which client
channel = agent._create_ai_chat_channel()
# Post an initial context message about this client
initial_prompt = (
f"I want to ask about client {self.display_name} "
f"(Profile ID: {self.id}, Health Card: {self.health_card_number or 'N/A'}). "
f"Please look up their details."
)
return {
'type': 'ir.actions.client',
'tag': 'agent_chat_action',
'params': {
'channelId': channel.id,
'user_prompt': initial_prompt,
},
}
return {'type': 'ir.actions.act_window_close'}