diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index 75520300abd..a7c13ba8af0 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -7,6 +7,7 @@ import json import frappe from frappe import _ from frappe.model.document import Document +from frappe.query_builder.functions import Date from frappe.utils import add_to_date, flt, get_datetime, getdate, time_diff_in_hours, time_diff_in_seconds from erpnext.controllers.queries import get_match_cond @@ -322,45 +323,62 @@ class Timesheet(Document): @frappe.whitelist() def get_projectwise_timesheet_data(project=None, parent=None, from_time=None, to_time=None): - condition = "" + 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) + .on(ts.name == tsd.parent) + .select( + tsd.name.as_("name"), + tsd.parent.as_("time_sheet"), + tsd.from_time.as_("from_time"), + tsd.to_time.as_("to_time"), + tsd.billing_hours.as_("billing_hours"), + tsd.billing_amount.as_("billing_amount"), + tsd.activity_type.as_("activity_type"), + tsd.description.as_("description"), + ts.currency.as_("currency"), + tsd.project_name.as_("project_name"), + ) + .where( + (tsd.parenttype == "Timesheet") + & (tsd.docstatus == 1) + & (tsd.is_billable == 1) + & tsd.sales_invoice.isnull() + & (tsd.parent.isin(allowed_timesheets)) + ) + ) + + if allowed_projects: + query = query.where((tsd.project.isin(allowed_projects)) | (tsd.project.isnull())) + else: + query = query.where(tsd.project.isnull()) + if project: - condition += "AND tsd.project = %(project)s " + query = query.where(tsd.project == project) if parent: - condition += "AND tsd.parent = %(parent)s " + query = query.where(tsd.parent == parent) if from_time and to_time: - condition += "AND CAST(tsd.from_time as DATE) BETWEEN %(from_time)s AND %(to_time)s" + query = query.where(Date(tsd.from_time).between(from_time, to_time)) - query = f""" - SELECT - tsd.name as name, - tsd.parent as time_sheet, - tsd.from_time as from_time, - tsd.to_time as to_time, - tsd.billing_hours as billing_hours, - tsd.billing_amount as billing_amount, - tsd.activity_type as activity_type, - tsd.description as description, - ts.currency as currency, - tsd.project_name as project_name - FROM `tabTimesheet Detail` tsd - INNER JOIN `tabTimesheet` ts - ON ts.name = tsd.parent - WHERE - tsd.parenttype = 'Timesheet' - AND tsd.docstatus = 1 - AND tsd.is_billable = 1 - AND tsd.sales_invoice is NULL - {condition} - ORDER BY tsd.from_time ASC - """ - - filters = {"project": project, "parent": parent, "from_time": from_time, "to_time": to_time} - - return frappe.db.sql(query, filters, as_dict=1) + return query.orderby(tsd.from_time).run(as_dict=1) @frappe.whitelist() def get_timesheet_detail_rate(timelog, currency): + 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") @@ -368,10 +386,20 @@ def get_timesheet_detail_rate(timelog, currency): 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) @@ -386,33 +414,42 @@ def get_timesheet(doctype, txt, searchfield, start, page_len, filters): if not filters: filters = {} - condition = "" - if filters.get("project"): - condition = "and tsd.project = %(project)s" + allowed_timesheets = frappe.get_list("Timesheet", pluck="name") - return frappe.db.sql( - f"""select distinct tsd.parent from `tabTimesheet Detail` tsd, - `tabTimesheet` ts where - ts.status in ('Submitted', 'Payslip') and tsd.parent = ts.name and - tsd.docstatus = 1 and ts.total_billable_amount > 0 - and tsd.parent LIKE %(txt)s {condition} - order by tsd.parent limit %(page_len)s offset %(start)s""", - { - "txt": "%" + txt + "%", - "start": start, - "page_len": page_len, - "project": filters.get("project"), - }, + if not allowed_timesheets: + return [] + + tsd = frappe.qb.DocType("Timesheet Detail") + ts = frappe.qb.DocType("Timesheet") + + query = ( + frappe.qb.from_(tsd) + .inner_join(ts) + .on(tsd.parent == ts.name) + .select(tsd.parent) + .distinct() + .where( + ts.status.isin(["Submitted", "Payslip"]) + & (tsd.docstatus == 1) + & (ts.total_billable_amount > 0) + & tsd.parent.like(f"%{txt}%") + & tsd.parent.isin(allowed_timesheets) + ) ) + if filters.get("project"): + query = query.where(tsd.project == filters.get("project")) + + return query.orderby(tsd.parent).limit(page_len).offset(start).run() + @frappe.whitelist() -def get_timesheet_data(name, project): +def get_timesheet_data(name, project=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=[ "(total_billable_amount - total_billed_amount) as billing_amt",