From 0d85063b5ee721a4f4542320032f3107c0989668 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Tue, 12 May 2026 13:30:37 -0400 Subject: [PATCH] feat(numbering): wire CoC/RCV/DLV/PU into parent-numbered mixin + rename counters Per-model counter fields on sale.order renamed to x_fc_pn_*_count to avoid collision with pre-existing compute fields of the same short name in bridge_mrp / receiving / configurator (silent compute-override was suppressing the storage). 4 child models (fp.certificate, fp.receiving, fusion.plating.delivery, fusion.plating.pickup.request) now derive names as PFX- with -NN suffix from the 2nd onward. fusion.plating.pickup.request gains a sale_order_id field (optional) so pickups created against an SO get parent-derived names, while standalone pickups (pre-SO) fall back to PU/YYYY/NNNN. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../fusion_plating/models/fp_job.py | 2 +- .../__manifest__.py | 2 +- .../models/fp_certificate.py | 36 +++- .../fusion_plating_jobs/__manifest__.py | 2 +- .../controllers/record_inputs.py | 14 ++ .../models/account_move.py | 2 +- .../fusion_plating_jobs/models/res_users.py | 9 + .../fusion_plating_jobs/models/sale_order.py | 33 +-- .../report/report_fp_job_wo_detail.xml | 204 ++++++++++++++++-- .../static/src/js/fp_record_inputs_dialog.js | 61 +++++- .../src/xml/fp_record_inputs_dialog.xml | 42 ++-- .../views/fp_job_form_inherit.xml | 19 ++ .../fusion_plating_logistics/__manifest__.py | 2 +- .../models/fp_delivery.py | 45 +++- .../models/fp_pickup_request.py | 45 +++- .../fusion_plating_receiving/__manifest__.py | 2 +- .../models/fp_receiving.py | 33 ++- 17 files changed, 489 insertions(+), 64 deletions(-) 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 ''"/> + + + + + + + + + @@ -280,7 +358,30 @@ - + + + +
+ +
+
+ + + + + + +
+ + + See Photo # + +
@@ -301,6 +402,68 @@

+ + + +
+
+
+

Photo Evidence ()

+
+ + + + + + + + + + + + +
+
+ +
+
+ Photo # +
+
+ + Step:
+
+ + Captured by: + + + on + +
+
+ + Note: + +
+
+
+
+
+ + + + + + - - - + + + + + + + +
@@ -338,7 +515,7 @@ Certification Statement: - Ref. WO# + Ref. WO#

@@ -347,6 +524,9 @@

Other Comments: +

