diff --git a/fusion_plating/fusion_plating/models/fp_job.py b/fusion_plating/fusion_plating/models/fp_job.py
index 1e61909d..4298bafe 100644
--- a/fusion_plating/fusion_plating/models/fp_job.py
+++ b/fusion_plating/fusion_plating/models/fp_job.py
@@ -399,7 +399,7 @@ class FpJob(models.Model):
return 'WO'
def _fp_parent_counter_field(self):
- return 'x_fc_wo_count'
+ return 'x_fc_pn_wo_count'
@api.model_create_multi
def create(self, vals_list):
diff --git a/fusion_plating/fusion_plating_certificates/__manifest__.py b/fusion_plating/fusion_plating_certificates/__manifest__.py
index 53315e54..9ba6bd6a 100644
--- a/fusion_plating/fusion_plating_certificates/__manifest__.py
+++ b/fusion_plating/fusion_plating_certificates/__manifest__.py
@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Certificates',
- 'version': '19.0.5.5.0',
+ 'version': '19.0.5.6.0',
'category': 'Manufacturing/Plating',
'summary': 'Certificate registry for CoC, thickness reports, and quality documents.',
'description': """
diff --git a/fusion_plating/fusion_plating_certificates/models/fp_certificate.py b/fusion_plating/fusion_plating_certificates/models/fp_certificate.py
index 13e2edf7..03e16a5a 100644
--- a/fusion_plating/fusion_plating_certificates/models/fp_certificate.py
+++ b/fusion_plating/fusion_plating_certificates/models/fp_certificate.py
@@ -20,7 +20,7 @@ class FpCertificate(models.Model):
"""
_name = 'fp.certificate'
_description = 'Fusion Plating — Certificate'
- _inherit = ['mail.thread', 'mail.activity.mixin']
+ _inherit = ['mail.thread', 'mail.activity.mixin', 'fp.parent.numbered.mixin']
_order = 'issue_date desc, id desc'
name = fields.Char(string='Reference', readonly=True, copy=False, default='New')
@@ -271,14 +271,22 @@ class FpCertificate(models.Model):
rec.trend_alert = alert
rec.trend_explanation = explanation
- # ----- Sequence + spec-limit auto-fill ---------------------------------
+ # ----- Parent-numbered mixin hooks -------------------------------------
+ def _fp_parent_sale_order(self):
+ return self.sale_order_id
+
+ def _fp_name_prefix(self):
+ return 'CoC'
+
+ def _fp_parent_counter_field(self):
+ return 'x_fc_pn_cert_count'
+
+ # ----- Create: parent-derived name (fallback to legacy sequence) -------
@api.model_create_multi
def create(self, vals_list):
SaleOrder = self.env['sale.order']
for vals in vals_list:
- if vals.get('name', 'New') == 'New':
- vals['name'] = self.env['ir.sequence'].next_by_code('fp.certificate') or 'New'
- # Pull thickness spec limits from coating config if not set
+ # Spec-limit auto-fill (existing behaviour, preserved).
already_set = vals.get('spec_min_mils') or vals.get('spec_max_mils')
if not already_set and vals.get('sale_order_id'):
so = SaleOrder.browse(vals['sale_order_id'])
@@ -286,7 +294,23 @@ class FpCertificate(models.Model):
if cfg and cfg.thickness_uom == 'mils':
vals.setdefault('spec_min_mils', cfg.thickness_min or 0.0)
vals.setdefault('spec_max_mils', cfg.thickness_max or 0.0)
- return super().create(vals_list)
+ # Defer naming: let the record exist so the mixin can write
+ # name via raw SQL, then fall back to the legacy sequence if
+ # no parent SO is reachable.
+ if not vals.get('name'):
+ vals['name'] = 'New'
+ records = super().create(vals_list)
+ for rec in records:
+ if rec.name and rec.name != 'New':
+ continue
+ if not rec._fp_assign_parent_name():
+ seq = self.env['ir.sequence'].next_by_code('fp.certificate') or 'New'
+ self.env.cr.execute(
+ "UPDATE fp_certificate SET name = %s WHERE id = %s",
+ (seq, rec.id),
+ )
+ rec.invalidate_recordset(['name'])
+ return records
# ----- State actions ----------------------------------------------------
def action_issue(self):
diff --git a/fusion_plating/fusion_plating_jobs/__manifest__.py b/fusion_plating/fusion_plating_jobs/__manifest__.py
index dee9bbee..85bbe4ee 100644
--- a/fusion_plating/fusion_plating_jobs/__manifest__.py
+++ b/fusion_plating/fusion_plating_jobs/__manifest__.py
@@ -3,7 +3,7 @@
# License OPL-1 (Odoo Proprietary License v1.0)
{
'name': 'Fusion Plating — Native Jobs',
- 'version': '19.0.8.22.4',
+ 'version': '19.0.8.22.5',
'category': 'Manufacturing/Plating',
'summary': 'Native plating job model — replaces mrp.production / mrp.workorder bridge.',
'author': 'Nexa Systems Inc.',
diff --git a/fusion_plating/fusion_plating_jobs/controllers/record_inputs.py b/fusion_plating/fusion_plating_jobs/controllers/record_inputs.py
index 31e44ac7..ffa499d0 100644
--- a/fusion_plating/fusion_plating_jobs/controllers/record_inputs.py
+++ b/fusion_plating/fusion_plating_jobs/controllers/record_inputs.py
@@ -88,16 +88,30 @@ class FpRecordInputsController(http.Controller):
if node and node.description:
instructions_html = node.description
+ # Recipe root id — surfaced so the dialog's "Edit Recipe" shortcut
+ # opens the Simple Editor on the EXACT recipe variant this job is
+ # reading from. Avoids the trap where the operator edits a sibling
+ # variant (e.g. the template, while the job runs the part-specific
+ # clone) and wonders why their min/max never appears.
+ recipe_root_id = False
+ if node:
+ root = node
+ while root.parent_id:
+ root = root.parent_id
+ recipe_root_id = root.id
+
return {
'ok': True,
'step': {
'id': step.id,
'name': step.name,
+ 'recipe_node_id': node.id if node else False,
},
'job': {
'id': step.job_id.id,
'name': step.job_id.name,
},
+ 'recipe_root_id': recipe_root_id,
'prompts': prompts,
'user_initials': user_initials or '',
'instructions_html': instructions_html or '',
diff --git a/fusion_plating/fusion_plating_jobs/models/account_move.py b/fusion_plating/fusion_plating_jobs/models/account_move.py
index 25fd0dd1..829ac535 100644
--- a/fusion_plating/fusion_plating_jobs/models/account_move.py
+++ b/fusion_plating/fusion_plating_jobs/models/account_move.py
@@ -51,7 +51,7 @@ class AccountMove(models.Model):
return 'CN' if self.move_type == 'out_refund' else 'IN'
def _fp_parent_counter_field(self):
- return 'x_fc_cn_count' if self.move_type == 'out_refund' else 'x_fc_invoice_count'
+ return 'x_fc_pn_cn_count' if self.move_type == 'out_refund' else 'x_fc_pn_invoice_count'
# =================================================================
# Create override: block off-flow + assign parent-derived name
diff --git a/fusion_plating/fusion_plating_jobs/models/res_users.py b/fusion_plating/fusion_plating_jobs/models/res_users.py
index aefe9d4a..f347bda6 100644
--- a/fusion_plating/fusion_plating_jobs/models/res_users.py
+++ b/fusion_plating/fusion_plating_jobs/models/res_users.py
@@ -17,6 +17,15 @@ class ResUsers(models.Model):
'a different value and saves, it persists here for every '
'future job and step.',
)
+ x_fc_signature_image = fields.Binary(
+ string='Plating Signature',
+ attachment=True,
+ help='Drawn or uploaded signature image. Used in WO detail and '
+ 'certificate reports for any signature-type prompt this user '
+ 'signed off on; falls back to typed initials when blank. '
+ 'Capture it once in user preferences; it stamps every '
+ 'future sign-off automatically.',
+ )
@api.model
def _fp_default_initials(self):
diff --git a/fusion_plating/fusion_plating_jobs/models/sale_order.py b/fusion_plating/fusion_plating_jobs/models/sale_order.py
index 591db981..d09537d7 100644
--- a/fusion_plating/fusion_plating_jobs/models/sale_order.py
+++ b/fusion_plating/fusion_plating_jobs/models/sale_order.py
@@ -55,17 +55,24 @@ class SaleOrder(models.Model):
# 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)
+ #
+ # 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
+ # are surface counters for smart buttons. Distinct names avoid
+ # the silent compute-override that made Tasks 3+9 fail until 9.5.
+ x_fc_pn_wo_count = fields.Integer(string='Parent: WO Count', readonly=True, copy=False, default=0)
+ x_fc_pn_invoice_count = fields.Integer(string='Parent: Invoice Count', readonly=True, copy=False, default=0)
+ x_fc_pn_cn_count = fields.Integer(string='Parent: Credit Note Count', readonly=True, copy=False, default=0)
+ x_fc_pn_cert_count = fields.Integer(string='Parent: Certificate Count', readonly=True, copy=False, default=0)
+ x_fc_pn_delivery_count = fields.Integer(string='Parent: Delivery Count', readonly=True, copy=False, default=0)
+ x_fc_pn_receiving_count = fields.Integer(string='Parent: Receiving Count', readonly=True, copy=False, default=0)
+ x_fc_pn_pickup_count = fields.Integer(string='Parent: Pickup Count', readonly=True, copy=False, default=0)
+ x_fc_pn_ncr_count = fields.Integer(string='Parent: NCR Count', readonly=True, copy=False, default=0)
+ x_fc_pn_capa_count = fields.Integer(string='Parent: CAPA Count', readonly=True, copy=False, default=0)
+ x_fc_pn_hold_count = fields.Integer(string='Parent: Hold Count', readonly=True, copy=False, default=0)
+ 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
@@ -507,10 +514,10 @@ class SaleOrder(models.Model):
# WO additions pick up from here via the mixin standard path.
if parent and n_groups:
self.env.cr.execute(
- "UPDATE sale_order SET x_fc_wo_count = %s WHERE id = %s",
+ "UPDATE sale_order SET x_fc_pn_wo_count = %s WHERE id = %s",
(n_groups, self.id),
)
- self.invalidate_recordset(['x_fc_wo_count'])
+ self.invalidate_recordset(['x_fc_pn_wo_count'])
return True
# ------------------------------------------------------------------
diff --git a/fusion_plating/fusion_plating_jobs/report/report_fp_job_wo_detail.xml b/fusion_plating/fusion_plating_jobs/report/report_fp_job_wo_detail.xml
index 298bd628..d377b1e9 100644
--- a/fusion_plating/fusion_plating_jobs/report/report_fp_job_wo_detail.xml
+++ b/fusion_plating/fusion_plating_jobs/report/report_fp_job_wo_detail.xml
@@ -74,6 +74,27 @@
t-value="primary_line and 'x_fc_serial_ids' in primary_line._fields
and ', '.join(primary_line.x_fc_serial_ids.mapped('name'))
or ''"/>
+
+
| @@ -338,7 +515,7 @@ | Certification Statement: - Ref. WO# + Ref. WO# @@ -347,6 +524,9 @@ |
| Other Comments: + | |