diff --git a/service_factory/hooks.py b/service_factory/hooks.py index 1e43834..3b27dca 100644 --- a/service_factory/hooks.py +++ b/service_factory/hooks.py @@ -43,7 +43,7 @@ app_license = "mit" # page_js = {"page" : "public/js/file.js"} # include js in doctype views -# doctype_js = {"doctype" : "public/js/doctype.js"} +doctype_js = {"Execution Card" : "doctype/execution_card/execution_card.js"} # doctype_list_js = {"doctype" : "public/js/doctype_list.js"} # doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} # doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} @@ -167,6 +167,14 @@ app_license = "mit" # ], # } +# Fixtures - sync Workspaces during migrate +# ----------------------------------------- +fixtures = [ + {"dt": "Workspace", "filters": [ + ["module", "=", "Service Factory"] + ]} +] + # Testing # ------- diff --git a/service_factory/service_factory/doctype/execution_card/execution_card.js b/service_factory/service_factory/doctype/execution_card/execution_card.js new file mode 100644 index 0000000..7eeffaf --- /dev/null +++ b/service_factory/service_factory/doctype/execution_card/execution_card.js @@ -0,0 +1,123 @@ +// Copyright (c) 2026, cclohmar and contributors +// For license information, please see license.txt + +frappe.ui.form.on("Execution Card", { + refresh: function (frm) { + setup_timer_widget(frm); + }, + + status: function (frm) { + setup_timer_widget(frm); + }, +}); + +function setup_timer_widget(frm) { + // Clear existing timer buttons + frm.remove_custom_button(__("Start Work"), "Actions"); + frm.remove_custom_button(__("Pause Work"), "Actions"); + frm.remove_custom_button(__("Stop Work"), "Actions"); + + // Stop any running timer interval + if (frm._timer_interval) { + clearInterval(frm._timer_interval); + frm._timer_interval = null; + } + + // Remove existing timer display + frm.$wrapper.find(".execution-card-timer").remove(); + + const status = frm.doc.status; + + if (status === "Open" || status === "Paused") { + frm.add_custom_button( + __("Start Work"), + function () { + frm.call("start_work").then((r) => { + frm.refresh(); + }); + }, + __("Actions") + ); + } + + if (status === "Running") { + frm.add_custom_button( + __("Pause Work"), + function () { + frm.call("pause_work").then((r) => { + frm.refresh(); + }); + }, + __("Actions") + ); + + frm.add_custom_button( + __("Stop Work"), + function () { + frm.call("stop_work").then((r) => { + frm.refresh(); + }); + }, + __("Actions") + ); + + // Start live timer display + start_live_timer(frm); + } + + if (status === "Submitted") { + frm.add_custom_button( + __("View Time Logs"), + function () { + frappe.set_route("List", "Execution Card Log", { + parent: frm.doc.name, + }); + }, + __("Actions") + ); + } +} + +function start_live_timer(frm) { + // Add a timer display element + const $timer = $(` +
+
+
+
⏱ ${__("Elapsed Time")}
+
+ 00:00:00 +
+
+
+
+ `); + + // Insert timer after the form title + frm.$wrapper.find(".form-page").prepend($timer); + + // Poll for elapsed time every second + frm._timer_interval = setInterval(function () { + frm.call("get_active_time").then((r) => { + if (r.message) { + const seconds = r.message.elapsed_seconds || 0; + const display = format_seconds(seconds); + $timer.find(".timer-display").text(display); + } + }); + }, 1000); +} + +function format_seconds(total_seconds) { + const hours = Math.floor(total_seconds / 3600); + const minutes = Math.floor((total_seconds % 3600) / 60); + const seconds = Math.floor(total_seconds % 60); + + return ( + String(hours).padStart(2, "0") + + ":" + + String(minutes).padStart(2, "0") + + ":" + + String(seconds).padStart(2, "0") + ); +} diff --git a/service_factory/service_factory/workspace/director_workspace/director_workspace.json b/service_factory/service_factory/workspace/director_workspace/director_workspace.json new file mode 100644 index 0000000..e520824 --- /dev/null +++ b/service_factory/service_factory/workspace/director_workspace/director_workspace.json @@ -0,0 +1,170 @@ +{ + "charts": [], + "content": "[{\"id\":\"director_header_1\",\"type\":\"header\",\"data\":{\"text\":\"Financial Margin Ledger\",\"col\":12}},{\"id\":\"director_card_margin\",\"type\":\"card\",\"data\":{\"card_name\":\"Margin Overview\",\"col\":6}},{\"id\":\"director_card_pnl\",\"type\":\"card\",\"data\":{\"card_name\":\"P&L Summary\",\"col\":6}},{\"id\":\"director_spacer_1\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"director_header_2\",\"type\":\"header\",\"data\":{\"text\":\"Delivery Performance\",\"col\":12}},{\"id\":\"director_card_efficiency\",\"type\":\"card\",\"data\":{\"card_name\":\"Delivery Efficiency\",\"col\":6}},{\"id\":\"director_card_burn\",\"type\":\"card\",\"data\":{\"card_name\":\"Resource Burn-Down\",\"col\":6}},{\"id\":\"director_spacer_2\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"director_header_3\",\"type\":\"header\",\"data\":{\"text\":\"Executive KPIs\",\"col\":12}},{\"id\":\"director_shortcut_revenue\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Revenue Pipeline\",\"col\":3}},{\"id\":\"director_shortcut_costs\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Delivery Costs\",\"col\":3}},{\"id\":\"director_shortcut_margin\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Gross Margin %\",\"col\":3}},{\"id\":\"director_shortcut_utilization\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Utilization Rate\",\"col\":3}}]", + "creation": "2026-07-15 19:00:00.000000", + "custom_blocks": [], + "docstatus": 0, + "doctype": "Workspace", + "for_user": "", + "hide_custom": 0, + "icon": "chart", + "idx": 1, + "is_hidden": 0, + "label": "Director Workspace", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Financial Overview", + "link_count": 3, + "link_type": "DocType", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Engagement Order", + "link_count": 0, + "link_to": "Engagement Order", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Execution Card", + "link_count": 0, + "link_to": "Execution Card", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Resource Center", + "link_count": 0, + "link_to": "Resource Center", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Analytics", + "link_count": 4, + "link_type": "DocType", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "All Orders", + "link_count": 0, + "link_to": "Engagement Order", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Completed Orders", + "link_count": 0, + "link_to": "Engagement Order", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Engagement Recipes", + "link_count": 0, + "link_to": "Engagement Recipe", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Submitted Cards", + "link_count": 0, + "link_to": "Execution Card", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2026-07-15 19:00:00.000000", + "modified_by": "Administrator", + "module": "Service Factory", + "name": "Director Workspace", + "number_cards": [], + "owner": "Administrator", + "parent_page": "", + "public": 1, + "quick_lists": [], + "roles": [], + "sequence_id": 3.0, + "shortcuts": [ + { + "action": "List", + "color": "Green", + "doc_type": "Engagement Order", + "format": "{}", + "icon": "currency", + "label": "Revenue Pipeline", + "link_to": "Engagement Order", + "stats_filter": "[[\"Engagement Order\",\"docstatus\",\"=\",1],[\"Engagement Order\",\"status\",\"!=\",\"Cancelled\"]]", + "type": "DocType" + }, + { + "action": "List", + "color": "Red", + "doc_type": "Execution Card", + "format": "{}", + "icon": "cost", + "label": "Delivery Costs", + "link_to": "Execution Card", + "stats_filter": "[[\"Execution Card\",\"status\",\"=\",\"Submitted\"]]", + "type": "DocType" + }, + { + "action": "Report", + "color": "Blue", + "doc_type": "Engagement Order", + "format": "{}", + "icon": "percent", + "label": "Gross Margin %", + "link_to": "Engagement Order", + "stats_filter": "[]", + "type": "DocType" + }, + { + "action": "List", + "color": "Orange", + "doc_type": "Resource Center", + "format": "{}", + "icon": "chart", + "label": "Utilization Rate", + "link_to": "Resource Center", + "stats_filter": "[]", + "type": "DocType" + } + ], + "title": "Director Workspace", + "type": "Workspace" +} diff --git a/service_factory/service_factory/workspace/engineer_workspace/engineer_workspace.json b/service_factory/service_factory/workspace/engineer_workspace/engineer_workspace.json new file mode 100644 index 0000000..0cc5cdc --- /dev/null +++ b/service_factory/service_factory/workspace/engineer_workspace/engineer_workspace.json @@ -0,0 +1,146 @@ +{ + "charts": [], + "content": "[{\"id\":\"engineer_header_1\",\"type\":\"header\",\"data\":{\"text\":\"My Active Deliverables\",\"col\":12}},{\"id\":\"engineer_card_my_cards\",\"type\":\"card\",\"data\":{\"card_name\":\"My Execution Cards\",\"col\":4}},{\"id\":\"engineer_card_orders\",\"type\":\"card\",\"data\":{\"card_name\":\"My Engagement Orders\",\"col\":4}},{\"id\":\"engineer_card_reports\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"engineer_spacer_1\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"engineer_header_2\",\"type\":\"header\",\"data\":{\"text\":\"Time Tracking\",\"col\":12}},{\"id\":\"engineer_card_timer\",\"type\":\"card\",\"data\":{\"card_name\":\"Execution Card Timer\",\"col\":6}},{\"id\":\"engineer_card_hours\",\"type\":\"card\",\"data\":{\"card_name\":\"My Hours This Week\",\"col\":6}},{\"id\":\"engineer_spacer_2\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"engineer_header_3\",\"type\":\"header\",\"data\":{\"text\":\"Quick Actions\",\"col\":12}},{\"id\":\"engineer_shortcut_new_card\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"New Execution Card\",\"col\":3}},{\"id\":\"engineer_shortcut_active\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"My Active Cards\",\"col\":3}}]", + "creation": "2026-07-15 19:00:00.000000", + "custom_blocks": [], + "docstatus": 0, + "doctype": "Workspace", + "for_user": "", + "hide_custom": 0, + "icon": "workflow", + "idx": 1, + "is_hidden": 0, + "label": "Engineer Workspace", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "My Execution Cards", + "link_count": 3, + "link_type": "DocType", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Execution Card", + "link_count": 0, + "link_to": "Execution Card", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "My Open Cards", + "link_count": 0, + "link_to": "Execution Card", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "My Submitted Cards", + "link_count": 0, + "link_to": "Execution Card", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "My Engagement Orders", + "link_count": 2, + "link_type": "DocType", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Engagement Order", + "link_count": 0, + "link_to": "Engagement Order", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "My Active Orders", + "link_count": 0, + "link_to": "Engagement Order", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Reports", + "link_count": 1, + "link_type": "DocType", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "My Hours", + "link_count": 0, + "link_to": "Execution Card", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2026-07-15 19:00:00.000000", + "modified_by": "Administrator", + "module": "Service Factory", + "name": "Engineer Workspace", + "number_cards": [], + "owner": "Administrator", + "parent_page": "", + "public": 1, + "quick_lists": [], + "roles": [], + "sequence_id": 1.0, + "shortcuts": [ + { + "action": "New", + "color": "Blue", + "doc_type": "Execution Card", + "format": "{}", + "icon": "add", + "label": "New Execution Card", + "link_to": "Execution Card", + "stats_filter": "[]", + "type": "DocType" + }, + { + "action": "List", + "color": "Grey", + "doc_type": "Execution Card", + "format": "{}", + "icon": "list", + "label": "My Active Cards", + "link_to": "Execution Card", + "stats_filter": "[[\"Execution Card\",\"assigned_employee\",\"=\",\"__user_employee__\"],[\"Execution Card\",\"status\",\"in\",[\"Open\",\"Running\",\"Paused\"]]]", + "type": "DocType" + } + ], + "title": "Engineer Workspace", + "type": "Workspace" +} diff --git a/service_factory/service_factory/workspace/manager_workspace/manager_workspace.json b/service_factory/service_factory/workspace/manager_workspace/manager_workspace.json new file mode 100644 index 0000000..f9f25d6 --- /dev/null +++ b/service_factory/service_factory/workspace/manager_workspace/manager_workspace.json @@ -0,0 +1,221 @@ +{ + "charts": [], + "content": "[{\"id\":\"manager_header_1\",\"type\":\"header\",\"data\":{\"text\":\"Operational Overview\",\"col\":12}},{\"id\":\"manager_card_orders\",\"type\":\"card\",\"data\":{\"card_name\":\"Engagement Orders\",\"col\":4}},{\"id\":\"manager_card_centers\",\"type\":\"card\",\"data\":{\"card_name\":\"Resource Centers\",\"col\":4}},{\"id\":\"manager_card_recipes\",\"type\":\"card\",\"data\":{\"card_name\":\"Engagement Recipes\",\"col\":4}},{\"id\":\"manager_spacer_1\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"manager_header_2\",\"type\":\"header\",\"data\":{\"text\":\"Resource Capacity\",\"col\":12}},{\"id\":\"manager_card_gantt\",\"type\":\"card\",\"data\":{\"card_name\":\"Schedule Gantt\",\"col\":6}},{\"id\":\"manager_card_alerts\",\"type\":\"card\",\"data\":{\"card_name\":\"Capacity Alerts\",\"col\":6}},{\"id\":\"manager_spacer_2\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"manager_header_3\",\"type\":\"header\",\"data\":{\"text\":\"Key Metrics\",\"col\":12}},{\"id\":\"manager_shortcut_active\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Active Engagements\",\"col\":3}},{\"id\":\"manager_shortcut_stalled\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stalled / Blocked\",\"col\":3}},{\"id\":\"manager_shortcut_capacity\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Capacity Overview\",\"col\":3}},{\"id\":\"manager_shortcut_executions\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"All Execution Cards\",\"col\":3}}]", + "creation": "2026-07-15 19:00:00.000000", + "custom_blocks": [], + "docstatus": 0, + "doctype": "Workspace", + "for_user": "", + "hide_custom": 0, + "icon": "share", + "idx": 1, + "is_hidden": 0, + "label": "Manager Workspace", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Engagement Orders", + "link_count": 4, + "link_type": "DocType", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Engagement Order", + "link_count": 0, + "link_to": "Engagement Order", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Scheduled Orders", + "link_count": 0, + "link_to": "Engagement Order", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "In Progress Orders", + "link_count": 0, + "link_to": "Engagement Order", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Completed Orders", + "link_count": 0, + "link_to": "Engagement Order", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Resource Centers", + "link_count": 2, + "link_type": "DocType", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Resource Center", + "link_count": 0, + "link_to": "Resource Center", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Resource Center Members", + "link_count": 0, + "link_to": "Resource Center Member", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Engagement Recipes", + "link_count": 2, + "link_type": "DocType", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Engagement Recipe", + "link_count": 0, + "link_to": "Engagement Recipe", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Engagement Recipe Steps", + "link_count": 0, + "link_to": "Engagement Recipe Step", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Execution Tracking", + "link_count": 2, + "link_type": "DocType", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Execution Card", + "link_count": 0, + "link_to": "Execution Card", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Execution Card Logs", + "link_count": 0, + "link_to": "Execution Card Log", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2026-07-15 19:00:00.000000", + "modified_by": "Administrator", + "module": "Service Factory", + "name": "Manager Workspace", + "number_cards": [], + "owner": "Administrator", + "parent_page": "", + "public": 1, + "quick_lists": [], + "roles": [], + "sequence_id": 2.0, + "shortcuts": [ + { + "action": "List", + "color": "Blue", + "doc_type": "Engagement Order", + "format": "{}", + "icon": "list", + "label": "Active Engagements", + "link_to": "Engagement Order", + "stats_filter": "[[\"Engagement Order\",\"status\",\"in\",[\"Scheduled\",\"In Progress\"]]]", + "type": "DocType" + }, + { + "action": "List", + "color": "Red", + "doc_type": "Engagement Order", + "format": "{}", + "icon": "info", + "label": "Stalled / Blocked", + "link_to": "Engagement Order", + "stats_filter": "[[\"Engagement Order\",\"status\",\"=\",\"On Hold\"]]", + "type": "DocType" + }, + { + "action": "List", + "color": "Green", + "doc_type": "Resource Center", + "format": "{}", + "icon": "organization", + "label": "Capacity Overview", + "link_to": "Resource Center", + "stats_filter": "[]", + "type": "DocType" + }, + { + "action": "List", + "color": "Grey", + "doc_type": "Execution Card", + "format": "{}", + "icon": "check", + "label": "All Execution Cards", + "link_to": "Execution Card", + "stats_filter": "[]", + "type": "DocType" + } + ], + "title": "Manager Workspace", + "type": "Workspace" +}