diff --git a/service_factory/service_factory/doctype/engagement_order/__init__.py b/service_factory/service_factory/doctype/engagement_order/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/service_factory/service_factory/doctype/engagement_order/engagement_order.json b/service_factory/service_factory/doctype/engagement_order/engagement_order.json new file mode 100644 index 0000000..3b51e13 --- /dev/null +++ b/service_factory/service_factory/doctype/engagement_order/engagement_order.json @@ -0,0 +1,549 @@ +{ + "actions": [], + "allow_auto_repeat": 0, + "allow_events_in_timeline": 1, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "naming_series:", + "creation": "2026-07-15 19:00:00.000000", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "customer", + "sales_order", + "engagement_recipe", + "column_break_1", + "status", + "start_date", + "end_date", + "section_break_totals", + "total_allocated_hours", + "total_logged_hours", + "column_break_totals_2", + "actual_cost", + "projected_billing_value", + "section_break_steps", + "run_steps" + ], + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "naming_series", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Series", + "length": 0, + "no_copy": 0, + "options": "ENG-ORD-.YYYY.-", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "customer", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Customer", + "length": 0, + "no_copy": 0, + "options": "Customer", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "sales_order", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Sales Order", + "length": 0, + "no_copy": 0, + "options": "Sales Order", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "engagement_recipe", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Engagement Recipe", + "length": 0, + "no_copy": 0, + "options": "Engagement Recipe", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_1", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "length": 0, + "no_copy": 0, + "options": "Draft\nScheduled\nIn Progress\nOn Hold\nCompleted\nCancelled", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "start_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Start Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "end_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "End Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_totals", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Totals", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "total_allocated_hours", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Total Allocated Hours", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "1", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "total_logged_hours", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Total Logged Hours", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "1", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_totals_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "actual_cost", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Actual Cost", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "projected_billing_value", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Projected Billing Value", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_steps", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Engagement Steps", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "run_steps", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Run Steps", + "length": 0, + "no_copy": 0, + "options": "Engagement Order Run Step", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + } + ], + "has_field_mappings": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 1, + "image_field": "", + "import_source": "", + "is_submittable": 1, + "is_table": 0, + "label": "Engagement Order", + "modified": "2026-07-15 19:00:00.000000", + "modified_by": "Administrator", + "module": "Service Factory", + "name": "Engagement Order", + "name_case": "", + "nsm_parent_field": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "select": 1, + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1, + "track_seen": 1, + "track_views": 0 +} diff --git a/service_factory/service_factory/doctype/engagement_order/engagement_order.py b/service_factory/service_factory/doctype/engagement_order/engagement_order.py new file mode 100644 index 0000000..f8091f5 --- /dev/null +++ b/service_factory/service_factory/doctype/engagement_order/engagement_order.py @@ -0,0 +1,74 @@ +# Copyright (c) 2026, cclohmar and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document + + +class EngagementOrder(Document): + def on_change_engagement_recipe(self): + """Fetch recipe steps into order run steps when recipe is selected.""" + if not self.engagement_recipe: + return + + recipe = frappe.get_doc("Engagement Recipe", self.engagement_recipe) + self.set("run_steps", []) + + for step in recipe.steps: + row = self.append("run_steps", {}) + row.step_name = step.step_name + row.sequence_no = step.sequence_no + row.resource_center = step.resource_center + row.allocated_hours = step.estimated_hours + row.status = "Pending" + + def before_save(self): + self.calculate_totals() + + def calculate_totals(self): + total_allocated = 0.0 + total_logged = 0.0 + actual_cost = 0.0 + projected_billing = 0.0 + + if self.get("run_steps"): + for step in self.run_steps: + allocated = step.get("allocated_hours", 0) or 0 + logged = step.get("actual_hours", 0) or 0 + total_allocated += allocated + total_logged += logged + + if step.resource_center: + rc = frappe.get_cached_value( + "Resource Center", + step.resource_center, + ["hourly_cost_rate", "hourly_billing_rate"], + as_dict=True, + ) + if rc: + actual_cost += logged * (rc.get("hourly_cost_rate", 0) or 0) + projected_billing += logged * (rc.get("hourly_billing_rate", 0) or 0) + + self.total_allocated_hours = total_allocated + self.total_logged_hours = total_logged + self.actual_cost = actual_cost + self.projected_billing_value = projected_billing + + def on_submit(self): + self.create_execution_cards() + + def create_execution_cards(self): + """Auto-generate Execution Cards for each run step.""" + for step in self.get("run_steps", []): + if not step.assigned_employee: + continue + + card = frappe.get_doc({ + "doctype": "Execution Card", + "engagement_order": self.name, + "step_name": step.step_name, + "assigned_employee": step.assigned_employee, + "allocated_hours": step.allocated_hours or 0, + "status": "Open", + }) + card.insert() diff --git a/service_factory/service_factory/doctype/engagement_order_run_step/__init__.py b/service_factory/service_factory/doctype/engagement_order_run_step/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/service_factory/service_factory/doctype/engagement_order_run_step/engagement_order_run_step.json b/service_factory/service_factory/doctype/engagement_order_run_step/engagement_order_run_step.json new file mode 100644 index 0000000..2ef562f --- /dev/null +++ b/service_factory/service_factory/doctype/engagement_order_run_step/engagement_order_run_step.json @@ -0,0 +1,281 @@ +{ + "actions": [], + "allow_events_in_timeline": 0, + "autoname": "hash", + "creation": "2026-07-15 19:00:00.000000", + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "step_name", + "sequence_no", + "resource_center", + "assigned_employee", + "column_break_1", + "allocated_hours", + "actual_hours", + "status" + ], + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "step_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Step Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "sequence_no", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Sequence No", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "resource_center", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Resource Center", + "length": 0, + "no_copy": 0, + "options": "Resource Center", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "assigned_employee", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Assigned Employee", + "length": 0, + "no_copy": 0, + "options": "Employee", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_1", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "allocated_hours", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Allocated Hours", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "1", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "actual_hours", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Actual Hours", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "1", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "length": 0, + "no_copy": 0, + "options": "Pending\nActive\nCompleted", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + } + ], + "has_field_mappings": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 1, + "image_field": "", + "import_source": "", + "is_submittable": 0, + "is_table": 1, + "label": "Engagement Order Run Step", + "modified": "2026-07-15 19:00:00.000000", + "modified_by": "Administrator", + "module": "Service Factory", + "name": "Engagement Order Run Step", + "name_case": "", + "nsm_parent_field": "", + "owner": "Administrator", + "permissions": [], + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} diff --git a/service_factory/service_factory/doctype/engagement_order_run_step/engagement_order_run_step.py b/service_factory/service_factory/doctype/engagement_order_run_step/engagement_order_run_step.py new file mode 100644 index 0000000..5bed55d --- /dev/null +++ b/service_factory/service_factory/doctype/engagement_order_run_step/engagement_order_run_step.py @@ -0,0 +1,8 @@ +# Copyright (c) 2026, cclohmar and contributors +# For license information, please see license.txt + +from frappe.model.document import Document + + +class EngagementOrderRunStep(Document): + pass diff --git a/service_factory/service_factory/doctype/engagement_recipe/__init__.py b/service_factory/service_factory/doctype/engagement_recipe/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/service_factory/service_factory/doctype/engagement_recipe/engagement_recipe.json b/service_factory/service_factory/doctype/engagement_recipe/engagement_recipe.json new file mode 100644 index 0000000..eff3306 --- /dev/null +++ b/service_factory/service_factory/doctype/engagement_recipe/engagement_recipe.json @@ -0,0 +1,245 @@ +{ + "actions": [], + "allow_auto_repeat": 0, + "allow_events_in_timeline": 1, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "field:recipe_name", + "creation": "2026-07-15 19:00:00.000000", + "doctype": "DocType", + "document_type": "Master", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "recipe_name", + "service_offering", + "column_break_1", + "estimated_total_duration_hours", + "section_break_steps", + "steps" + ], + "fields": [ + { + "allow_on_submit": 0, + "bold": 1, + "collapsible": 0, + "columns": 2, + "fieldname": "recipe_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Recipe Name", + "length": 140, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 1, + "sort_options": 0, + "unique": 1 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "service_offering", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Service Offering", + "length": 0, + "no_copy": 0, + "options": "Item", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_1", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "estimated_total_duration_hours", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Estimated Total Duration Hours", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "1", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_steps", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Engagement Steps", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "steps", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Steps", + "length": 0, + "no_copy": 0, + "options": "Engagement Recipe Step", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + } + ], + "has_field_mappings": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 1, + "image_field": "", + "import_source": "", + "is_submittable": 0, + "is_table": 0, + "label": "Engagement Recipe", + "modified": "2026-07-15 19:00:00.000000", + "modified_by": "Administrator", + "module": "Service Factory", + "name": "Engagement Recipe", + "name_case": "", + "nsm_parent_field": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "select": 1, + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} diff --git a/service_factory/service_factory/doctype/engagement_recipe/engagement_recipe.py b/service_factory/service_factory/doctype/engagement_recipe/engagement_recipe.py new file mode 100644 index 0000000..7a90d25 --- /dev/null +++ b/service_factory/service_factory/doctype/engagement_recipe/engagement_recipe.py @@ -0,0 +1,17 @@ +# Copyright (c) 2026, cclohmar and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document + + +class EngagementRecipe(Document): + def before_save(self): + self.calculate_total_duration() + + def calculate_total_duration(self): + total = 0.0 + if self.get("steps"): + for step in self.steps: + total += step.get("estimated_hours", 0) or 0 + self.estimated_total_duration_hours = total diff --git a/service_factory/service_factory/doctype/engagement_recipe_step/__init__.py b/service_factory/service_factory/doctype/engagement_recipe_step/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/service_factory/service_factory/doctype/engagement_recipe_step/engagement_recipe_step.json b/service_factory/service_factory/doctype/engagement_recipe_step/engagement_recipe_step.json new file mode 100644 index 0000000..dcbc5b4 --- /dev/null +++ b/service_factory/service_factory/doctype/engagement_recipe_step/engagement_recipe_step.json @@ -0,0 +1,219 @@ +{ + "actions": [], + "allow_events_in_timeline": 0, + "autoname": "hash", + "creation": "2026-07-15 19:00:00.000000", + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "step_name", + "sequence_no", + "resource_center", + "column_break_1", + "estimated_hours", + "dependency_step" + ], + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "step_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Step Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "sequence_no", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Sequence No", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "resource_center", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Resource Center", + "length": 0, + "no_copy": 0, + "options": "Resource Center", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_1", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "estimated_hours", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Estimated Hours", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "1", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "dependency_step", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Dependency Step", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + } + ], + "has_field_mappings": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 1, + "image_field": "", + "import_source": "", + "is_submittable": 0, + "is_table": 1, + "label": "Engagement Recipe Step", + "modified": "2026-07-15 19:00:00.000000", + "modified_by": "Administrator", + "module": "Service Factory", + "name": "Engagement Recipe Step", + "name_case": "", + "nsm_parent_field": "", + "owner": "Administrator", + "permissions": [], + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} diff --git a/service_factory/service_factory/doctype/engagement_recipe_step/engagement_recipe_step.py b/service_factory/service_factory/doctype/engagement_recipe_step/engagement_recipe_step.py new file mode 100644 index 0000000..140ec90 --- /dev/null +++ b/service_factory/service_factory/doctype/engagement_recipe_step/engagement_recipe_step.py @@ -0,0 +1,8 @@ +# Copyright (c) 2026, cclohmar and contributors +# For license information, please see license.txt + +from frappe.model.document import Document + + +class EngagementRecipeStep(Document): + pass diff --git a/service_factory/service_factory/doctype/execution_card/__init__.py b/service_factory/service_factory/doctype/execution_card/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/service_factory/service_factory/doctype/execution_card/execution_card.json b/service_factory/service_factory/doctype/execution_card/execution_card.json new file mode 100644 index 0000000..83a3046 --- /dev/null +++ b/service_factory/service_factory/doctype/execution_card/execution_card.json @@ -0,0 +1,422 @@ +{ + "actions": [ + { + "action_name": "Start Work", + "group": "Action", + "hidden": 0, + "action_type": "Server Action", + "action": "start_work" + }, + { + "action_name": "Pause Work", + "group": "Action", + "hidden": 0, + "action_type": "Server Action", + "action": "pause_work" + }, + { + "action_name": "Stop Work", + "group": "Action", + "hidden": 0, + "action_type": "Server Action", + "action": "stop_work" + } + ], + "allow_auto_repeat": 0, + "allow_events_in_timeline": 1, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "naming_series:", + "creation": "2026-07-15 19:00:00.000000", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "engagement_order", + "step_name", + "column_break_1", + "assigned_employee", + "allocated_hours", + "actual_hours_logged", + "status", + "section_break_timer", + "timer_status", + "elapsed_time", + "section_break_logs", + "time_logs" + ], + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "naming_series", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Series", + "length": 0, + "no_copy": 0, + "options": "EXE-CRD-.YYYY.-", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "engagement_order", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Engagement Order", + "length": 0, + "no_copy": 0, + "options": "Engagement Order", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "step_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Step Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_1", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "assigned_employee", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Assigned Employee", + "length": 0, + "no_copy": 0, + "options": "Employee", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "allocated_hours", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Allocated Hours", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "1", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "actual_hours_logged", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Actual Hours Logged", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "2", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "length": 0, + "no_copy": 0, + "options": "Open\nRunning\nPaused\nSubmitted", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_timer", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Time Tracking", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_logs", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Time Logs", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "time_logs", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Time Logs", + "length": 0, + "no_copy": 1, + "options": "Execution Card Log", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + } + ], + "has_field_mappings": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 1, + "image_field": "", + "import_source": "", + "is_submittable": 1, + "is_table": 0, + "label": "Execution Card", + "modified": "2026-07-15 19:00:00.000000", + "modified_by": "Administrator", + "module": "Service Factory", + "name": "Execution Card", + "name_case": "", + "nsm_parent_field": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "select": 1, + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1, + "track_seen": 1, + "track_views": 0 +} diff --git a/service_factory/service_factory/doctype/execution_card/execution_card.py b/service_factory/service_factory/doctype/execution_card/execution_card.py new file mode 100644 index 0000000..9c678fd --- /dev/null +++ b/service_factory/service_factory/doctype/execution_card/execution_card.py @@ -0,0 +1,90 @@ +# Copyright (c) 2026, cclohmar and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document +from frappe import _ + + +class ExecutionCard(Document): + def before_save(self): + self.calculate_logged_hours() + + def calculate_logged_hours(self): + total = 0.0 + if self.get("time_logs"): + for log in self.time_logs: + total += log.get("duration_hours", 0) or 0 + self.actual_hours_logged = total + + def on_update(self): + self.update_engagement_order_totals() + + def on_submit(self): + self.status = "Submitted" + self.update_engagement_order_totals() + + def on_cancel(self): + self.update_engagement_order_totals() + + def update_engagement_order_totals(self): + if not self.engagement_order: + return + + order = frappe.get_doc("Engagement Order", self.engagement_order) + order.calculate_totals() + order.save() + + @frappe.whitelist() + def start_work(self): + """Start work on this execution card.""" + if self.status not in ("Open", "Paused"): + frappe.throw(_("Card must be in 'Open' or 'Paused' status to start work.")) + + log = self.append("time_logs", {}) + log.start_time = frappe.utils.now_datetime() + self.status = "Running" + self.save() + + @frappe.whitelist() + def pause_work(self): + """Pause work on this execution card.""" + if self.status != "Running": + frappe.throw(_("Card must be in 'Running' status to pause work.")) + + if self.time_logs and not self.time_logs[-1].end_time: + log = self.time_logs[-1] + log.end_time = frappe.utils.now_datetime() + log.duration_hours = (log.end_time - log.start_time).total_seconds() / 3600 + + self.calculate_logged_hours() + self.status = "Paused" + self.save() + + @frappe.whitelist() + def stop_work(self): + """Stop work and mark the card as ready for submission.""" + if self.status not in ("Running", "Paused"): + frappe.throw(_("Card must be 'Running' or 'Paused' to stop work.")) + + if self.status == "Running" and self.time_logs and not self.time_logs[-1].end_time: + log = self.time_logs[-1] + log.end_time = frappe.utils.now_datetime() + log.duration_hours = (log.end_time - log.start_time).total_seconds() / 3600 + + self.calculate_logged_hours() + self.status = "Submitted" + self.save() + + @frappe.whitelist() + def get_active_time(self): + """Return current elapsed time if card is running.""" + if self.status != "Running" or not self.time_logs: + return {"elapsed_seconds": 0} + + log = self.time_logs[-1] + if log.start_time and not log.end_time: + elapsed = (frappe.utils.now_datetime() - log.start_time).total_seconds() + return {"elapsed_seconds": elapsed} + + return {"elapsed_seconds": 0} diff --git a/service_factory/service_factory/doctype/execution_card_log/__init__.py b/service_factory/service_factory/doctype/execution_card_log/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/service_factory/service_factory/doctype/execution_card_log/execution_card_log.json b/service_factory/service_factory/doctype/execution_card_log/execution_card_log.json new file mode 100644 index 0000000..750e9ff --- /dev/null +++ b/service_factory/service_factory/doctype/execution_card_log/execution_card_log.json @@ -0,0 +1,128 @@ +{ + "actions": [], + "allow_events_in_timeline": 0, + "autoname": "hash", + "creation": "2026-07-15 19:00:00.000000", + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "start_time", + "end_time", + "duration_hours" + ], + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "start_time", + "fieldtype": "Datetime", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Start Time", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "end_time", + "fieldtype": "Datetime", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "End Time", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "duration_hours", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Duration Hours", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "2", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + } + ], + "has_field_mappings": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 1, + "image_field": "", + "import_source": "", + "is_submittable": 0, + "is_table": 1, + "label": "Execution Card Log", + "modified": "2026-07-15 19:00:00.000000", + "modified_by": "Administrator", + "module": "Service Factory", + "name": "Execution Card Log", + "name_case": "", + "nsm_parent_field": "", + "owner": "Administrator", + "permissions": [], + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} diff --git a/service_factory/service_factory/doctype/execution_card_log/execution_card_log.py b/service_factory/service_factory/doctype/execution_card_log/execution_card_log.py new file mode 100644 index 0000000..f3d18df --- /dev/null +++ b/service_factory/service_factory/doctype/execution_card_log/execution_card_log.py @@ -0,0 +1,8 @@ +# Copyright (c) 2026, cclohmar and contributors +# For license information, please see license.txt + +from frappe.model.document import Document + + +class ExecutionCardLog(Document): + pass diff --git a/service_factory/service_factory/doctype/resource_center/__init__.py b/service_factory/service_factory/doctype/resource_center/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/service_factory/service_factory/doctype/resource_center/resource_center.json b/service_factory/service_factory/doctype/resource_center/resource_center.json new file mode 100644 index 0000000..55c31e2 --- /dev/null +++ b/service_factory/service_factory/doctype/resource_center/resource_center.json @@ -0,0 +1,335 @@ +{ + "actions": [], + "allow_auto_repeat": 0, + "allow_events_in_timeline": 1, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "field:center_name", + "creation": "2026-07-15 19:00:00.000000", + "doctype": "DocType", + "document_type": "Master", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "center_name", + "resource_type", + "column_break_1", + "hourly_cost_rate", + "hourly_billing_rate", + "section_break_capacity", + "daily_capacity_hours", + "section_break_members", + "members" + ], + "fields": [ + { + "allow_on_submit": 0, + "bold": 1, + "collapsible": 0, + "columns": 2, + "fieldname": "center_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Center Name", + "length": 140, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 1, + "sort_options": 0, + "unique": 1 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "resource_type", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Resource Type", + "length": 0, + "no_copy": 0, + "options": "Engineering\nSales Engineering\nSupport\nConsulting", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_1", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "hourly_cost_rate", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Hourly Cost Rate", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "hourly_billing_rate", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Hourly Billing Rate", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_capacity", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Capacity Planning", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "daily_capacity_hours", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Daily Capacity Hours", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "1", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_members", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Team Members", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "members", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Members", + "length": 0, + "no_copy": 0, + "options": "Resource Center Member", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + } + ], + "has_field_mappings": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 1, + "image_field": "", + "import_source": "", + "is_submittable": 0, + "is_table": 0, + "label": "Resource Center", + "modified": "2026-07-15 19:00:00.000000", + "modified_by": "Administrator", + "module": "Service Factory", + "name": "Resource Center", + "name_case": "", + "nsm_parent_field": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "select": 1, + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} diff --git a/service_factory/service_factory/doctype/resource_center/resource_center.py b/service_factory/service_factory/doctype/resource_center/resource_center.py new file mode 100644 index 0000000..57307c3 --- /dev/null +++ b/service_factory/service_factory/doctype/resource_center/resource_center.py @@ -0,0 +1,9 @@ +# Copyright (c) 2026, cclohmar and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document + + +class ResourceCenter(Document): + pass diff --git a/service_factory/service_factory/doctype/resource_center_member/__init__.py b/service_factory/service_factory/doctype/resource_center_member/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/service_factory/service_factory/doctype/resource_center_member/resource_center_member.json b/service_factory/service_factory/doctype/resource_center_member/resource_center_member.json new file mode 100644 index 0000000..61c58d0 --- /dev/null +++ b/service_factory/service_factory/doctype/resource_center_member/resource_center_member.json @@ -0,0 +1,99 @@ +{ + "actions": [], + "allow_events_in_timeline": 0, + "autoname": "field:employee", + "creation": "2026-07-15 19:00:00.000000", + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "employee", + "allocation_percentage" + ], + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "employee", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Employee", + "length": 0, + "no_copy": 0, + "options": "Employee", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "allocation_percentage", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Allocation Percentage", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "unique": 0 + } + ], + "has_field_mappings": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 1, + "image_field": "", + "import_source": "", + "is_submittable": 0, + "is_table": 1, + "label": "Resource Center Member", + "modified": "2026-07-15 19:00:00.000000", + "modified_by": "Administrator", + "module": "Service Factory", + "name": "Resource Center Member", + "name_case": "", + "nsm_parent_field": "", + "owner": "Administrator", + "permissions": [], + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} diff --git a/service_factory/service_factory/doctype/resource_center_member/resource_center_member.py b/service_factory/service_factory/doctype/resource_center_member/resource_center_member.py new file mode 100644 index 0000000..dc08087 --- /dev/null +++ b/service_factory/service_factory/doctype/resource_center_member/resource_center_member.py @@ -0,0 +1,8 @@ +# Copyright (c) 2026, cclohmar and contributors +# For license information, please see license.txt + +from frappe.model.document import Document + + +class ResourceCenterMember(Document): + pass