diff --git a/custom_ui/api/db/estimates.py b/custom_ui/api/db/estimates.py index c1ac907..cabeba6 100644 --- a/custom_ui/api/db/estimates.py +++ b/custom_ui/api/db/estimates.py @@ -429,6 +429,7 @@ def upsert_estimate(data): "customer_address": data.get("address_name"), "contact_person": data.get("contact_name"), "letter_head": data.get("company"), + "custom_project_template": data.get("project_template", None) }) for item in data.get("items", []): item = json.loads(item) if isinstance(item, str) else item diff --git a/custom_ui/api/db/jobs.py b/custom_ui/api/db/jobs.py index ac742a4..0b115a7 100644 --- a/custom_ui/api/db/jobs.py +++ b/custom_ui/api/db/jobs.py @@ -5,6 +5,17 @@ from custom_ui.db_utils import process_query_conditions, build_datatable_dict, g # JOB MANAGEMENT API METHODS # =============================================================================== +@frappe.whitelist() +def get_job_templates(company=None): + """Get list of job (project) templates.""" + filters = {} + if company: + filters["company"] = company + try: + templates = frappe.get_all("Project Template", fields=["*"], filters=filters) + return build_success_response(templates) + except Exception as e: + return build_error_response(str(e), 500) @frappe.whitelist() def create_job_from_sales_order(sales_order_name): diff --git a/custom_ui/fixtures/custom_field.json b/custom_ui/fixtures/custom_field.json index 7015e7c..0637a08 100644 --- a/custom_ui/fixtures/custom_field.json +++ b/custom_ui/fixtures/custom_field.json @@ -1,59 +1 @@ -[ - { - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Lead", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "custom_customer_name", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "last_name", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Customer Name", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2026-01-07 04:41:50.654606", - "module": null, - "name": "Lead-custom_customer_name", - "no_copy": 0, - "non_negative": 0, - "options": null, - "permlevel": 0, - "placeholder": null, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 1, - "unique": 0, - "width": null - } -] \ No newline at end of file +[] \ No newline at end of file diff --git a/custom_ui/fixtures/doctype.json b/custom_ui/fixtures/doctype.json index c06140a..5ffb4a3 100644 --- a/custom_ui/fixtures/doctype.json +++ b/custom_ui/fixtures/doctype.json @@ -498,8 +498,8 @@ "make_attachments_public": 0, "max_attachments": 0, "menu_index": null, - "migration_hash": null, - "modified": "2026-01-02 11:26:31.164108", + "migration_hash": "2521636464aadbebbd70dfbf13252950", + "modified": "2026-01-07 11:10:03.317996", "module": "Selling", "name": "Quotation Template", "naming_rule": "", @@ -996,8 +996,8 @@ "make_attachments_public": 0, "max_attachments": 0, "menu_index": null, - "migration_hash": null, - "modified": "2025-12-23 02:00:30.908719", + "migration_hash": "2521636464aadbebbd70dfbf13252950", + "modified": "2026-01-07 11:10:03.406758", "module": "Selling", "name": "Quotation Template Item", "naming_rule": "", diff --git a/custom_ui/hooks.py b/custom_ui/hooks.py index dd8f28e..5af4478 100644 --- a/custom_ui/hooks.py +++ b/custom_ui/hooks.py @@ -191,7 +191,17 @@ fixtures = [ "dt": "Custom Field", "filters": [ ["dt", "=", "Quotation"], - ["fieldname", "=", "custom_quotation_template"] + ["fieldname", "in", [ + "custom_quotation_template", + "custom_project_template" + ]] + ] + }, + { + "dt": "Custom Field", + "filters": [ + ["dt", "=", "Sales Order"], + ["fieldname", "=", "custom_project_template"] ] }, { @@ -200,6 +210,13 @@ fixtures = [ ["dt", "=", "Lead"], ["fieldname", "=", "custom_customer_name"] ] + }, + { + "dt": "Custom Field", + "filters": [ + ["dt", "=", "Project Template"], + ["fieldname", "=", "company"] + ] } ] diff --git a/custom_ui/install.py b/custom_ui/install.py index 190e5d6..ddb4348 100644 --- a/custom_ui/install.py +++ b/custom_ui/install.py @@ -193,6 +193,24 @@ def add_custom_fields(): fieldtype="Check", default=0, insert_after="custom_installation_address" + ), + dict( + fieldname="custom_project_template", + label="Project Template", + fieldtype="Link", + options="Project Template", + insert_after="custom_quotation_template", + module="custom_ui", + description="The project template to use when creating a project from this quotation." + ), + dict( + fieldname="custom_quotation_template", + label="Quotation Template", + fieldtype="Link", + options="Quotation Template", + insert_after="terms_and_conditions", + module="custom_ui", + description="The template used for generating this quotation." ) ], "Sales Order": [ @@ -202,6 +220,25 @@ def add_custom_fields(): fieldtype="Check", default=0, insert_after="custom_installation_address" + ), + dict( + fieldname="custom_project_template", + label="Project Template", + fieldtype="Link", + options="Project Template", + module="custom_ui", + description="The project template to use when creating a project from this sales order." + ) + ], + "Project Template": [ + dict( + fieldname="company", + label="Company", + fieldtype="Link", + options="Company", + insert_after="project_type", + module="custom_ui", + description="The company associated with this project template." ) ] } diff --git a/frontend/src/api.js b/frontend/src/api.js index d3e2b0c..979ff86 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -20,6 +20,7 @@ const FRAPPE_GET_JOBS_METHOD = "custom_ui.api.db.jobs.get_jobs_table_data"; const FRAPPE_UPSERT_JOB_METHOD = "custom_ui.api.db.jobs.upsert_job"; const FRAPPE_GET_JOB_TASK_LIST_METHOD = "custom_ui.api.db.jobs.get_job_task_table_data"; const FRAPPE_GET_INSTALL_PROJECTS_METHOD = "custom_ui.api.db.jobs.get_install_projects"; +const FRAPPE_GET_JOB_TEMPLATES_METHOD = "custom_ui.api.db.jobs.get_job_templates"; // Invoice methods const FRAPPE_GET_INVOICES_METHOD = "custom_ui.api.db.invoices.get_invoice_table_data"; const FRAPPE_UPSERT_INVOICE_METHOD = "custom_ui.api.db.invoices.upsert_invoice"; @@ -236,6 +237,10 @@ class Api { // JOB / PROJECT METHODS // ============================================================================ + static async getJobTemplates(company) { + return await this.request(FRAPPE_GET_JOB_TEMPLATES_METHOD, { company }); + } + static async getJobDetails() { const projects = await this.getDocsList("Project"); const data = []; diff --git a/frontend/src/components/pages/Estimate.vue b/frontend/src/components/pages/Estimate.vue index ff53e7b..b88e7eb 100644 --- a/frontend/src/components/pages/Estimate.vue +++ b/frontend/src/components/pages/Estimate.vue @@ -59,6 +59,23 @@ + +
+ +