Browse Source

fix: validate Standard Working Hours

- use standard working hours in metrics calculation
develop
Rucha Mahabal 4 years ago
parent
commit
0decc2b66a
  1. 3
      erpnext/hr/doctype/hr_settings/hr_settings.json
  2. 34
      erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py

3
erpnext/hr/doctype/hr_settings/hr_settings.json

@ -146,7 +146,6 @@
"label": "Send Leave Notification" "label": "Send Leave Notification"
}, },
{ {
"default": "8",
"fieldname": "standard_working_hours", "fieldname": "standard_working_hours",
"fieldtype": "Int", "fieldtype": "Int",
"label": "Standard Working Hours" "label": "Standard Working Hours"
@ -156,7 +155,7 @@
"idx": 1, "idx": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2021-04-16 15:45:18.467699", "modified": "2021-04-26 10:52:56.192773",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "HR Settings", "name": "HR Settings",

34
erpnext/projects/report/employee_hours_utilization_based_on_timesheet/employee_hours_utilization_based_on_timesheet.py

@ -19,13 +19,22 @@ class EmployeeHoursReport:
self.to_date = getdate(self.filters.to_date) self.to_date = getdate(self.filters.to_date)
self.validate_dates() self.validate_dates()
self.validate_standard_working_hours()
def validate_dates(self): def validate_dates(self):
self.day_span = (self.to_date - self.from_date).days self.day_span = (self.to_date - self.from_date).days
if self.day_span <= 0: if self.day_span <= 0:
frappe.throw(_('From Date must come before To Date')) frappe.throw(_('From Date must come before To Date'))
def validate_standard_working_hours(self):
self.standard_working_hours = frappe.db.get_single_value('HR Settings', 'standard_working_hours')
if not self.standard_working_hours:
msg = _('The metrics for this report are calculated based on the Standard Working Hours. Please set {0} in {1}.').format(
frappe.bold('Standard Working Hours'), frappe.utils.get_link_to_form('HR Settings', 'HR Settings'))
frappe.throw(msg)
def run(self): def run(self):
self.generate_columns() self.generate_columns()
self.generate_data() self.generate_data()
@ -47,7 +56,7 @@ class EmployeeHoursReport:
'label': _('Department'), 'label': _('Department'),
'options': 'Department', 'options': 'Department',
'fieldname': 'department', 'fieldname': 'department',
'fieldtype': 'Link', 'fieldtype': 'Link',
'width': 170 'width': 170
}, },
{ {
@ -81,7 +90,7 @@ class EmployeeHoursReport:
'width': 200 'width': 200
} }
] ]
def generate_data(self): def generate_data(self):
self.generate_filtered_time_logs() self.generate_filtered_time_logs()
self.generate_stats_by_employee() self.generate_stats_by_employee()
@ -108,7 +117,7 @@ class EmployeeHoursReport:
for emp, data in self.stats_by_employee.items(): for emp, data in self.stats_by_employee.items():
if data['department'] == self.filters.department: if data['department'] == self.filters.department:
filtered_data[emp] = data filtered_data[emp] = data
# Update stats # Update stats
self.stats_by_employee = filtered_data self.stats_by_employee = filtered_data
@ -126,8 +135,8 @@ class EmployeeHoursReport:
self.filtered_time_logs = frappe.db.sql(''' self.filtered_time_logs = frappe.db.sql('''
SELECT tt.employee AS employee, ttd.hours AS hours, ttd.billable AS billable, ttd.project AS project SELECT tt.employee AS employee, ttd.hours AS hours, ttd.billable AS billable, ttd.project AS project
FROM `tabTimesheet Detail` AS ttd FROM `tabTimesheet Detail` AS ttd
JOIN `tabTimesheet` AS tt JOIN `tabTimesheet` AS tt
ON ttd.parent = tt.name ON ttd.parent = tt.name
WHERE tt.employee IS NOT NULL WHERE tt.employee IS NOT NULL
AND tt.start_date BETWEEN '{0}' AND '{1}' AND tt.start_date BETWEEN '{0}' AND '{1}'
@ -161,10 +170,9 @@ class EmployeeHoursReport:
self.stats_by_employee[emp]['department'] = emp_dept self.stats_by_employee[emp]['department'] = emp_dept
self.stats_by_employee[emp]['employee_name'] = emp_name self.stats_by_employee[emp]['employee_name'] = emp_name
def calculate_utilizations(self): def calculate_utilizations(self):
# (9.0) Will be fetched from HR settings TOTAL_HOURS = flt(self.standard_working_hours * self.day_span, 2)
TOTAL_HOURS = flt(9.0 * self.day_span, 2)
for emp, data in iteritems(self.stats_by_employee): for emp, data in iteritems(self.stats_by_employee):
data['total_hours'] = TOTAL_HOURS data['total_hours'] = TOTAL_HOURS
data['untracked_hours'] = flt(TOTAL_HOURS - data['billed_hours'] - data['non_billed_hours'], 2) data['untracked_hours'] = flt(TOTAL_HOURS - data['billed_hours'] - data['non_billed_hours'], 2)
@ -172,14 +180,14 @@ class EmployeeHoursReport:
# To handle overtime edge-case # To handle overtime edge-case
if data['untracked_hours'] < 0: if data['untracked_hours'] < 0:
data['untracked_hours'] = 0.0 data['untracked_hours'] = 0.0
data['per_util'] = flt(((data['billed_hours'] + data['non_billed_hours']) / TOTAL_HOURS) * 100, 2) data['per_util'] = flt(((data['billed_hours'] + data['non_billed_hours']) / TOTAL_HOURS) * 100, 2)
def generate_report_summary(self): def generate_report_summary(self):
self.report_summary = [] self.report_summary = []
if not self.data: if not self.data:
return return
avg_utilization = 0.0 avg_utilization = 0.0
total_billed, total_non_billed = 0.0, 0.0 total_billed, total_non_billed = 0.0, 0.0
@ -233,7 +241,7 @@ class EmployeeHoursReport:
billed_hours.append(row.get('billed_hours')) billed_hours.append(row.get('billed_hours'))
non_billed_hours.append(row.get('non_billed_hours')) non_billed_hours.append(row.get('non_billed_hours'))
untracked_hours.append(row.get('untracked_hours')) untracked_hours.append(row.get('untracked_hours'))
self.chart = { self.chart = {
'data': { 'data': {
'labels': labels[:30], 'labels': labels[:30],

Loading…
Cancel
Save