From d5df40986d72a55d414ddaf4d382883f9df31e41 Mon Sep 17 00:00:00 2001 From: Diptanil Saha Date: Sun, 30 Aug 2026 23:16:32 +0530 Subject: [PATCH] fix(timesheet): scoping whitelisted methods output to projects and timesheets that are acccessible to users (#58267) --- .../projects/doctype/timesheet/timesheet.py | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index 0c136fcdeba..a91e1c1b042 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -311,6 +311,12 @@ def get_projectwise_timesheet_data( tsd = frappe.qb.DocType("Timesheet Detail") ts = frappe.qb.DocType("Timesheet") + allowed_timesheets = frappe.get_list("Timesheet", pluck="name") + allowed_projects = frappe.get_list("Project", pluck="name") + + if not allowed_timesheets: + return [] + query = ( frappe.qb.from_(tsd) .inner_join(ts) @@ -332,6 +338,8 @@ def get_projectwise_timesheet_data( & (tsd.docstatus == 1) & (tsd.is_billable == 1) & tsd.sales_invoice.isnull() + & (tsd.parent.isin(allowed_timesheets)) + & ((tsd.project.isin(allowed_projects)) | (tsd.project.isnull())) ) ) @@ -347,6 +355,11 @@ def get_projectwise_timesheet_data( @frappe.whitelist() def get_timesheet_detail_rate(timelog: str, currency: str): + allowed_timesheets = frappe.get_list("Timesheet", pluck="name") + + if not allowed_timesheets: + return 0.0 + ts = frappe.qb.DocType("Timesheet") ts_detail = frappe.qb.DocType("Timesheet Detail") @@ -354,10 +367,20 @@ def get_timesheet_detail_rate(timelog: str, currency: str): frappe.qb.from_(ts_detail) .inner_join(ts) .on(ts.name == ts_detail.parent) - .select(ts_detail.billing_amount.as_("billing_amount"), ts.currency.as_("currency")) - .where(ts_detail.name == timelog) + .select( + ts_detail.billing_amount.as_("billing_amount"), + ts.currency.as_("currency"), + ts.name.as_("timesheet"), + ) + .where((ts_detail.name == timelog) & ts_detail.parent.isin(allowed_timesheets)) + .limit(1) .run(as_dict=1) - )[0] + ) + + if not timelog_detail: + return 0.0 + + timelog_detail = timelog_detail[0] if timelog_detail.currency: exchange_rate = get_exchange_rate(timelog_detail.currency, currency) @@ -372,6 +395,11 @@ def get_timesheet(doctype: str, txt: str, searchfield: str, start: int, page_len if not filters: filters = {} + allowed_timesheets = frappe.get_list("Timesheet", pluck="name") + + if not allowed_timesheets: + return [] + tsd = frappe.qb.DocType("Timesheet Detail") ts = frappe.qb.DocType("Timesheet") @@ -386,6 +414,7 @@ def get_timesheet(doctype: str, txt: str, searchfield: str, start: int, page_len & (tsd.docstatus == 1) & (ts.total_billable_amount > 0) & tsd.parent.like(f"%{txt}%") + & tsd.parent.isin(allowed_timesheets) ) ) @@ -396,12 +425,12 @@ def get_timesheet(doctype: str, txt: str, searchfield: str, start: int, page_len @frappe.whitelist() -def get_timesheet_data(name: str, project: str): +def get_timesheet_data(name: str, project: str | None = None): data = None - if project and project != "": + if project: data = get_projectwise_timesheet_data(project, name) else: - data = frappe.get_all( + data = frappe.get_list( "Timesheet", fields=[ {"SUB": ["total_billable_amount", "total_billed_amount"], "as": "billing_amt"},