feat(fusion_portal): ADP/express->visit wiring, visit entry tile, email consolidation (live on westin 19.0.2.10.0)

- express save captures visit_id; visit-linked submit defers SO creation
  (saves draft + signature) and returns to the visit for grouping.
- portal dashboard 'Start a Visit' tile for sales reps.
- fix duplicate-authorizer completion email; visit grouped SOs email once per SO.
- define visit._assessment_sale_type (ADP grouping key) - fixes AttributeError.

Verified on a westin-v19 clone (load + ADP-grouping + combination-guard smoke
test, mail neutralised) then deployed to westin prod 19.0.2.10.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-06-02 08:50:25 -04:00
parent 7fcf38ca82
commit 9a8e1d7ab5
6 changed files with 99 additions and 17 deletions

View File

@@ -458,6 +458,7 @@ class AssessmentPortal(CustomerPortal):
'current_page': 1,
'total_pages': 2,
'assessment': None,
'visit_id': kw.get('visit_id', ''),
'google_maps_api_key': google_maps_api_key,
}
@@ -516,6 +517,7 @@ class AssessmentPortal(CustomerPortal):
'partner': partner,
'user': user,
'assessment': assessment,
'visit_id': kw.get('visit_id') or (assessment.visit_id.id if assessment.visit_id else ''),
'authorizers': authorizers,
'authorizers_json': authorizers_json,
'clients': clients,
@@ -630,6 +632,30 @@ class AssessmentPortal(CustomerPortal):
except Exception as e:
_logger.error(f"Error saving Page 11 signature: {e}")
# ===== Visit-linked: defer SO creation to visit completion =====
# Started from a visit workspace: do NOT complete into a standalone
# sale order. Leave it as a draft linked to the visit so
# visit.action_complete_visit() groups the visit's ADP devices
# (combination-checked) into ONE ADP order. The Page 11 signature is
# already saved above; pre-generate its PDF so it is ready.
if assessment.visit_id and action == 'submit':
if assessment.signature_page_11 and assessment.consent_declaration_accepted:
try:
pdf_bytes = assessment.generate_template_pdf('Page 11')
if pdf_bytes:
import base64 as b64
assessment.write({
'signed_page_11_pdf': b64.b64encode(pdf_bytes),
'signed_page_11_pdf_filename': f'ADP_Page11_{assessment.reference}.pdf',
})
except Exception as pdf_e:
_logger.warning(f"Visit-linked Page 11 PDF generation failed (non-blocking): {pdf_e}")
_logger.info(
f"Express assessment {assessment.reference} saved to visit "
f"{assessment.visit_id.name} (completion deferred to visit)"
)
return request.redirect(f'/my/visit/{assessment.visit_id.id}')
# Handle navigation
if action == 'submit':
# If already completed, we just saved consent/signature above -- redirect with success
@@ -803,6 +829,13 @@ class AssessmentPortal(CustomerPortal):
def _build_express_assessment_vals(self, kw):
"""Build values dict from express form POST data"""
vals = {}
# Visit linkage (assessment started from a visit workspace)
if kw.get('visit_id'):
try:
vals['visit_id'] = int(kw.get('visit_id'))
except (ValueError, TypeError):
pass
# Equipment type
if kw.get('equipment_type'):