feat(quality_dashboard): sixth 'Certificates' tab (Tasks 14-16)
Counts endpoint: certificates block — open=draft, overdue=draft+>24h.
Falls back to {open:0, overdue:0} when fp.certificate isn't installed.
JS: TABS array gains the 6th entry. Existing data-driven OWL template
auto-renders both the header tile and the body panel. Tab opens the
fp.certificate kanban grouped by state, filtered to draft by default.
Deep-link: setup() reads action.context.params.tab. The
cert_awaiting_issuance notification email links to
/odoo/action-fp_quality_dashboard?tab=certificates and lands the QM
on the right tab automatically.
Template: 'Open across all 5' → 'Open across all <tabs.length>' so
it stays correct if more tabs are added later.
Manifest: fusion_plating_quality 19.0.6.6.6 → 19.0.7.0.0
(fusion_plating_certificates already in depends — no change needed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
'name': 'Fusion Plating — Quality (QMS)',
|
'name': 'Fusion Plating — Quality (QMS)',
|
||||||
'version': '19.0.6.6.6',
|
'version': '19.0.7.0.0',
|
||||||
'category': 'Manufacturing/Plating',
|
'category': 'Manufacturing/Plating',
|
||||||
'summary': 'Native QMS for plating shops: NCR, CAPA, calibration, AVL, FAIR, '
|
'summary': 'Native QMS for plating shops: NCR, CAPA, calibration, AVL, FAIR, '
|
||||||
'internal audits, customer specs, document control. CE + EE compatible.',
|
'internal audits, customer specs, document control. CE + EE compatible.',
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class FpQualityDashboardController(http.Controller):
|
|||||||
- CAPA: due_date < today AND state not in (effective, closed)
|
- CAPA: due_date < today AND state not in (effective, closed)
|
||||||
- RMA: state='received' for > 5 days (triage past due) OR
|
- RMA: state='received' for > 5 days (triage past due) OR
|
||||||
state in (authorised, shipped_to_us) for > 14 days
|
state in (authorised, shipped_to_us) for > 14 days
|
||||||
|
- Certificate: state='draft' for > 1 day (spec 2026-05-25)
|
||||||
"""
|
"""
|
||||||
env = request.env
|
env = request.env
|
||||||
today = fields.Date.context_today(env.user)
|
today = fields.Date.context_today(env.user)
|
||||||
@@ -33,6 +34,7 @@ class FpQualityDashboardController(http.Controller):
|
|||||||
Ncr = env['fusion.plating.ncr']
|
Ncr = env['fusion.plating.ncr']
|
||||||
Capa = env['fusion.plating.capa']
|
Capa = env['fusion.plating.capa']
|
||||||
Rma = env['fusion.plating.rma']
|
Rma = env['fusion.plating.rma']
|
||||||
|
Cert = env['fp.certificate'] if 'fp.certificate' in env else None
|
||||||
|
|
||||||
d3 = fields.Datetime.subtract(now, days=3)
|
d3 = fields.Datetime.subtract(now, days=3)
|
||||||
d1 = fields.Datetime.subtract(now, days=1)
|
d1 = fields.Datetime.subtract(now, days=1)
|
||||||
@@ -87,4 +89,12 @@ class FpQualityDashboardController(http.Controller):
|
|||||||
('create_date', '<', d14),
|
('create_date', '<', d14),
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
# Spec 2026-05-25 — Certificates tab
|
||||||
|
'certificates': ({
|
||||||
|
'open': Cert.search_count([('state', '=', 'draft')]),
|
||||||
|
'overdue': Cert.search_count([
|
||||||
|
('state', '=', 'draft'),
|
||||||
|
('create_date', '<', d1),
|
||||||
|
]),
|
||||||
|
} if Cert is not None else {'open': 0, 'overdue': 0}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,14 @@ import { useService } from "@web/core/utils/hooks";
|
|||||||
import { rpc } from "@web/core/network/rpc";
|
import { rpc } from "@web/core/network/rpc";
|
||||||
|
|
||||||
const TABS = [
|
const TABS = [
|
||||||
{ id: "holds", label: "Holds", model: "fusion.plating.quality.hold", group: "state", domain: [["state", "in", ["on_hold", "under_review"]]] },
|
{ id: "holds", label: "Holds", model: "fusion.plating.quality.hold", group: "state", domain: [["state", "in", ["on_hold", "under_review"]]] },
|
||||||
{ id: "checks", label: "Checks", model: "fusion.plating.quality.check", group: "state", domain: [] },
|
{ id: "checks", label: "Checks", model: "fusion.plating.quality.check", group: "state", domain: [] },
|
||||||
{ id: "ncrs", label: "NCRs", model: "fusion.plating.ncr", group: "stage_id", domain: [["state", "!=", "closed"]] },
|
{ id: "ncrs", label: "NCRs", model: "fusion.plating.ncr", group: "stage_id", domain: [["state", "!=", "closed"]] },
|
||||||
{ id: "capas", label: "CAPAs", model: "fusion.plating.capa", group: "state", domain: [["state", "not in", ["closed", "effective"]]] },
|
{ id: "capas", label: "CAPAs", model: "fusion.plating.capa", group: "state", domain: [["state", "not in", ["closed", "effective"]]] },
|
||||||
{ id: "rmas", label: "RMAs", model: "fusion.plating.rma", group: "stage_id", domain: [["state", "not in", ["closed", "cancelled"]]] },
|
{ id: "rmas", label: "RMAs", model: "fusion.plating.rma", group: "stage_id", domain: [["state", "not in", ["closed", "cancelled"]]] },
|
||||||
|
// Spec 2026-05-25 — Certificates tab. QM-owned queue of certs
|
||||||
|
// awaiting issuance; drives the post-shop awaiting_cert workflow.
|
||||||
|
{ id: "certificates", label: "Certificates", model: "fp.certificate", group: "state", domain: [["state", "=", "draft"]] },
|
||||||
];
|
];
|
||||||
|
|
||||||
export class FpQualityDashboard extends Component {
|
export class FpQualityDashboard extends Component {
|
||||||
@@ -26,8 +29,14 @@ export class FpQualityDashboard extends Component {
|
|||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
this.action = useService("action");
|
this.action = useService("action");
|
||||||
|
// Spec 2026-05-25 — honor ?tab=<name> deep-link from the
|
||||||
|
// cert_awaiting_issuance notification email so the QM lands
|
||||||
|
// directly on the Certificates tab.
|
||||||
|
const tabParam = this.props.action?.context?.params?.tab
|
||||||
|
|| this.props.action?.params?.tab;
|
||||||
|
const validTab = TABS.find(t => t.id === tabParam);
|
||||||
this.state = useState({
|
this.state = useState({
|
||||||
activeTab: "ncrs",
|
activeTab: validTab ? validTab.id : "ncrs",
|
||||||
counts: TABS.reduce((acc, t) => ({ ...acc, [t.id]: { open: 0, overdue: 0 } }), {}),
|
counts: TABS.reduce((acc, t) => ({ ...acc, [t.id]: { open: 0, overdue: 0 } }), {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<h2 class="mb-2">Quality Overview</h2>
|
<h2 class="mb-2">Quality Overview</h2>
|
||||||
<div class="d-flex gap-4">
|
<div class="d-flex gap-4">
|
||||||
<div>
|
<div>
|
||||||
<div class="o_fp_qd_metric_label">Open across all 5</div>
|
<div class="o_fp_qd_metric_label">Open across all <t t-esc="tabs.length"/></div>
|
||||||
<div class="o_fp_qd_metric_value"><t t-esc="totalOpen"/></div>
|
<div class="o_fp_qd_metric_value"><t t-esc="totalOpen"/></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user