diff --git a/custom_ui/api/db/invoices.py b/custom_ui/api/db/invoices.py
new file mode 100644
index 0000000..a4fe47d
--- /dev/null
+++ b/custom_ui/api/db/invoices.py
@@ -0,0 +1,104 @@
+import frappe, json
+from custom_ui.db_utils import process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, build_error_response
+
+# ===============================================================================
+# ESTIMATES & INVOICES API METHODS
+# ===============================================================================
+
+
+@frappe.whitelist()
+def get_invoice_table_data(filters={}, sortings=[], page=1, page_size=10):
+ """Get paginated invoice table data with filtering and sorting support."""
+ print("DEBUG: Raw invoice options received:", filters, sortings, page, page_size)
+
+ processed_filters, processed_sortings, is_or, page, page_size = process_query_conditions(filters, sortings, page, page_size)
+
+ if is_or:
+ count = frappe.db.sql(*get_count_or_filters("Sales Invoice", processed_filters))[0][0]
+ else:
+ count = frappe.db.count("Sales Invoice", filters=processed_filters)
+
+ print(f"DEBUG: Number of invoice returned: {count}")
+
+ invoices = frappe.db.get_all(
+ "Sales Invoice",
+ fields=["*"],
+ filters=processed_filters if not is_or else None,
+ or_filters=processed_filters if is_or else None,
+ limit=page_size,
+ start=(page - 1) * page_size,
+ order_by=processed_sortings
+ )
+
+ tableRows = []
+ for invoice in invoices:
+ tableRow = {}
+ tableRow["id"] = invoice["name"]
+ tableRow["address"] = invoice.get("custom_installation_address", "")
+ tableRow["customer"] = invoice.get("customer", "")
+ tableRow["grand_total"] = invoice.get("grand_total", "")
+ tableRow["status"] = invoice.get("status", "")
+ tableRow["items"] = invoice.get("items", "")
+ tableRows.append(tableRow)
+
+ table_data_dict = build_datatable_dict(data=tableRows, count=count, page=page, page_size=page_size)
+ return build_success_response(table_data_dict)
+
+
+@frappe.whitelist()
+def get_invoice(invoice_name):
+ """Get detailed information for a specific invoice."""
+ try:
+ invoice = frappe.get_doc("Sales Invoice", invoice_name)
+ return build_success_response(invoice.as_dict())
+ except Exception as e:
+ return build_error_response(str(e), 500)
+
+
+@frappe.whitelist()
+def get_invoice_items():
+ items = frappe.db.get_all("Sales Invoice Item", fields=["*"])
+ return build_success_response(items)
+
+
+@frappe.whitelist()
+def get_invoice_from_address(full_address):
+ invoice = frappe.db.sql("""
+ SELECT i.name, i.custom_installation_address
+ FROM `tabSalesInvoice` i
+ JOIN `tabAddress` a
+ ON i.custom_installation_address = a.name
+ WHERE a.full_address =%s
+ """, (full_address,), as_dict=True)
+ if invoice:
+ return build_success_response(invoice)
+ else:
+ return build_error_response("No invoice found for the given address.", 404)
+
+
+@frappe.whitelist()
+def upsert_invoice(data):
+ """Create or update an invoice."""
+ print("DOIFJSEOFJISLFK")
+ try:
+ data = json.loads(data) if isinstance(data, str) else data
+ print("DEBUG: Retrieved address name:", data.get("address_name"))
+ new_invoice = frappe.get_doc({
+ "doctype": "Sales Invoice",
+ "custom_installation_address": data.get("address_name"),
+ "contact_email": data.get("contact_email"),
+ "party_name": data.get("contact_name"),
+ "customer_name": data.get("customer_name"),
+ })
+ for item in data.get("items", []):
+ item = json.loads(item) if isinstance(item, str) else item
+ new_invoice.append("items", {
+ "item_code": item.get("item_code"),
+ "qty": item.get("qty"),
+ })
+ new_invoice.insert()
+ print("DEBUG: New invoice created with name:", new_invoice.name)
+ return build_success_response(new_invoice.as_dict())
+ except Exception as e:
+ return build_error_response(str(e), 500)
+
diff --git a/frontend/src/api.js b/frontend/src/api.js
index cc25e51..84b138f 100644
--- a/frontend/src/api.js
+++ b/frontend/src/api.js
@@ -11,6 +11,7 @@ const FRAPPE_GET_ESTIMATES_METHOD = "custom_ui.api.db.estimates.get_estimate_tab
const FRAPPE_GET_JOBS_METHOD = "custom_ui.api.db.get_jobs";
const FRAPPE_UPSERT_JOB_METHOD = "custom_ui.api.db.jobs.upsert_job";
// 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";
// Warranty methods
const FRAPPE_GET_WARRANTY_CLAIMS_METHOD = "custom_ui.api.db.warranties.get_warranty_claims";
@@ -239,6 +240,31 @@ class Api {
return result;
}
+ static async getPaginatedInvoiceDetails(paginationParams = {}, filters = {}, sorting = null) {
+ const { page = 0, pageSize = 10, sortField = null, sortOrder = null } = paginationParams;
+
+ // Use sorting from the dedicated sorting parameter first, then fall back to pagination params
+ const actualSortField = sorting?.field || sortField;
+ const actualSortOrder = sorting?.order || sortOrder;
+
+ const options = {
+ page: page + 1, // Backend expects 1-based pages
+ page_size: pageSize,
+ filters,
+ sorting:
+ actualSortField && actualSortOrder
+ ? `${actualSortField} ${actualSortOrder === -1 ? "desc" : "asc"}`
+ : null,
+ for_table: true,
+ };
+
+ console.log("DEBUG: API - Sending invoice options to backend:", options);
+
+ const result = await this.request(FRAPPE_GET_INVOICES_METHOD, { options });
+ return result;
+
+ }
+
/**
* Get paginated job data with filtering and sorting
* @param {Object} paginationParams - Pagination parameters from store
diff --git a/frontend/src/components/pages/Invoices.vue b/frontend/src/components/pages/Invoices.vue
new file mode 100644
index 0000000..ab5a75f
--- /dev/null
+++ b/frontend/src/components/pages/Invoices.vue
@@ -0,0 +1,179 @@
+
+
+
Invoices
+
+
+
+
+
diff --git a/frontend/src/router.js b/frontend/src/router.js
index edbdb44..5c31b2f 100644
--- a/frontend/src/router.js
+++ b/frontend/src/router.js
@@ -3,6 +3,7 @@ import { createRouter, createWebHashHistory } from "vue-router";
import Calendar from "./components/pages/Calendar.vue";
import Clients from "./components/pages/Clients.vue";
import Jobs from "./components/pages/Jobs.vue";
+import Invoices from "./components/pages/Invoices.vue";
import Estimates from "./components/pages/Estimates.vue";
import Create from "./components/pages/Create.vue";
import Routes from "./components/pages/Routes.vue";
@@ -25,6 +26,7 @@ const routes = [
{ path: "/client", component: Client },
{ path: "/schedule-onsite", component: ScheduleOnSite },
{ path: "/jobs", component: Jobs },
+ { path: "/invoices", component: Invoices },
{ path: "/estimates", component: Estimates },
{ path: "/estimate", component: Estimate },
{ path: "/routes", component: Routes },