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

@@ -0,0 +1,95 @@
/** @odoo-module **/
// Sub 12 Phase D — Unified Quality Dashboard.
// Five tabs (Holds / Checks / NCRs / CAPAs / RMAs) backed by their list
// kanbans, with a header summary card showing open + overdue counts.
// Each tab embeds the corresponding model's kanban via an action service
// switch. The header counters refresh on tab switch and on a 60-second
// poll.
import { Component, useState, onWillStart, onMounted, onWillUnmount } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { rpc } from "@web/core/network/rpc";
const TABS = [
{ 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: "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: "rmas", label: "RMAs", model: "fusion.plating.rma", group: "stage_id", domain: [["state", "not in", ["closed", "cancelled"]]] },
];
export class FpQualityDashboard extends Component {
static template = "fusion_plating_quality.FpQualityDashboard";
static props = ["*"];
setup() {
this.action = useService("action");
this.state = useState({
activeTab: "ncrs",
counts: TABS.reduce((acc, t) => ({ ...acc, [t.id]: { open: 0, overdue: 0 } }), {}),
});
onWillStart(async () => {
await this._refreshCounts();
});
onMounted(() => {
this._poll = setInterval(() => this._refreshCounts(), 60000);
});
onWillUnmount(() => {
if (this._poll) clearInterval(this._poll);
});
}
async _refreshCounts() {
try {
const result = await rpc("/fp/quality/dashboard/counts");
if (result && typeof result === "object") {
for (const tab of TABS) {
if (result[tab.id]) {
this.state.counts[tab.id] = result[tab.id];
}
}
}
} catch (e) {
// Best-effort; leave counts at zero on RPC failure.
console.warn("FpQualityDashboard: count refresh failed", e);
}
}
selectTab(id) {
this.state.activeTab = id;
}
async openTab(tab) {
// Open the model's full kanban view in the main app area.
await this.action.doAction({
type: "ir.actions.act_window",
name: tab.label,
res_model: tab.model,
view_mode: "kanban,list,form",
views: [[false, "kanban"], [false, "list"], [false, "form"]],
domain: tab.domain,
context: { group_by: tab.group },
});
}
get tabs() {
return TABS;
}
get totalOpen() {
return TABS.reduce(
(sum, t) => sum + (this.state.counts[t.id]?.open || 0), 0,
);
}
get totalOverdue() {
return TABS.reduce(
(sum, t) => sum + (this.state.counts[t.id]?.overdue || 0), 0,
);
}
}
registry.category("actions").add("fp_quality_dashboard", FpQualityDashboard);

View File

@@ -0,0 +1,57 @@
// Sub 12 Phase D — Unified Quality Dashboard styling.
// Reuses the shopfloor SCSS tokens ($fp-page, $fp-card, $fp-border,
// $fp-ink, $fp-accent, etc.) — they are bundled before us via the
// fusion_plating_shopfloor dep, so no @import is needed.
.o_fp_quality_dashboard {
background-color: $fp-page;
min-height: 100%;
.o_fp_card {
background-color: $fp-card;
border: 1px solid $fp-border;
border-radius: 8px;
}
.o_fp_qd_summary {
min-width: 220px;
}
.o_fp_qd_tile {
cursor: pointer;
min-width: 130px;
text-align: left;
transition: transform 0.08s ease-in-out, box-shadow 0.08s ease-in-out;
&:hover {
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}
&.o_fp_qd_active {
border: 2px solid $fp-accent;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}
}
.o_fp_qd_metric_label {
font-size: 0.85em;
color: $fp-ink-mute;
font-weight: 500;
}
.o_fp_qd_metric_value {
font-size: 1.6em;
font-weight: 700;
color: $fp-ink;
line-height: 1.1;
}
.o_fp_qd_metric_sub {
margin-top: 0.25em;
}
.o_fp_qd_panel {
min-height: 200px;
}
}

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="fusion_plating_quality.FpQualityDashboard">
<div class="o_fp_quality_dashboard p-3">
<div class="o_fp_qd_header d-flex flex-wrap gap-3 mb-3">
<div class="o_fp_qd_summary o_fp_card flex-grow-1 p-3">
<h2 class="mb-2">Quality Overview</h2>
<div class="d-flex gap-4">
<div>
<div class="o_fp_qd_metric_label">Open across all 5</div>
<div class="o_fp_qd_metric_value"><t t-esc="totalOpen"/></div>
</div>
<div>
<div class="o_fp_qd_metric_label text-danger">Overdue</div>
<div class="o_fp_qd_metric_value text-danger"><t t-esc="totalOverdue"/></div>
</div>
</div>
</div>
<t t-foreach="tabs" t-as="tab" t-key="tab.id">
<button class="o_fp_qd_tile o_fp_card p-3 border-0"
t-att-class="{ 'o_fp_qd_active': state.activeTab === tab.id }"
t-on-click="() => this.selectTab(tab.id)">
<div class="o_fp_qd_metric_label"><t t-esc="tab.label"/></div>
<div class="o_fp_qd_metric_value">
<t t-esc="state.counts[tab.id]?.open || 0"/>
</div>
<div class="o_fp_qd_metric_sub text-muted small"
t-if="(state.counts[tab.id]?.overdue || 0) > 0">
<t t-esc="state.counts[tab.id].overdue"/> overdue
</div>
</button>
</t>
</div>
<div class="o_fp_qd_body">
<t t-foreach="tabs" t-as="tab" t-key="tab.id">
<div t-if="state.activeTab === tab.id" class="o_fp_qd_panel o_fp_card p-4">
<div class="d-flex justify-content-between align-items-start mb-3">
<div>
<h3 class="mb-1"><t t-esc="tab.label"/></h3>
<div class="text-muted small">
<t t-esc="state.counts[tab.id]?.open || 0"/> open
<t t-if="(state.counts[tab.id]?.overdue || 0) > 0">
<t t-esc="state.counts[tab.id].overdue"/> overdue
</t>
</div>
</div>
<button class="btn btn-primary"
t-on-click="() => this.openTab(tab)">
Open <t t-esc="tab.label"/> Kanban
</button>
</div>
<p class="text-muted">
Click "Open Kanban" to drill into the full
<t t-esc="tab.label.toLowerCase()"/> board with stage / state grouping,
drag-and-drop, and the standard filters.
</p>
</div>
</t>
</div>
</div>
</t>
</templates>