156 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			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)
 |