brotherton-erpnext/erpnext/projects/report/billing_summary.py
ViralKansodiya 75652799cd
feat: Tick on checkbox to include draft timesheets (#36577)
feat: Tick on Check box to include Draft Timesheets
2023-08-14 14:14:49 +05:30

156 lines
3.9 KiB
Python

# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.utils import flt, time_diff_in_hours
def get_columns():
return [
{
"label": _("Employee ID"),
"fieldtype": "Link",
"fieldname": "employee",
"options": "Employee",
"width": 300,
},
{
"label": _("Employee Name"),
"fieldtype": "data",
"fieldname": "employee_name",
"hidden": 1,
"width": 200,
},
{
"label": _("Timesheet"),
"fieldtype": "Link",
"fieldname": "timesheet",
"options": "Timesheet",
"width": 150,
},
{"label": _("Working Hours"), "fieldtype": "Float", "fieldname": "total_hours", "width": 150},
{
"label": _("Billable Hours"),
"fieldtype": "Float",
"fieldname": "total_billable_hours",
"width": 150,
},
{"label": _("Billing Amount"), "fieldtype": "Currency", "fieldname": "amount", "width": 150},
]
def get_data(filters):
data = []
if filters.from_date > filters.to_date:
frappe.msgprint(_("From Date can not be greater than To Date"))
return data
timesheets = get_timesheets(filters)
filters.from_date = frappe.utils.get_datetime(filters.from_date)
filters.to_date = frappe.utils.add_to_date(
frappe.utils.get_datetime(filters.to_date), days=1, seconds=-1
)
timesheet_details = get_timesheet_details(filters, timesheets.keys())
for ts, ts_details in timesheet_details.items():
total_hours = 0
total_billing_hours = 0
total_amount = 0
for row in ts_details:
from_time, to_time = filters.from_date, filters.to_date
if row.to_time < from_time or row.from_time > to_time:
continue
if row.from_time > from_time:
from_time = row.from_time
if row.to_time < to_time:
to_time = row.to_time
activity_duration, billing_duration = get_billable_and_total_duration(row, from_time, to_time)
total_hours += activity_duration
total_billing_hours += billing_duration
total_amount += billing_duration * flt(row.billing_rate)
if total_hours:
data.append(
{
"employee": timesheets.get(ts).employee,
"employee_name": timesheets.get(ts).employee_name,
"timesheet": ts,
"total_billable_hours": total_billing_hours,
"total_hours": total_hours,
"amount": total_amount,
}
)
return data
def get_timesheets(filters):
record_filters = [
["start_date", "<=", filters.to_date],
["end_date", ">=", filters.from_date],
]
if not filters.get("include_draft_timesheets"):
record_filters.append(["docstatus", "=", 1])
else:
record_filters.append(["docstatus", "!=", 2])
if "employee" in filters:
record_filters.append(["employee", "=", filters.employee])
timesheets = frappe.get_all(
"Timesheet", filters=record_filters, fields=["employee", "employee_name", "name"]
)
timesheet_map = frappe._dict()
for d in timesheets:
timesheet_map.setdefault(d.name, d)
return timesheet_map
def get_timesheet_details(filters, timesheet_list):
timesheet_details_filter = {"parent": ["in", timesheet_list]}
if "project" in filters:
timesheet_details_filter["project"] = filters.project
timesheet_details = frappe.get_all(
"Timesheet Detail",
filters=timesheet_details_filter,
fields=[
"from_time",
"to_time",
"hours",
"is_billable",
"billing_hours",
"billing_rate",
"parent",
],
)
timesheet_details_map = frappe._dict()
for d in timesheet_details:
timesheet_details_map.setdefault(d.parent, []).append(d)
return timesheet_details_map
def get_billable_and_total_duration(activity, start_time, end_time):
precision = frappe.get_precision("Timesheet Detail", "hours")
activity_duration = time_diff_in_hours(end_time, start_time)
billing_duration = 0.0
if activity.is_billable:
billing_duration = activity.billing_hours
if activity_duration != activity.billing_hours:
billing_duration = activity_duration * activity.billing_hours / activity.hours
return flt(activity_duration, precision), flt(billing_duration, precision)