# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) # # sale.order.action_confirm hook — creates fp.job records when the # x_fc_use_native_jobs setting is True. Mirrors bridge_mrp's # _fp_auto_create_mo but creates fp.job instead of mrp.production. # # When the setting is False (default), this hook is a no-op and # bridge_mrp's MO-creation hook handles the flow. import logging from odoo import _, api, fields, models _logger = logging.getLogger(__name__) class SaleOrder(models.Model): _inherit = 'sale.order' def action_confirm(self): result = super().action_confirm() # Only run when the native flag is on ICP = self.env['ir.config_parameter'].sudo() if ICP.get_param('fusion_plating_jobs.use_native_jobs') == 'True': for so in self: so._fp_auto_create_job() return result def _fp_auto_create_job(self): """Create fp.job(s) from the SO's plating lines. Lines that share a `x_fc_wo_group_tag` collapse into one job; untagged lines get one job per line. Mirrors bridge_mrp's _fp_auto_create_mo grouping logic. """ self.ensure_one() Job = self.env['fp.job'].sudo() # Idempotency: skip if a job already references this SO existing = Job.search([('sale_order_id', '=', self.id)], limit=1) if existing: return # Find plating lines (those with a part_catalog_id or coating_config_id) plating_lines = self.order_line.filtered( lambda l: ( ('x_fc_part_catalog_id' in l._fields and l.x_fc_part_catalog_id) or ('x_fc_coating_config_id' in l._fields and l.x_fc_coating_config_id) ) ) if not plating_lines: _logger.info('SO %s: no plating lines, skipping job creation.', self.name) return # Group by x_fc_wo_group_tag (untagged → distinct group per line) groups = {} # tag → recordset of lines untagged_idx = 0 for line in plating_lines: tag = ( 'x_fc_wo_group_tag' in line._fields and line.x_fc_wo_group_tag ) or False if not tag: untagged_idx += 1 tag = '__untagged_%d' % untagged_idx groups[tag] = groups.get(tag, self.env['sale.order.line']) | line # Create a job per group for tag, lines in groups.items(): first_line = lines[0] qty = sum(lines.mapped('product_uom_qty')) part = ( 'x_fc_part_catalog_id' in first_line._fields and first_line.x_fc_part_catalog_id or False ) coating = ( 'x_fc_coating_config_id' in first_line._fields and first_line.x_fc_coating_config_id or False ) # Recipe lookup: from coating, fallback to part recipe = False if 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 part and 'recipe_id' in part._fields and part.recipe_id: recipe = part.recipe_id vals = { 'partner_id': self.partner_id.id, 'product_id': first_line.product_id.id if first_line.product_id else False, 'qty': qty, 'origin': self.name, 'sale_order_id': self.id, 'sale_order_line_ids': [(6, 0, lines.ids)], 'date_deadline': self.commitment_date or self.date_order, } if part: vals['part_catalog_id'] = part.id if coating: vals['coating_config_id'] = coating.id if recipe: vals['recipe_id'] = recipe.id # 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: vals['facility_id'] = self.x_fc_facility_id.id if 'x_fc_manager_id' in self._fields and self.x_fc_manager_id: vals['manager_id'] = self.x_fc_manager_id.id # Quoted revenue: sum line totals vals['quoted_revenue'] = sum(lines.mapped('price_subtotal')) job = Job.create(vals) _logger.info( 'SO %s: created fp.job %s (qty=%s, recipe=%s)', self.name, job.name, qty, (recipe.name if recipe else '-'), ) return True