fix(reports+configurator): clean description, recipe propagation, uppercase rendering
H1 — Recipe propagation hardening for multi-part orders. The G3 onchange fires when material_process changes, but a newly-added line (especially via inline part create) sometimes didn't pick up the recipe before confirm. In action_create_order, just BEFORE building so_vals, force line.process_variant_id = wizard.material_process if the line is missing one. Also added the same fallback inside the so_vals dict so the SO line always carries the right recipe even if the wizard line missed it. H2 — Strip 'spec - PART Rev X (xN)' header from customer-facing description. Per user feedback, the customer-facing reports (SO confirmation, Invoice, CoC, packing slip, BoL) should show ONLY the typed description + thickness in the Description column. The legacy header that prepended part metadata to line.name duplicated info from the Part Number column. Wizard now writes ONLY the customer description to line.name; the Part Number column owns the part-rev-name display. H3 — Uppercase customer-facing description in reports. The shared customer_line_description macro now wraps the description, serial, and thickness in text-transform: uppercase divs. All reports that use the macro (SO confirmation, Invoice, CoC, packing slip, BoL) get the caps treatment automatically. Non-part lines (freight, rush fees) keep their natural casing. Manually cleaned up DOD-00154/SO-30062: - Backfilled line 682 with the header recipe (ENP ALUM BASIC HP) - Stripped the legacy 'No spec - PART Rev (xN)' header from both lines' names; descriptions now read 'THIS IS TEST SPECIFICATIONS...' and 'THIS IS BLB ABLA BOLL' cleanly.
This commit is contained in:
@@ -752,24 +752,25 @@ class FpDirectOrderWizard(models.Model):
|
|||||||
part = line._get_or_bump_revision()
|
part = line._get_or_bump_revision()
|
||||||
resolved_parts[line.id] = part
|
resolved_parts[line.id] = part
|
||||||
# Build the line header. Specification is optional; when
|
# Build the line header. Specification is optional; when
|
||||||
# missing, drop it from the header rather than printing
|
# 2026-05-27 — drop the legacy "spec - PART Rev (xN)" header
|
||||||
# "False - PartName Rev A". Same defensive treatment for
|
# entirely from the customer-facing line name. Per user
|
||||||
# part identifier and revision — `%s` on a False/NULL field
|
# request, customer-facing reports (SO confirmation, invoice,
|
||||||
# used to print the literal string "False".
|
# CoC, packing slip, BoL) should show ONLY:
|
||||||
spec = getattr(line, 'customer_spec_id', False)
|
# - the customer-typed description (line.line_description)
|
||||||
spec_label = (spec.display_name if spec else '') or _('No spec')
|
# - the thickness range (rendered on its own line)
|
||||||
# Prefer part_number (set on every saved part); fall back to
|
# The part number / revision / serial show in the dedicated
|
||||||
# name; skip the segment entirely if both are missing.
|
# Part Number column. The header was duplicating that info
|
||||||
part_label = part.part_number or part.name or ''
|
# in the Description column.
|
||||||
rev_suffix = (' Rev %s' % part.revision) if part.revision else ''
|
|
||||||
header = '%s - %s%s (x%d)' % (
|
|
||||||
spec_label,
|
|
||||||
part_label or _('Unspecified part'),
|
|
||||||
rev_suffix,
|
|
||||||
line.quantity,
|
|
||||||
)
|
|
||||||
extended = (line.line_description or '').strip()
|
extended = (line.line_description or '').strip()
|
||||||
line_desc = (header + '\n\n' + extended) if extended else header
|
line_desc = extended or (part.part_number or '—')
|
||||||
|
|
||||||
|
# G3 robustness (2026-05-27): make sure the line's process
|
||||||
|
# variant carries the order-level recipe if no per-line
|
||||||
|
# override exists. Belt-and-braces with the onchange/create
|
||||||
|
# propagation — catches the multi-part case where a new
|
||||||
|
# part's line never received the order recipe.
|
||||||
|
if not line.process_variant_id and self.material_process:
|
||||||
|
line.process_variant_id = self.material_process.id
|
||||||
if line.description_template_id:
|
if line.description_template_id:
|
||||||
line.description_template_id._register_usage()
|
line.description_template_id._register_usage()
|
||||||
|
|
||||||
@@ -792,7 +793,13 @@ class FpDirectOrderWizard(models.Model):
|
|||||||
'x_fc_start_at_node_id': line.start_at_node_id.id or False,
|
'x_fc_start_at_node_id': line.start_at_node_id.id or False,
|
||||||
'x_fc_is_one_off': line.is_one_off,
|
'x_fc_is_one_off': line.is_one_off,
|
||||||
'x_fc_quote_id': line.quote_id.id or False,
|
'x_fc_quote_id': line.quote_id.id or False,
|
||||||
'x_fc_process_variant_id': line.process_variant_id.id or False,
|
# Recipe propagation (G3) — line's process_variant_id, falling
|
||||||
|
# back to the order-level material_process recipe so multi-part
|
||||||
|
# orders still get the header recipe on every line.
|
||||||
|
'x_fc_process_variant_id': (
|
||||||
|
line.process_variant_id.id
|
||||||
|
or (self.material_process.id if self.material_process else False)
|
||||||
|
),
|
||||||
'x_fc_save_as_default_process': line.save_as_default_process,
|
'x_fc_save_as_default_process': line.save_as_default_process,
|
||||||
# Sub 5 / Phase 1 — carry serial M2M to the SO line.
|
# Sub 5 / Phase 1 — carry serial M2M to the SO line.
|
||||||
# x_fc_serial_id is back-compat alias and auto-resolves
|
# x_fc_serial_id is back-compat alias and auto-resolves
|
||||||
|
|||||||
@@ -77,35 +77,37 @@
|
|||||||
========================================================== -->
|
========================================================== -->
|
||||||
<template id="customer_line_description">
|
<template id="customer_line_description">
|
||||||
<t t-if="line.x_fc_part_catalog_id">
|
<t t-if="line.x_fc_part_catalog_id">
|
||||||
<!-- Strip the "[FP-SERVICE] Plating Service" prefix Odoo's
|
<!-- Customer-facing description column (2026-05-27 rebuild).
|
||||||
_compute_name keeps re-prepending. fp_customer_description
|
Per user request:
|
||||||
lives on sale.order.line + account.move.line; for any
|
- Only the description and thickness print
|
||||||
other model the macro might be called with we degrade to
|
- Both rendered in CAPITAL LETTERS via CSS
|
||||||
raw line.name. QWeb's eval context doesn't expose Python
|
The "spec - PART (xN)" header that used to prefix
|
||||||
builtins like hasattr/getattr, so probe the model's method
|
line.name has been removed at the wizard side; this
|
||||||
dict via line._name + env. white-space: pre-line preserves
|
macro just renders the cleaned description.
|
||||||
the estimator's line breaks. -->
|
white-space: pre-line preserves multi-line breaks. -->
|
||||||
<t t-set="_has_helper"
|
<t t-set="_has_helper"
|
||||||
t-value="line._name in ('sale.order.line', 'account.move.line')"/>
|
t-value="line._name in ('sale.order.line', 'account.move.line')"/>
|
||||||
<t t-set="_desc"
|
<t t-set="_desc"
|
||||||
t-value="line.fp_customer_description() if _has_helper else line.name"/>
|
t-value="line.fp_customer_description() if _has_helper else line.name"/>
|
||||||
<span t-esc="_desc" style="white-space: pre-line;"/>
|
<div style="text-transform: uppercase; white-space: pre-line; font-weight: 500;">
|
||||||
|
<t t-esc="_desc"/>
|
||||||
|
</div>
|
||||||
<!-- Serial line is suppressed when the calling template sets
|
<!-- Serial line is suppressed when the calling template sets
|
||||||
`fp_no_serial_in_desc=True` in `line.env.context`. SO
|
`fp_no_serial_in_desc=True` (SO portrait shows S/N in its
|
||||||
portrait does this because it shows the serial in its own
|
own column). Invoice / packing slip still display it here. -->
|
||||||
column in the part-number cell — otherwise it'd be
|
|
||||||
duplicated. Invoice / packing slip still display it here. -->
|
|
||||||
<t t-if="'x_fc_serial_id' in line._fields and line.x_fc_serial_id and not line.env.context.get('fp_no_serial_in_desc')">
|
<t t-if="'x_fc_serial_id' in line._fields and line.x_fc_serial_id and not line.env.context.get('fp_no_serial_in_desc')">
|
||||||
<br/>
|
<div style="text-transform: uppercase; margin-top: 4px;">
|
||||||
<small>Serial: <span t-esc="line.x_fc_serial_id.name"/></small>
|
<small>SERIAL: <span t-esc="line.x_fc_serial_id.name"/></small>
|
||||||
|
</div>
|
||||||
</t>
|
</t>
|
||||||
<t t-if="'x_fc_thickness_range' in line._fields and line.x_fc_thickness_range">
|
<t t-if="'x_fc_thickness_range' in line._fields and line.x_fc_thickness_range">
|
||||||
<br/>
|
<div style="text-transform: uppercase; margin-top: 4px;">
|
||||||
<small>Thickness: <span t-esc="line.x_fc_thickness_range"/></small>
|
<small>THICKNESS: <span t-esc="line.x_fc_thickness_range"/></small>
|
||||||
|
</div>
|
||||||
</t>
|
</t>
|
||||||
</t>
|
</t>
|
||||||
<t t-else="">
|
<t t-else="">
|
||||||
<!-- Fee / freight / non-part line: standard Odoo rendering -->
|
<!-- Fee / freight / non-part line: standard Odoo rendering, not uppercased -->
|
||||||
<strong t-esc="line.product_id.display_name or ''"/>
|
<strong t-esc="line.product_id.display_name or ''"/>
|
||||||
<t t-if="line.name and line.name != line.product_id.display_name">
|
<t t-if="line.name and line.name != line.product_id.display_name">
|
||||||
<br/>
|
<br/>
|
||||||
|
|||||||
Reference in New Issue
Block a user