feat(portal): fix configurator 500, hide manual measurements, upgrade job card
1. Configurator step 2/3 500 fix: fp.coating.config was retired
(Sub-11) but the controller still queried it -> KeyError. Swapped
to fusion.plating.process.type (the real coating taxonomy on entech:
Hard Chrome, EN Low Phos, Type I Anodize, etc). Step 2 template
dropped dead refs (coat.process_type_id / spec_reference / thickness_*
/ certification_level), now shows code + process_family + description.
Pricing helper relaxed: filters out rules keyed to the dead model
and silently returns {'available': False} -> template shows 'Quote
will be priced by EN Plating' instead of fake numbers.
2. Configurator step 1: manual measurements hidden per customer
feedback. Length/Width/Height/Surface Area are kept as hidden 0s so
the rest of the flow doesn't error; backend trimesh still auto-calcs
surface area silently when STL is uploaded. Single file input split
into two: separate Drawing (PDF) + 3D Model (STL/STP/STEP/IGES)
uploads so customer can send both. Multi-upload session shape:
attachment_ids list. Submit handler re-keys ALL uploads onto the
new quote_request.
3. Job card upgraded: new fp_portal_job_card macro shared by dashboard
+ jobs list. Renders wrap div containing main anchor (whole card
clickable -> detail page) + sibling actions footer (4 doc download
quick-buttons: SO / WO / CoC / Packing + Repeat Order form).
Forms-inside-anchor is invalid HTML so the footer lives as a
sibling, not a child. Card now shows part name+number and ship-to
address pulled inline from job.x_fc_job_id.sale_order_id chain.
Same data also added to detail-page hero for consistency.
Version bump: 19.0.3.6.0 -> 19.0.3.7.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -156,67 +156,49 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- File Upload -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label">Part Drawing or 3D Model</label>
|
||||
<div class="o_fp_file_drop_zone p-4">
|
||||
<i class="fa fa-cloud-upload"/>
|
||||
<p class="mb-1 fw-semibold">
|
||||
Drag and drop your file here, or click to browse
|
||||
</p>
|
||||
<p class="small text-muted mb-2">
|
||||
Accepted: STL, STP, STEP, IGES, PDF (max 50 MB)
|
||||
</p>
|
||||
<input type="file" name="part_file" id="part_file"
|
||||
class="form-control"
|
||||
accept=".stl,.stp,.step,.iges,.igs,.pdf"/>
|
||||
<!-- File Uploads — separate drawing + 3D model.
|
||||
Customer can upload either or both. STL gets
|
||||
trimesh surface-area auto-calc server-side
|
||||
(not shown to customer — backend uses it for
|
||||
future pricing). -->
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4">
|
||||
<label class="form-label">Drawing (PDF)</label>
|
||||
<div class="o_fp_file_drop_zone p-3">
|
||||
<i class="fa fa-file-pdf-o"/>
|
||||
<p class="mb-1 fw-semibold">PDF drawing</p>
|
||||
<p class="small text-muted mb-2">
|
||||
2D / dimensioned drawing
|
||||
</p>
|
||||
<input type="file" name="part_drawing" id="part_drawing"
|
||||
class="form-control"
|
||||
accept=".pdf"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<label class="form-label">3D Model</label>
|
||||
<div class="o_fp_file_drop_zone p-3">
|
||||
<i class="fa fa-cube"/>
|
||||
<p class="mb-1 fw-semibold">STL / STP / STEP / IGES</p>
|
||||
<p class="small text-muted mb-2">
|
||||
Optional — speeds up estimation
|
||||
</p>
|
||||
<input type="file" name="part_3d_model" id="part_3d_model"
|
||||
class="form-control"
|
||||
accept=".stl,.stp,.step,.iges,.igs"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4"/>
|
||||
|
||||
<!-- Manual Measurements -->
|
||||
<h6 class="mb-3">
|
||||
<i class="fa fa-ruler-combined me-2"/>Manual Measurements
|
||||
<span class="text-muted small fw-normal ms-2">
|
||||
(if no 3D model uploaded)
|
||||
</span>
|
||||
</h6>
|
||||
|
||||
<input type="hidden" name="geometry_source" value="manual"/>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<label for="dimensions_length" class="form-label">Length (in)</label>
|
||||
<input type="number" step="0.001" min="0"
|
||||
id="dimensions_length" name="dimensions_length"
|
||||
class="form-control" placeholder="0.000"/>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="dimensions_width" class="form-label">Width (in)</label>
|
||||
<input type="number" step="0.001" min="0"
|
||||
id="dimensions_width" name="dimensions_width"
|
||||
class="form-control" placeholder="0.000"/>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="dimensions_height" class="form-label">Height (in)</label>
|
||||
<input type="number" step="0.001" min="0"
|
||||
id="dimensions_height" name="dimensions_height"
|
||||
class="form-control" placeholder="0.000"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="surface_area" class="form-label">
|
||||
Surface Area (sq in)
|
||||
<span class="text-muted small fw-normal ms-1">
|
||||
-- auto-calculated if STL uploaded
|
||||
</span>
|
||||
</label>
|
||||
<input type="number" step="0.0001" min="0"
|
||||
id="surface_area" name="surface_area"
|
||||
class="form-control" placeholder="0.0000"/>
|
||||
</div>
|
||||
<!-- Manual measurements hidden per customer-feedback 2026-05-17:
|
||||
backend computes these (or doesn't) — not the
|
||||
customer's job. Fields kept as hidden inputs at 0
|
||||
so the controller doesn't error on missing keys. -->
|
||||
<input type="hidden" name="geometry_source" value="upload"/>
|
||||
<input type="hidden" name="dimensions_length" value="0"/>
|
||||
<input type="hidden" name="dimensions_width" value="0"/>
|
||||
<input type="hidden" name="dimensions_height" value="0"/>
|
||||
<input type="hidden" name="surface_area" value="0"/>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="d-flex justify-content-between">
|
||||
@@ -295,26 +277,13 @@
|
||||
<h6 class="card-title mb-2" style="color: var(--bs-body-color);">
|
||||
<t t-out="coat.name"/>
|
||||
</h6>
|
||||
<p t-if="coat.process_type_id" class="small text-muted mb-1">
|
||||
<p t-if="coat.code" class="small text-muted mb-1">
|
||||
<i class="fa fa-tag me-1"/>
|
||||
<t t-out="coat.code"/>
|
||||
</p>
|
||||
<p t-if="coat.process_family" class="small text-muted mb-1">
|
||||
<i class="fa fa-flask me-1"/>
|
||||
<t t-out="coat.process_type_id.name"/>
|
||||
</p>
|
||||
<p t-if="coat.spec_reference" class="small text-muted mb-1">
|
||||
<i class="fa fa-bookmark me-1"/>
|
||||
<t t-out="coat.spec_reference"/>
|
||||
</p>
|
||||
<p t-if="coat.thickness_min or coat.thickness_max" class="small text-muted mb-1">
|
||||
<i class="fa fa-arrows-v me-1"/>
|
||||
<t t-if="coat.thickness_min" t-out="coat.thickness_min"/>
|
||||
<t t-if="coat.thickness_min and coat.thickness_max"> - </t>
|
||||
<t t-if="coat.thickness_max" t-out="coat.thickness_max"/>
|
||||
<t t-out="coat.thickness_uom or 'mils'"/>
|
||||
</p>
|
||||
<p t-if="coat.certification_level and coat.certification_level != 'commercial'"
|
||||
class="small mb-0">
|
||||
<span class="badge text-bg-warning">
|
||||
<t t-out="dict(coat._fields['certification_level']._description_selection(coat.env)).get(coat.certification_level)"/>
|
||||
</span>
|
||||
<t t-out="dict(coat._fields['process_family']._description_selection(coat.env)).get(coat.process_family)"/>
|
||||
</p>
|
||||
<p t-if="coat.description" class="small text-muted mt-2 mb-0"
|
||||
t-out="coat.description"/>
|
||||
@@ -408,9 +377,9 @@
|
||||
<strong t-out="coating.name"/>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="coating.spec_reference" class="row mb-3">
|
||||
<div class="col-sm-4 text-muted small fw-semibold">Spec</div>
|
||||
<div class="col-sm-8" t-out="coating.spec_reference"/>
|
||||
<div t-if="coating.code" class="row mb-3">
|
||||
<div class="col-sm-4 text-muted small fw-semibold">Code</div>
|
||||
<div class="col-sm-8" t-out="coating.code"/>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-4 text-muted small fw-semibold">Quantity</div>
|
||||
|
||||
@@ -67,49 +67,9 @@
|
||||
|
||||
<t t-if="recent_jobs">
|
||||
<t t-foreach="recent_jobs[:3]" t-as="job">
|
||||
<a t-att-href="'/my/jobs/%s' % job.id" class="o_fp_job_card">
|
||||
<div class="o_fp_job_header">
|
||||
<div>
|
||||
<span class="o_fp_job_ref" t-out="job.name"/>
|
||||
<span class="o_fp_job_meta">
|
||||
<t t-if="job.quantity"><t t-out="job.quantity"/> units</t>
|
||||
<t t-if="job.target_ship_date"> · ETA <span t-field="job.target_ship_date" t-options='{"widget": "date"}'/></t>
|
||||
</span>
|
||||
</div>
|
||||
<t t-call="fusion_plating_portal.fp_portal_status_badge">
|
||||
<t t-set="state" t-value="job.state"/>
|
||||
<t t-set="label" t-value="dict(job._fields['state']._description_selection(job.env)).get(job.state)"/>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- State -> active step index mapping.
|
||||
5 customer-visible stages: Received / Inspected / Plating / QC / Ship.
|
||||
state_idx >= 5 means all done (shipped or complete). -->
|
||||
<t t-set="state_to_idx" t-value="{'received': 0, 'in_progress': 2, 'quality_check': 3, 'ready_to_ship': 4, 'shipped': 5, 'complete': 5}"/>
|
||||
<t t-set="state_idx" t-value="state_to_idx.get(job.state, 0)"/>
|
||||
<t t-set="steps" t-value="[
|
||||
{'label': 'Received', 'status': 'done' if state_idx > 0 else 'active', 'time_label': ''},
|
||||
{'label': 'Inspected', 'status': 'done' if state_idx > 1 else ('active' if state_idx == 1 else 'pending'), 'time_label': ''},
|
||||
{'label': 'Plating', 'status': 'done' if state_idx > 2 else ('active' if state_idx == 2 else 'pending'), 'time_label': ''},
|
||||
{'label': 'QC', 'status': 'done' if state_idx > 3 else ('active' if state_idx == 3 else 'pending'), 'time_label': ''},
|
||||
{'label': 'Ship', 'status': 'done' if state_idx > 4 else ('active' if state_idx == 4 else 'pending'), 'time_label': ''},
|
||||
]"/>
|
||||
<t t-set="active_state" t-value="'warn' if job.state == 'quality_check' else 'normal'"/>
|
||||
<t t-call="fusion_plating_portal.fp_portal_stepper"/>
|
||||
|
||||
<!-- Doc chips: CoC + tracking (V1) -->
|
||||
<div class="o_fp_job_docs">
|
||||
<t t-if="job.coc_attachment_id">
|
||||
<span class="o_fp_doc_chip">📑 CoC</span>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span class="o_fp_doc_chip o_fp_doc_chip_pending">📑 CoC · pending</span>
|
||||
</t>
|
||||
<t t-if="job.tracking_ref">
|
||||
<span class="o_fp_doc_chip">📦 <span t-out="job.tracking_ref"/></span>
|
||||
</t>
|
||||
</div>
|
||||
</a>
|
||||
<t t-call="fusion_plating_portal.fp_portal_job_card">
|
||||
<t t-set="job" t-value="job"/>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-if="job_count > 3">
|
||||
|
||||
@@ -78,6 +78,129 @@
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- Job card — shared between /my/home dashboard and /my/jobs list. -->
|
||||
<!-- Pass `job` (fusion.plating.portal.job). Renders a wrap div with -->
|
||||
<!-- inner anchor (whole card click target = detail page) and a sibling -->
|
||||
<!-- actions footer (doc download chips + Repeat Order form). Forms -->
|
||||
<!-- live OUTSIDE the anchor because forms-inside-anchors is invalid -->
|
||||
<!-- HTML and clicks would otherwise double-fire. -->
|
||||
<!-- ================================================================== -->
|
||||
<template id="fp_portal_job_card" name="Portal: Job Card">
|
||||
<!-- Pull related backend records inline (cards are 3-5 per page,
|
||||
query cost is fine). Each `t-set` is a no-op if the field
|
||||
chain breaks. -->
|
||||
<t t-set="backend_job" t-value="job.x_fc_job_id if 'x_fc_job_id' in job._fields else False"/>
|
||||
<t t-set="so" t-value="backend_job.sale_order_id if backend_job and 'sale_order_id' in backend_job._fields else False"/>
|
||||
<t t-set="part" t-value="backend_job.part_catalog_id if backend_job and 'part_catalog_id' in backend_job._fields else False"/>
|
||||
<t t-set="ship_to" t-value="so.partner_shipping_id if so else False"/>
|
||||
<t t-set="picking" t-value="so.picking_ids.filtered(lambda p: p.state == 'done')[:1] if so and 'picking_ids' in so._fields else False"/>
|
||||
|
||||
<!-- Stepper state mapping (same as detail page).
|
||||
received -> idx 0; in_progress -> 2; quality_check -> 3;
|
||||
ready_to_ship -> 4; shipped / complete -> 5 (all done). -->
|
||||
<t t-set="state_to_idx" t-value="{'received': 0, 'in_progress': 2, 'quality_check': 3, 'ready_to_ship': 4, 'shipped': 5, 'complete': 5}"/>
|
||||
<t t-set="state_idx" t-value="state_to_idx.get(job.state, 0)"/>
|
||||
<t t-set="steps" t-value="[
|
||||
{'label': 'Received', 'status': 'done' if state_idx > 0 else 'active', 'time_label': ''},
|
||||
{'label': 'Inspected', 'status': 'done' if state_idx > 1 else ('active' if state_idx == 1 else 'pending'), 'time_label': ''},
|
||||
{'label': 'Plating', 'status': 'done' if state_idx > 2 else ('active' if state_idx == 2 else 'pending'), 'time_label': ''},
|
||||
{'label': 'QC', 'status': 'done' if state_idx > 3 else ('active' if state_idx == 3 else 'pending'), 'time_label': ''},
|
||||
{'label': 'Ship', 'status': 'done' if state_idx > 4 else ('active' if state_idx == 4 else 'pending'), 'time_label': ''},
|
||||
]"/>
|
||||
<t t-set="active_state" t-value="'warn' if job.state == 'quality_check' else 'normal'"/>
|
||||
|
||||
<div class="o_fp_job_card">
|
||||
<a t-att-href="'/my/jobs/%s' % job.id" class="o_fp_job_card_main">
|
||||
<div class="o_fp_job_header">
|
||||
<div>
|
||||
<span class="o_fp_job_ref" t-out="job.name"/>
|
||||
<span class="o_fp_job_meta">
|
||||
<t t-if="job.quantity"><t t-out="job.quantity"/> units</t>
|
||||
<t t-if="job.target_ship_date"> · ETA <span t-field="job.target_ship_date" t-options='{"widget": "date"}'/></t>
|
||||
</span>
|
||||
</div>
|
||||
<t t-call="fusion_plating_portal.fp_portal_status_badge">
|
||||
<t t-set="state" t-value="job.state"/>
|
||||
<t t-set="label" t-value="dict(job._fields['state']._description_selection(job.env)).get(job.state)"/>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- Part info: prefer the part catalog ref, fall back to
|
||||
process types listed on the portal job. -->
|
||||
<t t-if="part">
|
||||
<div class="o_fp_job_part">
|
||||
<span class="o_fp_job_part_icon">📦</span>
|
||||
<t t-if="part.part_number"><b t-out="part.part_number"/> · </t>
|
||||
<t t-out="part.name or part.display_name"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-elif="job.process_type_ids">
|
||||
<div class="o_fp_job_part">
|
||||
<span class="o_fp_job_part_icon">📦</span>
|
||||
<t t-out="', '.join(job.process_type_ids.mapped('name'))"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<!-- Shipping address: customer may have multiple sites; the
|
||||
SO carries which one this job ships to. -->
|
||||
<t t-if="ship_to and ship_to.id != job.partner_id.commercial_partner_id.id">
|
||||
<div class="o_fp_job_ship">
|
||||
<span class="o_fp_job_ship_icon">📍</span>
|
||||
Ship to: <t t-out="ship_to.name"/>
|
||||
<t t-if="ship_to.city"> · <t t-out="ship_to.city"/></t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-call="fusion_plating_portal.fp_portal_stepper"/>
|
||||
</a>
|
||||
|
||||
<!-- Actions footer: doc quick-downloads + Repeat Order. Lives
|
||||
OUTSIDE the .o_fp_job_card_main anchor so the form button
|
||||
doesn't double-fire navigation + form submission. -->
|
||||
<div class="o_fp_job_card_actions">
|
||||
<div class="o_fp_job_card_docs">
|
||||
<t t-if="so">
|
||||
<a t-attf-href="/my/jobs/#{job.id}/so_confirmation"
|
||||
class="o_fp_doc_quick_btn" title="Sales Order Confirmation">
|
||||
📄 SO
|
||||
</a>
|
||||
</t>
|
||||
<t t-if="backend_job">
|
||||
<a t-attf-href="/my/jobs/#{job.id}/wo_detail"
|
||||
class="o_fp_doc_quick_btn" title="Work Order Detail">
|
||||
🛠 WO
|
||||
</a>
|
||||
</t>
|
||||
<t t-if="job.coc_attachment_id">
|
||||
<a t-attf-href="/my/jobs/#{job.id}/coc"
|
||||
class="o_fp_doc_quick_btn" title="Certificate of Conformance">
|
||||
📑 CoC
|
||||
</a>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span class="o_fp_doc_quick_btn o_fp_doc_quick_btn_pending"
|
||||
title="Will appear after QC completes">
|
||||
📑 CoC
|
||||
</span>
|
||||
</t>
|
||||
<t t-if="job.packing_list_attachment_id">
|
||||
<a t-attf-href="/web/content/#{job.packing_list_attachment_id.id}?download=true"
|
||||
class="o_fp_doc_quick_btn" title="Packing Slip">
|
||||
📦 Packing
|
||||
</a>
|
||||
</t>
|
||||
</div>
|
||||
<form t-attf-action="/my/jobs/#{job.id}/repeat" method="post" class="m-0">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<button type="submit" class="o_fp_btn_secondary o_fp_btn_sm">
|
||||
<i class="fa fa-repeat"/> Repeat Order
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<!-- Doc group (detail page) — pass label + docs list of dicts: -->
|
||||
<!-- {label, sub, url, icon_class, pending} -->
|
||||
|
||||
@@ -436,34 +436,9 @@
|
||||
<t t-if="jobs">
|
||||
<div class="o_fp_dashboard">
|
||||
<t t-foreach="jobs" t-as="job">
|
||||
<a t-att-href="'/my/jobs/%s' % job.id" class="o_fp_job_card">
|
||||
<div class="o_fp_job_header">
|
||||
<div>
|
||||
<span class="o_fp_job_ref" t-out="job.name"/>
|
||||
<span class="o_fp_job_meta">
|
||||
<t t-if="job.quantity"><t t-out="job.quantity"/> units</t>
|
||||
<t t-if="job.target_ship_date"> · ETA <span t-field="job.target_ship_date" t-options='{"widget": "date"}'/></t>
|
||||
</span>
|
||||
</div>
|
||||
<t t-call="fusion_plating_portal.fp_portal_status_badge">
|
||||
<t t-set="state" t-value="job.state"/>
|
||||
<t t-set="label" t-value="dict(job._fields['state']._description_selection(job.env)).get(job.state)"/>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- State -> active step index map (same as dashboard) -->
|
||||
<t t-set="state_to_idx" t-value="{'received': 0, 'in_progress': 2, 'quality_check': 3, 'ready_to_ship': 4, 'shipped': 5, 'complete': 5}"/>
|
||||
<t t-set="state_idx" t-value="state_to_idx.get(job.state, 0)"/>
|
||||
<t t-set="steps" t-value="[
|
||||
{'label': 'Received', 'status': 'done' if state_idx > 0 else 'active', 'time_label': ''},
|
||||
{'label': 'Inspected', 'status': 'done' if state_idx > 1 else ('active' if state_idx == 1 else 'pending'), 'time_label': ''},
|
||||
{'label': 'Plating', 'status': 'done' if state_idx > 2 else ('active' if state_idx == 2 else 'pending'), 'time_label': ''},
|
||||
{'label': 'QC', 'status': 'done' if state_idx > 3 else ('active' if state_idx == 3 else 'pending'), 'time_label': ''},
|
||||
{'label': 'Ship', 'status': 'done' if state_idx > 4 else ('active' if state_idx == 4 else 'pending'), 'time_label': ''},
|
||||
]"/>
|
||||
<t t-set="active_state" t-value="'warn' if job.state == 'quality_check' else 'normal'"/>
|
||||
<t t-call="fusion_plating_portal.fp_portal_stepper"/>
|
||||
</a>
|
||||
<t t-call="fusion_plating_portal.fp_portal_job_card">
|
||||
<t t-set="job" t-value="job"/>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
@@ -477,15 +452,28 @@
|
||||
<t t-call="portal.portal_layout">
|
||||
<div class="o_fp_job_detail">
|
||||
|
||||
<!-- Hero header -->
|
||||
<!-- Hero header: WO ref + part + ship-to + key facts -->
|
||||
<t t-set="backend_job" t-value="job.x_fc_job_id if 'x_fc_job_id' in job._fields else False"/>
|
||||
<t t-set="so" t-value="backend_job.sale_order_id if backend_job and 'sale_order_id' in backend_job._fields else False"/>
|
||||
<t t-set="part" t-value="backend_job.part_catalog_id if backend_job and 'part_catalog_id' in backend_job._fields else False"/>
|
||||
<t t-set="ship_to" t-value="so.partner_shipping_id if so else False"/>
|
||||
|
||||
<div class="o_fp_job_detail_hero">
|
||||
<div class="d-flex justify-content-between align-items-start gap-3 flex-wrap">
|
||||
<div>
|
||||
<div class="o_fp_detail_label">Work Order</div>
|
||||
<h2><span t-out="job.name"/></h2>
|
||||
<div t-if="job.process_type_ids" class="o_fp_detail_subtitle">
|
||||
<span t-out="', '.join(job.process_type_ids.mapped('name'))"/>
|
||||
</div>
|
||||
<t t-if="part">
|
||||
<div class="o_fp_detail_subtitle">
|
||||
<t t-if="part.part_number"><b t-out="part.part_number"/> · </t>
|
||||
<t t-out="part.name or part.display_name"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-elif="job.process_type_ids">
|
||||
<div class="o_fp_detail_subtitle">
|
||||
<span t-out="', '.join(job.process_type_ids.mapped('name'))"/>
|
||||
</div>
|
||||
</t>
|
||||
<div class="o_fp_detail_facts">
|
||||
<div t-if="job.quantity">
|
||||
<span class="o_fp_fact_label">Qty </span>
|
||||
@@ -503,6 +491,12 @@
|
||||
<span class="o_fp_fact_label">Tracking </span>
|
||||
<span class="o_fp_fact_value" t-out="job.tracking_ref"/>
|
||||
</div>
|
||||
<div t-if="ship_to and ship_to.id != job.partner_id.commercial_partner_id.id">
|
||||
<span class="o_fp_fact_label">Ship to </span>
|
||||
<span class="o_fp_fact_value">
|
||||
<t t-out="ship_to.name"/><t t-if="ship_to.city"> · <t t-out="ship_to.city"/></t>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-column align-items-end gap-2">
|
||||
|
||||
Reference in New Issue
Block a user