Files
Odoo-Modules/fusion_quotations/views/portal_quotation_templates.xml
gsinghpal f81e0cd918 changes
2026-03-09 23:45:00 -04:00

1052 lines
63 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ============================================================ -->
<!-- ASSESSMENT LIST PAGE -->
<!-- ============================================================ -->
<template id="portal_quotation_list" name="Equipment Assessments List">
<t t-call="portal.portal_layout">
<t t-set="breadcrumbs_searchbar" t-value="True"/>
<div class="o_portal_my_home">
<div class="d-flex justify-content-between align-items-center mb-3">
<h3>Equipment Assessments</h3>
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button"
data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa fa-plus me-1"/> New Assessment
</button>
<ul class="dropdown-menu dropdown-menu-end">
<t t-foreach="equipment_types" t-as="etype">
<li>
<a class="dropdown-item"
t-attf-href="/my/quotation/builder/new?equipment_type=#{etype.code}">
<i t-attf-class="fa #{etype.icon or 'fa-cog'} me-2"/>
<t t-out="etype.name"/>
</a>
</li>
</t>
</ul>
</div>
</div>
<t t-if="not assessments">
<div class="alert alert-info text-center">
No assessments yet. Click "New Assessment" to start.
</div>
</t>
<t t-if="assessments">
<table class="table table-hover">
<thead>
<tr>
<th>Reference</th>
<th>Client</th>
<th>Equipment</th>
<th>Date</th>
<th>Status</th>
<th>Total</th>
<th/>
</tr>
</thead>
<tbody>
<t t-foreach="assessments" t-as="a">
<tr>
<td><t t-out="a.reference"/></td>
<td><t t-out="a.client_name or 'N/A'"/></td>
<td><t t-out="equip_type_map.get(a.equipment_type, a.equipment_type or '')"/></td>
<td><t t-out="a.assessment_date" t-options="{'widget': 'date'}"/></td>
<td>
<span t-attf-class="badge #{
'text-bg-info' if a.state == 'draft' else
'text-bg-warning' if a.state == 'review' else
'text-bg-success' if a.state == 'quoted' else
'text-bg-secondary'
}">
<t t-out="dict(a._fields['state'].selection).get(a.state, '')"/>
</span>
</td>
<td>$<t t-out="'%.2f' % a.total_estimate"/></td>
<td>
<a t-attf-href="/my/quotation/builder/#{a.id}/edit"
class="btn btn-sm btn-outline-primary">
<t t-if="a.state == 'draft'">Continue</t>
<t t-else="">View</t>
</a>
</td>
</tr>
</t>
</tbody>
</table>
</t>
</div>
</t>
</template>
<!-- ============================================================ -->
<!-- MULTI-STEP ASSESSMENT FORM (Dynamic) -->
<!-- ============================================================ -->
<template id="portal_quotation_form" name="Equipment Assessment Form">
<t t-call="portal.portal_layout">
<t t-set="additional_title">Equipment Assessment</t>
<div class="wc-assessment-form" id="wcAssessmentForm">
<!-- Success/Error Messages -->
<t t-if="request.params.get('success') == 'saved'">
<div class="alert alert-success alert-dismissible fade show">
Assessment saved successfully.
<button type="button" class="btn-close" data-bs-dismiss="alert"/>
</div>
</t>
<t t-if="request.params.get('success') == 'quotation_generated'">
<div class="alert alert-success alert-dismissible fade show">
<i class="fa fa-check-circle me-1"/> Quotation generated successfully!
<button type="button" class="btn-close" data-bs-dismiss="alert"/>
</div>
</t>
<t t-if="request.params.get('error')">
<div class="alert alert-danger alert-dismissible fade show">
<t t-out="request.params.get('error')"/>
<button type="button" class="btn-close" data-bs-dismiss="alert"/>
</div>
</t>
<!-- ===== DYNAMIC Step Indicator ===== -->
<div class="wc-steps mb-4">
<div class="d-flex justify-content-between">
<t t-foreach="flow_steps" t-as="fstep">
<div t-attf-class="wc-step-indicator text-center flex-fill #{'active' if fstep_index == 0 else ''}"
t-att-data-step="fstep_index + 1">
<div class="wc-step-number rounded-circle d-inline-flex align-items-center justify-content-center">
<t t-out="fstep_index + 1"/>
</div>
<div class="wc-step-label small mt-1">
<t t-out="fstep.name"/>
</div>
</div>
</t>
</div>
</div>
<!-- Open in Backend link — only for internal users -->
<t t-if="assessment and request.env.user.has_group('base.group_user')">
<div class="text-end mb-2">
<a t-attf-href="/odoo/fusion-quotation-builder/#{assessment.id}"
class="btn btn-sm btn-outline-secondary" target="_blank">
<i class="fa fa-pencil me-1"/> Open in Backend
</a>
</div>
</t>
<form method="post"
t-att-action="'/quotation/form/' + access_token + '/save'
if is_public else '/my/quotation/builder/save'"
id="wcForm" class="wc-form">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<input type="hidden" name="assessment_id"
t-att-value="assessment.id if assessment else 0"/>
<input type="hidden" name="current_step" id="currentStep" value="1"/>
<input type="hidden" name="action" id="formAction" value="save"/>
<!-- Store access token for JS to detect public mode -->
<t t-if="is_public">
<input type="hidden" name="access_token" id="accessToken"
t-att-value="access_token"/>
</t>
<!-- Hidden fields for line data (populated by JS) -->
<input type="hidden" name="line_product_ids" id="lineProductIds" value=""/>
<input type="hidden" name="line_section_ids" id="lineSectionIds" value=""/>
<input type="hidden" name="line_build_types" id="lineBuildTypes" value=""/>
<input type="hidden" name="line_quantities" id="lineQuantities" value=""/>
<input type="hidden" name="line_rationales" id="lineRationales" value=""/>
<!-- ===== DYNAMIC Step Content ===== -->
<t t-foreach="flow_steps" t-as="fstep">
<div t-attf-class="wc-step #{'d-none' if fstep_index > 0 else ''}"
t-att-data-step="fstep_index + 1"
t-att-data-step-type="fstep.step_type">
<t t-if="fstep.step_type == 'client_info'"
t-call="fusion_quotations.fq_step_client"/>
<t t-if="fstep.step_type == 'measurements'"
t-call="fusion_quotations.fq_step_measurements"/>
<t t-if="fstep.step_type == 'product_select'"
t-call="fusion_quotations.fq_step_product_select"/>
<t t-if="fstep.step_type == 'options'"
t-call="fusion_quotations.fq_step_options"/>
<t t-if="fstep.step_type == 'review'"
t-call="fusion_quotations.fq_step_review"/>
<t t-if="fstep.step_type == 'custom'"
t-call="fusion_quotations.fq_step_custom"/>
</div>
</t>
<!-- ============================================ -->
<!-- NAVIGATION BUTTONS -->
<!-- ============================================ -->
<div class="d-flex justify-content-between mt-4 pt-3 border-top">
<button type="button" class="btn btn-outline-secondary" id="btnPrev"
style="display:none;">
<i class="fa fa-arrow-left me-1"/> Previous
</button>
<div class="ms-auto d-flex gap-2">
<button type="submit" class="btn btn-outline-primary" id="btnSave"
name="action" value="save">
<i class="fa fa-save me-1"/> Save Draft
</button>
<button type="button" class="btn btn-primary" id="btnNext">
Next <i class="fa fa-arrow-right ms-1"/>
</button>
<button type="submit" class="btn btn-success d-none" id="btnGenerate"
name="action" value="generate">
<i class="fa fa-magic me-1"/> Generate Quotation
</button>
</div>
</div>
</form>
</div>
</t>
</template>
<!-- ============================================================ -->
<!-- SUB-TEMPLATE: Client &amp; Equipment (step_type=client_info) -->
<!-- ============================================================ -->
<template id="fq_step_client" name="Step: Client &amp; Equipment">
<!--
Config-driven client step. The step's fields_json controls which
optional field groups are shown. Example fields_json:
{
"show_health_card": true,
"show_dob": true,
"show_adp_fields": true,
"show_wheelchair_category": true,
"show_powerchair_category": false
}
Steps with no fields_json (or empty {}) show only the base fields:
name, contact, address, and equipment type selector.
-->
<t t-set="client_config" t-value="step_fields.get(fstep.id, {})"/>
<h4 class="mb-3">Client &amp; Equipment</h4>
<div class="row g-3">
<!-- ── LEFT COLUMN: Client Info ── -->
<div class="col-md-6">
<div class="card wc-section-card h-100">
<div class="card-header"><h5 class="mb-0"><i class="fa fa-user me-2"/>Client</h5></div>
<div class="card-body">
<div class="mb-3 wc-search-container" style="position:relative;z-index:100;">
<label class="form-label fw-bold">Search Existing Client</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-search"/></span>
<input type="text" class="form-control" id="clientSearch"
placeholder="Type name, phone, or health card..."
autocomplete="off"/>
</div>
<div id="clientSearchResults" class="list-group d-none"
style="position:absolute;z-index:1060;width:100%;max-height:300px;overflow-y:auto;background:var(--bs-body-bg);box-shadow:0 8px 30px rgba(0,0,0,.18);border:1px solid var(--bs-border-color);border-top:none;border-radius:0 0 .5rem .5rem;"/>
<input type="hidden" name="partner_id" id="partnerId"
t-att-value="assessment.partner_id.id if assessment and assessment.partner_id else 0"/>
</div>
<div id="selectedClient" class="alert alert-info d-none mb-3">
<strong id="selectedClientName"/>
<button type="button" class="btn btn-sm btn-outline-secondary ms-2"
id="clearClient">Change</button>
</div>
<div id="newClientFields">
<div class="row g-2 mb-2">
<div class="col">
<input type="text" name="client_first_name" class="form-control"
placeholder="First Name"
t-att-value="assessment.client_first_name if assessment else ''"/>
</div>
<div class="col">
<input type="text" name="client_last_name" class="form-control"
placeholder="Last Name"
t-att-value="assessment.client_last_name if assessment else ''"/>
</div>
</div>
<div class="row g-2 mb-2">
<div class="col">
<input type="tel" name="client_phone" class="form-control"
placeholder="Phone"
t-att-value="assessment.client_phone if assessment else ''"/>
</div>
<div class="col">
<input type="email" name="client_email" class="form-control"
placeholder="Email"
t-att-value="assessment.client_email if assessment else ''"/>
</div>
</div>
<input type="text" name="client_street" class="form-control mb-2"
placeholder="Street Address"
t-att-value="assessment.client_street if assessment else ''"/>
<div class="row g-2 mb-2">
<div class="col">
<input type="text" name="client_city" class="form-control"
placeholder="City"
t-att-value="assessment.client_city if assessment else ''"/>
</div>
<div class="col">
<select name="client_state_id" class="form-select">
<option value="">Province</option>
<t t-foreach="provinces" t-as="prov">
<option t-att-value="prov.id"
t-att-selected="assessment and assessment.client_state_id.id == prov.id">
<t t-out="prov.name"/>
</option>
</t>
</select>
</div>
<div class="col-3">
<input type="text" name="client_zip" class="form-control"
placeholder="Postal Code"
t-att-value="assessment.client_zip if assessment else ''"/>
</div>
</div>
</div>
<!-- Health Card — controlled by step config -->
<t t-if="client_config.get('show_health_card') or client_config.get('show_dob')">
<div class="row g-2 mb-2">
<t t-if="client_config.get('show_health_card')">
<div class="col">
<label class="form-label">Health Card</label>
<input type="text" name="client_health_card" class="form-control"
t-att-value="assessment.client_health_card if assessment else ''"/>
</div>
</t>
<t t-if="client_config.get('show_dob')">
<div class="col">
<label class="form-label">Date of Birth</label>
<input type="date" name="client_dob" class="form-control"
t-att-value="assessment.client_dob if assessment else ''"/>
</div>
</t>
</div>
</t>
</div><!-- /card-body -->
</div><!-- /card -->
</div>
<!-- ── RIGHT COLUMN: Equipment Config ── -->
<div class="col-md-6">
<div class="card wc-section-card h-100">
<div class="card-header"><h5 class="mb-0"><i class="fa fa-cogs me-2"/>Equipment</h5></div>
<div class="card-body">
<!-- Equipment type — show current type as label + hidden field -->
<div class="mb-3">
<label class="form-label fw-bold">Equipment Type</label>
<div class="d-flex align-items-center gap-2">
<t t-foreach="equipment_types" t-as="etype">
<t t-if="etype.code == equipment_type">
<span class="badge bg-primary fs-6 py-2 px-3">
<i t-attf-class="fa #{etype.icon or 'fa-cog'} me-1"/> <t t-out="etype.name"/>
</span>
</t>
</t>
<input type="hidden" name="equipment_type" t-att-value="equipment_type"/>
</div>
</div>
<!-- Wheelchair category — controlled by step config -->
<t t-if="client_config.get('show_wheelchair_category')">
<div class="mb-3" id="wheelchairTypeGroup">
<label class="form-label">Wheelchair Category</label>
<select name="wheelchair_type" class="form-select">
<option value="">Select...</option>
<option value="type_1" t-att-selected="assessment and assessment.wheelchair_type == 'type_1'">Type 1 - Standard</option>
<option value="type_2" t-att-selected="assessment and assessment.wheelchair_type == 'type_2'">Type 2 - Lightweight</option>
<option value="type_3" t-att-selected="assessment and assessment.wheelchair_type == 'type_3'">Type 3 - Ultra Lightweight</option>
<option value="type_4" t-att-selected="assessment and assessment.wheelchair_type == 'type_4'">Type 4 - Rigid Frame</option>
<option value="type_5" t-att-selected="assessment and assessment.wheelchair_type == 'type_5'">Type 5 - Dynamic Tilt</option>
</select>
</div>
</t>
<!-- Powerchair category — controlled by step config -->
<t t-if="client_config.get('show_powerchair_category')">
<div class="mb-3" id="powerchairTypeGroup">
<label class="form-label">Powerchair Category</label>
<select name="powerchair_type" class="form-select">
<option value="">Select...</option>
<option value="type_1" t-att-selected="assessment and assessment.powerchair_type == 'type_1'">Power Base Type 1</option>
<option value="type_2" t-att-selected="assessment and assessment.powerchair_type == 'type_2'">Power Base Type 2</option>
<option value="type_3" t-att-selected="assessment and assessment.powerchair_type == 'type_3'">Power Base Type 3</option>
</select>
</div>
</t>
<!-- ADP fields (client type + reason) — controlled by step config -->
<t t-if="client_config.get('show_adp_fields')">
<div class="mb-3">
<label class="form-label">Client Type</label>
<select name="client_type" class="form-select">
<option value="reg" t-att-selected="not assessment or assessment.client_type == 'reg'">REG - Regular ADP (75/25)</option>
<option value="ods" t-att-selected="assessment and assessment.client_type == 'ods'">ODS - ODSP (100% ADP)</option>
<option value="acs" t-att-selected="assessment and assessment.client_type == 'acs'">ACS - ACSD (100% ADP)</option>
<option value="owp" t-att-selected="assessment and assessment.client_type == 'owp'">OWP - Ontario Works (100% ADP)</option>
</select>
</div>
<input type="hidden" name="build_type" value="modular"/>
<div class="mb-3">
<label class="form-label">Reason for Application</label>
<select name="reason_for_application" class="form-select">
<option value="">Select...</option>
<option value="first_access" t-att-selected="assessment and assessment.reason_for_application == 'first_access'">First Access</option>
<option value="additions" t-att-selected="assessment and assessment.reason_for_application == 'additions'">Additions</option>
<option value="modifications" t-att-selected="assessment and assessment.reason_for_application == 'modifications'">Modifications</option>
<option value="replacements" t-att-selected="assessment and assessment.reason_for_application == 'replacements'">Replacements</option>
</select>
</div>
</t>
</div><!-- /card-body -->
</div><!-- /card -->
</div>
</div>
</template>
<!-- ============================================================ -->
<!-- SUB-TEMPLATE: Measurements (step_type=measurements) -->
<!-- ============================================================ -->
<template id="fq_step_measurements" name="Step: Measurements">
<h4 class="mb-3"><t t-out="fstep.name"/></h4>
<t t-if="fstep.help_text">
<p class="text-muted"><t t-out="fstep.help_text"/></p>
</t>
<t t-if="not fstep.help_text">
<p class="text-muted">All measurements are required for the ADP application.</p>
</t>
<!-- Dynamic fields from fields_json -->
<t t-set="mfields" t-value="step_fields.get(fstep.id, [])"/>
<t t-if="mfields">
<div class="row g-3">
<t t-foreach="mfields" t-as="mf">
<div class="col-md-6">
<div class="wc-measurement-field p-3 border rounded">
<label class="form-label fw-bold" t-out="mf.get('label', '')"/>
<t t-if="mf.get('type') == 'selection'">
<select t-att-name="mf.get('name')" class="form-select"
t-att-required="mf.get('required')">
<option value="">Select...</option>
<t t-foreach="mf.get('options', [])" t-as="opt">
<option t-att-value="opt[0]"
t-att-selected="field_values.get(mf.get('name')) == str(opt[0])"
t-out="opt[1]"/>
</t>
</select>
</t>
<t t-if="mf.get('type') != 'selection'">
<div class="d-flex gap-2 align-items-center">
<input t-att-type="'number' if mf.get('type') in ('float', 'integer') else 'text'"
t-att-step="mf.get('step', '0.25') if mf.get('type') == 'float' else ('1' if mf.get('type') == 'integer' else '')"
t-att-name="mf.get('name')"
class="form-control wc-measurement-input"
t-att-data-upcharge-field="mf.get('name')"
t-att-value="field_values.get(mf.get('name'), '')"
t-att-required="mf.get('required')"/>
<t t-if="mf.get('units')">
<select t-att-name="mf.get('unit_field', mf.get('name') + '_unit')"
class="form-select wc-unit-select" style="width:100px;">
<t t-foreach="mf.get('units', [])" t-as="unit">
<option t-att-value="unit"
t-att-selected="field_values.get(mf.get('unit_field', mf.get('name') + '_unit')) == unit"
t-out="unit"/>
</t>
</select>
</t>
<t t-if="mf.get('unit') and not mf.get('units')">
<span class="input-group-text" t-out="mf.get('unit')"/>
</t>
<span class="wc-upcharge-badge d-none"/>
</div>
</t>
</div>
</div>
</t>
</div>
</t>
<!-- Fallback: hardcoded wheelchair measurement fields -->
<t t-if="not mfields">
<div class="row g-3">
<t t-foreach="[
('seat_width', 'Seat Width', 'seat_width_unit', 'inches'),
('seat_depth', 'Seat Depth', 'seat_depth_unit', 'inches'),
('finished_seat_to_floor_height', 'Finished Seat to Floor Height', 'seat_to_floor_unit', 'inches'),
('back_cane_height', 'Back Cane Height', 'cane_height_unit', 'inches'),
('finished_back_height', 'Finished Back Height', 'back_height_unit', 'inches'),
('finished_leg_rest_length', 'Finished Leg Rest Length', 'leg_rest_unit', 'inches'),
]" t-as="mfield">
<div class="col-md-6">
<div class="wc-measurement-field p-3 border rounded">
<label class="form-label fw-bold" t-out="mfield[1]"/>
<div class="d-flex gap-2 align-items-center">
<input type="number" step="0.25" t-att-name="mfield[0]"
class="form-control wc-measurement-input"
t-att-data-upcharge-field="mfield[0]"
t-att-value="field_values.get(mfield[0], '')"/>
<select t-att-name="mfield[2]" class="form-select wc-unit-select" style="width:100px;">
<option value="cm"
t-att-selected="field_values.get(mfield[2]) == 'cm'">cm</option>
<option value="inches"
t-att-selected="not field_values.get(mfield[2]) or field_values.get(mfield[2]) == 'inches'">inches</option>
</select>
<span class="wc-upcharge-badge d-none"/>
</div>
</div>
</div>
</t>
<!-- Client Weight -->
<div class="col-md-6">
<div class="wc-measurement-field p-3 border rounded">
<label class="form-label fw-bold">Client Weight</label>
<div class="d-flex gap-2 align-items-center">
<input type="number" step="1" name="client_weight"
class="form-control wc-measurement-input"
data-upcharge-field="client_weight"
t-att-value="assessment and assessment.client_weight or ''"/>
<select name="client_weight_unit" class="form-select wc-unit-select" style="width:100px;">
<option value="kg"
t-att-selected="assessment and assessment.client_weight_unit == 'kg'">kg</option>
<option value="lbs"
t-att-selected="not assessment or assessment.client_weight_unit == 'lbs'">lbs</option>
</select>
<span class="wc-upcharge-badge d-none"/>
</div>
</div>
</div>
</div>
</t>
<!-- Upcharges Preview -->
<div id="upchargePreview" class="mt-3 d-none">
<h5>Auto-Applied Upcharges</h5>
<div id="upchargeList"/>
</div>
</template>
<!-- ============================================================ -->
<!-- SUB-TEMPLATE: Product Selection (step_type=product_select) -->
<!-- ============================================================ -->
<template id="fq_step_product_select" name="Step: Product Selection">
<h4 class="mb-3"><t t-out="fstep.name"/></h4>
<t t-if="fstep.help_text">
<p class="text-muted"><t t-out="fstep.help_text"/></p>
</t>
<div class="mb-3 wc-search-container" style="position:relative;z-index:100;">
<label class="form-label fw-bold">Search Product</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-search"/></span>
<input type="text" class="form-control wc-product-search"
id="frameSearch" t-att-data-section="fstep.section_code or 'frame'"
placeholder="Type product name, model, or ADP code..."
autocomplete="off"/>
</div>
<div id="frameSearchResults" class="list-group d-none"
style="position:absolute;z-index:1060;width:100%;max-height:300px;overflow-y:auto;background:var(--bs-body-bg);box-shadow:0 8px 30px rgba(0,0,0,.18);border:1px solid var(--bs-border-color);border-top:none;border-radius:0 0 .5rem .5rem;"/>
<input type="hidden" name="frame_product_tmpl_id" id="frameProductTmplId"
t-att-value="assessment.frame_product_tmpl_id.id if assessment and assessment.frame_product_tmpl_id else ''"/>
<input type="hidden" name="frame_product_id" id="frameProductId"
t-att-value="assessment.frame_product_id.id if assessment and assessment.frame_product_id else ''"/>
</div>
<div id="selectedFrame" class="d-none" style="position:relative;z-index:1;">
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div>
<h5 class="card-title mb-1" id="selectedFrameName"/>
<div class="d-flex gap-4 text-muted small">
<span>ADP Code: <strong id="selectedFrameCode"/></span>
<span>ADP Price: $<strong id="selectedFramePrice"/></span>
</div>
</div>
<button type="button" class="btn btn-sm btn-outline-secondary"
id="clearFrame"><i class="fa fa-times me-1"/>Change</button>
</div>
<!-- Frame Configurator (shown when template has multiple variants) -->
<div id="frameConfigurator" class="wc-configurator-panel d-none">
<div class="wc-config-title">
<i class="fa fa-sliders me-2"/>Configure Options
</div>
<div id="frameAttributeSelectors" class="row g-2"/>
<div id="frameVariantInfo" class="d-none">
<span class="wc-variant-resolved">
<i class="fa fa-check-circle"/>
<span>Resolved: <strong id="frameVariantName"/></span>
</span>
</div>
</div>
</div>
</div>
</div>
<!-- Standard options for this section -->
<div id="standardFrameOptions" class="mt-3" style="position:relative;z-index:1;">
<h5>Standard Options</h5>
<div id="frameOptionsGrid" class="row g-2"/>
</div>
<div class="mt-3" style="position:relative;z-index:1;">
<label class="form-label">Notes</label>
<textarea name="frame_notes" class="form-control" rows="2"
t-out="assessment.frame_notes if assessment else ''"/>
</div>
</template>
<!-- ============================================================ -->
<!-- SUB-TEMPLATE: Options &amp; Accessories (step_type=options) -->
<!-- ============================================================ -->
<template id="fq_step_options" name="Step: Options">
<h4 class="mb-3"><t t-out="fstep.name"/></h4>
<t t-if="fstep.help_text">
<p class="text-muted"><t t-out="fstep.help_text"/></p>
</t>
<!-- Seating/Positioning sections with side tabs -->
<t t-if="fstep.section_code == 'seating'">
<p class="text-muted mb-3">Select seating and positioning devices. Choose Modular or Custom Fabricated for each.</p>
<div class="wc-seating-tabs" id="seatingTabsContainer">
<div class="row g-0">
<!-- ── TAB NAV (left on desktop, top on mobile) ── -->
<div class="col-md-3 wc-tab-nav-col">
<div class="nav flex-md-column wc-tab-nav" id="seatingTabNav" role="tablist"
aria-orientation="vertical">
<t t-set="first_visible" t-value="True"/>
<t t-foreach="sections" t-as="section">
<t t-if="section.has_build_type and section.code != 'accessories'">
<button type="button" role="tab"
t-attf-class="wc-tab-btn wc-equipment-section #{'active' if first_visible and (section.equipment_type in ('both', equipment_type) or (section.equipment_type == 'wheelchair' and equipment_type in ('manual_wheelchair', 'power_wheelchair'))) else ''}"
t-att-data-equipment-type="section.equipment_type"
t-attf-id="tab_#{section.code}"
t-attf-data-bs-target="#panel_#{section.code}"
t-att-aria-controls="'panel_' + section.code"
t-att-data-section-code="section.code"
t-att-style="'' if (section.equipment_type in ('both', equipment_type) or (section.equipment_type == 'wheelchair' and equipment_type in ('manual_wheelchair', 'power_wheelchair'))) else 'display:none;'">
<i t-attf-class="fa #{section.icon or 'fa-cube'} wc-tab-icon"/>
<span class="wc-tab-label"><t t-out="section.name"/></span>
<span class="wc-tab-badge" title="Selected items">0</span>
</button>
<t t-if="first_visible and (section.equipment_type in ('both', equipment_type) or (section.equipment_type == 'wheelchair' and equipment_type in ('manual_wheelchair', 'power_wheelchair')))">
<t t-set="first_visible" t-value="False"/>
</t>
</t>
</t>
</div>
</div>
<!-- ── TAB CONTENT (right on desktop, below on mobile) ── -->
<div class="col-md-9 wc-tab-content-col">
<div class="wc-tab-content" id="seatingTabContent">
<t t-set="first_visible" t-value="True"/>
<t t-foreach="sections" t-as="section">
<t t-if="section.has_build_type and section.code != 'accessories'">
<div role="tabpanel"
t-attf-class="wc-tab-panel wc-equipment-section #{'active' if first_visible and (section.equipment_type in ('both', equipment_type) or (section.equipment_type == 'wheelchair' and equipment_type in ('manual_wheelchair', 'power_wheelchair'))) else ''}"
t-attf-id="panel_#{section.code}"
t-att-data-equipment-type="section.equipment_type"
t-att-data-section-code="section.code"
t-attf-aria-labelledby="tab_#{section.code}">
<!-- Build Type + Measurements row (always visible) -->
<div class="d-flex flex-wrap align-items-end gap-3 mb-3">
<div>
<label class="form-label fw-bold mb-1" style="font-size:0.78rem;">Build Type</label>
<div class="d-flex gap-2">
<label class="btn btn-sm btn-outline-secondary wc-radio-btn">
<input type="radio" t-attf-name="section_build_#{section.code}"
value="modular" checked="checked"
class="wc-section-build-type"
t-att-data-section="section.code"/>
Modular
</label>
<label class="btn btn-sm btn-outline-secondary wc-radio-btn">
<input type="radio" t-attf-name="section_build_#{section.code}"
value="custom_fabricated"
class="wc-section-build-type"
t-att-data-section="section.code"/>
Custom Fabricated
</label>
</div>
</div>
<t t-if="section.has_width">
<div style="min-width:100px;">
<label class="form-label mb-1" style="font-size:0.78rem;" t-out="section.width_label"/>
<input type="number" step="0.25" class="form-control form-control-sm"
t-attf-name="section_width_#{section.code}"/>
</div>
</t>
<t t-if="section.has_depth">
<div style="min-width:100px;">
<label class="form-label mb-1" style="font-size:0.78rem;" t-out="section.depth_label"/>
<input type="number" step="0.25" class="form-control form-control-sm"
t-attf-name="section_depth_#{section.code}"/>
</div>
</t>
<t t-if="section.has_height">
<div style="min-width:100px;">
<label class="form-label mb-1" style="font-size:0.78rem;" t-out="section.height_label"/>
<input type="number" step="0.25" class="form-control form-control-sm"
t-attf-name="section_height_#{section.code}"/>
</div>
</t>
</div>
<!-- ── Collapsible section groups (accordion within tab) ── -->
<div class="wc-section-groups">
<!-- Group: Main section products -->
<div class="wc-section-group open">
<button type="button" class="wc-group-header">
<i t-attf-class="fa #{section.icon or 'fa-cube'} wc-group-icon"/>
<span class="wc-group-title"><t t-out="section.name"/></span>
<span class="wc-group-count">0</span>
<i class="fa fa-chevron-down wc-group-chevron"/>
</button>
<div class="wc-group-body">
<div class="wc-section-options" t-att-data-section="section.code"
t-att-data-section-id="section.id">
<div class="wc-option-filter-wrap mb-2">
<div class="input-group input-group-sm">
<span class="input-group-text"><i class="fa fa-filter"/></span>
<input type="text" class="form-control wc-option-filter"
placeholder="Filter products..."
autocomplete="off"/>
</div>
</div>
<div class="wc-options-grid row g-2"/>
<div class="mt-2 wc-search-container">
<input type="text" class="form-control form-control-sm wc-product-search"
t-att-data-section="section.code"
placeholder="Search catalogue for more..."
autocomplete="off"/>
<div class="wc-search-results list-group d-none"/>
</div>
</div>
</div>
</div>
<!-- Group: Sub-sections (collapsed by default) -->
<t t-foreach="section.child_ids" t-as="child">
<div class="wc-section-group">
<button type="button" class="wc-group-header">
<i class="fa fa-cube wc-group-icon"/>
<span class="wc-group-title"><t t-out="child.name"/></span>
<span class="wc-group-count">0</span>
<i class="fa fa-chevron-down wc-group-chevron"/>
</button>
<div class="wc-group-body">
<div class="wc-section-options" t-att-data-section="child.code"
t-att-data-section-id="child.id">
<div class="wc-option-filter-wrap mb-2">
<div class="input-group input-group-sm">
<span class="input-group-text"><i class="fa fa-filter"/></span>
<input type="text" class="form-control wc-option-filter"
placeholder="Filter products..."
autocomplete="off"/>
</div>
</div>
<div class="wc-options-grid row g-2"/>
<div class="mt-1 wc-search-container">
<input type="text" class="form-control form-control-sm wc-product-search"
t-att-data-section="child.code"
placeholder="Search catalogue for more..."
autocomplete="off"/>
<div class="wc-search-results list-group d-none"/>
</div>
</div>
</div>
</div>
</t>
</div>
</div>
<t t-if="first_visible and (section.equipment_type in ('both', equipment_type) or (section.equipment_type == 'wheelchair' and equipment_type in ('manual_wheelchair', 'power_wheelchair')))">
<t t-set="first_visible" t-value="False"/>
</t>
</t>
</t>
</div>
</div>
</div>
</div>
</t>
<!-- ADP Options &amp; Accessories (flat checkbox grids) -->
<t t-if="fstep.section_code != 'seating'">
<p class="text-muted">
Select applicable options. Items marked with * require clinical rationale.
</p>
<div id="adpOptionsGrid" class="row g-2">
<!-- Populated by JS from section options -->
</div>
<h4 class="mt-4 mb-3">Accessories</h4>
<div id="accessoriesGrid" class="row g-2">
<!-- Populated by JS -->
</div>
<div class="mt-2 wc-search-container">
<input type="text" class="form-control wc-product-search"
data-section="accessories"
placeholder="Search for accessories..."
autocomplete="off"/>
<div class="wc-search-results list-group d-none"/>
</div>
</t>
</template>
<!-- ============================================================ -->
<!-- SUB-TEMPLATE: Review &amp; Generate (step_type=review) -->
<!-- ============================================================ -->
<template id="fq_step_review" name="Step: Review &amp; Generate">
<h4 class="mb-3">Review &amp; Generate Quotation</h4>
<!-- Selected Items Summary -->
<div id="reviewSummary">
<table class="table table-sm">
<thead>
<tr>
<th>Section</th>
<th>Product</th>
<th>ADP Code</th>
<th>Build</th>
<th class="text-end">Qty</th>
<th class="text-end">Price</th>
</tr>
</thead>
<tbody id="reviewTableBody"/>
<tfoot>
<tr class="fw-bold">
<td colspan="5" class="text-end">Estimated Total:</td>
<td class="text-end" id="reviewTotal">$0.00</td>
</tr>
</tfoot>
</table>
</div>
<!-- Upcharges Summary -->
<div id="reviewUpcharges" class="d-none">
<h5>Auto-Applied Upcharges</h5>
<div id="reviewUpchargeList"/>
</div>
<!-- Measurements Summary -->
<div class="card mt-3">
<div class="card-header">
<h5 class="mb-0">Measurements Summary</h5>
</div>
<div class="card-body" id="reviewMeasurements"/>
</div>
<div class="mt-3">
<label class="form-label">Additional Notes</label>
<textarea name="notes" class="form-control" rows="3"
t-out="assessment.notes if assessment else ''"/>
</div>
</template>
<!-- ============================================================ -->
<!-- SUB-TEMPLATE: Custom Fields (step_type=custom) -->
<!-- ============================================================ -->
<template id="fq_step_custom" name="Step: Custom Fields">
<h4 class="mb-3"><t t-out="fstep.name"/></h4>
<t t-if="fstep.help_text">
<p class="text-muted"><t t-out="fstep.help_text"/></p>
</t>
<t t-set="cfields" t-value="step_fields.get(fstep.id, [])"/>
<t t-if="cfields">
<div class="row g-3">
<t t-foreach="cfields" t-as="cf">
<div class="col-md-6">
<div class="p-3 border rounded">
<label class="form-label fw-bold" t-out="cf.get('label', '')"/>
<t t-if="cf.get('type') == 'selection'">
<select t-att-name="cf.get('name')" class="form-select"
t-att-required="cf.get('required')">
<option value="">Select...</option>
<t t-foreach="cf.get('options', [])" t-as="opt">
<option t-att-value="opt[0]"
t-att-selected="field_values.get(cf.get('name')) == str(opt[0])"
t-out="opt[1]"/>
</t>
</select>
</t>
<t t-if="cf.get('type') == 'text'">
<textarea t-att-name="cf.get('name')" class="form-control" rows="3"
t-att-required="cf.get('required')"
t-out="field_values.get(cf.get('name'), '')"/>
</t>
<t t-if="cf.get('type') == 'boolean'">
<div class="form-check">
<input type="checkbox" class="form-check-input"
t-att-name="cf.get('name')"
t-att-checked="field_values.get(cf.get('name'))"/>
<label class="form-check-label" t-out="cf.get('label', '')"/>
</div>
</t>
<t t-if="cf.get('type') in ('float', 'integer')">
<div class="input-group">
<input t-att-type="'number'"
t-att-step="'0.01' if cf.get('type') == 'float' else '1'"
t-att-name="cf.get('name')"
class="form-control"
t-att-value="field_values.get(cf.get('name'), '')"
t-att-required="cf.get('required')"/>
<t t-if="cf.get('unit')">
<span class="input-group-text" t-out="cf.get('unit')"/>
</t>
</div>
</t>
<t t-if="cf.get('type') not in ('selection', 'text', 'boolean', 'float', 'integer')">
<input type="text" t-att-name="cf.get('name')" class="form-control"
t-att-value="field_values.get(cf.get('name'), '')"
t-att-required="cf.get('required')"/>
</t>
</div>
</div>
</t>
</div>
</t>
<t t-if="not cfields">
<div class="alert alert-info">
No custom fields configured for this step.
</div>
</t>
</template>
<!-- ============================================================ -->
<!-- EMBEDDABLE FORM (no portal chrome — for iframe embedding) -->
<!-- ============================================================ -->
<template id="portal_quotation_form_embed" name="Equipment Assessment Form (Embed)">
<t t-call="web.frontend_layout">
<t t-set="no_header" t-value="True"/>
<t t-set="no_footer" t-value="True"/>
<div class="container-fluid py-3 wc-assessment-form" id="wcAssessmentForm">
<!-- Success/Error Messages -->
<t t-if="request.params.get('success') == 'saved'">
<div class="alert alert-success alert-dismissible fade show">
Assessment saved successfully.
<button type="button" class="btn-close" data-bs-dismiss="alert"/>
</div>
</t>
<t t-if="request.params.get('success') == 'quotation_generated'">
<div class="alert alert-success alert-dismissible fade show">
<i class="fa fa-check-circle me-1"/> Quotation generated!
<button type="button" class="btn-close" data-bs-dismiss="alert"/>
</div>
</t>
<t t-if="request.params.get('error')">
<div class="alert alert-danger alert-dismissible fade show">
<t t-out="request.params.get('error')"/>
<button type="button" class="btn-close" data-bs-dismiss="alert"/>
</div>
</t>
<!-- Dynamic Step Indicator -->
<div class="wc-steps mb-4">
<div class="d-flex justify-content-between">
<t t-foreach="flow_steps" t-as="fstep">
<div t-attf-class="wc-step-indicator text-center flex-fill #{'active' if fstep_index == 0 else ''}"
t-att-data-step="fstep_index + 1">
<div class="wc-step-number rounded-circle d-inline-flex align-items-center justify-content-center">
<t t-out="fstep_index + 1"/>
</div>
<div class="wc-step-label small mt-1">
<t t-out="fstep.name"/>
</div>
</div>
</t>
</div>
</div>
<form method="post"
t-att-action="'/quotation/form/' + access_token + '/save'"
id="wcForm" class="wc-form">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<input type="hidden" name="assessment_id"
t-att-value="assessment.id if assessment else 0"/>
<input type="hidden" name="current_step" id="currentStep" value="1"/>
<input type="hidden" name="action" id="formAction" value="save"/>
<input type="hidden" name="access_token" id="accessToken"
t-att-value="access_token"/>
<!-- Hidden fields for line data -->
<input type="hidden" name="line_product_ids" id="lineProductIds" value=""/>
<input type="hidden" name="line_section_ids" id="lineSectionIds" value=""/>
<input type="hidden" name="line_build_types" id="lineBuildTypes" value=""/>
<input type="hidden" name="line_quantities" id="lineQuantities" value=""/>
<input type="hidden" name="line_rationales" id="lineRationales" value=""/>
<p class="text-muted text-center mb-3">
<i class="fa fa-cogs me-1"/>
Equipment Assessment Form
</p>
<!-- NOTE: The embed template reuses the same step structure.
For a full implementation, you would include the same step
content from the main template. For now we render a minimal
placeholder that the JS widget will populate. -->
<div class="text-center py-5 text-muted">
<i class="fa fa-spinner fa-spin fa-2x mb-3 d-block"/>
Loading assessment form...
</div>
<!-- Navigation Buttons -->
<div class="wc-form-navigation mt-4 border-top pt-3">
<div class="d-flex justify-content-between">
<button type="submit" class="btn btn-outline-secondary" id="btnSaveDraft">
<i class="fa fa-save me-1"/> Save Draft
</button>
<div>
<button type="button" class="btn btn-secondary d-none" id="btnPrev">
<i class="fa fa-arrow-left me-1"/> Previous
</button>
<button type="button" class="btn btn-primary" id="btnNext">
Next <i class="fa fa-arrow-right ms-1"/>
</button>
<button type="submit" class="btn btn-success d-none" id="btnGenerate"
name="action" value="generate">
<i class="fa fa-magic me-1"/> Generate Quotation
</button>
</div>
</div>
</div>
</form>
</div>
</t>
</template>
<!-- ============================================================ -->
<!-- PUBLIC FORM — INVALID TOKEN PAGE -->
<!-- ============================================================ -->
<template id="public_form_invalid" name="Invalid Assessment Link">
<t t-call="web.frontend_layout">
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-6 text-center">
<i class="fa fa-chain-broken fa-4x text-muted mb-3 d-block"/>
<h3 class="mb-3">This link is no longer valid</h3>
<t t-if="status == 'not_found'">
<p class="text-muted">
The assessment form you're trying to access doesn't exist
or the link has expired.
</p>
</t>
<t t-if="status == 'cancelled'">
<p class="text-muted">
This assessment has been cancelled and is no longer
available for editing.
</p>
</t>
<a href="/" class="btn btn-primary mt-3">
<i class="fa fa-home me-1"/> Go Home
</a>
</div>
</div>
</div>
</t>
</template>
</odoo>