chore(plating): de-dash shipped code + intake-neutral customer emails
Replace em-dashes and en-dashes with hyphens across 789 shipped source files (py/xml/js/scss) so the delivered module reads as human-written; em-dashes had become a recognizable AI-generated tell. Internal .md dev notes are excluded. The WO-sticker mojibake strippers keep their dash search targets (now written — / –). No logic changes: comments and display strings only; validated with py_compile + lxml parse. Rewrite the 7 customer notification emails to be intake-neutral (ship-in / drop-off / pickup) and repair-aware, and fix the Shipped email documents line (packing slip vs bill of lading; certificate only when issued). Subjects use a hyphen separator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
#
|
||||
# sale.order.action_confirm hook — creates fp.job records on confirm.
|
||||
# sale.order.action_confirm hook - creates fp.job records on confirm.
|
||||
# Sub 11 (2026-04-26) removed MRP entirely; fp.job is the only fulfilment
|
||||
# path. The former x_fc_use_native_jobs migration toggle was dropped in
|
||||
# 19.0.8.19.0 once the legacy bridge_mrp fallback became unreachable.
|
||||
@@ -59,11 +59,11 @@ class SaleOrder(models.Model):
|
||||
help='The quote-stage name (e.g. Q202605-200). Preserved when '
|
||||
'the SO is renamed on confirm.',
|
||||
)
|
||||
# Per-model counters — monotonic, never decrement. Source of truth
|
||||
# Per-model counters - monotonic, never decrement. Source of truth
|
||||
# for the next sibling's x_fc_doc_index. Updated via row-locked SQL
|
||||
# in fp.parent.numbered.mixin so concurrent creates can't drift.
|
||||
#
|
||||
# Naming: `x_fc_pn_*_count` — the `pn_` infix distinguishes our
|
||||
# Naming: `x_fc_pn_*_count` - the `pn_` infix distinguishes our
|
||||
# storage counters from pre-existing compute fields (e.g. the
|
||||
# `x_fc_delivery_count` compute in bridge_mrp, `x_fc_ncr_count`
|
||||
# in configurator, `x_fc_receiving_count` in fp_receiving) which
|
||||
@@ -82,7 +82,7 @@ class SaleOrder(models.Model):
|
||||
x_fc_pn_rma_count = fields.Integer(string='Parent: RMA Count', readonly=True, copy=False, default=0)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Phase 4 (Sub 11) — workflow-stage field + assigned-manager field
|
||||
# Phase 4 (Sub 11) - workflow-stage field + assigned-manager field
|
||||
# relocated from fusion_plating_bridge_mrp. Field re-declared with
|
||||
# the same selection + compute pointer; jobs is now the source of
|
||||
# truth so Phase 5 can delete bridge_mrp without losing the field.
|
||||
@@ -236,7 +236,7 @@ class SaleOrder(models.Model):
|
||||
return action
|
||||
|
||||
def action_view_fp_certificates(self):
|
||||
"""Smart-button target — open the certificate(s) linked to this
|
||||
"""Smart-button target - open the certificate(s) linked to this
|
||||
SO. One cert → form view; many → list view filtered to this SO."""
|
||||
self.ensure_one()
|
||||
certs = self.env['fp.certificate'].search([
|
||||
@@ -258,7 +258,7 @@ class SaleOrder(models.Model):
|
||||
return action
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Parent-number hierarchy — quote naming on create
|
||||
# Parent-number hierarchy - quote naming on create
|
||||
# ------------------------------------------------------------------
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
@@ -287,7 +287,7 @@ class SaleOrder(models.Model):
|
||||
|
||||
Parent number is drawn from fp.parent.number; the quote name
|
||||
was already saved to x_fc_quote_ref on create() so it survives
|
||||
the rename. Idempotent — if x_fc_parent_number is already set,
|
||||
the rename. Idempotent - if x_fc_parent_number is already set,
|
||||
the rename is skipped (re-confirm scenarios)."""
|
||||
Seq = self.env['ir.sequence']
|
||||
for so in self:
|
||||
@@ -344,7 +344,7 @@ class SaleOrder(models.Model):
|
||||
))._create_invoices(grouped=grouped, final=final, date=date)
|
||||
|
||||
def unlink(self):
|
||||
"""Spec §6.2 — confirmed SOs are part of the compliance audit
|
||||
"""Spec §6.2 - confirmed SOs are part of the compliance audit
|
||||
trail and cannot be deleted. Cancellation must go through the
|
||||
state machine instead. Draft SOs (no parent_number assigned
|
||||
yet) remain freely deletable per Odoo standard. Applies to
|
||||
@@ -352,7 +352,7 @@ class SaleOrder(models.Model):
|
||||
for so in self:
|
||||
if so.x_fc_parent_number:
|
||||
raise UserError(_(
|
||||
'Sale Order "%(name)s" cannot be deleted — it has '
|
||||
'Sale Order "%(name)s" cannot be deleted - it has '
|
||||
'been confirmed (parent number %(parent)s issued) '
|
||||
'and is part of the compliance audit trail. Cancel '
|
||||
'it instead. This rule applies to all users '
|
||||
@@ -364,14 +364,14 @@ class SaleOrder(models.Model):
|
||||
"""Recipe resolution with Express-Orders SO header fallback.
|
||||
|
||||
Priority (most-specific first):
|
||||
1. line.x_fc_process_variant_id — explicit per-line variant
|
||||
1. line.x_fc_process_variant_id - explicit per-line variant
|
||||
(always wins; this is where G3 propagation lands a value).
|
||||
2. self.x_fc_material_process — Express Orders order-level
|
||||
2. self.x_fc_material_process - Express Orders order-level
|
||||
recipe. Catches the case where G3 propagation failed to
|
||||
reach the line but the header has the recipe.
|
||||
3. part.default_process_id — part's flagged default
|
||||
3. part.default_process_id - part's flagged default
|
||||
variant. Customer-and-part-tuned recipe.
|
||||
4. part.recipe_id — legacy fallback.
|
||||
4. part.recipe_id - legacy fallback.
|
||||
Returns the recipe record or an empty recordset.
|
||||
"""
|
||||
Node = self.env['fusion.plating.process.node']
|
||||
@@ -428,7 +428,7 @@ class SaleOrder(models.Model):
|
||||
'x_fc_part_catalog_id' in self._fields and self.x_fc_part_catalog_id
|
||||
):
|
||||
_logger.info(
|
||||
'SO %s: no line-level part but header carries one — '
|
||||
'SO %s: no line-level part but header carries one - '
|
||||
'treating all lines as a single plating job.', self.name,
|
||||
)
|
||||
plating_lines = self.order_line
|
||||
@@ -439,7 +439,7 @@ class SaleOrder(models.Model):
|
||||
# Group by (recipe, part, spec, thickness, serial). Lines that
|
||||
# share ALL FIVE collapse into one WO. Bundling lines with
|
||||
# different specs / thicknesses / serials under one WO would
|
||||
# carry the first line's values onto the cert + sticker —
|
||||
# carry the first line's values onto the cert + sticker -
|
||||
# silent mis-attestation. No-recipe lines still get their own
|
||||
# group each.
|
||||
groups = {}
|
||||
@@ -513,7 +513,7 @@ class SaleOrder(models.Model):
|
||||
if recipe:
|
||||
vals['recipe_id'] = recipe.id
|
||||
|
||||
# Customer references — mirror onto the job so the shop floor
|
||||
# Customer references - mirror onto the job so the shop floor
|
||||
# has them without round-tripping to the SO.
|
||||
if 'x_fc_customer_job_number' in self._fields \
|
||||
and self.x_fc_customer_job_number:
|
||||
@@ -523,7 +523,7 @@ class SaleOrder(models.Model):
|
||||
if 'x_fc_rush_order' in self._fields:
|
||||
vals['x_fc_rush_order'] = bool(self.x_fc_rush_order)
|
||||
|
||||
# Scheduling targets — mirror the SO's customer-facing dates.
|
||||
# Scheduling targets - mirror the SO's customer-facing dates.
|
||||
if 'x_fc_internal_deadline' in self._fields \
|
||||
and self.x_fc_internal_deadline:
|
||||
vals['x_fc_internal_deadline'] = self.x_fc_internal_deadline
|
||||
@@ -531,7 +531,7 @@ class SaleOrder(models.Model):
|
||||
and self.x_fc_planned_start_date:
|
||||
vals['x_fc_planned_start_date'] = self.x_fc_planned_start_date
|
||||
|
||||
# Operational notes — mirror so the shop has them on the WO.
|
||||
# Operational notes - mirror so the shop has them on the WO.
|
||||
if 'x_fc_internal_note' in self._fields \
|
||||
and self.x_fc_internal_note:
|
||||
vals['x_fc_internal_note'] = self.x_fc_internal_note
|
||||
@@ -539,7 +539,7 @@ class SaleOrder(models.Model):
|
||||
and self.x_fc_external_note:
|
||||
vals['x_fc_external_note'] = self.x_fc_external_note
|
||||
|
||||
# Customer spec / facility / manager — copy from SO if present
|
||||
# Customer spec / facility / manager - copy from SO if present
|
||||
if 'x_fc_customer_spec_id' in self._fields and self.x_fc_customer_spec_id:
|
||||
vals['customer_spec_id'] = self.x_fc_customer_spec_id.id
|
||||
if 'x_fc_facility_id' in self._fields and self.x_fc_facility_id:
|
||||
@@ -568,12 +568,12 @@ class SaleOrder(models.Model):
|
||||
self.name, job.name, qty, (recipe.name if recipe else '-'),
|
||||
)
|
||||
|
||||
# Express Orders (2026-05-26) — apply per-line masking + bake
|
||||
# Express Orders (2026-05-26) - apply per-line masking + bake
|
||||
# overrides to the new job. This runs BEFORE step generation
|
||||
# (which happens in fp.job.action_confirm) so the override rows
|
||||
# are in place when _generate_steps_from_recipe reads override_map.
|
||||
# Step.instructions writes are deferred to a second pass after
|
||||
# step gen — see fp.job.action_confirm override.
|
||||
# step gen - see fp.job.action_confirm override.
|
||||
if job.recipe_id and 'x_fc_masking_enabled' in self.env['sale.order.line']._fields:
|
||||
for sol in lines:
|
||||
if hasattr(sol, '_fp_apply_express_overrides_to_job'):
|
||||
@@ -590,7 +590,7 @@ class SaleOrder(models.Model):
|
||||
return True
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Phase 4 (Sub 11) — workflow stage action buttons.
|
||||
# Phase 4 (Sub 11) - workflow stage action buttons.
|
||||
# Native versions of bridge_mrp's action_fp_* methods. Drop the
|
||||
# mrp.production lookups; talk to fp.job and fp.receiving directly.
|
||||
# ------------------------------------------------------------------
|
||||
@@ -619,7 +619,7 @@ class SaleOrder(models.Model):
|
||||
if Recv is None:
|
||||
return False
|
||||
for rec in Recv.search([('sale_order_id', '=', self.id)]):
|
||||
# Push receiving to its terminal state — 'closed' is the
|
||||
# Push receiving to its terminal state - 'closed' is the
|
||||
# post-Sub-8 terminal; 'accepted' kept as a legacy fallback
|
||||
# only for old records still in pre-Sub-8 states.
|
||||
if rec.state in ('draft', 'counted', 'staged'):
|
||||
@@ -628,7 +628,7 @@ class SaleOrder(models.Model):
|
||||
rec.state = 'accepted'
|
||||
if 'x_fc_receiving_status' in self._fields:
|
||||
self.x_fc_receiving_status = 'received'
|
||||
self.message_post(body=_('Parts accepted — ready to assign manager.'))
|
||||
self.message_post(body=_('Parts accepted - ready to assign manager.'))
|
||||
return True
|
||||
|
||||
def action_fp_assign_to_me(self):
|
||||
@@ -687,6 +687,6 @@ class SaleOrder(models.Model):
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'fp_plant_overview',
|
||||
'name': _('Shop Floor — %s') % self.name,
|
||||
'name': _('Shop Floor - %s') % self.name,
|
||||
'target': 'current',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user