diff --git a/fusion_plating/fusion_plating_jobs/static/src/js/fp_record_inputs_dialog.js b/fusion_plating/fusion_plating_jobs/static/src/js/fp_record_inputs_dialog.js index 9c9884b1..0f9f837e 100644 --- a/fusion_plating/fusion_plating_jobs/static/src/js/fp_record_inputs_dialog.js +++ b/fusion_plating/fusion_plating_jobs/static/src/js/fp_record_inputs_dialog.js @@ -69,6 +69,7 @@ export class FpRecordInputsDialog extends Component { saving: false, stepName: "", jobName: "", + recipeRootId: false, rows: [], // Operator's persisted initials — pre-filled into signature // / "Reviewer Initials" prompts on load. When the operator @@ -103,6 +104,7 @@ export class FpRecordInputsDialog extends Component { } this.state.stepName = data.step.name; this.state.jobName = data.job.name; + this.state.recipeRootId = data.recipe_root_id || false; this.state.userInitials = data.user_initials || ""; this.state.instructionsHtml = data.instructions_html || ""; this.state.instructionImages = data.instruction_images || []; @@ -193,13 +195,14 @@ export class FpRecordInputsDialog extends Component { isSelection(row) { return row.input_type === "selection"; } isPassFail(row) { return row.input_type === "pass_fail"; } isSignature(row) { return row.input_type === "signature"; } - // Fallback to text for anything else (text, time_hms, ...) + isTimeHms(row) { return row.input_type === "time_hms"; } + // Fallback to text for anything else isText(row) { return !this.isNumeric(row) && !this.isBoolean(row) && !this.isDate(row) && !this.isPhoto(row) && !this.isMulti(row) && !this.isPanel(row) && !this.isSelection(row) && !this.isPassFail(row) - && !this.isSignature(row); + && !this.isSignature(row) && !this.isTimeHms(row); } // Friendly label for the type pill — defaults to the raw key when no @@ -208,6 +211,60 @@ export class FpRecordInputsDialog extends Component { return TYPE_LABELS[row.input_type] || row.input_type || "Text"; } + // Step granularity for — drives the up/down + // arrow increment AND the typed-decimal validity. Defaults of step=1 + // make tablet entry painful when the spec is 0.03 – 0.05 mil because + // every arrow press jumps a full unit. Derive from the recipe-author's + // target_min / target_max precision so operator arrow-taps move in the + // same decimal magnitude the spec was written in. Falls back to + // input-type defaults when no targets are set. + stepFor(row) { + const decimals = Math.max( + this._fpCountDecimals(row.target_min), + this._fpCountDecimals(row.target_max), + ); + if (decimals > 0) { + return Math.pow(10, -decimals).toFixed(decimals); + } + const t = row.input_type || ""; + if (t === "thickness" || t === "multi_point_thickness") return "0.0001"; + if (t === "ph") return "0.01"; + if (t === "temperature" || t === "time_seconds") return "1"; + return "any"; + } + + _fpCountDecimals(n) { + if (n === null || n === undefined || n === "" || n === 0) return 0; + const s = String(n); + const idx = s.indexOf("."); + if (idx < 0) return 0; + // Trim trailing zeros so "0.0500" doesn't look like 4-decimals + // when the author actually wrote 2-decimal precision. + return s.slice(idx + 1).replace(/0+$/, "").length; + } + + // Jump from the runtime dialog into the Simple Recipe Editor on the + // EXACT recipe variant this job step is bound to. Closes the dialog + // (operator returns by re-opening Record Inputs after editing). The + // intent is to remove the "I edited the recipe but nothing changed" + // confusion — they were editing a sibling variant. + async openSimpleEditor() { + if (!this.state.recipeRootId) { + this.notification.add( + _t("No recipe linked to this step yet."), + { type: "warning" }, + ); + return; + } + this.props.close(); + await this.action.doAction({ + type: "ir.actions.client", + tag: "fp_simple_recipe_editor", + name: _t("Edit Recipe"), + context: { recipe_id: this.state.recipeRootId }, + }); + } + // True when the recipe author defined BOTH target_min and target_max // on the prompt — the signal that the operator is expected to capture // a range (multiple readings → record their min and max observation). diff --git a/fusion_plating/fusion_plating_jobs/static/src/xml/fp_record_inputs_dialog.xml b/fusion_plating/fusion_plating_jobs/static/src/xml/fp_record_inputs_dialog.xml index ddcb5650..d021546f 100644 --- a/fusion_plating/fusion_plating_jobs/static/src/xml/fp_record_inputs_dialog.xml +++ b/fusion_plating/fusion_plating_jobs/static/src/xml/fp_record_inputs_dialog.xml @@ -11,6 +11,12 @@ Job
+
@@ -116,7 +122,7 @@ class="o_fp_ri_numeric"> @@ -136,7 +142,7 @@ Min Reading @@ -144,7 +150,7 @@ Max Reading @@ -167,7 +173,7 @@ Min Reading @@ -175,7 +181,7 @@ Max Reading @@ -301,19 +307,19 @@
Avg @@ -325,20 +331,28 @@
- + + + + + + 1 + + + + 1 + + + + + + + + + diff --git a/fusion_plating/fusion_plating_logistics/__manifest__.py b/fusion_plating/fusion_plating_logistics/__manifest__.py index eacad4b8..e3609235 100644 --- a/fusion_plating/fusion_plating_logistics/__manifest__.py +++ b/fusion_plating/fusion_plating_logistics/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating — Logistics', - 'version': '19.0.3.5.0', + 'version': '19.0.3.6.0', 'category': 'Manufacturing/Plating', 'summary': ( 'Pickup & delivery for plating shops: vehicle master, driver ' diff --git a/fusion_plating/fusion_plating_logistics/models/fp_delivery.py b/fusion_plating/fusion_plating_logistics/models/fp_delivery.py index df2bf4d4..780e6beb 100644 --- a/fusion_plating/fusion_plating_logistics/models/fp_delivery.py +++ b/fusion_plating/fusion_plating_logistics/models/fp_delivery.py @@ -24,14 +24,14 @@ class FpDelivery(models.Model): """ _name = 'fusion.plating.delivery' _description = 'Fusion Plating — Delivery' - _inherit = ['mail.thread', 'mail.activity.mixin'] + _inherit = ['mail.thread', 'mail.activity.mixin', 'fp.parent.numbered.mixin'] _order = 'scheduled_date desc, id desc' name = fields.Char( string='Reference', required=True, copy=False, - default=lambda self: self._default_name(), + default='New', tracking=True, ) partner_id = fields.Many2one( @@ -159,8 +159,49 @@ class FpDelivery(models.Model): compute='_compute_custody_count', ) + # ------------------------------------------------------------------ + # Parent-numbered mixin hooks (2026-05-12 numbering hierarchy) + # ------------------------------------------------------------------ + def _fp_parent_sale_order(self): + """No direct sale_order_id on this model — resolve via + job_ref → fp.job.name → job.sale_order_id.""" + if not self.job_ref or 'fp.job' not in self.env: + return self.env['sale.order'] + job = self.env['fp.job'].sudo().search( + [('name', '=', self.job_ref)], limit=1, + ) + return job.sale_order_id if job else self.env['sale.order'] + + def _fp_name_prefix(self): + return 'DLV' + + def _fp_parent_counter_field(self): + return 'x_fc_pn_delivery_count' + + @api.model_create_multi + def create(self, vals_list): + """Parent-derived name (DLV-[-NN]) with legacy-sequence + fallback for deliveries that don't link back to an SO.""" + for vals in vals_list: + 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('fusion.plating.delivery') or 'New' + self.env.cr.execute( + "UPDATE fusion_plating_delivery SET name = %s WHERE id = %s", + (seq, rec.id), + ) + rec.invalidate_recordset(['name']) + return records + @api.model def _default_name(self): + """Retained for any legacy caller. New code should rely on + create() — the parent-numbered mixin sets the name there.""" seq = self.env['ir.sequence'].next_by_code('fusion.plating.delivery') return seq or '/' diff --git a/fusion_plating/fusion_plating_logistics/models/fp_pickup_request.py b/fusion_plating/fusion_plating_logistics/models/fp_pickup_request.py index 8bb9713f..a13b79fa 100644 --- a/fusion_plating/fusion_plating_logistics/models/fp_pickup_request.py +++ b/fusion_plating/fusion_plating_logistics/models/fp_pickup_request.py @@ -21,16 +21,26 @@ class FpPickupRequest(models.Model): """ _name = 'fusion.plating.pickup.request' _description = 'Fusion Plating — Pickup Request' - _inherit = ['mail.thread', 'mail.activity.mixin'] + _inherit = ['mail.thread', 'mail.activity.mixin', 'fp.parent.numbered.mixin'] _order = 'requested_date desc, id desc' name = fields.Char( string='Reference', required=True, copy=False, - default=lambda self: self._default_name(), + default='New', tracking=True, ) + sale_order_id = fields.Many2one( + 'sale.order', + string='Sale Order', + ondelete='set null', + index=True, + help='Sale order this pickup is associated with. Pickup may be ' + 'created BEFORE the SO exists; in that case the ' + 'parent-number naming falls back to the standalone ' + 'PU/YYYY/NNNN sequence and the link can be set later.', + ) partner_id = fields.Many2one( 'res.partner', string='Customer', @@ -126,8 +136,39 @@ class FpPickupRequest(models.Model): compute='_compute_custody_count', ) + # ------------------------------------------------------------------ + # Parent-numbered mixin hooks + # ------------------------------------------------------------------ + def _fp_parent_sale_order(self): + return self.sale_order_id + + def _fp_name_prefix(self): + return 'PU' + + def _fp_parent_counter_field(self): + return 'x_fc_pn_pickup_count' + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + 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('fusion.plating.pickup.request') or 'New' + self.env.cr.execute( + "UPDATE fusion_plating_pickup_request SET name = %s WHERE id = %s", + (seq, rec.id), + ) + rec.invalidate_recordset(['name']) + return records + @api.model def _default_name(self): + """Retained for legacy callers; new flow uses the create() override.""" seq = self.env['ir.sequence'].next_by_code('fusion.plating.pickup.request') return seq or '/' diff --git a/fusion_plating/fusion_plating_receiving/__manifest__.py b/fusion_plating/fusion_plating_receiving/__manifest__.py index ea011f41..bfd9460e 100644 --- a/fusion_plating/fusion_plating_receiving/__manifest__.py +++ b/fusion_plating/fusion_plating_receiving/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating — Receiving & Inspection', - 'version': '19.0.3.7.3', + 'version': '19.0.3.8.0', 'category': 'Manufacturing/Plating', 'summary': 'Parts receiving, inspection, damage logging, and manufacturing gate.', 'description': """ diff --git a/fusion_plating/fusion_plating_receiving/models/fp_receiving.py b/fusion_plating/fusion_plating_receiving/models/fp_receiving.py index ca125ca9..70a63dfe 100644 --- a/fusion_plating/fusion_plating_receiving/models/fp_receiving.py +++ b/fusion_plating/fusion_plating_receiving/models/fp_receiving.py @@ -16,7 +16,7 @@ class FpReceiving(models.Model): """ _name = 'fp.receiving' _description = 'Fusion Plating — Receiving' - _inherit = ['mail.thread', 'mail.activity.mixin'] + _inherit = ['mail.thread', 'mail.activity.mixin', 'fp.parent.numbered.mixin'] _order = 'received_date desc, id desc' name = fields.Char(string='Reference', readonly=True, copy=False, default='New') @@ -97,19 +97,38 @@ class FpReceiving(models.Model): rec.unresolved_damage_count = len(rec.damage_ids.filtered(lambda d: not d.resolved)) # ------------------------------------------------------------------------- - # Sequence + # Sequence + parent-derived naming # ------------------------------------------------------------------------- + def _fp_parent_sale_order(self): + return self.sale_order_id + + def _fp_name_prefix(self): + return 'RCV' + + def _fp_parent_counter_field(self): + return 'x_fc_pn_receiving_count' + @api.model_create_multi def create(self, vals_list): for vals in vals_list: - if vals.get('name', 'New') == 'New': - vals['name'] = self.env['ir.sequence'].next_by_code('fp.receiving') or 'New' # Prefill received_qty from expected_qty so the operator only - # types when the count is wrong (the common case is "all - # arrived"). Saves a step on every routine receipt. + # types when the count is wrong. if vals.get('expected_qty') and not vals.get('received_qty'): vals['received_qty'] = vals['expected_qty'] - return super().create(vals_list) + 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.receiving') or 'New' + self.env.cr.execute( + "UPDATE fp_receiving SET name = %s WHERE id = %s", + (seq, rec.id), + ) + rec.invalidate_recordset(['name']) + return records # ------------------------------------------------------------------------- # Sub 8 — box-count-only actions (new primary flow)