Files
Odoo-Modules/fusion_portal/controllers/portal_page11_sign.py
gsinghpal 747c814249 refactor(fusion_portal): rename from fusion_authorizer_portal + modern photo cards on accessibility selector
Rename module fusion_authorizer_portal -> fusion_portal everywhere:
manifest/assets, controllers, models, views, JS (odoo.define + asset URLs),
migration MODULE constants; plus cross-module refs in fusion_schedule,
fusion_repairs, fusion_quotations (depends + inherit_id) and the pdf_filler
import in fusion_claims. Add rename_module.sql for the one-time in-place DB
rename (ir_module_module, ir_model_data, ir_ui_view.key,
ir_module_module_dependency) required on installed envs before -u fusion_portal.
Document the rename gotcha as rule 16 in CLAUDE.md.

Redesign the Accessibility Assessment selector: replace Font Awesome icon tiles
with photo-banner cards using 7 optimized images (1000x750 PNG -> 800x600 JPEG,
~8MB -> 488KB), per-type colour accent bar + centered pill button, hover
lift/zoom. Images ship as module static files so they deploy/sync with the module.

Drop the regenerable graphify-out cache from the module.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 22:38:14 -04:00

207 lines
7.9 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2024-2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
import base64
import json
import logging
from odoo import http, fields, _
from odoo.http import request
_logger = logging.getLogger(__name__)
class Page11PublicSignController(http.Controller):
def _get_sign_request(self, token):
"""Look up and validate a signing request by token."""
req = request.env['fusion.page11.sign.request'].sudo().search([
('access_token', '=', token),
], limit=1)
if not req:
return None, 'not_found'
if req.state == 'signed':
return req, 'already_signed'
if req.state == 'cancelled':
return req, 'cancelled'
if req.state == 'expired' or (
req.expiry_date and req.expiry_date < fields.Datetime.now()
):
if req.state != 'expired':
req.state = 'expired'
return req, 'expired'
return req, 'ok'
@http.route('/page11/sign/<string:token>', type='http', auth='public',
website=True, sitemap=False)
def page11_sign_form(self, token, **kw):
"""Display the Page 11 signing form."""
sign_req, status = self._get_sign_request(token)
if status == 'not_found':
return request.render(
'fusion_portal.portal_page11_sign_invalid', {}
)
if status in ('expired', 'cancelled'):
return request.render(
'fusion_portal.portal_page11_sign_expired',
{'sign_request': sign_req},
)
if status == 'already_signed':
return request.render(
'fusion_portal.portal_page11_sign_success',
{'sign_request': sign_req, 'token': token},
)
order = sign_req.sale_order_id
partner = order.partner_id
assessment = request.env['fusion.assessment'].sudo().search([
('sale_order_id', '=', order.id),
], limit=1, order='create_date desc')
ICP = request.env['ir.config_parameter'].sudo()
google_maps_api_key = ICP.get_param('fusion_claims.google_maps_api_key', '')
client_first_name = ''
client_last_name = ''
client_middle_name = ''
client_health_card = ''
client_health_card_version = ''
if assessment:
client_first_name = assessment.client_first_name or ''
client_last_name = assessment.client_last_name or ''
client_middle_name = assessment.client_middle_name or ''
client_health_card = assessment.client_health_card or ''
client_health_card_version = assessment.client_health_card_version or ''
else:
first, last = order._get_client_name_parts()
client_first_name = first
client_last_name = last
values = {
'sign_request': sign_req,
'order': order,
'partner': partner,
'assessment': assessment,
'company': order.company_id,
'token': token,
'signer_type': sign_req.signer_type,
'is_agent': sign_req.signer_type != 'client',
'google_maps_api_key': google_maps_api_key,
'client_first_name': client_first_name,
'client_last_name': client_last_name,
'client_middle_name': client_middle_name,
'client_health_card': client_health_card,
'client_health_card_version': client_health_card_version,
}
return request.render(
'fusion_portal.portal_page11_public_sign', values,
)
@http.route('/page11/sign/<string:token>/submit', type='http',
auth='public', methods=['POST'], website=True,
csrf=True, sitemap=False)
def page11_sign_submit(self, token, **post):
"""Process the submitted Page 11 signature."""
sign_req, status = self._get_sign_request(token)
if status != 'ok':
return request.redirect(f'/page11/sign/{token}')
signature_data = post.get('signature_data', '')
if not signature_data:
return request.redirect(f'/page11/sign/{token}?error=no_signature')
if signature_data.startswith('data:image'):
signature_data = signature_data.split(',', 1)[1]
consent_accepted = post.get('consent_declaration', '') == 'on'
if not consent_accepted:
return request.redirect(f'/page11/sign/{token}?error=no_consent')
signer_name = post.get('signer_name', sign_req.signer_name or '')
chosen_signer_type = post.get('signer_type', sign_req.signer_type or 'client')
consent_signed_by = 'applicant' if chosen_signer_type == 'client' else 'agent'
signer_type_labels = {
'spouse': 'Spouse', 'parent': 'Parent',
'legal_guardian': 'Legal Guardian',
'poa': 'Power of Attorney',
'public_trustee': 'Public Trustee',
}
vals = {
'signature_data': signature_data,
'signer_name': signer_name,
'signer_type': chosen_signer_type,
'consent_declaration_accepted': True,
'consent_signed_by': consent_signed_by,
'signed_date': fields.Datetime.now(),
'state': 'signed',
'client_first_name': post.get('client_first_name', ''),
'client_last_name': post.get('client_last_name', ''),
'client_health_card': post.get('client_health_card', ''),
'client_health_card_version': post.get('client_health_card_version', ''),
}
if consent_signed_by == 'agent':
vals.update({
'agent_first_name': post.get('agent_first_name', ''),
'agent_last_name': post.get('agent_last_name', ''),
'agent_middle_initial': post.get('agent_middle_initial', ''),
'agent_phone': post.get('agent_phone', ''),
'agent_unit': post.get('agent_unit', ''),
'agent_street_number': post.get('agent_street_number', ''),
'agent_street': post.get('agent_street', ''),
'agent_city': post.get('agent_city', ''),
'agent_province': post.get('agent_province', 'Ontario'),
'agent_postal_code': post.get('agent_postal_code', ''),
'signer_relationship': signer_type_labels.get(chosen_signer_type, chosen_signer_type),
})
sign_req.sudo().write(vals)
try:
sign_req.sudo()._generate_signed_pdf()
except Exception as e:
_logger.error("PDF generation failed for sign request %s: %s", sign_req.id, e)
try:
sign_req.sudo()._update_sale_order()
except Exception as e:
_logger.error("Sale order update failed for sign request %s: %s", sign_req.id, e)
return request.render(
'fusion_portal.portal_page11_sign_success',
{'sign_request': sign_req, 'token': token},
)
@http.route('/page11/sign/<string:token>/download', type='http',
auth='public', website=True, sitemap=False)
def page11_download_pdf(self, token, **kw):
"""Download the signed Page 11 PDF."""
sign_req = request.env['fusion.page11.sign.request'].sudo().search([
('access_token', '=', token),
('state', '=', 'signed'),
], limit=1)
if not sign_req or not sign_req.signed_pdf:
return request.redirect(f'/page11/sign/{token}')
pdf_content = base64.b64decode(sign_req.signed_pdf)
filename = sign_req.signed_pdf_filename or 'Page11_Signed.pdf'
return request.make_response(
pdf_content,
headers=[
('Content-Type', 'application/pdf'),
('Content-Disposition', f'attachment; filename="{filename}"'),
('Content-Length', str(len(pdf_content))),
],
)