diff --git a/custom_ui/api/db/estimates.py b/custom_ui/api/db/estimates.py
index b961ee5..c1ac907 100644
--- a/custom_ui/api/db/estimates.py
+++ b/custom_ui/api/db/estimates.py
@@ -260,8 +260,119 @@ def update_response(name, response):
html = frappe.render_template(template, {"error": str(e)})
return Response(html, mimetype="text/html")
+@frappe.whitelist()
+def get_estimate_templates(company):
+ """Get available estimate templates."""
+ filters = {"is_active": 1}
+ if company:
+ filters["company"] = company
+ try:
+ print("DEBUG: Fetching estimate templates for company:", company)
+ templates = frappe.get_all("Quotation Template", fields=["*"], filters=filters)
+
+ result = []
+ if not templates:
+ print("DEBUG: No templates found.")
+ return build_success_response(result)
+ print(f"DEBUG: Found {len(templates)} templates.")
+ for template in templates:
+ print("DEBUG: Processing template:", template)
+ items = frappe.get_all("Quotation Template Item",
+ fields=["item_code", "item_name", "description", "quantity", "discount_percentage", "rate"],
+ filters={"parent": template.name},
+ order_by="idx")
+
+ # Map fields to camelCase as requested
+ mapped_items = []
+ for item in items:
+ mapped_items.append({
+ "itemCode": item.item_code,
+ "itemName": item.item_name,
+ "description": item.description,
+ "quantity": item.qty,
+ "discountPercentage": item.discount_percentage,
+ "rate": item.rate
+ })
+
+ result.append({
+ "name": template.name,
+ "templateName": template.template_name,
+ "active": template.is_active,
+ "description": template.description,
+ "items": mapped_items
+ })
+
+ return build_success_response(result)
+ except Exception as e:
+ return build_error_response(str(e), 500)
+@frappe.whitelist()
+def create_estimate_template(data):
+ """Create a new estimate template."""
+ try:
+ print("DEBUG: Creating estimate template with data:", data)
+ data = json.loads(data) if isinstance(data, str) else data
+
+ doc_data = {
+ "doctype": "Quotation Template",
+ "is_active": 1,
+ "description": data.get("description"),
+ "company": data.get("company"),
+ "items": [],
+ "template_name": data.get("template_name"),
+ "source_quotation": data.get("source_quotation", "")
+ }
+
+ new_template = frappe.get_doc(doc_data)
+
+ for item in data.get("items", []):
+ new_template.append("items", {
+ "item_code": item.get("item_code"),
+ "item_name": item.get("item_name"),
+ "description": item.get("description"),
+ "qty": item.get("qty") or item.get("quantity"),
+ "rate": item.get("standard_rate") or item.get("rate"),
+ "discount_percentage": item.get("discount_percentage")
+ })
+
+ new_template.insert()
+ return build_success_response(new_template.name)
+ except Exception as e:
+ return build_error_response(str(e), 500)
+
+# @frappe.whitelist()
+# def create_template(data):
+# """Create a new estimate template."""
+# try:
+# data = json.loads(data) if isinstance(data, str) else data
+# print("DEBUG: Creating estimate template with data:", data)
+
+# new_template = frappe.get_doc({
+# "doctype": "Quotation Template",
+# "template_name": data.get("templateName"),
+# "is_active": data.get("active", 1),
+# "description": data.get("description", ""),
+# "company": data.get("company", ""),
+# "source_quotation": data.get("source_quotation", "")
+# })
+
+# for item in data.get("items", []):
+# item = json.loads(item) if isinstance(item, str) else item
+# new_template.append("items", {
+# "item_code": item.get("itemCode"),
+# "item_name": item.get("itemName"),
+# "description": item.get("description"),
+# "qty": item.get("quantity"),
+# "discount_percentage": item.get("discountPercentage"),
+# "rate": item.get("rate")
+# })
+
+# new_template.insert()
+# print("DEBUG: New estimate template created with name:", new_template.name)
+# return build_success_response(new_template.as_dict())
+# except Exception as e:
+# return build_error_response(str(e), 500)
@frappe.whitelist()
def upsert_estimate(data):
diff --git a/custom_ui/fixtures/doctype.json b/custom_ui/fixtures/doctype.json
index 72f23a9..c06140a 100644
--- a/custom_ui/fixtures/doctype.json
+++ b/custom_ui/fixtures/doctype.json
@@ -413,6 +413,70 @@
"trigger": null,
"unique": 0,
"width": null
+ },
+ {
+ "allow_bulk_edit": 0,
+ "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,
+ "documentation_url": null,
+ "fetch_from": null,
+ "fetch_if_empty": 0,
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "hide_border": 0,
+ "hide_days": 0,
+ "hide_seconds": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_preview": 0,
+ "in_standard_filter": 0,
+ "is_virtual": 0,
+ "label": "Company",
+ "length": 0,
+ "link_filters": null,
+ "make_attachment_public": 0,
+ "mandatory_depends_on": null,
+ "max_height": null,
+ "no_copy": 0,
+ "non_negative": 0,
+ "oldfieldname": null,
+ "oldfieldtype": null,
+ "options": "Company",
+ "parent": "Quotation Template",
+ "parentfield": "fields",
+ "parenttype": "DocType",
+ "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,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "show_dashboard": 0,
+ "show_on_timeline": 0,
+ "show_preview_popup": 0,
+ "sort_options": 0,
+ "translatable": 0,
+ "trigger": null,
+ "unique": 0,
+ "width": null
}
],
"force_re_route_to_default_view": 0,
@@ -435,7 +499,7 @@
"max_attachments": 0,
"menu_index": null,
"migration_hash": null,
- "modified": "2025-12-23 02:03:44.840865",
+ "modified": "2026-01-02 11:26:31.164108",
"module": "Selling",
"name": "Quotation Template",
"naming_rule": "",
diff --git a/frontend/src/api.js b/frontend/src/api.js
index a895290..d3e2b0c 100644
--- a/frontend/src/api.js
+++ b/frontend/src/api.js
@@ -12,6 +12,8 @@ const FRAPPE_GET_ESTIMATE_BY_ADDRESS_METHOD = "custom_ui.api.db.estimates.get_es
const FRAPPE_SEND_ESTIMATE_EMAIL_METHOD = "custom_ui.api.db.estimates.send_estimate_email";
const FRAPPE_LOCK_ESTIMATE_METHOD = "custom_ui.api.db.estimates.lock_estimate";
const FRAPPE_ESTIMATE_UPDATE_RESPONSE_METHOD = "custom_ui.api.db.estimates.manual_response";
+const FRAPPE_GET_ESTIMATE_TEMPLATES_METHOD = "custom_ui.api.db.estimates.get_estimate_templates";
+const FRAPPE_CREATE_ESTIMATE_TEMPLATE_METHOD = "custom_ui.api.db.estimates.create_estimate_template";
// Job methods
const FRAPPE_GET_JOB_METHOD = "custom_ui.api.db.jobs.get_job";
const FRAPPE_GET_JOBS_METHOD = "custom_ui.api.db.jobs.get_jobs_table_data";
@@ -222,6 +224,14 @@ class Api {
return await this.request(FRAPPE_ESTIMATE_UPDATE_RESPONSE_METHOD, {name: estimateName, response});
}
+ static async getEstimateTemplates(company) {
+ return await this.request(FRAPPE_GET_ESTIMATE_TEMPLATES_METHOD, { company });
+ }
+
+ static async createEstimateTemplate(data) {
+ return await this.request(FRAPPE_CREATE_ESTIMATE_TEMPLATE_METHOD, { data });
+ }
+
// ============================================================================
// JOB / PROJECT METHODS
// ============================================================================
diff --git a/frontend/src/components/clientSubPages/History.vue b/frontend/src/components/clientSubPages/History.vue
index a6c2b13..4da28ec 100644
--- a/frontend/src/components/clientSubPages/History.vue
+++ b/frontend/src/components/clientSubPages/History.vue
@@ -1,24 +1,26 @@
- History
- History
+