feat(plating): hide quality smart buttons at zero + order-scope NCR/CAPA counts
On the sale.order and fp.job forms, the Holds/Checks/NCRs/CAPAs/RMAs quality smart buttons (and the SO WO button) now hide when their count is 0, so the row shows only quality work that exists. NCR and CAPA counts are re-scoped from customer-wide to order/job via a shared _fp_quality_ncr_ids() helper (NCRs reached through the order's RMAs + the order/job's holds), so each badge and the list its button opens always agree. Also aligned the job RMA button's list domain to its (already SO-scoped) count. Reverts the Sub-12 Phase D "always-visible (zero is OK)" choice back to the module's documented hide-at-zero convention. - fusion_plating_quality 19.0.8.1.0 -> 19.0.8.2.0 - fusion_plating_jobs 19.0.12.4.0 -> 19.0.12.5.0 Deployed + verified on entech (badge == helper across sampled SOs/jobs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
|
||||
{
|
||||
'name': 'Fusion Plating — Quality (QMS)',
|
||||
'version': '19.0.8.1.0',
|
||||
'version': '19.0.8.2.0',
|
||||
'category': 'Manufacturing/Plating',
|
||||
'summary': 'Native QMS for plating shops: NCR, CAPA, calibration, AVL, FAIR, '
|
||||
'internal audits, customer specs, document control. CE + EE compatible.',
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
# Sub 12 Phase D — smart-button counts on fp.job, sale.order, res.partner.
|
||||
#
|
||||
# Each parent record gets badge counts for: Holds, Checks, NCRs, CAPAs,
|
||||
# RMAs. Counts always render (zero is acceptable). Action methods open
|
||||
# the relevant kanban filtered to that record.
|
||||
# RMAs. The buttons hide at a count of 0 (set in the view XML), so the row
|
||||
# only shows quality work that actually exists. NCR/CAPA counts are scoped
|
||||
# to the order/job via RMAs + holds (see _fp_quality_ncr_ids) — NOT the
|
||||
# customer's whole NCR history. Action methods open the matching list.
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
@@ -35,7 +37,6 @@ class FpJobQualitySmart(models.Model):
|
||||
def _compute_fp_quality_counts(self):
|
||||
Hold = self.env['fusion.plating.quality.hold']
|
||||
Check = self.env['fusion.plating.quality.check']
|
||||
Ncr = self.env['fusion.plating.ncr']
|
||||
Capa = self.env['fusion.plating.capa']
|
||||
Rma = self.env['fusion.plating.rma']
|
||||
for job in self:
|
||||
@@ -43,23 +44,39 @@ class FpJobQualitySmart(models.Model):
|
||||
[('job_id', '=', job.id)])
|
||||
job.fp_qc_check_count = Check.search_count(
|
||||
[('job_id', '=', job.id)])
|
||||
ncr_ids = []
|
||||
capa_ids = []
|
||||
rma_ids = []
|
||||
if job.sale_order_id:
|
||||
rma_ids = Rma.search(
|
||||
[('sale_order_id', '=', job.sale_order_id.id)]).ids
|
||||
if rma_ids:
|
||||
ncr_ids = Ncr.search([('rma_id', 'in', rma_ids)]).ids
|
||||
if job.partner_id:
|
||||
ncr_ids = list(set(ncr_ids + Ncr.search([
|
||||
('customer_partner_id', '=', job.partner_id.id),
|
||||
]).ids))
|
||||
if ncr_ids:
|
||||
capa_ids = Capa.search([('ncr_id', 'in', ncr_ids)]).ids
|
||||
job.fp_qc_rma_count = Rma.search_count(
|
||||
[('sale_order_id', '=', job.sale_order_id.id)]
|
||||
) if job.sale_order_id else 0
|
||||
ncr_ids = job._fp_quality_ncr_ids()
|
||||
job.fp_qc_ncr_count = len(ncr_ids)
|
||||
job.fp_qc_capa_count = len(capa_ids)
|
||||
job.fp_qc_rma_count = len(rma_ids)
|
||||
job.fp_qc_capa_count = Capa.search_count(
|
||||
[('ncr_id', 'in', ncr_ids)]) if ncr_ids else 0
|
||||
|
||||
def _fp_quality_ncr_ids(self):
|
||||
"""Job-scoped NCR ids — single source of truth for both the badge
|
||||
count and the smart-button list.
|
||||
|
||||
There is no ncr.job_id / ncr.sale_order_id field, so the only honest
|
||||
links are:
|
||||
- NCRs reached via this job's order's RMAs (ncr.rma_id), and
|
||||
- NCRs spawned from this job's holds (hold.ncr_id).
|
||||
An NCR with neither link is customer-level history with no tie to a
|
||||
single job, so it is intentionally excluded.
|
||||
"""
|
||||
self.ensure_one()
|
||||
Ncr = self.env['fusion.plating.ncr']
|
||||
Hold = self.env['fusion.plating.quality.hold']
|
||||
Rma = self.env['fusion.plating.rma']
|
||||
ncr_ids = set()
|
||||
if self.sale_order_id:
|
||||
rma_ids = Rma.search(
|
||||
[('sale_order_id', '=', self.sale_order_id.id)]).ids
|
||||
if rma_ids:
|
||||
ncr_ids.update(Ncr.search([('rma_id', 'in', rma_ids)]).ids)
|
||||
ncr_ids.update(Hold.search([
|
||||
('job_id', '=', self.id), ('ncr_id', '!=', False),
|
||||
]).mapped('ncr_id').ids)
|
||||
return list(ncr_ids)
|
||||
|
||||
def action_view_fp_holds(self):
|
||||
self.ensure_one()
|
||||
@@ -85,43 +102,39 @@ class FpJobQualitySmart(models.Model):
|
||||
|
||||
def action_view_fp_ncrs(self):
|
||||
self.ensure_one()
|
||||
domain = [('customer_partner_id', '=', self.partner_id.id)]
|
||||
return {
|
||||
'name': _('NCRs'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'fusion.plating.ncr',
|
||||
'view_mode': 'kanban,list,form',
|
||||
'domain': domain,
|
||||
'domain': [('id', 'in', self._fp_quality_ncr_ids())],
|
||||
'context': {'default_customer_partner_id': self.partner_id.id},
|
||||
}
|
||||
|
||||
def action_view_fp_capas(self):
|
||||
self.ensure_one()
|
||||
Ncr = self.env['fusion.plating.ncr']
|
||||
ncr_ids = Ncr.search([
|
||||
('customer_partner_id', '=', self.partner_id.id),
|
||||
]).ids
|
||||
return {
|
||||
'name': _('CAPAs'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'fusion.plating.capa',
|
||||
'view_mode': 'list,form',
|
||||
'domain': [('ncr_id', 'in', ncr_ids)],
|
||||
'domain': [('ncr_id', 'in', self._fp_quality_ncr_ids())],
|
||||
}
|
||||
|
||||
def action_view_fp_rmas(self):
|
||||
self.ensure_one()
|
||||
domain = [('partner_id', '=', self.partner_id.id)]
|
||||
if self.sale_order_id:
|
||||
domain = ['|', ('sale_order_id', '=', self.sale_order_id.id),
|
||||
('partner_id', '=', self.partner_id.id)]
|
||||
# SO-scoped to match the badge count. RMA.sale_order_id is required,
|
||||
# so a job with no order matches nothing — same as the count's 0.
|
||||
return {
|
||||
'name': _('RMAs'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'fusion.plating.rma',
|
||||
'view_mode': 'kanban,list,form',
|
||||
'domain': domain,
|
||||
'context': {'default_partner_id': self.partner_id.id},
|
||||
'domain': [('sale_order_id', '=', self.sale_order_id.id)],
|
||||
'context': {
|
||||
'default_partner_id': self.partner_id.id,
|
||||
'default_sale_order_id': self.sale_order_id.id,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +161,6 @@ class SaleOrderQualitySmart(models.Model):
|
||||
def _compute_fp_qc_counts(self):
|
||||
Hold = self.env['fusion.plating.quality.hold']
|
||||
Check = self.env['fusion.plating.quality.check']
|
||||
Ncr = self.env['fusion.plating.ncr']
|
||||
Capa = self.env['fusion.plating.capa']
|
||||
Rma = self.env['fusion.plating.rma']
|
||||
Job = self.env['fp.job']
|
||||
@@ -158,19 +170,40 @@ class SaleOrderQualitySmart(models.Model):
|
||||
[('job_id', 'in', job_ids)]) if job_ids else 0
|
||||
so.fp_qc_check_count = Check.search_count(
|
||||
[('job_id', 'in', job_ids)]) if job_ids else 0
|
||||
rma_ids = Rma.search([('sale_order_id', '=', so.id)]).ids
|
||||
so.fp_qc_rma_count = len(rma_ids)
|
||||
ncr_ids = []
|
||||
if rma_ids:
|
||||
ncr_ids = Ncr.search([('rma_id', 'in', rma_ids)]).ids
|
||||
if so.partner_id:
|
||||
ncr_ids = list(set(ncr_ids + Ncr.search([
|
||||
('customer_partner_id', '=', so.partner_id.id),
|
||||
]).ids))
|
||||
so.fp_qc_rma_count = Rma.search_count(
|
||||
[('sale_order_id', '=', so.id)])
|
||||
ncr_ids = so._fp_quality_ncr_ids()
|
||||
so.fp_qc_ncr_count_so = len(ncr_ids)
|
||||
so.fp_qc_capa_count = Capa.search_count(
|
||||
[('ncr_id', 'in', ncr_ids)]) if ncr_ids else 0
|
||||
|
||||
def _fp_quality_ncr_ids(self):
|
||||
"""Order-scoped NCR ids — single source of truth for both the badge
|
||||
count and the smart-button list.
|
||||
|
||||
There is no ncr.sale_order_id / ncr.job_id field, so the only honest
|
||||
links are:
|
||||
- NCRs reached via this order's RMAs (ncr.rma_id), and
|
||||
- NCRs spawned from holds on this order's jobs (hold.ncr_id).
|
||||
An NCR with neither link is customer-level history with no tie to a
|
||||
single order, so it is intentionally excluded.
|
||||
"""
|
||||
self.ensure_one()
|
||||
Ncr = self.env['fusion.plating.ncr']
|
||||
Hold = self.env['fusion.plating.quality.hold']
|
||||
Rma = self.env['fusion.plating.rma']
|
||||
Job = self.env['fp.job']
|
||||
ncr_ids = set()
|
||||
rma_ids = Rma.search([('sale_order_id', '=', self.id)]).ids
|
||||
if rma_ids:
|
||||
ncr_ids.update(Ncr.search([('rma_id', 'in', rma_ids)]).ids)
|
||||
job_ids = Job.search([('sale_order_id', '=', self.id)]).ids
|
||||
if job_ids:
|
||||
ncr_ids.update(Hold.search([
|
||||
('job_id', 'in', job_ids), ('ncr_id', '!=', False),
|
||||
]).mapped('ncr_id').ids)
|
||||
return list(ncr_ids)
|
||||
|
||||
def action_view_fp_holds(self):
|
||||
self.ensure_one()
|
||||
Job = self.env['fp.job']
|
||||
@@ -202,21 +235,17 @@ class SaleOrderQualitySmart(models.Model):
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'fusion.plating.ncr',
|
||||
'view_mode': 'kanban,list,form',
|
||||
'domain': [('customer_partner_id', '=', self.partner_id.id)],
|
||||
'domain': [('id', 'in', self._fp_quality_ncr_ids())],
|
||||
}
|
||||
|
||||
def action_view_fp_capas(self):
|
||||
self.ensure_one()
|
||||
Ncr = self.env['fusion.plating.ncr']
|
||||
ncr_ids = Ncr.search([
|
||||
('customer_partner_id', '=', self.partner_id.id),
|
||||
]).ids
|
||||
return {
|
||||
'name': _('CAPAs'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'fusion.plating.capa',
|
||||
'view_mode': 'list,form',
|
||||
'domain': [('ncr_id', 'in', ncr_ids)],
|
||||
'domain': [('ncr_id', 'in', self._fp_quality_ncr_ids())],
|
||||
}
|
||||
|
||||
def action_view_fp_rmas(self):
|
||||
|
||||
@@ -25,24 +25,31 @@
|
||||
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='button_box']" position="inside">
|
||||
<!-- Hidden at a count of 0 so the row shows only quality
|
||||
work that actually exists on this order (2026-06-04). -->
|
||||
<button name="action_view_fp_holds" type="object"
|
||||
class="oe_stat_button" icon="fa-hand-paper-o">
|
||||
class="oe_stat_button" icon="fa-hand-paper-o"
|
||||
invisible="fp_qc_hold_count == 0">
|
||||
<field name="fp_qc_hold_count" widget="statinfo" string="Holds"/>
|
||||
</button>
|
||||
<button name="action_view_fp_checks" type="object"
|
||||
class="oe_stat_button" icon="fa-check-square-o">
|
||||
class="oe_stat_button" icon="fa-check-square-o"
|
||||
invisible="fp_qc_check_count == 0">
|
||||
<field name="fp_qc_check_count" widget="statinfo" string="Checks"/>
|
||||
</button>
|
||||
<button name="action_view_fp_ncrs_so" type="object"
|
||||
class="oe_stat_button" icon="fa-exclamation-triangle">
|
||||
class="oe_stat_button" icon="fa-exclamation-triangle"
|
||||
invisible="fp_qc_ncr_count_so == 0">
|
||||
<field name="fp_qc_ncr_count_so" widget="statinfo" string="NCRs"/>
|
||||
</button>
|
||||
<button name="action_view_fp_capas" type="object"
|
||||
class="oe_stat_button" icon="fa-wrench">
|
||||
class="oe_stat_button" icon="fa-wrench"
|
||||
invisible="fp_qc_capa_count == 0">
|
||||
<field name="fp_qc_capa_count" widget="statinfo" string="CAPAs"/>
|
||||
</button>
|
||||
<button name="action_view_fp_rmas" type="object"
|
||||
class="oe_stat_button" icon="fa-undo">
|
||||
class="oe_stat_button" icon="fa-undo"
|
||||
invisible="fp_qc_rma_count == 0">
|
||||
<field name="fp_qc_rma_count" widget="statinfo" string="RMAs"/>
|
||||
</button>
|
||||
</xpath>
|
||||
|
||||
Reference in New Issue
Block a user