This commit is contained in:
gsinghpal
2026-04-27 00:11:18 -04:00
parent d9f58b9851
commit f08f328688
116 changed files with 9891 additions and 359 deletions

View File

@@ -25,6 +25,14 @@ class SaleOrder(models.Model):
string='Plating Jobs',
compute='_compute_fp_job_count',
)
x_fc_fp_certificate_count = fields.Integer(
string='Certificates',
compute='_compute_fp_certificate_count',
help='Number of fp.certificate records issued (or draft) against '
'this sale order. Surfaced as a smart button so Sarah/Tom '
'can jump straight from the SO to the cert without having '
'to drill through the linked Plating Job first.',
)
# ------------------------------------------------------------------
# Phase 4 (Sub 11) — workflow-stage field + assigned-manager field
@@ -66,6 +74,13 @@ class SaleOrder(models.Model):
[('sale_order_id', '=', so.id)]
)
def _compute_fp_certificate_count(self):
Cert = self.env['fp.certificate'].sudo()
for so in self:
so.x_fc_fp_certificate_count = Cert.search_count(
[('sale_order_id', '=', so.id)]
)
def _compute_workflow_stage(self):
"""Native-jobs override — walks fp.job state instead of mrp.production.
@@ -162,6 +177,28 @@ class SaleOrder(models.Model):
})
return action
def action_view_fp_certificates(self):
"""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([
('sale_order_id', '=', self.id),
])
action = {
'type': 'ir.actions.act_window',
'name': _('Certificates'),
'res_model': 'fp.certificate',
'view_mode': 'list,form',
'domain': [('sale_order_id', '=', self.id)],
'context': {
'default_sale_order_id': self.id,
'default_partner_id': self.partner_id.id,
},
}
if len(certs) == 1:
action.update({'view_mode': 'form', 'res_id': certs.id})
return action
def action_confirm(self):
result = super().action_confirm()
# Only run when the native flag is on
@@ -209,6 +246,18 @@ class SaleOrder(models.Model):
or ('x_fc_coating_config_id' in l._fields and l.x_fc_coating_config_id)
)
)
# Fallback: legacy/configurator SOs that carry part+coating on the
# header but not on the line. Treat the entire order as one
# plating line so the planner gets an fp.job to work against.
if not plating_lines and self.order_line and (
('x_fc_part_catalog_id' in self._fields and self.x_fc_part_catalog_id)
or ('x_fc_coating_config_id' in self._fields and self.x_fc_coating_config_id)
):
_logger.info(
'SO %s: no line-level part/coating but header carries one — '
'treating all lines as a single plating job.', self.name,
)
plating_lines = self.order_line
if not plating_lines:
_logger.info('SO %s: no plating lines, skipping job creation.', self.name)
return
@@ -239,13 +288,38 @@ class SaleOrder(models.Model):
and first_line.x_fc_coating_config_id
or False
)
# Recipe lookup: from coating, fallback to part
# Header fallback for legacy/configurator SOs that put part +
# coating on the SO header instead of the line.
if not part and 'x_fc_part_catalog_id' in self._fields:
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:
# 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.
# 4. part.recipe_id — legacy fallback.
#
# 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`).
recipe = False
if coating and 'recipe_id' in coating._fields and coating.recipe_id:
picked_variant = (
'x_fc_process_variant_id' in first_line._fields
and first_line.x_fc_process_variant_id
or False
)
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:
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:
if not recipe and part and 'recipe_id' in part._fields \
and part.recipe_id:
recipe = part.recipe_id
vals = {