From a9eda9c460000412a6472652d9808d427d260def Mon Sep 17 00:00:00 2001 From: root Date: Fri, 17 Jul 2026 09:57:38 +0000 Subject: [PATCH] chore: only show teams with members in dropdown, fallback to placeholder --- AGENTS.md | 2 +- .../engagement_card/engagement_card.js | 18 ++- .../engagement_card/engagement_card.js | 142 ++++++++++++++++-- .../engagement_card/engagement_card.py | 6 +- 4 files changed, 148 insertions(+), 20 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e992fed..4aedd5f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Service Factory — ERPNext Custom App Project -> **Current Version: V0.1.0029** — Each completed prompt/step increments the patch number by 1 (V0.1.0000 → V0.1.0001 → V0.1.0002…). The agent automatically commits and pushes after every step. +> **Current Version: V0.1.0030** — Each completed prompt/step increments the patch number by 1 (V0.1.0000 → V0.1.0001 → V0.1.0002…). The agent automatically commits and pushes after every step. ## Project Overview diff --git a/service_factory/service_factory/doctype/engagement_card/engagement_card.js b/service_factory/service_factory/doctype/engagement_card/engagement_card.js index 043f3ca..041403d 100644 --- a/service_factory/service_factory/doctype/engagement_card/engagement_card.js +++ b/service_factory/service_factory/doctype/engagement_card/engagement_card.js @@ -173,11 +173,19 @@ function load_select_options(frm) { frm.refresh_field("task"); } - if (doc.teams && doc.teams.length) { - let opts = "\n" + doc.teams.map((t) => t.team_name).join("\n"); - frm.set_df_property("team", "options", opts); - frm.refresh_field("team"); - } + // Load teams with members via the filtered API + frappe.call({ + method: "service_factory.service_factory.doctype.engagement_card.engagement_card.get_options_for", + args: { fieldname: "team" }, + callback: function (r2) { + let teams = r2.message || []; + let opts = teams.length + ? "\n" + teams.join("\n") + : "\n--- Configure teams in Service Factory Settings ---"; + frm.set_df_property("team", "options", opts); + frm.refresh_field("team"); + }, + }); }, }); } diff --git a/service_factory/service_factory/service_factory/doctype/engagement_card/engagement_card.js b/service_factory/service_factory/service_factory/doctype/engagement_card/engagement_card.js index 3d766d1..041403d 100644 --- a/service_factory/service_factory/service_factory/doctype/engagement_card/engagement_card.js +++ b/service_factory/service_factory/service_factory/doctype/engagement_card/engagement_card.js @@ -1,14 +1,32 @@ // Copyright (c) 2026, cclohmar and contributors -// For license information, please see license.txt +// For license information, see please license.txt frappe.ui.form.on("Engagement Card", { onload: function (frm) { + // Ensure customer starts empty on new forms + if (frm.is_new()) { + clear_customer_field(frm); + } load_select_options(frm); toggle_creation_fields(frm); + update_opportunity_state(frm); + + // Filter opportunities by selected customer only + frm.set_query("opportunity", function () { + if (!frm.doc.customer) { + return { filters: { name: "" } }; + } + return { filters: { opportunity_from: "Customer", party_name: frm.doc.customer } }; + }); }, refresh: function (frm) { + // Re-clear customer if awesomplete auto-selected on render + if (frm.is_new()) { + clear_customer_field(frm); + } load_select_options(frm); toggle_creation_fields(frm); + update_opportunity_state(frm); // Quick status actions if (frm.doc.status === "Discovery" && !frm.is_new()) { @@ -29,15 +47,30 @@ frappe.ui.form.on("Engagement Card", { }); } - // Log Activity quick action + // Log Activity quick action (only for saved cards) if (!frm.is_new()) { frm.add_custom_button(__("Log Activity"), function () { let d = new frappe.ui.Dialog({ title: __("Log Activity"), fields: [ - { label: "Activity Type", fieldname: "activity_type", fieldtype: "Select", options: ["Comment", "Milestone", "Note"], reqd: 1 }, - { label: "Content", fieldname: "content", fieldtype: "Text Editor", reqd: 1 }, - { label: "Mark as Milestone", fieldname: "is_milestone", fieldtype: "Check" }, + { + label: "Activity Type", + fieldname: "activity_type", + fieldtype: "Select", + options: ["Comment", "Milestone", "Note"], + reqd: 1, + }, + { + label: "Content", + fieldname: "content", + fieldtype: "Text Editor", + reqd: 1, + }, + { + label: "Mark as Milestone", + fieldname: "is_milestone", + fieldtype: "Check", + }, ], primary_action: function () { let data = d.get_values(); @@ -55,25 +88,104 @@ frappe.ui.form.on("Engagement Card", { }); } }, + // When customer changes, clear and re-filter opportunity + customer: function (frm) { + frm._cust_user_selected = true; + + // Clear opportunity if customer changed + if (frm.doc.opportunity) { + frm.set_value("opportunity", null); + } + + // Only show opportunities linked to selected customer + frm.set_query("opportunity", function () { + if (!frm.doc.customer) { + return { filters: { name: "" } }; + } + return { filters: { opportunity_from: "Customer", party_name: frm.doc.customer } }; + }); + + update_opportunity_state(frm); + }, }); +function update_opportunity_state(frm) { + // Enable opportunity only when a customer is selected + frm.toggle_enable("opportunity", !!frm.doc.customer); +} + +function clear_customer_field(frm) { + if (!frm.is_new()) return; + + // Prevent Frappe from caching this field's value across sessions + if (frappe.boot?.user?.last_selected_values) { + delete frappe.boot.user.last_selected_values.Customer; + } + + // Clear at the model level (direct, bypasses event triggers) + frm.doc.customer = null; + + // Also clear through Frappe's model API + frappe.model.set_value(frm.doctype, frm.docname, "customer", null); + + // Force-clear at the DOM and Awesomplete level repeatedly + // to catch any delayed re-population during form init + let attempts = 0; + const max_attempts = 10; + const timer = setInterval(() => { + attempts++; + let field = frm.get_field("customer"); + if (field) { + if (field.$input) { + field.$input.val(""); + } + if (field.awesomplete) { + field.awesomplete.index = -1; + field.awesomplete.selected = false; + field.awesomplete.autoFirst = false; + } + field.last_value = null; + field.value = null; + } + if (frm.doc) { + frm.doc.customer = null; + } + if (attempts >= max_attempts || !frm.is_new() || frm.doc.__islocal === 0) { + clearInterval(timer); + } + }, 150); +} + function load_select_options(frm) { frappe.call({ method: "frappe.client.get", - args: { doctype: "Service Factory Settings", name: "Service Factory Settings" }, + args: { + doctype: "Service Factory Settings", + name: "Service Factory Settings", + }, callback: function (r) { if (!r.message) return; let doc = r.message; + if (doc.task_types && doc.task_types.length) { let opts = "\n" + doc.task_types.map((t) => t.task_type_name).join("\n"); frm.set_df_property("task", "options", opts); frm.refresh_field("task"); } - if (doc.teams && doc.teams.length) { - let opts = "\n" + doc.teams.map((t) => t.team_name).join("\n"); - frm.set_df_property("team", "options", opts); - frm.refresh_field("team"); - } + + // Load teams with members via the filtered API + frappe.call({ + method: "service_factory.service_factory.doctype.engagement_card.engagement_card.get_options_for", + args: { fieldname: "team" }, + callback: function (r2) { + let teams = r2.message || []; + let opts = teams.length + ? "\n" + teams.join("\n") + : "\n--- Configure teams in Service Factory Settings ---"; + frm.set_df_property("team", "options", opts); + frm.refresh_field("team"); + }, + }); }, }); } @@ -94,6 +206,10 @@ function toggle_creation_fields(frm) { // Time log rollup frappe.ui.form.on("Engagement Card Time Log", { - hours: function (frm) { frm.trigger("update_total_hours"); }, - time_log_remove: function (frm) { frm.trigger("update_total_hours"); }, + hours: function (frm) { + frm.trigger("update_total_hours"); + }, + time_log_remove: function (frm) { + frm.trigger("update_total_hours"); + }, }); diff --git a/service_factory/service_factory/service_factory/doctype/engagement_card/engagement_card.py b/service_factory/service_factory/service_factory/doctype/engagement_card/engagement_card.py index 4f0042d..c4d6f07 100644 --- a/service_factory/service_factory/service_factory/doctype/engagement_card/engagement_card.py +++ b/service_factory/service_factory/service_factory/doctype/engagement_card/engagement_card.py @@ -93,7 +93,11 @@ def get_options_for(fieldname): if fieldname == "task": return [r.task_type_name for r in settings.task_types] elif fieldname == "team": - return [r.team_name for r in settings.teams] + # Only return teams that have at least one member configured + return [ + r.team_name for r in settings.teams + if len(r.get("members", [])) > 0 + ] return []