feat(configurator): direct-order wizard dual-description inputs + onchange (Sub 2 Task 16)
Adds an `internal_description` text field to the direct-order wizard line so the shop-floor copy is captured at order entry alongside the customer-facing text. Picking a template now fires both sides of the onchange: `line_description` gets `customer_facing_description` (with fallback to the legacy `description` field for backward compat) and `internal_description` gets the template's internal text. The auto-suggest onchange was refactored around a tiny `_apply` helper so all three fallback paths populate both fields consistently. The template picker is surfaced as an optional column on the wizard list (hidden until a part is chosen, domain-scoped to that part) and as a dedicated labeled row in the per-line form. The internal text field is also surfaced in the form under "Line Description" so the estimator can review / edit it before confirm. On create_order, both `x_fc_description_template_id` and `x_fc_internal_description` are written through to the generated sale.order.line so the audit trail and WO printout stay linked. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -125,9 +125,15 @@ class FpDirectOrderLine(models.TransientModel):
|
|||||||
)
|
)
|
||||||
line_description = fields.Text(
|
line_description = fields.Text(
|
||||||
string='Line Description',
|
string='Line Description',
|
||||||
help='This text becomes the description of the sale order line. '
|
help='Customer-facing text. Becomes the SO line description and '
|
||||||
|
'prints on the acknowledgement, invoice, and packing slip. '
|
||||||
'Edit freely — your changes override the template.',
|
'Edit freely — your changes override the template.',
|
||||||
)
|
)
|
||||||
|
internal_description = fields.Text(
|
||||||
|
string='Internal Description',
|
||||||
|
help='Shop-floor instructions. Prints on WO / traveler. Never on '
|
||||||
|
'customer docs. Edit freely — your changes override the template.',
|
||||||
|
)
|
||||||
|
|
||||||
# ---- Missing info per line ----
|
# ---- Missing info per line ----
|
||||||
is_missing_info = fields.Boolean(
|
is_missing_info = fields.Boolean(
|
||||||
@@ -195,8 +201,22 @@ class FpDirectOrderLine(models.TransientModel):
|
|||||||
|
|
||||||
@api.onchange('description_template_id')
|
@api.onchange('description_template_id')
|
||||||
def _onchange_description_template(self):
|
def _onchange_description_template(self):
|
||||||
|
"""Auto-fill both descriptions from the chosen template.
|
||||||
|
|
||||||
|
Customer-facing text lands in `line_description` (ends up on the
|
||||||
|
SO line `name`, prints on customer docs). Internal text lands in
|
||||||
|
`internal_description` (ends up in x_fc_internal_description on
|
||||||
|
the SO line, prints on WO / traveler only). Falls back to the
|
||||||
|
legacy `description` field when the new dual fields are empty so
|
||||||
|
pre-Sub-2 templates keep working.
|
||||||
|
"""
|
||||||
if self.description_template_id:
|
if self.description_template_id:
|
||||||
self.line_description = self.description_template_id.description
|
tpl = self.description_template_id
|
||||||
|
customer_text = tpl.customer_facing_description or tpl.description
|
||||||
|
if customer_text:
|
||||||
|
self.line_description = customer_text
|
||||||
|
if tpl.internal_description:
|
||||||
|
self.internal_description = tpl.internal_description
|
||||||
|
|
||||||
@api.onchange('part_catalog_id', 'coating_config_id')
|
@api.onchange('part_catalog_id', 'coating_config_id')
|
||||||
def _onchange_suggest_template(self):
|
def _onchange_suggest_template(self):
|
||||||
@@ -213,14 +233,21 @@ class FpDirectOrderLine(models.TransientModel):
|
|||||||
Template = self.env['fp.sale.description.template']
|
Template = self.env['fp.sale.description.template']
|
||||||
partner = self.wizard_id.partner_id
|
partner = self.wizard_id.partner_id
|
||||||
|
|
||||||
|
def _apply(match):
|
||||||
|
self.description_template_id = match.id
|
||||||
|
customer_text = match.customer_facing_description or match.description
|
||||||
|
if customer_text:
|
||||||
|
self.line_description = customer_text
|
||||||
|
if match.internal_description:
|
||||||
|
self.internal_description = match.internal_description
|
||||||
|
|
||||||
if self.part_catalog_id:
|
if self.part_catalog_id:
|
||||||
match = Template.search([
|
match = Template.search([
|
||||||
('active', '=', True),
|
('active', '=', True),
|
||||||
('part_catalog_id', '=', self.part_catalog_id.id),
|
('part_catalog_id', '=', self.part_catalog_id.id),
|
||||||
], order='sequence', limit=1)
|
], order='sequence', limit=1)
|
||||||
if match:
|
if match:
|
||||||
self.description_template_id = match.id
|
_apply(match)
|
||||||
self.line_description = match.description
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if partner:
|
if partner:
|
||||||
@@ -230,8 +257,7 @@ class FpDirectOrderLine(models.TransientModel):
|
|||||||
('partner_id', '=', partner.id),
|
('partner_id', '=', partner.id),
|
||||||
], order='sequence', limit=1)
|
], order='sequence', limit=1)
|
||||||
if match:
|
if match:
|
||||||
self.description_template_id = match.id
|
_apply(match)
|
||||||
self.line_description = match.description
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.coating_config_id:
|
if self.coating_config_id:
|
||||||
@@ -242,8 +268,7 @@ class FpDirectOrderLine(models.TransientModel):
|
|||||||
('coating_config_id', '=', self.coating_config_id.id),
|
('coating_config_id', '=', self.coating_config_id.id),
|
||||||
], order='sequence', limit=1)
|
], order='sequence', limit=1)
|
||||||
if match:
|
if match:
|
||||||
self.description_template_id = match.id
|
_apply(match)
|
||||||
self.line_description = match.description
|
|
||||||
|
|
||||||
# ---- Helpers ----
|
# ---- Helpers ----
|
||||||
def _get_or_bump_revision(self):
|
def _get_or_bump_revision(self):
|
||||||
|
|||||||
@@ -259,6 +259,8 @@ class FpDirectOrderWizard(models.TransientModel):
|
|||||||
'product_uom_qty': line.quantity,
|
'product_uom_qty': line.quantity,
|
||||||
'price_unit': line.unit_price or 0.0,
|
'price_unit': line.unit_price or 0.0,
|
||||||
'x_fc_part_catalog_id': part.id,
|
'x_fc_part_catalog_id': part.id,
|
||||||
|
'x_fc_description_template_id': line.description_template_id.id or False,
|
||||||
|
'x_fc_internal_description': line.internal_description or False,
|
||||||
'x_fc_coating_config_id': line.coating_config_id.id,
|
'x_fc_coating_config_id': line.coating_config_id.id,
|
||||||
'x_fc_treatment_ids': [(6, 0, line.treatment_ids.ids)],
|
'x_fc_treatment_ids': [(6, 0, line.treatment_ids.ids)],
|
||||||
'x_fc_part_deadline': line.part_deadline,
|
'x_fc_part_deadline': line.part_deadline,
|
||||||
|
|||||||
@@ -98,6 +98,13 @@
|
|||||||
context="{'default_partner_id': parent.partner_id}"
|
context="{'default_partner_id': parent.partner_id}"
|
||||||
domain="[('partner_id', '=', parent.partner_id), ('is_latest_revision', '=', True)]"
|
domain="[('partner_id', '=', parent.partner_id), ('is_latest_revision', '=', True)]"
|
||||||
options="{'no_create_edit': True}"/>
|
options="{'no_create_edit': True}"/>
|
||||||
|
<field name="description_template_id"
|
||||||
|
domain="[('part_catalog_id', '=', part_catalog_id)]"
|
||||||
|
options="{'no_create': True}"
|
||||||
|
invisible="not part_catalog_id"
|
||||||
|
optional="hide"/>
|
||||||
|
<field name="internal_description"
|
||||||
|
optional="hide"/>
|
||||||
<field name="coating_config_id"/>
|
<field name="coating_config_id"/>
|
||||||
<field name="treatment_ids"
|
<field name="treatment_ids"
|
||||||
widget="many2many_tags"
|
widget="many2many_tags"
|
||||||
@@ -159,9 +166,16 @@
|
|||||||
<field name="description_template_id"
|
<field name="description_template_id"
|
||||||
options="{'no_create': True, 'no_open': True}"
|
options="{'no_create': True, 'no_open': True}"
|
||||||
placeholder="Start typing to search saved descriptions..."/>
|
placeholder="Start typing to search saved descriptions..."/>
|
||||||
|
<label for="line_description"
|
||||||
|
string="Customer-Facing"/>
|
||||||
<field name="line_description"
|
<field name="line_description"
|
||||||
nolabel="1" colspan="2"
|
nolabel="1" colspan="2"
|
||||||
placeholder="Pick a template above, then tweak the text here."/>
|
placeholder="Prints on SO, invoice, packing slip. Pick a template above, then tweak the text here."/>
|
||||||
|
<label for="internal_description"
|
||||||
|
string="Internal (WO / Traveler)"/>
|
||||||
|
<field name="internal_description"
|
||||||
|
nolabel="1" colspan="2"
|
||||||
|
placeholder="Shop-floor instructions. Never shown to the customer."/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Work Order (internal)">
|
<group string="Work Order (internal)">
|
||||||
<field name="part_wo_description"
|
<field name="part_wo_description"
|
||||||
|
|||||||
Reference in New Issue
Block a user