feat(numbering): add parent_number + counters to sale.order

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-12 13:10:55 -04:00
parent 105909470f
commit bc3f584851
2 changed files with 51 additions and 7 deletions

View File

@@ -3,7 +3,7 @@
# License OPL-1 (Odoo Proprietary License v1.0)
{
'name': 'Fusion Plating — Native Jobs',
'version': '19.0.8.21.5',
'version': '19.0.8.22.0',
'category': 'Manufacturing/Plating',
'summary': 'Native plating job model — replaces mrp.production / mrp.workorder bridge.',
'author': 'Nexa Systems Inc.',

View File

@@ -32,6 +32,40 @@ class SaleOrder(models.Model):
'to drill through the linked Plating Job first.',
)
# ------------------------------------------------------------------
# Parent-number hierarchy (2026-05-12 design)
# See docs/superpowers/specs/2026-05-12-parent-number-hierarchy-design.md
# ------------------------------------------------------------------
x_fc_parent_number = fields.Integer(
string='Parent Number',
readonly=True,
copy=False,
index=True,
help='Set on confirm. Drives every linked document\'s name '
'(WO-NNN, IN-NNN, CoC-NNN, ...). Immutable post-assignment.',
)
x_fc_quote_ref = fields.Char(
string='Originally Quoted As',
readonly=True,
copy=False,
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
# 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.
x_fc_wo_count = fields.Integer(string='WO Count', readonly=True, copy=False, default=0)
x_fc_invoice_count = fields.Integer(string='Invoice Count', readonly=True, copy=False, default=0)
x_fc_cn_count = fields.Integer(string='Credit Note Count', readonly=True, copy=False, default=0)
x_fc_cert_count = fields.Integer(string='Certificate Count', readonly=True, copy=False, default=0)
x_fc_delivery_count = fields.Integer(string='Delivery Count', readonly=True, copy=False, default=0)
x_fc_receiving_count = fields.Integer(string='Receiving Count', readonly=True, copy=False, default=0)
x_fc_pickup_count = fields.Integer(string='Pickup Count', readonly=True, copy=False, default=0)
x_fc_ncr_count = fields.Integer(string='NCR Count', readonly=True, copy=False, default=0)
x_fc_capa_count = fields.Integer(string='CAPA Count', readonly=True, copy=False, default=0)
x_fc_hold_count = fields.Integer(string='Hold Count', readonly=True, copy=False, default=0)
x_fc_rma_count = fields.Integer(string='RMA Count', readonly=True, copy=False, default=0)
# ------------------------------------------------------------------
# Phase 4 (Sub 11) — workflow-stage field + assigned-manager field
# relocated from fusion_plating_bridge_mrp. Field re-declared with
@@ -278,13 +312,23 @@ class SaleOrder(models.Model):
part = self.x_fc_part_catalog_id or False
if not coating and 'x_fc_coating_config_id' in self._fields:
coating = self.x_fc_coating_config_id or False
# Recipe lookup priority:
# Recipe lookup priority (specific → generic):
# 1. line.x_fc_process_variant_id — Sarah explicitly picked
# a part-scoped variant on this order line. Always wins.
# 2. coating.recipe_id — coating-config recipe.
# 3. part.default_process_id — part's flagged default.
# 2. part.default_process_id — part's flagged default
# variant. This is the customer-and-part-tuned recipe
# (months of process refinement) and must beat any
# generic template attached to the coating config.
# 3. coating.recipe_id — coating-config recipe
# (generic template fallback when no part variant exists).
# 4. part.recipe_id — legacy fallback.
#
# Order swap 2026-05-12: before this, step (3) ran before (2),
# so jobs with both a part variant AND a coating-attached
# template silently picked the template. WO #01373 (S00063)
# is the documented case — part 9876699373 Rev A had its own
# variant but the job linked to ENP-ALUM-BASIC instead.
#
# If multiple lines in the same WO group have different
# variants we use the FIRST line's variant (consistent with
# everything else in this loop using `first_line`).
@@ -296,12 +340,12 @@ class SaleOrder(models.Model):
)
if picked_variant:
recipe = picked_variant
if not recipe and coating and 'recipe_id' in coating._fields \
and coating.recipe_id:
recipe = coating.recipe_id
if not recipe and part and 'default_process_id' in part._fields \
and part.default_process_id:
recipe = part.default_process_id
if not recipe and coating and 'recipe_id' in coating._fields \
and coating.recipe_id:
recipe = coating.recipe_id
if not recipe and part and 'recipe_id' in part._fields \
and part.recipe_id:
recipe = part.recipe_id