style: format code with black
diff --git a/erpnext/hr/doctype/appointment_letter/appointment_letter.py b/erpnext/hr/doctype/appointment_letter/appointment_letter.py
index 71327bf..a58589a 100644
--- a/erpnext/hr/doctype/appointment_letter/appointment_letter.py
+++ b/erpnext/hr/doctype/appointment_letter/appointment_letter.py
@@ -9,18 +9,21 @@
class AppointmentLetter(Document):
pass
+
@frappe.whitelist()
def get_appointment_letter_details(template):
body = []
- intro = frappe.get_list('Appointment Letter Template',
- fields=['introduction', 'closing_notes'],
- filters={'name': template}
+ intro = frappe.get_list(
+ "Appointment Letter Template",
+ fields=["introduction", "closing_notes"],
+ filters={"name": template},
)[0]
- content = frappe.get_all('Appointment Letter content',
- fields=['title', 'description'],
- filters={'parent': template},
- order_by='idx'
+ content = frappe.get_all(
+ "Appointment Letter content",
+ fields=["title", "description"],
+ filters={"parent": template},
+ order_by="idx",
)
body.append(intro)
- body.append({'description': content})
+ body.append({"description": content})
return body
diff --git a/erpnext/hr/doctype/appraisal/appraisal.py b/erpnext/hr/doctype/appraisal/appraisal.py
index 83273f8..382c643 100644
--- a/erpnext/hr/doctype/appraisal/appraisal.py
+++ b/erpnext/hr/doctype/appraisal/appraisal.py
@@ -34,46 +34,61 @@
frappe.throw(_("End Date can not be less than Start Date"))
def validate_existing_appraisal(self):
- chk = frappe.db.sql("""select name from `tabAppraisal` where employee=%s
+ chk = frappe.db.sql(
+ """select name from `tabAppraisal` where employee=%s
and (status='Submitted' or status='Completed')
and ((start_date>=%s and start_date<=%s)
or (end_date>=%s and end_date<=%s))""",
- (self.employee,self.start_date,self.end_date,self.start_date,self.end_date))
+ (self.employee, self.start_date, self.end_date, self.start_date, self.end_date),
+ )
if chk:
- frappe.throw(_("Appraisal {0} created for Employee {1} in the given date range").format(chk[0][0], self.employee_name))
+ frappe.throw(
+ _("Appraisal {0} created for Employee {1} in the given date range").format(
+ chk[0][0], self.employee_name
+ )
+ )
def calculate_total(self):
- total, total_w = 0, 0
- for d in self.get('goals'):
+ total, total_w = 0, 0
+ for d in self.get("goals"):
if d.score:
d.score_earned = flt(d.score) * flt(d.per_weightage) / 100
total = total + d.score_earned
total_w += flt(d.per_weightage)
if int(total_w) != 100:
- frappe.throw(_("Total weightage assigned should be 100%.<br>It is {0}").format(str(total_w) + "%"))
+ frappe.throw(
+ _("Total weightage assigned should be 100%.<br>It is {0}").format(str(total_w) + "%")
+ )
- if frappe.db.get_value("Employee", self.employee, "user_id") != \
- frappe.session.user and total == 0:
+ if (
+ frappe.db.get_value("Employee", self.employee, "user_id") != frappe.session.user and total == 0
+ ):
frappe.throw(_("Total cannot be zero"))
self.total_score = total
def on_submit(self):
- frappe.db.set(self, 'status', 'Submitted')
+ frappe.db.set(self, "status", "Submitted")
def on_cancel(self):
- frappe.db.set(self, 'status', 'Cancelled')
+ frappe.db.set(self, "status", "Cancelled")
+
@frappe.whitelist()
def fetch_appraisal_template(source_name, target_doc=None):
- target_doc = get_mapped_doc("Appraisal Template", source_name, {
- "Appraisal Template": {
- "doctype": "Appraisal",
+ target_doc = get_mapped_doc(
+ "Appraisal Template",
+ source_name,
+ {
+ "Appraisal Template": {
+ "doctype": "Appraisal",
+ },
+ "Appraisal Template Goal": {
+ "doctype": "Appraisal Goal",
+ },
},
- "Appraisal Template Goal": {
- "doctype": "Appraisal Goal",
- }
- }, target_doc)
+ target_doc,
+ )
return target_doc
diff --git a/erpnext/hr/doctype/appraisal/test_appraisal.py b/erpnext/hr/doctype/appraisal/test_appraisal.py
index 90c30ef..13a39f3 100644
--- a/erpnext/hr/doctype/appraisal/test_appraisal.py
+++ b/erpnext/hr/doctype/appraisal/test_appraisal.py
@@ -5,5 +5,6 @@
# test_records = frappe.get_test_records('Appraisal')
+
class TestAppraisal(unittest.TestCase):
pass
diff --git a/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py b/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py
index 116a3f9..476de4f 100644
--- a/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py
+++ b/erpnext/hr/doctype/appraisal_template/appraisal_template_dashboard.py
@@ -1,9 +1,7 @@
def get_data():
- return {
- 'fieldname': 'kra_template',
- 'transactions': [
- {
- 'items': ['Appraisal']
- },
- ],
- }
+ return {
+ "fieldname": "kra_template",
+ "transactions": [
+ {"items": ["Appraisal"]},
+ ],
+ }
diff --git a/erpnext/hr/doctype/appraisal_template/test_appraisal_template.py b/erpnext/hr/doctype/appraisal_template/test_appraisal_template.py
index d0e81a7..560e992 100644
--- a/erpnext/hr/doctype/appraisal_template/test_appraisal_template.py
+++ b/erpnext/hr/doctype/appraisal_template/test_appraisal_template.py
@@ -5,5 +5,6 @@
# test_records = frappe.get_test_records('Appraisal Template')
+
class TestAppraisalTemplate(unittest.TestCase):
pass
diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py
index b1e373e..7f4bd83 100644
--- a/erpnext/hr/doctype/attendance/attendance.py
+++ b/erpnext/hr/doctype/attendance/attendance.py
@@ -13,6 +13,7 @@
class Attendance(Document):
def validate(self):
from erpnext.controllers.status_updater import validate_status
+
validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"])
validate_active_employee(self.employee)
self.validate_attendance_date()
@@ -24,62 +25,84 @@
date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
# leaves can be marked for future dates
- if self.status != 'On Leave' and not self.leave_application and getdate(self.attendance_date) > getdate(nowdate()):
+ if (
+ self.status != "On Leave"
+ and not self.leave_application
+ and getdate(self.attendance_date) > getdate(nowdate())
+ ):
frappe.throw(_("Attendance can not be marked for future dates"))
elif date_of_joining and getdate(self.attendance_date) < getdate(date_of_joining):
frappe.throw(_("Attendance date can not be less than employee's joining date"))
def validate_duplicate_record(self):
- res = frappe.db.sql("""
+ res = frappe.db.sql(
+ """
select name from `tabAttendance`
where employee = %s
and attendance_date = %s
and name != %s
and docstatus != 2
- """, (self.employee, getdate(self.attendance_date), self.name))
+ """,
+ (self.employee, getdate(self.attendance_date), self.name),
+ )
if res:
- frappe.throw(_("Attendance for employee {0} is already marked for the date {1}").format(
- frappe.bold(self.employee), frappe.bold(self.attendance_date)))
+ frappe.throw(
+ _("Attendance for employee {0} is already marked for the date {1}").format(
+ frappe.bold(self.employee), frappe.bold(self.attendance_date)
+ )
+ )
def validate_employee_status(self):
if frappe.db.get_value("Employee", self.employee, "status") == "Inactive":
frappe.throw(_("Cannot mark attendance for an Inactive employee {0}").format(self.employee))
def check_leave_record(self):
- leave_record = frappe.db.sql("""
+ leave_record = frappe.db.sql(
+ """
select leave_type, half_day, half_day_date
from `tabLeave Application`
where employee = %s
and %s between from_date and to_date
and status = 'Approved'
and docstatus = 1
- """, (self.employee, self.attendance_date), as_dict=True)
+ """,
+ (self.employee, self.attendance_date),
+ as_dict=True,
+ )
if leave_record:
for d in leave_record:
self.leave_type = d.leave_type
if d.half_day_date == getdate(self.attendance_date):
- self.status = 'Half Day'
- frappe.msgprint(_("Employee {0} on Half day on {1}")
- .format(self.employee, formatdate(self.attendance_date)))
+ self.status = "Half Day"
+ frappe.msgprint(
+ _("Employee {0} on Half day on {1}").format(self.employee, formatdate(self.attendance_date))
+ )
else:
- self.status = 'On Leave'
- frappe.msgprint(_("Employee {0} is on Leave on {1}")
- .format(self.employee, formatdate(self.attendance_date)))
+ self.status = "On Leave"
+ frappe.msgprint(
+ _("Employee {0} is on Leave on {1}").format(self.employee, formatdate(self.attendance_date))
+ )
if self.status in ("On Leave", "Half Day"):
if not leave_record:
- frappe.msgprint(_("No leave record found for employee {0} on {1}")
- .format(self.employee, formatdate(self.attendance_date)), alert=1)
+ frappe.msgprint(
+ _("No leave record found for employee {0} on {1}").format(
+ self.employee, formatdate(self.attendance_date)
+ ),
+ alert=1,
+ )
elif self.leave_type:
self.leave_type = None
self.leave_application = None
def validate_employee(self):
- emp = frappe.db.sql("select name from `tabEmployee` where name = %s and status = 'Active'",
- self.employee)
+ emp = frappe.db.sql(
+ "select name from `tabEmployee` where name = %s and status = 'Active'", self.employee
+ )
if not emp:
frappe.throw(_("Employee {0} is not active or does not exist").format(self.employee))
+
@frappe.whitelist()
def get_events(start, end, filters=None):
events = []
@@ -90,10 +113,12 @@
return events
from frappe.desk.reportview import get_filters_cond
+
conditions = get_filters_cond("Attendance", filters, [])
add_attendance(events, start, end, conditions=conditions)
return events
+
def add_attendance(events, start, end, conditions=None):
query = """select name, attendance_date, status
from `tabAttendance` where
@@ -102,81 +127,97 @@
if conditions:
query += conditions
- for d in frappe.db.sql(query, {"from_date":start, "to_date":end}, as_dict=True):
+ for d in frappe.db.sql(query, {"from_date": start, "to_date": end}, as_dict=True):
e = {
"name": d.name,
"doctype": "Attendance",
"start": d.attendance_date,
"end": d.attendance_date,
"title": cstr(d.status),
- "docstatus": d.docstatus
+ "docstatus": d.docstatus,
}
if e not in events:
events.append(e)
-def mark_attendance(employee, attendance_date, status, shift=None, leave_type=None, ignore_validate=False):
- if not frappe.db.exists('Attendance', {'employee':employee, 'attendance_date':attendance_date, 'docstatus':('!=', '2')}):
- company = frappe.db.get_value('Employee', employee, 'company')
- attendance = frappe.get_doc({
- 'doctype': 'Attendance',
- 'employee': employee,
- 'attendance_date': attendance_date,
- 'status': status,
- 'company': company,
- 'shift': shift,
- 'leave_type': leave_type
- })
+
+def mark_attendance(
+ employee, attendance_date, status, shift=None, leave_type=None, ignore_validate=False
+):
+ if not frappe.db.exists(
+ "Attendance",
+ {"employee": employee, "attendance_date": attendance_date, "docstatus": ("!=", "2")},
+ ):
+ company = frappe.db.get_value("Employee", employee, "company")
+ attendance = frappe.get_doc(
+ {
+ "doctype": "Attendance",
+ "employee": employee,
+ "attendance_date": attendance_date,
+ "status": status,
+ "company": company,
+ "shift": shift,
+ "leave_type": leave_type,
+ }
+ )
attendance.flags.ignore_validate = ignore_validate
attendance.insert()
attendance.submit()
return attendance.name
+
@frappe.whitelist()
def mark_bulk_attendance(data):
import json
+
if isinstance(data, str):
data = json.loads(data)
data = frappe._dict(data)
- company = frappe.get_value('Employee', data.employee, 'company')
+ company = frappe.get_value("Employee", data.employee, "company")
if not data.unmarked_days:
frappe.throw(_("Please select a date."))
return
for date in data.unmarked_days:
doc_dict = {
- 'doctype': 'Attendance',
- 'employee': data.employee,
- 'attendance_date': get_datetime(date),
- 'status': data.status,
- 'company': company,
+ "doctype": "Attendance",
+ "employee": data.employee,
+ "attendance_date": get_datetime(date),
+ "status": data.status,
+ "company": company,
}
attendance = frappe.get_doc(doc_dict).insert()
attendance.submit()
def get_month_map():
- return frappe._dict({
- "January": 1,
- "February": 2,
- "March": 3,
- "April": 4,
- "May": 5,
- "June": 6,
- "July": 7,
- "August": 8,
- "September": 9,
- "October": 10,
- "November": 11,
- "December": 12
- })
+ return frappe._dict(
+ {
+ "January": 1,
+ "February": 2,
+ "March": 3,
+ "April": 4,
+ "May": 5,
+ "June": 6,
+ "July": 7,
+ "August": 8,
+ "September": 9,
+ "October": 10,
+ "November": 11,
+ "December": 12,
+ }
+ )
+
@frappe.whitelist()
def get_unmarked_days(employee, month, exclude_holidays=0):
import calendar
+
month_map = get_month_map()
today = get_datetime()
- joining_date, relieving_date = frappe.get_cached_value("Employee", employee, ["date_of_joining", "relieving_date"])
+ joining_date, relieving_date = frappe.get_cached_value(
+ "Employee", employee, ["date_of_joining", "relieving_date"]
+ )
start_day = 1
end_day = calendar.monthrange(today.year, month_map[month])[1] + 1
@@ -186,15 +227,21 @@
if relieving_date and relieving_date.month == month_map[month]:
end_day = relieving_date.day + 1
- dates_of_month = ['{}-{}-{}'.format(today.year, month_map[month], r) for r in range(start_day, end_day)]
+ dates_of_month = [
+ "{}-{}-{}".format(today.year, month_map[month], r) for r in range(start_day, end_day)
+ ]
month_start, month_end = dates_of_month[0], dates_of_month[-1]
- records = frappe.get_all("Attendance", fields=['attendance_date', 'employee'], filters=[
- ["attendance_date", ">=", month_start],
- ["attendance_date", "<=", month_end],
- ["employee", "=", employee],
- ["docstatus", "!=", 2]
- ])
+ records = frappe.get_all(
+ "Attendance",
+ fields=["attendance_date", "employee"],
+ filters=[
+ ["attendance_date", ">=", month_start],
+ ["attendance_date", "<=", month_end],
+ ["employee", "=", employee],
+ ["docstatus", "!=", 2],
+ ],
+ )
marked_days = [get_datetime(record.attendance_date) for record in records]
if cint(exclude_holidays):
diff --git a/erpnext/hr/doctype/attendance/attendance_dashboard.py b/erpnext/hr/doctype/attendance/attendance_dashboard.py
index 4bb36a0..abed78f 100644
--- a/erpnext/hr/doctype/attendance/attendance_dashboard.py
+++ b/erpnext/hr/doctype/attendance/attendance_dashboard.py
@@ -1,10 +1,2 @@
def get_data():
- return {
- 'fieldname': 'attendance',
- 'transactions': [
- {
- 'label': '',
- 'items': ['Employee Checkin']
- }
- ]
- }
+ return {"fieldname": "attendance", "transactions": [{"label": "", "items": ["Employee Checkin"]}]}
diff --git a/erpnext/hr/doctype/attendance/test_attendance.py b/erpnext/hr/doctype/attendance/test_attendance.py
index 585059f..058bc93 100644
--- a/erpnext/hr/doctype/attendance/test_attendance.py
+++ b/erpnext/hr/doctype/attendance/test_attendance.py
@@ -13,7 +13,8 @@
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.leave_application.test_leave_application import get_first_sunday
-test_records = frappe.get_test_records('Attendance')
+test_records = frappe.get_test_records("Attendance")
+
class TestAttendance(FrappeTestCase):
def setUp(self):
@@ -26,9 +27,11 @@
def test_mark_absent(self):
employee = make_employee("test_mark_absent@example.com")
date = nowdate()
- frappe.db.delete('Attendance', {'employee':employee, 'attendance_date':date})
- attendance = mark_attendance(employee, date, 'Absent')
- fetch_attendance = frappe.get_value('Attendance', {'employee':employee, 'attendance_date':date, 'status':'Absent'})
+ frappe.db.delete("Attendance", {"employee": employee, "attendance_date": date})
+ attendance = mark_attendance(employee, date, "Absent")
+ fetch_attendance = frappe.get_value(
+ "Attendance", {"employee": employee, "attendance_date": date, "status": "Absent"}
+ )
self.assertEqual(attendance, fetch_attendance)
def test_unmarked_days(self):
@@ -36,12 +39,14 @@
previous_month = now.month - 1
first_day = now.replace(day=1).replace(month=previous_month).date()
- employee = make_employee('test_unmarked_days@example.com', date_of_joining=add_days(first_day, -1))
- frappe.db.delete('Attendance', {'employee': employee})
- frappe.db.set_value('Employee', employee, 'holiday_list', self.holiday_list)
+ employee = make_employee(
+ "test_unmarked_days@example.com", date_of_joining=add_days(first_day, -1)
+ )
+ frappe.db.delete("Attendance", {"employee": employee})
+ frappe.db.set_value("Employee", employee, "holiday_list", self.holiday_list)
first_sunday = get_first_sunday(self.holiday_list, for_date=first_day)
- mark_attendance(employee, first_day, 'Present')
+ mark_attendance(employee, first_day, "Present")
month_name = get_month_name(first_day)
unmarked_days = get_unmarked_days(employee, month_name)
@@ -59,13 +64,15 @@
previous_month = now.month - 1
first_day = now.replace(day=1).replace(month=previous_month).date()
- employee = make_employee('test_unmarked_days@example.com', date_of_joining=add_days(first_day, -1))
- frappe.db.delete('Attendance', {'employee': employee})
+ employee = make_employee(
+ "test_unmarked_days@example.com", date_of_joining=add_days(first_day, -1)
+ )
+ frappe.db.delete("Attendance", {"employee": employee})
- frappe.db.set_value('Employee', employee, 'holiday_list', self.holiday_list)
+ frappe.db.set_value("Employee", employee, "holiday_list", self.holiday_list)
first_sunday = get_first_sunday(self.holiday_list, for_date=first_day)
- mark_attendance(employee, first_day, 'Present')
+ mark_attendance(employee, first_day, "Present")
month_name = get_month_name(first_day)
unmarked_days = get_unmarked_days(employee, month_name, exclude_holidays=True)
@@ -85,14 +92,15 @@
doj = add_days(first_day, 1)
relieving_date = add_days(first_day, 5)
- employee = make_employee('test_unmarked_days_as_per_doj@example.com', date_of_joining=doj,
- relieving_date=relieving_date)
- frappe.db.delete('Attendance', {'employee': employee})
+ employee = make_employee(
+ "test_unmarked_days_as_per_doj@example.com", date_of_joining=doj, relieving_date=relieving_date
+ )
+ frappe.db.delete("Attendance", {"employee": employee})
- frappe.db.set_value('Employee', employee, 'holiday_list', self.holiday_list)
+ frappe.db.set_value("Employee", employee, "holiday_list", self.holiday_list)
attendance_date = add_days(first_day, 2)
- mark_attendance(employee, attendance_date, 'Present')
+ mark_attendance(employee, attendance_date, "Present")
month_name = get_month_name(first_day)
unmarked_days = get_unmarked_days(employee, month_name)
diff --git a/erpnext/hr/doctype/attendance_request/attendance_request.py b/erpnext/hr/doctype/attendance_request/attendance_request.py
index 8fbe7c7..78652f6 100644
--- a/erpnext/hr/doctype/attendance_request/attendance_request.py
+++ b/erpnext/hr/doctype/attendance_request/attendance_request.py
@@ -16,17 +16,19 @@
validate_active_employee(self.employee)
validate_dates(self, self.from_date, self.to_date)
if self.half_day:
- if not getdate(self.from_date)<=getdate(self.half_day_date)<=getdate(self.to_date):
+ if not getdate(self.from_date) <= getdate(self.half_day_date) <= getdate(self.to_date):
frappe.throw(_("Half day date should be in between from date and to date"))
def on_submit(self):
self.create_attendance()
def on_cancel(self):
- attendance_list = frappe.get_list("Attendance", {'employee': self.employee, 'attendance_request': self.name})
+ attendance_list = frappe.get_list(
+ "Attendance", {"employee": self.employee, "attendance_request": self.name}
+ )
if attendance_list:
for attendance in attendance_list:
- attendance_obj = frappe.get_doc("Attendance", attendance['name'])
+ attendance_obj = frappe.get_doc("Attendance", attendance["name"])
attendance_obj.cancel()
def create_attendance(self):
@@ -53,15 +55,24 @@
def validate_if_attendance_not_applicable(self, attendance_date):
# Check if attendance_date is a Holiday
if is_holiday(self.employee, attendance_date):
- frappe.msgprint(_("Attendance not submitted for {0} as it is a Holiday.").format(attendance_date), alert=1)
+ frappe.msgprint(
+ _("Attendance not submitted for {0} as it is a Holiday.").format(attendance_date), alert=1
+ )
return True
# Check if employee on Leave
- leave_record = frappe.db.sql("""select half_day from `tabLeave Application`
+ leave_record = frappe.db.sql(
+ """select half_day from `tabLeave Application`
where employee = %s and %s between from_date and to_date
- and docstatus = 1""", (self.employee, attendance_date), as_dict=True)
+ and docstatus = 1""",
+ (self.employee, attendance_date),
+ as_dict=True,
+ )
if leave_record:
- frappe.msgprint(_("Attendance not submitted for {0} as {1} on leave.").format(attendance_date, self.employee), alert=1)
+ frappe.msgprint(
+ _("Attendance not submitted for {0} as {1} on leave.").format(attendance_date, self.employee),
+ alert=1,
+ )
return True
return False
diff --git a/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py b/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py
index 9197057..059725c 100644
--- a/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py
+++ b/erpnext/hr/doctype/attendance_request/attendance_request_dashboard.py
@@ -1,9 +1,2 @@
def get_data():
- return {
- 'fieldname': 'attendance_request',
- 'transactions': [
- {
- 'items': ['Attendance']
- }
- ]
- }
+ return {"fieldname": "attendance_request", "transactions": [{"items": ["Attendance"]}]}
diff --git a/erpnext/hr/doctype/attendance_request/test_attendance_request.py b/erpnext/hr/doctype/attendance_request/test_attendance_request.py
index 3f0442c..ee436f5 100644
--- a/erpnext/hr/doctype/attendance_request/test_attendance_request.py
+++ b/erpnext/hr/doctype/attendance_request/test_attendance_request.py
@@ -9,6 +9,7 @@
test_dependencies = ["Employee"]
+
class TestAttendanceRequest(unittest.TestCase):
def setUp(self):
for doctype in ["Attendance Request", "Attendance"]:
@@ -34,10 +35,10 @@
"Attendance",
filters={
"attendance_request": attendance_request.name,
- "attendance_date": date(date.today().year, 1, 1)
+ "attendance_date": date(date.today().year, 1, 1),
},
fieldname=["status", "docstatus"],
- as_dict=True
+ as_dict=True,
)
self.assertEqual(attendance.status, "Present")
self.assertEqual(attendance.docstatus, 1)
@@ -51,9 +52,9 @@
"Attendance",
filters={
"attendance_request": attendance_request.name,
- "attendance_date": date(date.today().year, 1, 1)
+ "attendance_date": date(date.today().year, 1, 1),
},
- fieldname="docstatus"
+ fieldname="docstatus",
)
self.assertEqual(attendance_docstatus, 2)
@@ -74,11 +75,11 @@
"Attendance",
filters={
"attendance_request": attendance_request.name,
- "attendance_date": date(date.today().year, 1, 1)
+ "attendance_date": date(date.today().year, 1, 1),
},
- fieldname="status"
+ fieldname="status",
)
- self.assertEqual(attendance_status, 'Work From Home')
+ self.assertEqual(attendance_status, "Work From Home")
attendance_request.cancel()
@@ -88,11 +89,12 @@
"Attendance",
filters={
"attendance_request": attendance_request.name,
- "attendance_date": date(date.today().year, 1, 1)
+ "attendance_date": date(date.today().year, 1, 1),
},
- fieldname="docstatus"
+ fieldname="docstatus",
)
self.assertEqual(attendance_docstatus, 2)
+
def get_employee():
return frappe.get_doc("Employee", "_T-Employee-00001")
diff --git a/erpnext/hr/doctype/branch/test_branch.py b/erpnext/hr/doctype/branch/test_branch.py
index e84c6e4..c14d4aa 100644
--- a/erpnext/hr/doctype/branch/test_branch.py
+++ b/erpnext/hr/doctype/branch/test_branch.py
@@ -3,4 +3,4 @@
import frappe
-test_records = frappe.get_test_records('Branch')
+test_records = frappe.get_test_records("Branch")
diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
index 7d60515..d233226 100644
--- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
+++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
@@ -18,14 +18,15 @@
class CompensatoryLeaveRequest(Document):
-
def validate(self):
validate_active_employee(self.employee)
validate_dates(self, self.work_from_date, self.work_end_date)
if self.half_day:
if not self.half_day_date:
frappe.throw(_("Half Day Date is mandatory"))
- if not getdate(self.work_from_date)<=getdate(self.half_day_date)<=getdate(self.work_end_date):
+ if (
+ not getdate(self.work_from_date) <= getdate(self.half_day_date) <= getdate(self.work_end_date)
+ ):
frappe.throw(_("Half Day Date should be in between Work From Date and Work End Date"))
validate_overlap(self, self.work_from_date, self.work_end_date)
self.validate_holidays()
@@ -34,13 +35,16 @@
frappe.throw(_("Leave Type is madatory"))
def validate_attendance(self):
- attendance = frappe.get_all('Attendance',
+ attendance = frappe.get_all(
+ "Attendance",
filters={
- 'attendance_date': ['between', (self.work_from_date, self.work_end_date)],
- 'status': 'Present',
- 'docstatus': 1,
- 'employee': self.employee
- }, fields=['attendance_date', 'status'])
+ "attendance_date": ["between", (self.work_from_date, self.work_end_date)],
+ "status": "Present",
+ "docstatus": 1,
+ "employee": self.employee,
+ },
+ fields=["attendance_date", "status"],
+ )
if len(attendance) < date_diff(self.work_end_date, self.work_from_date) + 1:
frappe.throw(_("You are not present all day(s) between compensatory leave request days"))
@@ -49,7 +53,9 @@
holidays = get_holiday_dates_for_employee(self.employee, self.work_from_date, self.work_end_date)
if len(holidays) < date_diff(self.work_end_date, self.work_from_date) + 1:
if date_diff(self.work_end_date, self.work_from_date):
- msg = _("The days between {0} to {1} are not valid holidays.").format(frappe.bold(format_date(self.work_from_date)), frappe.bold(format_date(self.work_end_date)))
+ msg = _("The days between {0} to {1} are not valid holidays.").format(
+ frappe.bold(format_date(self.work_from_date)), frappe.bold(format_date(self.work_end_date))
+ )
else:
msg = _("{0} is not a holiday.").format(frappe.bold(format_date(self.work_from_date)))
@@ -70,13 +76,19 @@
leave_allocation.db_set("total_leaves_allocated", leave_allocation.total_leaves_allocated)
# generate additional ledger entry for the new compensatory leaves off
- create_additional_leave_ledger_entry(leave_allocation, date_difference, add_days(self.work_end_date, 1))
+ create_additional_leave_ledger_entry(
+ leave_allocation, date_difference, add_days(self.work_end_date, 1)
+ )
else:
leave_allocation = self.create_leave_allocation(leave_period, date_difference)
self.db_set("leave_allocation", leave_allocation.name)
else:
- frappe.throw(_("There is no leave period in between {0} and {1}").format(format_date(self.work_from_date), format_date(self.work_end_date)))
+ frappe.throw(
+ _("There is no leave period in between {0} and {1}").format(
+ format_date(self.work_from_date), format_date(self.work_end_date)
+ )
+ )
def on_cancel(self):
if self.leave_allocation:
@@ -93,10 +105,13 @@
leave_allocation.db_set("total_leaves_allocated", leave_allocation.total_leaves_allocated)
# create reverse entry on cancelation
- create_additional_leave_ledger_entry(leave_allocation, date_difference * -1, add_days(self.work_end_date, 1))
+ create_additional_leave_ledger_entry(
+ leave_allocation, date_difference * -1, add_days(self.work_end_date, 1)
+ )
def get_existing_allocation_for_period(self, leave_period):
- leave_allocation = frappe.db.sql("""
+ leave_allocation = frappe.db.sql(
+ """
select name
from `tabLeave Allocation`
where employee=%(employee)s and leave_type=%(leave_type)s
@@ -104,12 +119,15 @@
and (from_date between %(from_date)s and %(to_date)s
or to_date between %(from_date)s and %(to_date)s
or (from_date < %(from_date)s and to_date > %(to_date)s))
- """, {
- "from_date": leave_period[0].from_date,
- "to_date": leave_period[0].to_date,
- "employee": self.employee,
- "leave_type": self.leave_type
- }, as_dict=1)
+ """,
+ {
+ "from_date": leave_period[0].from_date,
+ "to_date": leave_period[0].to_date,
+ "employee": self.employee,
+ "leave_type": self.leave_type,
+ },
+ as_dict=1,
+ )
if leave_allocation:
return frappe.get_doc("Leave Allocation", leave_allocation[0].name)
@@ -118,18 +136,20 @@
def create_leave_allocation(self, leave_period, date_difference):
is_carry_forward = frappe.db.get_value("Leave Type", self.leave_type, "is_carry_forward")
- allocation = frappe.get_doc(dict(
- doctype="Leave Allocation",
- employee=self.employee,
- employee_name=self.employee_name,
- leave_type=self.leave_type,
- from_date=add_days(self.work_end_date, 1),
- to_date=leave_period[0].to_date,
- carry_forward=cint(is_carry_forward),
- new_leaves_allocated=date_difference,
- total_leaves_allocated=date_difference,
- description=self.reason
- ))
+ allocation = frappe.get_doc(
+ dict(
+ doctype="Leave Allocation",
+ employee=self.employee,
+ employee_name=self.employee_name,
+ leave_type=self.leave_type,
+ from_date=add_days(self.work_end_date, 1),
+ to_date=leave_period[0].to_date,
+ carry_forward=cint(is_carry_forward),
+ new_leaves_allocated=date_difference,
+ total_leaves_allocated=date_difference,
+ description=self.reason,
+ )
+ )
allocation.insert(ignore_permissions=True)
allocation.submit()
return allocation
diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
index 5e51879..7bbec29 100644
--- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
+++ b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
@@ -12,12 +12,17 @@
test_dependencies = ["Employee"]
+
class TestCompensatoryLeaveRequest(unittest.TestCase):
def setUp(self):
- frappe.db.sql(''' delete from `tabCompensatory Leave Request`''')
- frappe.db.sql(''' delete from `tabLeave Ledger Entry`''')
- frappe.db.sql(''' delete from `tabLeave Allocation`''')
- frappe.db.sql(''' delete from `tabAttendance` where attendance_date in {0} '''.format((today(), add_days(today(), -1)))) #nosec
+ frappe.db.sql(""" delete from `tabCompensatory Leave Request`""")
+ frappe.db.sql(""" delete from `tabLeave Ledger Entry`""")
+ frappe.db.sql(""" delete from `tabLeave Allocation`""")
+ frappe.db.sql(
+ """ delete from `tabAttendance` where attendance_date in {0} """.format(
+ (today(), add_days(today(), -1))
+ )
+ ) # nosec
create_leave_period(add_months(today(), -3), add_months(today(), 3), "_Test Company")
create_holiday_list()
@@ -26,7 +31,7 @@
employee.save()
def test_leave_balance_on_submit(self):
- ''' check creation of leave allocation on submission of compensatory leave request '''
+ """check creation of leave allocation on submission of compensatory leave request"""
employee = get_employee()
mark_attendance(employee)
compensatory_leave_request = get_compensatory_leave_request(employee.name)
@@ -34,18 +39,27 @@
before = get_leave_balance_on(employee.name, compensatory_leave_request.leave_type, today())
compensatory_leave_request.submit()
- self.assertEqual(get_leave_balance_on(employee.name, compensatory_leave_request.leave_type, add_days(today(), 1)), before + 1)
+ self.assertEqual(
+ get_leave_balance_on(
+ employee.name, compensatory_leave_request.leave_type, add_days(today(), 1)
+ ),
+ before + 1,
+ )
def test_leave_allocation_update_on_submit(self):
employee = get_employee()
mark_attendance(employee, date=add_days(today(), -1))
- compensatory_leave_request = get_compensatory_leave_request(employee.name, leave_date=add_days(today(), -1))
+ compensatory_leave_request = get_compensatory_leave_request(
+ employee.name, leave_date=add_days(today(), -1)
+ )
compensatory_leave_request.submit()
# leave allocation creation on submit
- leaves_allocated = frappe.db.get_value('Leave Allocation', {
- 'name': compensatory_leave_request.leave_allocation
- }, ['total_leaves_allocated'])
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"name": compensatory_leave_request.leave_allocation},
+ ["total_leaves_allocated"],
+ )
self.assertEqual(leaves_allocated, 1)
mark_attendance(employee)
@@ -53,20 +67,22 @@
compensatory_leave_request.submit()
# leave allocation updates on submission of second compensatory leave request
- leaves_allocated = frappe.db.get_value('Leave Allocation', {
- 'name': compensatory_leave_request.leave_allocation
- }, ['total_leaves_allocated'])
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"name": compensatory_leave_request.leave_allocation},
+ ["total_leaves_allocated"],
+ )
self.assertEqual(leaves_allocated, 2)
def test_creation_of_leave_ledger_entry_on_submit(self):
- ''' check creation of leave ledger entry on submission of leave request '''
+ """check creation of leave ledger entry on submission of leave request"""
employee = get_employee()
mark_attendance(employee)
compensatory_leave_request = get_compensatory_leave_request(employee.name)
compensatory_leave_request.submit()
filters = dict(transaction_name=compensatory_leave_request.leave_allocation)
- leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters)
+ leave_ledger_entry = frappe.get_all("Leave Ledger Entry", fields="*", filters=filters)
self.assertEqual(len(leave_ledger_entry), 1)
self.assertEqual(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
@@ -75,60 +91,67 @@
# check reverse leave ledger entry on cancellation
compensatory_leave_request.cancel()
- leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters, order_by = 'creation desc')
+ leave_ledger_entry = frappe.get_all(
+ "Leave Ledger Entry", fields="*", filters=filters, order_by="creation desc"
+ )
self.assertEqual(len(leave_ledger_entry), 2)
self.assertEqual(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
self.assertEqual(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
self.assertEqual(leave_ledger_entry[0].leaves, -1)
+
def get_compensatory_leave_request(employee, leave_date=today()):
- prev_comp_leave_req = frappe.db.get_value('Compensatory Leave Request',
- dict(leave_type='Compensatory Off',
+ prev_comp_leave_req = frappe.db.get_value(
+ "Compensatory Leave Request",
+ dict(
+ leave_type="Compensatory Off",
work_from_date=leave_date,
work_end_date=leave_date,
- employee=employee), 'name')
- if prev_comp_leave_req:
- return frappe.get_doc('Compensatory Leave Request', prev_comp_leave_req)
-
- return frappe.get_doc(dict(
- doctype='Compensatory Leave Request',
employee=employee,
- leave_type='Compensatory Off',
+ ),
+ "name",
+ )
+ if prev_comp_leave_req:
+ return frappe.get_doc("Compensatory Leave Request", prev_comp_leave_req)
+
+ return frappe.get_doc(
+ dict(
+ doctype="Compensatory Leave Request",
+ employee=employee,
+ leave_type="Compensatory Off",
work_from_date=leave_date,
work_end_date=leave_date,
- reason='test'
- )).insert()
+ reason="test",
+ )
+ ).insert()
-def mark_attendance(employee, date=today(), status='Present'):
- if not frappe.db.exists(dict(doctype='Attendance', employee=employee.name, attendance_date=date, status='Present')):
- attendance = frappe.get_doc({
- "doctype": "Attendance",
- "employee": employee.name,
- "attendance_date": date,
- "status": status
- })
+
+def mark_attendance(employee, date=today(), status="Present"):
+ if not frappe.db.exists(
+ dict(doctype="Attendance", employee=employee.name, attendance_date=date, status="Present")
+ ):
+ attendance = frappe.get_doc(
+ {"doctype": "Attendance", "employee": employee.name, "attendance_date": date, "status": status}
+ )
attendance.save()
attendance.submit()
+
def create_holiday_list():
if frappe.db.exists("Holiday List", "_Test Compensatory Leave"):
return
- holiday_list = frappe.get_doc({
- "doctype": "Holiday List",
- "from_date": add_months(today(), -3),
- "to_date": add_months(today(), 3),
- "holidays": [
- {
- "description": "Test Holiday",
- "holiday_date": today()
- },
- {
- "description": "Test Holiday 1",
- "holiday_date": add_days(today(), -1)
- }
- ],
- "holiday_list_name": "_Test Compensatory Leave"
- })
+ holiday_list = frappe.get_doc(
+ {
+ "doctype": "Holiday List",
+ "from_date": add_months(today(), -3),
+ "to_date": add_months(today(), 3),
+ "holidays": [
+ {"description": "Test Holiday", "holiday_date": today()},
+ {"description": "Test Holiday 1", "holiday_date": add_days(today(), -1)},
+ ],
+ "holiday_list_name": "_Test Compensatory Leave",
+ }
+ )
holiday_list.save()
diff --git a/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py b/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py
index fe11c47..bcb0161 100644
--- a/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py
+++ b/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py
@@ -11,53 +11,59 @@
class DailyWorkSummary(Document):
def send_mails(self, dws_group, emails):
- '''Send emails to get daily work summary to all users \
- in selected daily work summary group'''
- incoming_email_account = frappe.db.get_value('Email Account',
- dict(enable_incoming=1, default_incoming=1),
- 'email_id')
+ """Send emails to get daily work summary to all users \
+ in selected daily work summary group"""
+ incoming_email_account = frappe.db.get_value(
+ "Email Account", dict(enable_incoming=1, default_incoming=1), "email_id"
+ )
- self.db_set('email_sent_to', '\n'.join(emails))
- frappe.sendmail(recipients=emails,
+ self.db_set("email_sent_to", "\n".join(emails))
+ frappe.sendmail(
+ recipients=emails,
message=dws_group.message,
subject=dws_group.subject,
reference_doctype=self.doctype,
reference_name=self.name,
- reply_to=incoming_email_account)
+ reply_to=incoming_email_account,
+ )
def send_summary(self):
- '''Send summary of all replies. Called at midnight'''
+ """Send summary of all replies. Called at midnight"""
args = self.get_message_details()
emails = get_user_emails_from_group(self.daily_work_summary_group)
- frappe.sendmail(recipients=emails,
- template='daily_work_summary',
+ frappe.sendmail(
+ recipients=emails,
+ template="daily_work_summary",
args=args,
subject=_(self.daily_work_summary_group),
reference_doctype=self.doctype,
- reference_name=self.name)
+ reference_name=self.name,
+ )
- self.db_set('status', 'Sent')
+ self.db_set("status", "Sent")
def get_message_details(self):
- '''Return args for template'''
- dws_group = frappe.get_doc('Daily Work Summary Group',
- self.daily_work_summary_group)
+ """Return args for template"""
+ dws_group = frappe.get_doc("Daily Work Summary Group", self.daily_work_summary_group)
- replies = frappe.get_all('Communication',
- fields=['content', 'text_content', 'sender'],
- filters=dict(reference_doctype=self.doctype,
+ replies = frappe.get_all(
+ "Communication",
+ fields=["content", "text_content", "sender"],
+ filters=dict(
+ reference_doctype=self.doctype,
reference_name=self.name,
- communication_type='Communication',
- sent_or_received='Received'),
- order_by='creation asc')
+ communication_type="Communication",
+ sent_or_received="Received",
+ ),
+ order_by="creation asc",
+ )
did_not_reply = self.email_sent_to.split()
for d in replies:
- user = frappe.db.get_values("User",
- {"email": d.sender},
- ["full_name", "user_image"],
- as_dict=True)
+ user = frappe.db.get_values(
+ "User", {"email": d.sender}, ["full_name", "user_image"], as_dict=True
+ )
d.sender_name = user[0].full_name if user else d.sender
d.image = user[0].image if user and user[0].image else None
@@ -66,17 +72,13 @@
# make thumbnail image
try:
if original_image:
- file_name = frappe.get_list('File',
- {'file_url': original_image})
+ file_name = frappe.get_list("File", {"file_url": original_image})
if file_name:
file_name = file_name[0].name
- file_doc = frappe.get_doc('File', file_name)
+ file_doc = frappe.get_doc("File", file_name)
thumbnail_image = file_doc.make_thumbnail(
- set_as_thumbnail=False,
- width=100,
- height=100,
- crop=True
+ set_as_thumbnail=False, width=100, height=100, crop=True
)
d.image = thumbnail_image
except Exception:
@@ -85,34 +87,33 @@
if d.sender in did_not_reply:
did_not_reply.remove(d.sender)
if d.text_content:
- d.content = frappe.utils.md_to_html(
- EmailReplyParser.parse_reply(d.text_content)
- )
+ d.content = frappe.utils.md_to_html(EmailReplyParser.parse_reply(d.text_content))
- did_not_reply = [(frappe.db.get_value("User", {"email": email}, "full_name") or email)
- for email in did_not_reply]
+ did_not_reply = [
+ (frappe.db.get_value("User", {"email": email}, "full_name") or email) for email in did_not_reply
+ ]
- return dict(replies=replies,
+ return dict(
+ replies=replies,
original_message=dws_group.message,
- title=_('Work Summary for {0}').format(
- global_date_format(self.creation)
- ),
- did_not_reply=', '.join(did_not_reply) or '',
- did_not_reply_title=_('No replies from'))
+ title=_("Work Summary for {0}").format(global_date_format(self.creation)),
+ did_not_reply=", ".join(did_not_reply) or "",
+ did_not_reply_title=_("No replies from"),
+ )
def get_user_emails_from_group(group):
- '''Returns list of email of enabled users from the given group
+ """Returns list of email of enabled users from the given group
- :param group: Daily Work Summary Group `name`'''
+ :param group: Daily Work Summary Group `name`"""
group_doc = group
if isinstance(group_doc, str):
- group_doc = frappe.get_doc('Daily Work Summary Group', group)
+ group_doc = frappe.get_doc("Daily Work Summary Group", group)
emails = get_users_email(group_doc)
return emails
+
def get_users_email(doc):
- return [d.email for d in doc.users
- if frappe.db.get_value("User", d.user, "enabled")]
+ return [d.email for d in doc.users if frappe.db.get_value("User", d.user, "enabled")]
diff --git a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py
index 5edfb31..7034365 100644
--- a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py
+++ b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py
@@ -9,82 +9,96 @@
# test_records = frappe.get_test_records('Daily Work Summary')
+
class TestDailyWorkSummary(unittest.TestCase):
def test_email_trigger(self):
self.setup_and_prepare_test()
for d in self.users:
# check that email is sent to users
if d.message:
- self.assertTrue(d.email in [d.recipient for d in self.emails
- if self.groups.subject in d.message])
+ self.assertTrue(
+ d.email in [d.recipient for d in self.emails if self.groups.subject in d.message]
+ )
def test_email_trigger_failed(self):
- hour = '00:00'
- if frappe.utils.nowtime().split(':')[0] == '00':
- hour = '01:00'
+ hour = "00:00"
+ if frappe.utils.nowtime().split(":")[0] == "00":
+ hour = "01:00"
self.setup_and_prepare_test(hour)
for d in self.users:
# check that email is not sent to users
- self.assertFalse(d.email in [d.recipient for d in self.emails
- if self.groups.subject in d.message])
+ self.assertFalse(
+ d.email in [d.recipient for d in self.emails if self.groups.subject in d.message]
+ )
def test_incoming(self):
# get test mail with message-id as in-reply-to
self.setup_and_prepare_test()
with open(os.path.join(os.path.dirname(__file__), "test_data", "test-reply.raw"), "r") as f:
- if not self.emails: return
- test_mails = [f.read().replace('{{ sender }}',
- self.users[-1].email).replace('{{ message_id }}',
- self.emails[-1].message_id)]
+ if not self.emails:
+ return
+ test_mails = [
+ f.read()
+ .replace("{{ sender }}", self.users[-1].email)
+ .replace("{{ message_id }}", self.emails[-1].message_id)
+ ]
# pull the mail
email_account = frappe.get_doc("Email Account", "_Test Email Account 1")
- email_account.db_set('enable_incoming', 1)
+ email_account.db_set("enable_incoming", 1)
email_account.receive(test_mails=test_mails)
- daily_work_summary = frappe.get_doc('Daily Work Summary',
- frappe.get_all('Daily Work Summary')[0].name)
+ daily_work_summary = frappe.get_doc(
+ "Daily Work Summary", frappe.get_all("Daily Work Summary")[0].name
+ )
args = daily_work_summary.get_message_details()
- self.assertTrue('I built Daily Work Summary!' in args.get('replies')[0].content)
+ self.assertTrue("I built Daily Work Summary!" in args.get("replies")[0].content)
def setup_and_prepare_test(self, hour=None):
- frappe.db.sql('delete from `tabDaily Work Summary`')
- frappe.db.sql('delete from `tabEmail Queue`')
- frappe.db.sql('delete from `tabEmail Queue Recipient`')
- frappe.db.sql('delete from `tabCommunication`')
- frappe.db.sql('delete from `tabDaily Work Summary Group`')
+ frappe.db.sql("delete from `tabDaily Work Summary`")
+ frappe.db.sql("delete from `tabEmail Queue`")
+ frappe.db.sql("delete from `tabEmail Queue Recipient`")
+ frappe.db.sql("delete from `tabCommunication`")
+ frappe.db.sql("delete from `tabDaily Work Summary Group`")
- self.users = frappe.get_all('User',
- fields=['email'],
- filters=dict(email=('!=', 'test@example.com')))
+ self.users = frappe.get_all(
+ "User", fields=["email"], filters=dict(email=("!=", "test@example.com"))
+ )
self.setup_groups(hour)
from erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group import trigger_emails
+
trigger_emails()
# check if emails are created
- self.emails = frappe.db.sql("""select r.recipient, q.message, q.message_id \
+ self.emails = frappe.db.sql(
+ """select r.recipient, q.message, q.message_id \
from `tabEmail Queue` as q, `tabEmail Queue Recipient` as r \
- where q.name = r.parent""", as_dict=1)
-
+ where q.name = r.parent""",
+ as_dict=1,
+ )
def setup_groups(self, hour=None):
# setup email to trigger at this hour
if not hour:
- hour = frappe.utils.nowtime().split(':')[0]
- hour = hour+':00'
+ hour = frappe.utils.nowtime().split(":")[0]
+ hour = hour + ":00"
- groups = frappe.get_doc(dict(doctype="Daily Work Summary Group",
- name="Daily Work Summary",
- users=self.users,
- send_emails_at=hour,
- subject="this is a subject for testing summary emails",
- message='this is a message for testing summary emails'))
+ groups = frappe.get_doc(
+ dict(
+ doctype="Daily Work Summary Group",
+ name="Daily Work Summary",
+ users=self.users,
+ send_emails_at=hour,
+ subject="this is a subject for testing summary emails",
+ message="this is a message for testing summary emails",
+ )
+ )
groups.insert()
self.groups = groups
diff --git a/erpnext/hr/doctype/daily_work_summary_group/daily_work_summary_group.py b/erpnext/hr/doctype/daily_work_summary_group/daily_work_summary_group.py
index ed98168..4342f1c 100644
--- a/erpnext/hr/doctype/daily_work_summary_group/daily_work_summary_group.py
+++ b/erpnext/hr/doctype/daily_work_summary_group/daily_work_summary_group.py
@@ -15,37 +15,41 @@
def validate(self):
if self.users:
if not frappe.flags.in_test and not is_incoming_account_enabled():
- frappe.throw(_('Please enable default incoming account before creating Daily Work Summary Group'))
+ frappe.throw(
+ _("Please enable default incoming account before creating Daily Work Summary Group")
+ )
def trigger_emails():
- '''Send emails to Employees at the given hour asking
- them what did they work on today'''
+ """Send emails to Employees at the given hour asking
+ them what did they work on today"""
groups = frappe.get_all("Daily Work Summary Group")
for d in groups:
group_doc = frappe.get_doc("Daily Work Summary Group", d)
- if (is_current_hour(group_doc.send_emails_at)
+ if (
+ is_current_hour(group_doc.send_emails_at)
and not is_holiday(group_doc.holiday_list)
- and group_doc.enabled):
+ and group_doc.enabled
+ ):
emails = get_user_emails_from_group(group_doc)
# find emails relating to a company
if emails:
daily_work_summary = frappe.get_doc(
- dict(doctype='Daily Work Summary', daily_work_summary_group=group_doc.name)
+ dict(doctype="Daily Work Summary", daily_work_summary_group=group_doc.name)
).insert()
daily_work_summary.send_mails(group_doc, emails)
def is_current_hour(hour):
- return frappe.utils.nowtime().split(':')[0] == hour.split(':')[0]
+ return frappe.utils.nowtime().split(":")[0] == hour.split(":")[0]
def send_summary():
- '''Send summary to everyone'''
- for d in frappe.get_all('Daily Work Summary', dict(status='Open')):
- daily_work_summary = frappe.get_doc('Daily Work Summary', d.name)
+ """Send summary to everyone"""
+ for d in frappe.get_all("Daily Work Summary", dict(status="Open")):
+ daily_work_summary = frappe.get_doc("Daily Work Summary", d.name)
daily_work_summary.send_summary()
def is_incoming_account_enabled():
- return frappe.db.get_value('Email Account', dict(enable_incoming=1, default_incoming=1))
+ return frappe.db.get_value("Email Account", dict(enable_incoming=1, default_incoming=1))
diff --git a/erpnext/hr/doctype/department/department.py b/erpnext/hr/doctype/department/department.py
index 71300c4..159fa02 100644
--- a/erpnext/hr/doctype/department/department.py
+++ b/erpnext/hr/doctype/department/department.py
@@ -9,7 +9,7 @@
class Department(NestedSet):
- nsm_parent_field = 'parent_department'
+ nsm_parent_field = "parent_department"
def autoname(self):
root = get_root_of("Department")
@@ -26,7 +26,7 @@
def before_rename(self, old, new, merge=False):
# renaming consistency with abbreviation
- if not frappe.get_cached_value('Company', self.company, 'abbr') in new:
+ if not frappe.get_cached_value("Company", self.company, "abbr") in new:
new = get_abbreviated_name(new, self.company)
return new
@@ -39,17 +39,20 @@
super(Department, self).on_trash()
delete_events(self.doctype, self.name)
+
def on_doctype_update():
frappe.db.add_index("Department", ["lft", "rgt"])
+
def get_abbreviated_name(name, company):
- abbr = frappe.get_cached_value('Company', company, 'abbr')
- new_name = '{0} - {1}'.format(name, abbr)
+ abbr = frappe.get_cached_value("Company", company, "abbr")
+ new_name = "{0} - {1}".format(name, abbr)
return new_name
+
@frappe.whitelist()
def get_children(doctype, parent=None, company=None, is_root=False):
- condition = ''
+ condition = ""
var_dict = {
"name": get_root_of("Department"),
"parent": parent,
@@ -62,18 +65,26 @@
else:
condition = "parent_department = %(parent)s"
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
name as value,
is_group as expandable
from `tab{doctype}`
where
{condition}
- order by name""".format(doctype=doctype, condition=condition), var_dict, as_dict=1)
+ order by name""".format(
+ doctype=doctype, condition=condition
+ ),
+ var_dict,
+ as_dict=1,
+ )
+
@frappe.whitelist()
def add_node():
from frappe.desk.treeview import make_tree_args
+
args = frappe.form_dict
args = make_tree_args(**args)
diff --git a/erpnext/hr/doctype/department/test_department.py b/erpnext/hr/doctype/department/test_department.py
index 95bf663..b8c043f 100644
--- a/erpnext/hr/doctype/department/test_department.py
+++ b/erpnext/hr/doctype/department/test_department.py
@@ -6,20 +6,26 @@
import frappe
test_ignore = ["Leave Block List"]
+
+
class TestDepartment(unittest.TestCase):
- def test_remove_department_data(self):
- doc = create_department("Test Department")
- frappe.delete_doc('Department', doc.name)
+ def test_remove_department_data(self):
+ doc = create_department("Test Department")
+ frappe.delete_doc("Department", doc.name)
+
def create_department(department_name, parent_department=None):
- doc = frappe.get_doc({
- 'doctype': 'Department',
- 'is_group': 0,
- 'parent_department': parent_department,
- 'department_name': department_name,
- 'company': frappe.defaults.get_defaults().company
- }).insert()
+ doc = frappe.get_doc(
+ {
+ "doctype": "Department",
+ "is_group": 0,
+ "parent_department": parent_department,
+ "department_name": department_name,
+ "company": frappe.defaults.get_defaults().company,
+ }
+ ).insert()
- return doc
+ return doc
-test_records = frappe.get_test_records('Department')
+
+test_records = frappe.get_test_records("Department")
diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py
index 375ae7c..d849900 100644
--- a/erpnext/hr/doctype/department_approver/department_approver.py
+++ b/erpnext/hr/doctype/department_approver/department_approver.py
@@ -10,6 +10,7 @@
class DepartmentApprover(Document):
pass
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_approvers(doctype, txt, searchfield, start, page_len, filters):
@@ -20,25 +21,44 @@
approvers = []
department_details = {}
department_list = []
- employee = frappe.get_value("Employee", filters.get("employee"), ["employee_name","department", "leave_approver", "expense_approver", "shift_request_approver"], as_dict=True)
+ employee = frappe.get_value(
+ "Employee",
+ filters.get("employee"),
+ ["employee_name", "department", "leave_approver", "expense_approver", "shift_request_approver"],
+ as_dict=True,
+ )
employee_department = filters.get("department") or employee.department
if employee_department:
- department_details = frappe.db.get_value("Department", {"name": employee_department}, ["lft", "rgt"], as_dict=True)
+ department_details = frappe.db.get_value(
+ "Department", {"name": employee_department}, ["lft", "rgt"], as_dict=True
+ )
if department_details:
- department_list = frappe.db.sql("""select name from `tabDepartment` where lft <= %s
+ department_list = frappe.db.sql(
+ """select name from `tabDepartment` where lft <= %s
and rgt >= %s
and disabled=0
- order by lft desc""", (department_details.lft, department_details.rgt), as_list=True)
+ order by lft desc""",
+ (department_details.lft, department_details.rgt),
+ as_list=True,
+ )
if filters.get("doctype") == "Leave Application" and employee.leave_approver:
- approvers.append(frappe.db.get_value("User", employee.leave_approver, ['name', 'first_name', 'last_name']))
+ approvers.append(
+ frappe.db.get_value("User", employee.leave_approver, ["name", "first_name", "last_name"])
+ )
if filters.get("doctype") == "Expense Claim" and employee.expense_approver:
- approvers.append(frappe.db.get_value("User", employee.expense_approver, ['name', 'first_name', 'last_name']))
+ approvers.append(
+ frappe.db.get_value("User", employee.expense_approver, ["name", "first_name", "last_name"])
+ )
if filters.get("doctype") == "Shift Request" and employee.shift_request_approver:
- approvers.append(frappe.db.get_value("User", employee.shift_request_approver, ['name', 'first_name', 'last_name']))
+ approvers.append(
+ frappe.db.get_value(
+ "User", employee.shift_request_approver, ["name", "first_name", "last_name"]
+ )
+ )
if filters.get("doctype") == "Leave Application":
parentfield = "leave_approvers"
@@ -51,15 +71,21 @@
field_name = "Shift Request Approver"
if department_list:
for d in department_list:
- approvers += frappe.db.sql("""select user.name, user.first_name, user.last_name from
+ approvers += frappe.db.sql(
+ """select user.name, user.first_name, user.last_name from
tabUser user, `tabDepartment Approver` approver where
approver.parent = %s
and user.name like %s
and approver.parentfield = %s
- and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True)
+ and approver.approver=user.name""",
+ (d, "%" + txt + "%", parentfield),
+ as_list=True,
+ )
if len(approvers) == 0:
- error_msg = _("Please set {0} for the Employee: {1}").format(field_name, frappe.bold(employee.employee_name))
+ error_msg = _("Please set {0} for the Employee: {1}").format(
+ field_name, frappe.bold(employee.employee_name)
+ )
if department_list:
error_msg += _(" or for Department: {0}").format(frappe.bold(employee_department))
frappe.throw(error_msg, title=_(field_name + " Missing"))
diff --git a/erpnext/hr/doctype/designation/test_designation.py b/erpnext/hr/doctype/designation/test_designation.py
index f2d6d36..0840d13 100644
--- a/erpnext/hr/doctype/designation/test_designation.py
+++ b/erpnext/hr/doctype/designation/test_designation.py
@@ -5,15 +5,18 @@
# test_records = frappe.get_test_records('Designation')
-def create_designation(**args):
- args = frappe._dict(args)
- if frappe.db.exists("Designation", args.designation_name or "_Test designation"):
- return frappe.get_doc("Designation", args.designation_name or "_Test designation")
- designation = frappe.get_doc({
- "doctype": "Designation",
- "designation_name": args.designation_name or "_Test designation",
- "description": args.description or "_Test description"
- })
- designation.save()
- return designation
+def create_designation(**args):
+ args = frappe._dict(args)
+ if frappe.db.exists("Designation", args.designation_name or "_Test designation"):
+ return frappe.get_doc("Designation", args.designation_name or "_Test designation")
+
+ designation = frappe.get_doc(
+ {
+ "doctype": "Designation",
+ "designation_name": args.designation_name or "_Test designation",
+ "description": args.description or "_Test description",
+ }
+ )
+ designation.save()
+ return designation
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index 6e52eb9..d6a911d 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -18,22 +18,25 @@
class EmployeeUserDisabledError(frappe.ValidationError):
pass
+
+
class InactiveEmployeeStatusError(frappe.ValidationError):
pass
+
class Employee(NestedSet):
- nsm_parent_field = 'reports_to'
+ nsm_parent_field = "reports_to"
def autoname(self):
naming_method = frappe.db.get_value("HR Settings", None, "emp_created_by")
if not naming_method:
throw(_("Please setup Employee Naming System in Human Resource > HR Settings"))
else:
- if naming_method == 'Naming Series':
+ if naming_method == "Naming Series":
set_name_by_naming_series(self)
- elif naming_method == 'Employee Number':
+ elif naming_method == "Employee Number":
self.name = self.employee_number
- elif naming_method == 'Full Name':
+ elif naming_method == "Full Name":
self.set_employee_name()
self.name = self.employee_name
@@ -41,6 +44,7 @@
def validate(self):
from erpnext.controllers.status_updater import validate_status
+
validate_status(self.status, ["Active", "Inactive", "Suspended", "Left"])
self.employee = self.name
@@ -58,25 +62,25 @@
else:
existing_user_id = frappe.db.get_value("Employee", self.name, "user_id")
if existing_user_id:
- remove_user_permission(
- "Employee", self.name, existing_user_id)
+ remove_user_permission("Employee", self.name, existing_user_id)
def after_rename(self, old, new, merge):
self.db_set("employee", new)
def set_employee_name(self):
- self.employee_name = ' '.join(filter(lambda x: x, [self.first_name, self.middle_name, self.last_name]))
+ self.employee_name = " ".join(
+ filter(lambda x: x, [self.first_name, self.middle_name, self.last_name])
+ )
def validate_user_details(self):
if self.user_id:
- data = frappe.db.get_value('User',
- self.user_id, ['enabled', 'user_image'], as_dict=1)
+ data = frappe.db.get_value("User", self.user_id, ["enabled", "user_image"], as_dict=1)
if not data:
self.user_id = None
return
- if data.get("user_image") and self.image == '':
+ if data.get("user_image") and self.image == "":
self.image = data.get("user_image")
self.validate_for_enabled_user_id(data.get("enabled", 0))
self.validate_duplicate_user_id()
@@ -93,14 +97,14 @@
self.update_approver_role()
def update_user_permissions(self):
- if not self.create_user_permission: return
- if not has_permission('User Permission', ptype='write', raise_exception=False): return
+ if not self.create_user_permission:
+ return
+ if not has_permission("User Permission", ptype="write", raise_exception=False):
+ return
- employee_user_permission_exists = frappe.db.exists('User Permission', {
- 'allow': 'Employee',
- 'for_value': self.name,
- 'user': self.user_id
- })
+ employee_user_permission_exists = frappe.db.exists(
+ "User Permission", {"allow": "Employee", "for_value": self.name, "user": self.user_id}
+ )
if employee_user_permission_exists:
return
@@ -137,12 +141,14 @@
if not user.user_image:
user.user_image = self.image
try:
- frappe.get_doc({
- "doctype": "File",
- "file_url": self.image,
- "attached_to_doctype": "User",
- "attached_to_name": self.user_id
- }).insert(ignore_if_duplicate=True)
+ frappe.get_doc(
+ {
+ "doctype": "File",
+ "file_url": self.image,
+ "attached_to_doctype": "User",
+ "attached_to_name": self.user_id,
+ }
+ ).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
# already exists
pass
@@ -164,16 +170,32 @@
if self.date_of_birth and getdate(self.date_of_birth) > getdate(today()):
throw(_("Date of Birth cannot be greater than today."))
- if self.date_of_birth and self.date_of_joining and getdate(self.date_of_birth) >= getdate(self.date_of_joining):
+ if (
+ self.date_of_birth
+ and self.date_of_joining
+ and getdate(self.date_of_birth) >= getdate(self.date_of_joining)
+ ):
throw(_("Date of Joining must be greater than Date of Birth"))
- elif self.date_of_retirement and self.date_of_joining and (getdate(self.date_of_retirement) <= getdate(self.date_of_joining)):
+ elif (
+ self.date_of_retirement
+ and self.date_of_joining
+ and (getdate(self.date_of_retirement) <= getdate(self.date_of_joining))
+ ):
throw(_("Date Of Retirement must be greater than Date of Joining"))
- elif self.relieving_date and self.date_of_joining and (getdate(self.relieving_date) < getdate(self.date_of_joining)):
+ elif (
+ self.relieving_date
+ and self.date_of_joining
+ and (getdate(self.relieving_date) < getdate(self.date_of_joining))
+ ):
throw(_("Relieving Date must be greater than or equal to Date of Joining"))
- elif self.contract_end_date and self.date_of_joining and (getdate(self.contract_end_date) <= getdate(self.date_of_joining)):
+ elif (
+ self.contract_end_date
+ and self.date_of_joining
+ and (getdate(self.contract_end_date) <= getdate(self.date_of_joining))
+ ):
throw(_("Contract End Date must be greater than Date of Joining"))
def validate_email(self):
@@ -189,14 +211,20 @@
self.prefered_email = preferred_email
def validate_status(self):
- if self.status == 'Left':
- reports_to = frappe.db.get_all('Employee',
- filters={'reports_to': self.name, 'status': "Active"},
- fields=['name','employee_name']
+ if self.status == "Left":
+ reports_to = frappe.db.get_all(
+ "Employee",
+ filters={"reports_to": self.name, "status": "Active"},
+ fields=["name", "employee_name"],
)
if reports_to:
- link_to_employees = [frappe.utils.get_link_to_form('Employee', employee.name, label=employee.employee_name) for employee in reports_to]
- message = _("The following employees are currently still reporting to {0}:").format(frappe.bold(self.employee_name))
+ link_to_employees = [
+ frappe.utils.get_link_to_form("Employee", employee.name, label=employee.employee_name)
+ for employee in reports_to
+ ]
+ message = _("The following employees are currently still reporting to {0}:").format(
+ frappe.bold(self.employee_name)
+ )
message += "<br><br><ul><li>" + "</li><li>".join(link_to_employees)
message += "</li></ul><br>"
message += _("Please make sure the employees above report to another Active employee.")
@@ -205,7 +233,7 @@
throw(_("Please enter relieving date."))
def validate_for_enabled_user_id(self, enabled):
- if not self.status == 'Active':
+ if not self.status == "Active":
return
if enabled is None:
@@ -214,11 +242,16 @@
frappe.throw(_("User {0} is disabled").format(self.user_id), EmployeeUserDisabledError)
def validate_duplicate_user_id(self):
- employee = frappe.db.sql_list("""select name from `tabEmployee` where
- user_id=%s and status='Active' and name!=%s""", (self.user_id, self.name))
+ employee = frappe.db.sql_list(
+ """select name from `tabEmployee` where
+ user_id=%s and status='Active' and name!=%s""",
+ (self.user_id, self.name),
+ )
if employee:
- throw(_("User {0} is already assigned to Employee {1}").format(
- self.user_id, employee[0]), frappe.DuplicateEntryError)
+ throw(
+ _("User {0} is already assigned to Employee {1}").format(self.user_id, employee[0]),
+ frappe.DuplicateEntryError,
+ )
def validate_reports_to(self):
if self.reports_to == self.name:
@@ -227,17 +260,25 @@
def on_trash(self):
self.update_nsm_model()
delete_events(self.doctype, self.name)
- if frappe.db.exists("Employee Transfer", {'new_employee_id': self.name, 'docstatus': 1}):
- emp_transfer = frappe.get_doc("Employee Transfer", {'new_employee_id': self.name, 'docstatus': 1})
- emp_transfer.db_set("new_employee_id", '')
+ if frappe.db.exists("Employee Transfer", {"new_employee_id": self.name, "docstatus": 1}):
+ emp_transfer = frappe.get_doc(
+ "Employee Transfer", {"new_employee_id": self.name, "docstatus": 1}
+ )
+ emp_transfer.db_set("new_employee_id", "")
def validate_preferred_email(self):
if self.prefered_contact_email and not self.get(scrub(self.prefered_contact_email)):
frappe.msgprint(_("Please enter {0}").format(self.prefered_contact_email))
def validate_onboarding_process(self):
- employee_onboarding = frappe.get_all("Employee Onboarding",
- filters={"job_applicant": self.job_applicant, "docstatus": 1, "boarding_status": ("!=", "Completed")})
+ employee_onboarding = frappe.get_all(
+ "Employee Onboarding",
+ filters={
+ "job_applicant": self.job_applicant,
+ "docstatus": 1,
+ "boarding_status": ("!=", "Completed"),
+ },
+ )
if employee_onboarding:
doc = frappe.get_doc("Employee Onboarding", employee_onboarding[0].name)
doc.validate_employee_creation()
@@ -245,20 +286,26 @@
def reset_employee_emails_cache(self):
prev_doc = self.get_doc_before_save() or {}
- cell_number = cstr(self.get('cell_number'))
- prev_number = cstr(prev_doc.get('cell_number'))
- if (cell_number != prev_number or
- self.get('user_id') != prev_doc.get('user_id')):
- frappe.cache().hdel('employees_with_number', cell_number)
- frappe.cache().hdel('employees_with_number', prev_number)
+ cell_number = cstr(self.get("cell_number"))
+ prev_number = cstr(prev_doc.get("cell_number"))
+ if cell_number != prev_number or self.get("user_id") != prev_doc.get("user_id"):
+ frappe.cache().hdel("employees_with_number", cell_number)
+ frappe.cache().hdel("employees_with_number", prev_number)
+
def get_timeline_data(doctype, name):
- '''Return timeline for attendance'''
- return dict(frappe.db.sql('''select unix_timestamp(attendance_date), count(*)
+ """Return timeline for attendance"""
+ return dict(
+ frappe.db.sql(
+ """select unix_timestamp(attendance_date), count(*)
from `tabAttendance` where employee=%s
and attendance_date > date_sub(curdate(), interval 1 year)
and status in ('Present', 'Half Day')
- group by attendance_date''', name))
+ group by attendance_date""",
+ name,
+ )
+ )
+
@frappe.whitelist()
def get_retirement_date(date_of_birth=None):
@@ -266,14 +313,15 @@
if date_of_birth:
try:
retirement_age = int(frappe.db.get_single_value("HR Settings", "retirement_age") or 60)
- dt = add_years(getdate(date_of_birth),retirement_age)
- ret = {'date_of_retirement': dt.strftime('%Y-%m-%d')}
+ dt = add_years(getdate(date_of_birth), retirement_age)
+ ret = {"date_of_retirement": dt.strftime("%Y-%m-%d")}
except ValueError:
# invalid date
ret = {}
return ret
+
def validate_employee_role(doc, method):
# called via User hook
if "Employee" in [d.role for d in doc.get("roles")]:
@@ -281,39 +329,52 @@
frappe.msgprint(_("Please set User ID field in an Employee record to set Employee Role"))
doc.get("roles").remove(doc.get("roles", {"role": "Employee"})[0])
+
def update_user_permissions(doc, method):
# called via User hook
if "Employee" in [d.role for d in doc.get("roles")]:
- if not has_permission('User Permission', ptype='write', raise_exception=False): return
+ if not has_permission("User Permission", ptype="write", raise_exception=False):
+ return
employee = frappe.get_doc("Employee", {"user_id": doc.name})
employee.update_user_permissions()
+
def get_employee_email(employee_doc):
- return employee_doc.get("user_id") or employee_doc.get("personal_email") or employee_doc.get("company_email")
+ return (
+ employee_doc.get("user_id")
+ or employee_doc.get("personal_email")
+ or employee_doc.get("company_email")
+ )
+
def get_holiday_list_for_employee(employee, raise_exception=True):
if employee:
holiday_list, company = frappe.db.get_value("Employee", employee, ["holiday_list", "company"])
else:
- holiday_list=''
- company=frappe.db.get_value("Global Defaults", None, "default_company")
+ holiday_list = ""
+ company = frappe.db.get_value("Global Defaults", None, "default_company")
if not holiday_list:
- holiday_list = frappe.get_cached_value('Company', company, "default_holiday_list")
+ holiday_list = frappe.get_cached_value("Company", company, "default_holiday_list")
if not holiday_list and raise_exception:
- frappe.throw(_('Please set a default Holiday List for Employee {0} or Company {1}').format(employee, company))
+ frappe.throw(
+ _("Please set a default Holiday List for Employee {0} or Company {1}").format(employee, company)
+ )
return holiday_list
-def is_holiday(employee, date=None, raise_exception=True, only_non_weekly=False, with_description=False):
- '''
+
+def is_holiday(
+ employee, date=None, raise_exception=True, only_non_weekly=False, with_description=False
+):
+ """
Returns True if given Employee has an holiday on the given date
- :param employee: Employee `name`
- :param date: Date to check. Will check for today if None
- :param raise_exception: Raise an exception if no holiday list found, default is True
- :param only_non_weekly: Check only non-weekly holidays, default is False
- '''
+ :param employee: Employee `name`
+ :param date: Date to check. Will check for today if None
+ :param raise_exception: Raise an exception if no holiday list found, default is True
+ :param only_non_weekly: Check only non-weekly holidays, default is False
+ """
holiday_list = get_holiday_list_for_employee(employee, raise_exception)
if not date:
@@ -322,34 +383,28 @@
if not holiday_list:
return False
- filters = {
- 'parent': holiday_list,
- 'holiday_date': date
- }
+ filters = {"parent": holiday_list, "holiday_date": date}
if only_non_weekly:
- filters['weekly_off'] = False
+ filters["weekly_off"] = False
- holidays = frappe.get_all(
- 'Holiday',
- fields=['description'],
- filters=filters,
- pluck='description'
- )
+ holidays = frappe.get_all("Holiday", fields=["description"], filters=filters, pluck="description")
if with_description:
return len(holidays) > 0, holidays
return len(holidays) > 0
+
@frappe.whitelist()
-def deactivate_sales_person(status = None, employee = None):
+def deactivate_sales_person(status=None, employee=None):
if status == "Left":
sales_person = frappe.db.get_value("Sales Person", {"Employee": employee})
if sales_person:
frappe.db.set_value("Sales Person", sales_person, "enabled", 0)
+
@frappe.whitelist()
-def create_user(employee, user = None, email=None):
+def create_user(employee, user=None, email=None):
emp = frappe.get_doc("Employee", employee)
employee_name = emp.employee_name.split(" ")
@@ -367,93 +422,98 @@
emp.prefered_email = email
user = frappe.new_doc("User")
- user.update({
- "name": emp.employee_name,
- "email": emp.prefered_email,
- "enabled": 1,
- "first_name": first_name,
- "middle_name": middle_name,
- "last_name": last_name,
- "gender": emp.gender,
- "birth_date": emp.date_of_birth,
- "phone": emp.cell_number,
- "bio": emp.bio
- })
+ user.update(
+ {
+ "name": emp.employee_name,
+ "email": emp.prefered_email,
+ "enabled": 1,
+ "first_name": first_name,
+ "middle_name": middle_name,
+ "last_name": last_name,
+ "gender": emp.gender,
+ "birth_date": emp.date_of_birth,
+ "phone": emp.cell_number,
+ "bio": emp.bio,
+ }
+ )
user.insert()
return user.name
+
def get_all_employee_emails(company):
- '''Returns list of employee emails either based on user_id or company_email'''
- employee_list = frappe.get_all('Employee',
- fields=['name','employee_name'],
- filters={
- 'status': 'Active',
- 'company': company
- }
+ """Returns list of employee emails either based on user_id or company_email"""
+ employee_list = frappe.get_all(
+ "Employee", fields=["name", "employee_name"], filters={"status": "Active", "company": company}
)
employee_emails = []
for employee in employee_list:
if not employee:
continue
- user, company_email, personal_email = frappe.db.get_value('Employee',
- employee, ['user_id', 'company_email', 'personal_email'])
+ user, company_email, personal_email = frappe.db.get_value(
+ "Employee", employee, ["user_id", "company_email", "personal_email"]
+ )
email = user or company_email or personal_email
if email:
employee_emails.append(email)
return employee_emails
+
def get_employee_emails(employee_list):
- '''Returns list of employee emails either based on user_id or company_email'''
+ """Returns list of employee emails either based on user_id or company_email"""
employee_emails = []
for employee in employee_list:
if not employee:
continue
- user, company_email, personal_email = frappe.db.get_value('Employee', employee,
- ['user_id', 'company_email', 'personal_email'])
+ user, company_email, personal_email = frappe.db.get_value(
+ "Employee", employee, ["user_id", "company_email", "personal_email"]
+ )
email = user or company_email or personal_email
if email:
employee_emails.append(email)
return employee_emails
+
@frappe.whitelist()
def get_children(doctype, parent=None, company=None, is_root=False, is_tree=False):
- filters = [['status', '=', 'Active']]
- if company and company != 'All Companies':
- filters.append(['company', '=', company])
+ filters = [["status", "=", "Active"]]
+ if company and company != "All Companies":
+ filters.append(["company", "=", company])
- fields = ['name as value', 'employee_name as title']
+ fields = ["name as value", "employee_name as title"]
if is_root:
- parent = ''
- if parent and company and parent!=company:
- filters.append(['reports_to', '=', parent])
+ parent = ""
+ if parent and company and parent != company:
+ filters.append(["reports_to", "=", parent])
else:
- filters.append(['reports_to', '=', ''])
+ filters.append(["reports_to", "=", ""])
- employees = frappe.get_list(doctype, fields=fields,
- filters=filters, order_by='name')
+ employees = frappe.get_list(doctype, fields=fields, filters=filters, order_by="name")
for employee in employees:
- is_expandable = frappe.get_all(doctype, filters=[
- ['reports_to', '=', employee.get('value')]
- ])
+ is_expandable = frappe.get_all(doctype, filters=[["reports_to", "=", employee.get("value")]])
employee.expandable = 1 if is_expandable else 0
return employees
+
def on_doctype_update():
frappe.db.add_index("Employee", ["lft", "rgt"])
-def has_user_permission_for_employee(user_name, employee_name):
- return frappe.db.exists({
- 'doctype': 'User Permission',
- 'user': user_name,
- 'allow': 'Employee',
- 'for_value': employee_name
- })
-def has_upload_permission(doc, ptype='read', user=None):
+def has_user_permission_for_employee(user_name, employee_name):
+ return frappe.db.exists(
+ {
+ "doctype": "User Permission",
+ "user": user_name,
+ "allow": "Employee",
+ "for_value": employee_name,
+ }
+ )
+
+
+def has_upload_permission(doc, ptype="read", user=None):
if not user:
user = frappe.session.user
if get_doc_permissions(doc, user=user, ptype=ptype).get(ptype):
diff --git a/erpnext/hr/doctype/employee/employee_dashboard.py b/erpnext/hr/doctype/employee/employee_dashboard.py
index fb62b04..da89a1d 100644
--- a/erpnext/hr/doctype/employee/employee_dashboard.py
+++ b/erpnext/hr/doctype/employee/employee_dashboard.py
@@ -3,53 +3,48 @@
def get_data():
return {
- 'heatmap': True,
- 'heatmap_message': _('This is based on the attendance of this Employee'),
- 'fieldname': 'employee',
- 'non_standard_fieldnames': {
- 'Bank Account': 'party',
- 'Employee Grievance': 'raised_by'
- },
- 'transactions': [
+ "heatmap": True,
+ "heatmap_message": _("This is based on the attendance of this Employee"),
+ "fieldname": "employee",
+ "non_standard_fieldnames": {"Bank Account": "party", "Employee Grievance": "raised_by"},
+ "transactions": [
+ {"label": _("Attendance"), "items": ["Attendance", "Attendance Request", "Employee Checkin"]},
{
- 'label': _('Attendance'),
- 'items': ['Attendance', 'Attendance Request', 'Employee Checkin']
+ "label": _("Leave"),
+ "items": ["Leave Application", "Leave Allocation", "Leave Policy Assignment"],
},
{
- 'label': _('Leave'),
- 'items': ['Leave Application', 'Leave Allocation', 'Leave Policy Assignment']
+ "label": _("Lifecycle"),
+ "items": [
+ "Employee Onboarding",
+ "Employee Transfer",
+ "Employee Promotion",
+ "Employee Grievance",
+ ],
},
{
- 'label': _('Lifecycle'),
- 'items': ['Employee Onboarding', 'Employee Transfer', 'Employee Promotion', 'Employee Grievance']
+ "label": _("Exit"),
+ "items": ["Employee Separation", "Exit Interview", "Full and Final Statement"],
+ },
+ {"label": _("Shift"), "items": ["Shift Request", "Shift Assignment"]},
+ {"label": _("Expense"), "items": ["Expense Claim", "Travel Request", "Employee Advance"]},
+ {"label": _("Benefit"), "items": ["Employee Benefit Application", "Employee Benefit Claim"]},
+ {
+ "label": _("Payroll"),
+ "items": [
+ "Salary Structure Assignment",
+ "Salary Slip",
+ "Additional Salary",
+ "Timesheet",
+ "Employee Incentive",
+ "Retention Bonus",
+ "Bank Account",
+ ],
},
{
- 'label': _('Exit'),
- 'items': ['Employee Separation', 'Exit Interview', 'Full and Final Statement']
+ "label": _("Training"),
+ "items": ["Training Event", "Training Result", "Training Feedback", "Employee Skill Map"],
},
- {
- 'label': _('Shift'),
- 'items': ['Shift Request', 'Shift Assignment']
- },
- {
- 'label': _('Expense'),
- 'items': ['Expense Claim', 'Travel Request', 'Employee Advance']
- },
- {
- 'label': _('Benefit'),
- 'items': ['Employee Benefit Application', 'Employee Benefit Claim']
- },
- {
- 'label': _('Payroll'),
- 'items': ['Salary Structure Assignment', 'Salary Slip', 'Additional Salary', 'Timesheet','Employee Incentive', 'Retention Bonus', 'Bank Account']
- },
- {
- 'label': _('Training'),
- 'items': ['Training Event', 'Training Result', 'Training Feedback', 'Employee Skill Map']
- },
- {
- 'label': _('Evaluation'),
- 'items': ['Appraisal']
- },
- ]
+ {"label": _("Evaluation"), "items": ["Appraisal"]},
+ ],
}
diff --git a/erpnext/hr/doctype/employee/employee_reminders.py b/erpnext/hr/doctype/employee/employee_reminders.py
index 0bb6637..1829bc4 100644
--- a/erpnext/hr/doctype/employee/employee_reminders.py
+++ b/erpnext/hr/doctype/employee/employee_reminders.py
@@ -44,13 +44,10 @@
else:
return
- employees = frappe.db.get_all('Employee', filters={'status': 'Active'}, pluck='name')
+ employees = frappe.db.get_all("Employee", filters={"status": "Active"}, pluck="name")
for employee in employees:
holidays = get_holidays_for_employee(
- employee,
- start_date, end_date,
- only_non_weekly=True,
- raise_exception=False
+ employee, start_date, end_date, only_non_weekly=True, raise_exception=False
)
send_holidays_reminder_in_advance(employee, holidays)
@@ -60,7 +57,7 @@
if not holidays:
return
- employee_doc = frappe.get_doc('Employee', employee)
+ employee_doc = frappe.get_doc("Employee", employee)
employee_email = get_employee_email(employee_doc)
frequency = frappe.db.get_single_value("HR Settings", "frequency")
@@ -70,15 +67,18 @@
subject=_("Upcoming Holidays Reminder"),
template="holiday_reminder",
args=dict(
- reminder_text=_("Hey {}! This email is to remind you about the upcoming holidays.").format(employee_doc.get('first_name')),
+ reminder_text=_("Hey {}! This email is to remind you about the upcoming holidays.").format(
+ employee_doc.get("first_name")
+ ),
message=_("Below is the list of upcoming holidays for you:"),
advance_holiday_reminder=True,
holidays=holidays,
- frequency=frequency[:-2]
+ frequency=frequency[:-2],
),
- header=email_header
+ header=email_header,
)
+
# ------------------
# BIRTHDAY REMINDERS
# ------------------
@@ -109,10 +109,10 @@
def get_birthday_reminder_text_and_message(birthday_persons):
if len(birthday_persons) == 1:
- birthday_person_text = birthday_persons[0]['name']
+ birthday_person_text = birthday_persons[0]["name"]
else:
# converts ["Jim", "Rim", "Dim"] to Jim, Rim & Dim
- person_names = [d['name'] for d in birthday_persons]
+ person_names = [d["name"] for d in birthday_persons]
birthday_person_text = comma_sep(person_names, frappe._("{0} & {1}"), False)
reminder_text = _("Today is {0}'s birthday 🎉").format(birthday_person_text)
@@ -133,7 +133,7 @@
birthday_persons=birthday_persons,
message=message,
),
- header=_("Birthday Reminder 🎂")
+ header=_("Birthday Reminder 🎂"),
)
@@ -150,15 +150,16 @@
from collections import defaultdict
# Set column based on event type
- if event_type == 'birthday':
- condition_column = 'date_of_birth'
- elif event_type == 'work_anniversary':
- condition_column = 'date_of_joining'
+ if event_type == "birthday":
+ condition_column = "date_of_birth"
+ elif event_type == "work_anniversary":
+ condition_column = "date_of_joining"
else:
return
- employees_born_today = frappe.db.multisql({
- "mariadb": f"""
+ employees_born_today = frappe.db.multisql(
+ {
+ "mariadb": f"""
SELECT `personal_email`, `company`, `company_email`, `user_id`, `employee_name` AS 'name', `image`, `date_of_joining`
FROM `tabEmployee`
WHERE
@@ -170,7 +171,7 @@
AND
`status` = 'Active'
""",
- "postgres": f"""
+ "postgres": f"""
SELECT "personal_email", "company", "company_email", "user_id", "employee_name" AS 'name', "image"
FROM "tabEmployee"
WHERE
@@ -182,12 +183,15 @@
AND
"status" = 'Active'
""",
- }, dict(today=today(), condition_column=condition_column), as_dict=1)
+ },
+ dict(today=today(), condition_column=condition_column),
+ as_dict=1,
+ )
grouped_employees = defaultdict(lambda: [])
for employee_doc in employees_born_today:
- grouped_employees[employee_doc.get('company')].append(employee_doc)
+ grouped_employees[employee_doc.get("company")].append(employee_doc)
return grouped_employees
@@ -222,19 +226,19 @@
def get_work_anniversary_reminder_text_and_message(anniversary_persons):
if len(anniversary_persons) == 1:
- anniversary_person = anniversary_persons[0]['name']
+ anniversary_person = anniversary_persons[0]["name"]
persons_name = anniversary_person
# Number of years completed at the company
- completed_years = getdate().year - anniversary_persons[0]['date_of_joining'].year
+ completed_years = getdate().year - anniversary_persons[0]["date_of_joining"].year
anniversary_person += f" completed {completed_years} year(s)"
else:
person_names_with_years = []
names = []
for person in anniversary_persons:
- person_text = person['name']
+ person_text = person["name"]
names.append(person_text)
# Number of years completed at the company
- completed_years = getdate().year - person['date_of_joining'].year
+ completed_years = getdate().year - person["date_of_joining"].year
person_text += f" completed {completed_years} year(s)"
person_names_with_years.append(person_text)
@@ -260,5 +264,5 @@
anniversary_persons=anniversary_persons,
message=message,
),
- header=_("Work Anniversary Reminder")
+ header=_("Work Anniversary Reminder"),
)
diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py
index 67cbea6..50894b6 100644
--- a/erpnext/hr/doctype/employee/test_employee.py
+++ b/erpnext/hr/doctype/employee/test_employee.py
@@ -9,7 +9,8 @@
import erpnext
from erpnext.hr.doctype.employee.employee import InactiveEmployeeStatusError
-test_records = frappe.get_test_records('Employee')
+test_records = frappe.get_test_records("Employee")
+
class TestEmployee(unittest.TestCase):
def test_employee_status_left(self):
@@ -21,7 +22,7 @@
employee2_doc.reports_to = employee1_doc.name
employee2_doc.save()
employee1_doc.reload()
- employee1_doc.status = 'Left'
+ employee1_doc.status = "Left"
self.assertRaises(InactiveEmployeeStatusError, employee1_doc.save)
def test_employee_status_inactive(self):
@@ -36,11 +37,19 @@
employee_doc.reload()
make_holiday_list()
- frappe.db.set_value("Company", employee_doc.company, "default_holiday_list", "Salary Slip Test Holiday List")
+ frappe.db.set_value(
+ "Company", employee_doc.company, "default_holiday_list", "Salary Slip Test Holiday List"
+ )
- frappe.db.sql("""delete from `tabSalary Structure` where name='Test Inactive Employee Salary Slip'""")
- salary_structure = make_salary_structure("Test Inactive Employee Salary Slip", "Monthly",
- employee=employee_doc.name, company=employee_doc.company)
+ frappe.db.sql(
+ """delete from `tabSalary Structure` where name='Test Inactive Employee Salary Slip'"""
+ )
+ salary_structure = make_salary_structure(
+ "Test Inactive Employee Salary Slip",
+ "Monthly",
+ employee=employee_doc.name,
+ company=employee_doc.company,
+ )
salary_slip = make_salary_slip(salary_structure.name, employee=employee_doc.name)
self.assertRaises(InactiveEmployeeStatusError, salary_slip.save)
@@ -48,38 +57,43 @@
def tearDown(self):
frappe.db.rollback()
+
def make_employee(user, company=None, **kwargs):
if not frappe.db.get_value("User", user):
- frappe.get_doc({
- "doctype": "User",
- "email": user,
- "first_name": user,
- "new_password": "password",
- "send_welcome_email": 0,
- "roles": [{"doctype": "Has Role", "role": "Employee"}]
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "User",
+ "email": user,
+ "first_name": user,
+ "new_password": "password",
+ "send_welcome_email": 0,
+ "roles": [{"doctype": "Has Role", "role": "Employee"}],
+ }
+ ).insert()
if not frappe.db.get_value("Employee", {"user_id": user}):
- employee = frappe.get_doc({
- "doctype": "Employee",
- "naming_series": "EMP-",
- "first_name": user,
- "company": company or erpnext.get_default_company(),
- "user_id": user,
- "date_of_birth": "1990-05-08",
- "date_of_joining": "2013-01-01",
- "department": frappe.get_all("Department", fields="name")[0].name,
- "gender": "Female",
- "company_email": user,
- "prefered_contact_email": "Company Email",
- "prefered_email": user,
- "status": "Active",
- "employment_type": "Intern"
- })
+ employee = frappe.get_doc(
+ {
+ "doctype": "Employee",
+ "naming_series": "EMP-",
+ "first_name": user,
+ "company": company or erpnext.get_default_company(),
+ "user_id": user,
+ "date_of_birth": "1990-05-08",
+ "date_of_joining": "2013-01-01",
+ "department": frappe.get_all("Department", fields="name")[0].name,
+ "gender": "Female",
+ "company_email": user,
+ "prefered_contact_email": "Company Email",
+ "prefered_email": user,
+ "status": "Active",
+ "employment_type": "Intern",
+ }
+ )
if kwargs:
employee.update(kwargs)
employee.insert()
return employee.name
else:
- frappe.db.set_value("Employee", {"employee_name":user}, "status", "Active")
- return frappe.get_value("Employee", {"employee_name":user}, "name")
+ frappe.db.set_value("Employee", {"employee_name": user}, "status", "Active")
+ return frappe.get_value("Employee", {"employee_name": user}, "name")
diff --git a/erpnext/hr/doctype/employee/test_employee_reminders.py b/erpnext/hr/doctype/employee/test_employee_reminders.py
index a4097ab..9bde77c 100644
--- a/erpnext/hr/doctype/employee/test_employee_reminders.py
+++ b/erpnext/hr/doctype/employee/test_employee_reminders.py
@@ -21,23 +21,22 @@
# Create a test holiday list
test_holiday_dates = cls.get_test_holiday_dates()
test_holiday_list = make_holiday_list(
- 'TestHolidayRemindersList',
+ "TestHolidayRemindersList",
holiday_dates=[
- {'holiday_date': test_holiday_dates[0], 'description': 'test holiday1'},
- {'holiday_date': test_holiday_dates[1], 'description': 'test holiday2'},
- {'holiday_date': test_holiday_dates[2], 'description': 'test holiday3', 'weekly_off': 1},
- {'holiday_date': test_holiday_dates[3], 'description': 'test holiday4'},
- {'holiday_date': test_holiday_dates[4], 'description': 'test holiday5'},
- {'holiday_date': test_holiday_dates[5], 'description': 'test holiday6'},
+ {"holiday_date": test_holiday_dates[0], "description": "test holiday1"},
+ {"holiday_date": test_holiday_dates[1], "description": "test holiday2"},
+ {"holiday_date": test_holiday_dates[2], "description": "test holiday3", "weekly_off": 1},
+ {"holiday_date": test_holiday_dates[3], "description": "test holiday4"},
+ {"holiday_date": test_holiday_dates[4], "description": "test holiday5"},
+ {"holiday_date": test_holiday_dates[5], "description": "test holiday6"},
],
- from_date=getdate()-timedelta(days=10),
- to_date=getdate()+timedelta(weeks=5)
+ from_date=getdate() - timedelta(days=10),
+ to_date=getdate() + timedelta(weeks=5),
)
# Create a test employee
test_employee = frappe.get_doc(
- 'Employee',
- make_employee('test@gopher.io', company="_Test Company")
+ "Employee", make_employee("test@gopher.io", company="_Test Company")
)
# Attach the holiday list to employee
@@ -49,16 +48,16 @@
cls.test_holiday_dates = test_holiday_dates
# Employee without holidays in this month/week
- test_employee_2 = make_employee('test@empwithoutholiday.io', company="_Test Company")
- test_employee_2 = frappe.get_doc('Employee', test_employee_2)
+ test_employee_2 = make_employee("test@empwithoutholiday.io", company="_Test Company")
+ test_employee_2 = frappe.get_doc("Employee", test_employee_2)
test_holiday_list = make_holiday_list(
- 'TestHolidayRemindersList2',
+ "TestHolidayRemindersList2",
holiday_dates=[
- {'holiday_date': add_months(getdate(), 1), 'description': 'test holiday1'},
+ {"holiday_date": add_months(getdate(), 1), "description": "test holiday1"},
],
from_date=add_months(getdate(), -2),
- to_date=add_months(getdate(), 2)
+ to_date=add_months(getdate(), 2),
)
test_employee_2.holiday_list = test_holiday_list.name
test_employee_2.save()
@@ -71,11 +70,11 @@
today_date = getdate()
return [
today_date,
- today_date-timedelta(days=4),
- today_date-timedelta(days=3),
- today_date+timedelta(days=1),
- today_date+timedelta(days=3),
- today_date+timedelta(weeks=3)
+ today_date - timedelta(days=4),
+ today_date - timedelta(days=3),
+ today_date + timedelta(days=1),
+ today_date + timedelta(days=3),
+ today_date + timedelta(weeks=3),
]
def setUp(self):
@@ -88,19 +87,23 @@
self.assertTrue(is_holiday(self.test_employee.name))
self.assertTrue(is_holiday(self.test_employee.name, date=self.test_holiday_dates[1]))
- self.assertFalse(is_holiday(self.test_employee.name, date=getdate()-timedelta(days=1)))
+ self.assertFalse(is_holiday(self.test_employee.name, date=getdate() - timedelta(days=1)))
# Test weekly_off holidays
self.assertTrue(is_holiday(self.test_employee.name, date=self.test_holiday_dates[2]))
- self.assertFalse(is_holiday(self.test_employee.name, date=self.test_holiday_dates[2], only_non_weekly=True))
+ self.assertFalse(
+ is_holiday(self.test_employee.name, date=self.test_holiday_dates[2], only_non_weekly=True)
+ )
# Test with descriptions
has_holiday, descriptions = is_holiday(self.test_employee.name, with_description=True)
self.assertTrue(has_holiday)
- self.assertTrue('test holiday1' in descriptions)
+ self.assertTrue("test holiday1" in descriptions)
def test_birthday_reminders(self):
- employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
+ employee = frappe.get_doc(
+ "Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0]
+ )
employee.date_of_birth = "1992" + frappe.utils.nowdate()[4:]
employee.company_email = "test@example.com"
employee.company = "_Test Company"
@@ -124,7 +127,8 @@
self.assertTrue("Subject: Birthday Reminder" in email_queue[0].message)
def test_work_anniversary_reminders(self):
- make_employee("test_work_anniversary@gmail.com",
+ make_employee(
+ "test_work_anniversary@gmail.com",
date_of_joining="1998" + frappe.utils.nowdate()[4:],
company="_Test Company",
)
@@ -134,7 +138,7 @@
send_work_anniversary_reminders,
)
- employees_having_work_anniversary = get_employees_having_an_event_today('work_anniversary')
+ employees_having_work_anniversary = get_employees_having_an_event_today("work_anniversary")
employees = employees_having_work_anniversary.get("_Test Company") or []
user_ids = []
for entry in employees:
@@ -152,14 +156,15 @@
self.assertTrue("Subject: Work Anniversary Reminder" in email_queue[0].message)
def test_work_anniversary_reminder_not_sent_for_0_years(self):
- make_employee("test_work_anniversary_2@gmail.com",
+ make_employee(
+ "test_work_anniversary_2@gmail.com",
date_of_joining=getdate(),
company="_Test Company",
)
from erpnext.hr.doctype.employee.employee_reminders import get_employees_having_an_event_today
- employees_having_work_anniversary = get_employees_having_an_event_today('work_anniversary')
+ employees_having_work_anniversary = get_employees_having_an_event_today("work_anniversary")
employees = employees_having_work_anniversary.get("_Test Company") or []
user_ids = []
for entry in employees:
@@ -168,20 +173,18 @@
self.assertTrue("test_work_anniversary_2@gmail.com" not in user_ids)
def test_send_holidays_reminder_in_advance(self):
- setup_hr_settings('Weekly')
+ setup_hr_settings("Weekly")
holidays = get_holidays_for_employee(
- self.test_employee.get('name'),
- getdate(), getdate() + timedelta(days=3),
- only_non_weekly=True,
- raise_exception=False
- )
-
- send_holidays_reminder_in_advance(
- self.test_employee.get('name'),
- holidays
+ self.test_employee.get("name"),
+ getdate(),
+ getdate() + timedelta(days=3),
+ only_non_weekly=True,
+ raise_exception=False,
)
+ send_holidays_reminder_in_advance(self.test_employee.get("name"), holidays)
+
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
self.assertEqual(len(email_queue), 1)
self.assertTrue("Holidays this Week." in email_queue[0].message)
@@ -189,67 +192,69 @@
def test_advance_holiday_reminders_monthly(self):
from erpnext.hr.doctype.employee.employee_reminders import send_reminders_in_advance_monthly
- setup_hr_settings('Monthly')
+ setup_hr_settings("Monthly")
# disable emp 2, set same holiday list
- frappe.db.set_value('Employee', self.test_employee_2.name, {
- 'status': 'Left',
- 'holiday_list': self.test_employee.holiday_list
- })
+ frappe.db.set_value(
+ "Employee",
+ self.test_employee_2.name,
+ {"status": "Left", "holiday_list": self.test_employee.holiday_list},
+ )
send_reminders_in_advance_monthly()
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
self.assertTrue(len(email_queue) > 0)
# even though emp 2 has holiday, non-active employees should not be recipients
- recipients = frappe.db.get_all('Email Queue Recipient', pluck='recipient')
+ recipients = frappe.db.get_all("Email Queue Recipient", pluck="recipient")
self.assertTrue(self.test_employee_2.user_id not in recipients)
# teardown: enable emp 2
- frappe.db.set_value('Employee', self.test_employee_2.name, {
- 'status': 'Active',
- 'holiday_list': self.holiday_list_2.name
- })
+ frappe.db.set_value(
+ "Employee",
+ self.test_employee_2.name,
+ {"status": "Active", "holiday_list": self.holiday_list_2.name},
+ )
def test_advance_holiday_reminders_weekly(self):
from erpnext.hr.doctype.employee.employee_reminders import send_reminders_in_advance_weekly
- setup_hr_settings('Weekly')
+ setup_hr_settings("Weekly")
# disable emp 2, set same holiday list
- frappe.db.set_value('Employee', self.test_employee_2.name, {
- 'status': 'Left',
- 'holiday_list': self.test_employee.holiday_list
- })
+ frappe.db.set_value(
+ "Employee",
+ self.test_employee_2.name,
+ {"status": "Left", "holiday_list": self.test_employee.holiday_list},
+ )
send_reminders_in_advance_weekly()
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
self.assertTrue(len(email_queue) > 0)
# even though emp 2 has holiday, non-active employees should not be recipients
- recipients = frappe.db.get_all('Email Queue Recipient', pluck='recipient')
+ recipients = frappe.db.get_all("Email Queue Recipient", pluck="recipient")
self.assertTrue(self.test_employee_2.user_id not in recipients)
# teardown: enable emp 2
- frappe.db.set_value('Employee', self.test_employee_2.name, {
- 'status': 'Active',
- 'holiday_list': self.holiday_list_2.name
- })
+ frappe.db.set_value(
+ "Employee",
+ self.test_employee_2.name,
+ {"status": "Active", "holiday_list": self.holiday_list_2.name},
+ )
def test_reminder_not_sent_if_no_holdays(self):
- setup_hr_settings('Monthly')
+ setup_hr_settings("Monthly")
# reminder not sent if there are no holidays
holidays = get_holidays_for_employee(
- self.test_employee_2.get('name'),
- getdate(), getdate() + timedelta(days=3),
+ self.test_employee_2.get("name"),
+ getdate(),
+ getdate() + timedelta(days=3),
only_non_weekly=True,
- raise_exception=False
+ raise_exception=False,
)
- send_holidays_reminder_in_advance(
- self.test_employee_2.get('name'),
- holidays
- )
+ send_holidays_reminder_in_advance(self.test_employee_2.get("name"), holidays)
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
self.assertEqual(len(email_queue), 0)
@@ -259,5 +264,5 @@
hr_settings = frappe.get_doc("HR Settings", "HR Settings")
hr_settings.send_holiday_reminders = 1
set_proceed_with_frequency_change()
- hr_settings.frequency = frequency or 'Weekly'
- hr_settings.save()
\ No newline at end of file
+ hr_settings.frequency = frequency or "Weekly"
+ hr_settings.save()
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py
index 79d389d..c1876b1 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.py
@@ -16,17 +16,19 @@
class EmployeeAdvanceOverPayment(frappe.ValidationError):
pass
+
class EmployeeAdvance(Document):
def onload(self):
- self.get("__onload").make_payment_via_journal_entry = frappe.db.get_single_value('Accounts Settings',
- 'make_payment_via_journal_entry')
+ self.get("__onload").make_payment_via_journal_entry = frappe.db.get_single_value(
+ "Accounts Settings", "make_payment_via_journal_entry"
+ )
def validate(self):
validate_active_employee(self.employee)
self.set_status()
def on_cancel(self):
- self.ignore_linked_doctypes = ('GL Entry')
+ self.ignore_linked_doctypes = "GL Entry"
self.set_status(update=True)
def set_status(self, update=False):
@@ -37,13 +39,23 @@
if self.docstatus == 0:
status = "Draft"
elif self.docstatus == 1:
- if flt(self.claimed_amount) > 0 and flt(self.claimed_amount, precision) == flt(self.paid_amount, precision):
+ if flt(self.claimed_amount) > 0 and flt(self.claimed_amount, precision) == flt(
+ self.paid_amount, precision
+ ):
status = "Claimed"
- elif flt(self.return_amount) > 0 and flt(self.return_amount, precision) == flt(self.paid_amount, precision):
+ elif flt(self.return_amount) > 0 and flt(self.return_amount, precision) == flt(
+ self.paid_amount, precision
+ ):
status = "Returned"
- elif flt(self.claimed_amount) > 0 and (flt(self.return_amount) > 0) and total_amount == flt(self.paid_amount, precision):
+ elif (
+ flt(self.claimed_amount) > 0
+ and (flt(self.return_amount) > 0)
+ and total_amount == flt(self.paid_amount, precision)
+ ):
status = "Partly Claimed and Returned"
- elif flt(self.paid_amount) > 0 and flt(self.advance_amount, precision) == flt(self.paid_amount, precision):
+ elif flt(self.paid_amount) > 0 and flt(self.advance_amount, precision) == flt(
+ self.paid_amount, precision
+ ):
status = "Paid"
else:
status = "Unpaid"
@@ -60,30 +72,30 @@
paid_amount = (
frappe.qb.from_(gle)
- .select(Sum(gle.debit).as_("paid_amount"))
- .where(
- (gle.against_voucher_type == 'Employee Advance')
- & (gle.against_voucher == self.name)
- & (gle.party_type == 'Employee')
- & (gle.party == self.employee)
- & (gle.docstatus == 1)
- & (gle.is_cancelled == 0)
- )
- ).run(as_dict=True)[0].paid_amount or 0
+ .select(Sum(gle.debit).as_("paid_amount"))
+ .where(
+ (gle.against_voucher_type == "Employee Advance")
+ & (gle.against_voucher == self.name)
+ & (gle.party_type == "Employee")
+ & (gle.party == self.employee)
+ & (gle.docstatus == 1)
+ & (gle.is_cancelled == 0)
+ )
+ ).run(as_dict=True)[0].paid_amount or 0
return_amount = (
frappe.qb.from_(gle)
- .select(Sum(gle.credit).as_("return_amount"))
- .where(
- (gle.against_voucher_type == 'Employee Advance')
- & (gle.voucher_type != 'Expense Claim')
- & (gle.against_voucher == self.name)
- & (gle.party_type == 'Employee')
- & (gle.party == self.employee)
- & (gle.docstatus == 1)
- & (gle.is_cancelled == 0)
- )
- ).run(as_dict=True)[0].return_amount or 0
+ .select(Sum(gle.credit).as_("return_amount"))
+ .where(
+ (gle.against_voucher_type == "Employee Advance")
+ & (gle.voucher_type != "Expense Claim")
+ & (gle.against_voucher == self.name)
+ & (gle.party_type == "Employee")
+ & (gle.party == self.employee)
+ & (gle.docstatus == 1)
+ & (gle.is_cancelled == 0)
+ )
+ ).run(as_dict=True)[0].return_amount or 0
if paid_amount != 0:
paid_amount = flt(paid_amount) / flt(self.exchange_rate)
@@ -91,8 +103,10 @@
return_amount = flt(return_amount) / flt(self.exchange_rate)
if flt(paid_amount) > self.advance_amount:
- frappe.throw(_("Row {0}# Paid Amount cannot be greater than requested advance amount"),
- EmployeeAdvanceOverPayment)
+ frappe.throw(
+ _("Row {0}# Paid Amount cannot be greater than requested advance amount"),
+ EmployeeAdvanceOverPayment,
+ )
if flt(return_amount) > self.paid_amount - self.claimed_amount:
frappe.throw(_("Return amount cannot be greater unclaimed amount"))
@@ -102,7 +116,9 @@
self.set_status(update=True)
def update_claimed_amount(self):
- claimed_amount = frappe.db.sql("""
+ claimed_amount = (
+ frappe.db.sql(
+ """
SELECT sum(ifnull(allocated_amount, 0))
FROM `tabExpense Claim Advance` eca, `tabExpense Claim` ec
WHERE
@@ -111,7 +127,11 @@
AND ec.name = eca.parent
AND ec.docstatus=1
AND eca.allocated_amount > 0
- """, self.name)[0][0] or 0
+ """,
+ self.name,
+ )[0][0]
+ or 0
+ )
frappe.db.set_value("Employee Advance", self.name, "claimed_amount", flt(claimed_amount))
self.reload()
@@ -120,56 +140,69 @@
@frappe.whitelist()
def get_pending_amount(employee, posting_date):
- employee_due_amount = frappe.get_all("Employee Advance", \
- filters = {"employee":employee, "docstatus":1, "posting_date":("<=", posting_date)}, \
- fields = ["advance_amount", "paid_amount"])
+ employee_due_amount = frappe.get_all(
+ "Employee Advance",
+ filters={"employee": employee, "docstatus": 1, "posting_date": ("<=", posting_date)},
+ fields=["advance_amount", "paid_amount"],
+ )
return sum([(emp.advance_amount - emp.paid_amount) for emp in employee_due_amount])
+
@frappe.whitelist()
def make_bank_entry(dt, dn):
doc = frappe.get_doc(dt, dn)
- payment_account = get_default_bank_cash_account(doc.company, account_type="Cash",
- mode_of_payment=doc.mode_of_payment)
+ payment_account = get_default_bank_cash_account(
+ doc.company, account_type="Cash", mode_of_payment=doc.mode_of_payment
+ )
if not payment_account:
frappe.throw(_("Please set a Default Cash Account in Company defaults"))
- advance_account_currency = frappe.db.get_value('Account', doc.advance_account, 'account_currency')
+ advance_account_currency = frappe.db.get_value("Account", doc.advance_account, "account_currency")
- advance_amount, advance_exchange_rate = get_advance_amount_advance_exchange_rate(advance_account_currency,doc )
+ advance_amount, advance_exchange_rate = get_advance_amount_advance_exchange_rate(
+ advance_account_currency, doc
+ )
paying_amount, paying_exchange_rate = get_paying_amount_paying_exchange_rate(payment_account, doc)
je = frappe.new_doc("Journal Entry")
je.posting_date = nowdate()
- je.voucher_type = 'Bank Entry'
+ je.voucher_type = "Bank Entry"
je.company = doc.company
- je.remark = 'Payment against Employee Advance: ' + dn + '\n' + doc.purpose
+ je.remark = "Payment against Employee Advance: " + dn + "\n" + doc.purpose
je.multi_currency = 1 if advance_account_currency != payment_account.account_currency else 0
- je.append("accounts", {
- "account": doc.advance_account,
- "account_currency": advance_account_currency,
- "exchange_rate": flt(advance_exchange_rate),
- "debit_in_account_currency": flt(advance_amount),
- "reference_type": "Employee Advance",
- "reference_name": doc.name,
- "party_type": "Employee",
- "cost_center": erpnext.get_default_cost_center(doc.company),
- "party": doc.employee,
- "is_advance": "Yes"
- })
+ je.append(
+ "accounts",
+ {
+ "account": doc.advance_account,
+ "account_currency": advance_account_currency,
+ "exchange_rate": flt(advance_exchange_rate),
+ "debit_in_account_currency": flt(advance_amount),
+ "reference_type": "Employee Advance",
+ "reference_name": doc.name,
+ "party_type": "Employee",
+ "cost_center": erpnext.get_default_cost_center(doc.company),
+ "party": doc.employee,
+ "is_advance": "Yes",
+ },
+ )
- je.append("accounts", {
- "account": payment_account.account,
- "cost_center": erpnext.get_default_cost_center(doc.company),
- "credit_in_account_currency": flt(paying_amount),
- "account_currency": payment_account.account_currency,
- "account_type": payment_account.account_type,
- "exchange_rate": flt(paying_exchange_rate)
- })
+ je.append(
+ "accounts",
+ {
+ "account": payment_account.account,
+ "cost_center": erpnext.get_default_cost_center(doc.company),
+ "credit_in_account_currency": flt(paying_amount),
+ "account_currency": payment_account.account_currency,
+ "account_type": payment_account.account_type,
+ "exchange_rate": flt(paying_exchange_rate),
+ },
+ )
return je.as_dict()
+
def get_advance_amount_advance_exchange_rate(advance_account_currency, doc):
if advance_account_currency != doc.currency:
advance_amount = flt(doc.advance_amount) * flt(doc.exchange_rate)
@@ -180,6 +213,7 @@
return advance_amount, advance_exchange_rate
+
def get_paying_amount_paying_exchange_rate(payment_account, doc):
if payment_account.account_currency != doc.currency:
paying_amount = flt(doc.advance_amount) * flt(doc.exchange_rate)
@@ -190,6 +224,7 @@
return paying_amount, paying_exchange_rate
+
@frappe.whitelist()
def create_return_through_additional_salary(doc):
import json
@@ -197,7 +232,7 @@
if isinstance(doc, str):
doc = frappe._dict(json.loads(doc))
- additional_salary = frappe.new_doc('Additional Salary')
+ additional_salary = frappe.new_doc("Additional Salary")
additional_salary.employee = doc.employee
additional_salary.currency = doc.currency
additional_salary.amount = doc.paid_amount - doc.claimed_amount
@@ -207,56 +242,81 @@
return additional_salary
+
@frappe.whitelist()
-def make_return_entry(employee, company, employee_advance_name, return_amount, advance_account, currency, exchange_rate, mode_of_payment=None):
- bank_cash_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
+def make_return_entry(
+ employee,
+ company,
+ employee_advance_name,
+ return_amount,
+ advance_account,
+ currency,
+ exchange_rate,
+ mode_of_payment=None,
+):
+ bank_cash_account = get_default_bank_cash_account(
+ company, account_type="Cash", mode_of_payment=mode_of_payment
+ )
if not bank_cash_account:
frappe.throw(_("Please set a Default Cash Account in Company defaults"))
- advance_account_currency = frappe.db.get_value('Account', advance_account, 'account_currency')
+ advance_account_currency = frappe.db.get_value("Account", advance_account, "account_currency")
- je = frappe.new_doc('Journal Entry')
+ je = frappe.new_doc("Journal Entry")
je.posting_date = nowdate()
je.voucher_type = get_voucher_type(mode_of_payment)
je.company = company
- je.remark = 'Return against Employee Advance: ' + employee_advance_name
+ je.remark = "Return against Employee Advance: " + employee_advance_name
je.multi_currency = 1 if advance_account_currency != bank_cash_account.account_currency else 0
- advance_account_amount = flt(return_amount) if advance_account_currency==currency \
+ advance_account_amount = (
+ flt(return_amount)
+ if advance_account_currency == currency
else flt(return_amount) * flt(exchange_rate)
+ )
- je.append('accounts', {
- 'account': advance_account,
- 'credit_in_account_currency': advance_account_amount,
- 'account_currency': advance_account_currency,
- 'exchange_rate': flt(exchange_rate) if advance_account_currency == currency else 1,
- 'reference_type': 'Employee Advance',
- 'reference_name': employee_advance_name,
- 'party_type': 'Employee',
- 'party': employee,
- 'is_advance': 'Yes',
- 'cost_center': erpnext.get_default_cost_center(company)
- })
+ je.append(
+ "accounts",
+ {
+ "account": advance_account,
+ "credit_in_account_currency": advance_account_amount,
+ "account_currency": advance_account_currency,
+ "exchange_rate": flt(exchange_rate) if advance_account_currency == currency else 1,
+ "reference_type": "Employee Advance",
+ "reference_name": employee_advance_name,
+ "party_type": "Employee",
+ "party": employee,
+ "is_advance": "Yes",
+ "cost_center": erpnext.get_default_cost_center(company),
+ },
+ )
- bank_amount = flt(return_amount) if bank_cash_account.account_currency==currency \
+ bank_amount = (
+ flt(return_amount)
+ if bank_cash_account.account_currency == currency
else flt(return_amount) * flt(exchange_rate)
+ )
- je.append("accounts", {
- "account": bank_cash_account.account,
- "debit_in_account_currency": bank_amount,
- "account_currency": bank_cash_account.account_currency,
- "account_type": bank_cash_account.account_type,
- "exchange_rate": flt(exchange_rate) if bank_cash_account.account_currency == currency else 1,
- "cost_center": erpnext.get_default_cost_center(company)
- })
+ je.append(
+ "accounts",
+ {
+ "account": bank_cash_account.account,
+ "debit_in_account_currency": bank_amount,
+ "account_currency": bank_cash_account.account_currency,
+ "account_type": bank_cash_account.account_type,
+ "exchange_rate": flt(exchange_rate) if bank_cash_account.account_currency == currency else 1,
+ "cost_center": erpnext.get_default_cost_center(company),
+ },
+ )
return je.as_dict()
+
def get_voucher_type(mode_of_payment=None):
voucher_type = "Cash Entry"
if mode_of_payment:
- mode_of_payment_type = frappe.get_cached_value('Mode of Payment', mode_of_payment, 'type')
+ mode_of_payment_type = frappe.get_cached_value("Mode of Payment", mode_of_payment, "type")
if mode_of_payment_type == "Bank":
voucher_type = "Bank Entry"
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py
index 9450258..73fac51 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py
@@ -1,16 +1,9 @@
def get_data():
return {
- 'fieldname': 'employee_advance',
- 'non_standard_fieldnames': {
- 'Payment Entry': 'reference_name',
- 'Journal Entry': 'reference_name'
+ "fieldname": "employee_advance",
+ "non_standard_fieldnames": {
+ "Payment Entry": "reference_name",
+ "Journal Entry": "reference_name",
},
- 'transactions': [
- {
- 'items': ['Expense Claim']
- },
- {
- 'items': ['Payment Entry', 'Journal Entry']
- }
- ]
+ "transactions": [{"items": ["Expense Claim"]}, {"items": ["Payment Entry", "Journal Entry"]}],
}
diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.py b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
index e3c1487..44d68c9 100644
--- a/erpnext/hr/doctype/employee_advance/test_employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
@@ -68,7 +68,9 @@
def test_claimed_status(self):
# CLAIMED Status check, full amount claimed
payable_account = get_payable_account("_Test Company")
- claim = make_expense_claim(payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+ claim = make_expense_claim(
+ payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True
+ )
advance = make_employee_advance(claim.employee)
pe = make_payment_entry(advance)
@@ -95,7 +97,9 @@
def test_partly_claimed_and_returned_status(self):
payable_account = get_payable_account("_Test Company")
- claim = make_expense_claim(payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+ claim = make_expense_claim(
+ payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True
+ )
advance = make_employee_advance(claim.employee)
pe = make_payment_entry(advance)
@@ -103,7 +107,9 @@
# PARTLY CLAIMED AND RETURNED status check
# 500 Claimed, 500 Returned
- claim = make_expense_claim(payable_account, 500, 500, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+ claim = make_expense_claim(
+ payable_account, 500, 500, "_Test Company", "Travel Expenses - _TC", do_not_submit=True
+ )
advance = make_employee_advance(claim.employee)
pe = make_payment_entry(advance)
@@ -125,7 +131,7 @@
advance_account=advance.advance_account,
mode_of_payment=advance.mode_of_payment,
currency=advance.currency,
- exchange_rate=advance.exchange_rate
+ exchange_rate=advance.exchange_rate,
)
entry = frappe.get_doc(entry)
@@ -160,7 +166,9 @@
args = {"type": "Deduction"}
create_salary_component("Advance Salary - Deduction", **args)
- make_salary_structure("Test Additional Salary for Advance Return", "Monthly", employee=employee_name)
+ make_salary_structure(
+ "Test Additional Salary for Advance Return", "Monthly", employee=employee_name
+ )
# additional salary for 700 first
advance.reload()
@@ -204,10 +212,11 @@
return journal_entry
+
def make_employee_advance(employee_name, args=None):
doc = frappe.new_doc("Employee Advance")
doc.employee = employee_name
- doc.company = "_Test company"
+ doc.company = "_Test company"
doc.purpose = "For site visit"
doc.currency = erpnext.get_company_currency("_Test company")
doc.exchange_rate = 1
@@ -233,13 +242,16 @@
else:
allocated_amount = flt(entry.paid_amount) - flt(entry.claimed_amount)
- claim.append("advances", {
- "employee_advance": entry.name,
- "posting_date": entry.posting_date,
- "advance_account": entry.advance_account,
- "advance_paid": entry.paid_amount,
- "unclaimed_amount": allocated_amount,
- "allocated_amount": allocated_amount
- })
+ claim.append(
+ "advances",
+ {
+ "employee_advance": entry.name,
+ "posting_date": entry.posting_date,
+ "advance_account": entry.advance_account,
+ "advance_paid": entry.paid_amount,
+ "unclaimed_amount": allocated_amount,
+ "allocated_amount": allocated_amount,
+ },
+ )
- return claim
\ No newline at end of file
+ return claim
diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py
index af2ca50..43665cc 100644
--- a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py
+++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py
@@ -14,32 +14,31 @@
@frappe.whitelist()
-def get_employees(date, department = None, branch = None, company = None):
+def get_employees(date, department=None, branch=None, company=None):
attendance_not_marked = []
attendance_marked = []
filters = {"status": "Active", "date_of_joining": ["<=", date]}
- for field, value in {'department': department,
- 'branch': branch, 'company': company}.items():
+ for field, value in {"department": department, "branch": branch, "company": company}.items():
if value:
filters[field] = value
- employee_list = frappe.get_list("Employee", fields=["employee", "employee_name"], filters=filters, order_by="employee_name")
+ employee_list = frappe.get_list(
+ "Employee", fields=["employee", "employee_name"], filters=filters, order_by="employee_name"
+ )
marked_employee = {}
- for emp in frappe.get_list("Attendance", fields=["employee", "status"],
- filters={"attendance_date": date}):
- marked_employee[emp['employee']] = emp['status']
+ for emp in frappe.get_list(
+ "Attendance", fields=["employee", "status"], filters={"attendance_date": date}
+ ):
+ marked_employee[emp["employee"]] = emp["status"]
for employee in employee_list:
- employee['status'] = marked_employee.get(employee['employee'])
- if employee['employee'] not in marked_employee:
+ employee["status"] = marked_employee.get(employee["employee"])
+ if employee["employee"] not in marked_employee:
attendance_not_marked.append(employee)
else:
attendance_marked.append(employee)
- return {
- "marked": attendance_marked,
- "unmarked": attendance_not_marked
- }
+ return {"marked": attendance_marked, "unmarked": attendance_not_marked}
@frappe.whitelist()
@@ -53,16 +52,18 @@
else:
leave_type = None
- company = frappe.db.get_value("Employee", employee['employee'], "Company", cache=True)
+ company = frappe.db.get_value("Employee", employee["employee"], "Company", cache=True)
- attendance=frappe.get_doc(dict(
- doctype='Attendance',
- employee=employee.get('employee'),
- employee_name=employee.get('employee_name'),
- attendance_date=getdate(date),
- status=status,
- leave_type=leave_type,
- company=company
- ))
+ attendance = frappe.get_doc(
+ dict(
+ doctype="Attendance",
+ employee=employee.get("employee"),
+ employee_name=employee.get("employee_name"),
+ attendance_date=getdate(date),
+ status=status,
+ leave_type=leave_type,
+ company=company,
+ )
+ )
attendance.insert()
- attendance.submit()
\ No newline at end of file
+ attendance.submit()
diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.py b/erpnext/hr/doctype/employee_checkin/employee_checkin.py
index c1d4ac7..87f48b7 100644
--- a/erpnext/hr/doctype/employee_checkin/employee_checkin.py
+++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.py
@@ -20,20 +20,31 @@
self.fetch_shift()
def validate_duplicate_log(self):
- doc = frappe.db.exists('Employee Checkin', {
- 'employee': self.employee,
- 'time': self.time,
- 'name': ['!=', self.name]})
+ doc = frappe.db.exists(
+ "Employee Checkin", {"employee": self.employee, "time": self.time, "name": ["!=", self.name]}
+ )
if doc:
- doc_link = frappe.get_desk_link('Employee Checkin', doc)
- frappe.throw(_('This employee already has a log with the same timestamp.{0}')
- .format("<Br>" + doc_link))
+ doc_link = frappe.get_desk_link("Employee Checkin", doc)
+ frappe.throw(
+ _("This employee already has a log with the same timestamp.{0}").format("<Br>" + doc_link)
+ )
def fetch_shift(self):
- shift_actual_timings = get_actual_start_end_datetime_of_shift(self.employee, get_datetime(self.time), True)
+ shift_actual_timings = get_actual_start_end_datetime_of_shift(
+ self.employee, get_datetime(self.time), True
+ )
if shift_actual_timings[0] and shift_actual_timings[1]:
- if shift_actual_timings[2].shift_type.determine_check_in_and_check_out == 'Strictly based on Log Type in Employee Checkin' and not self.log_type and not self.skip_auto_attendance:
- frappe.throw(_('Log Type is required for check-ins falling in the shift: {0}.').format(shift_actual_timings[2].shift_type.name))
+ if (
+ shift_actual_timings[2].shift_type.determine_check_in_and_check_out
+ == "Strictly based on Log Type in Employee Checkin"
+ and not self.log_type
+ and not self.skip_auto_attendance
+ ):
+ frappe.throw(
+ _("Log Type is required for check-ins falling in the shift: {0}.").format(
+ shift_actual_timings[2].shift_type.name
+ )
+ )
if not self.attendance:
self.shift = shift_actual_timings[2].shift_type.name
self.shift_actual_start = shift_actual_timings[0]
@@ -43,8 +54,16 @@
else:
self.shift = None
+
@frappe.whitelist()
-def add_log_based_on_employee_field(employee_field_value, timestamp, device_id=None, log_type=None, skip_auto_attendance=0, employee_fieldname='attendance_device_id'):
+def add_log_based_on_employee_field(
+ employee_field_value,
+ timestamp,
+ device_id=None,
+ log_type=None,
+ skip_auto_attendance=0,
+ employee_fieldname="attendance_device_id",
+):
"""Finds the relevant Employee using the employee field value and creates a Employee Checkin.
:param employee_field_value: The value to look for in employee field.
@@ -58,11 +77,20 @@
if not employee_field_value or not timestamp:
frappe.throw(_("'employee_field_value' and 'timestamp' are required."))
- employee = frappe.db.get_values("Employee", {employee_fieldname: employee_field_value}, ["name", "employee_name", employee_fieldname], as_dict=True)
+ employee = frappe.db.get_values(
+ "Employee",
+ {employee_fieldname: employee_field_value},
+ ["name", "employee_name", employee_fieldname],
+ as_dict=True,
+ )
if employee:
employee = employee[0]
else:
- frappe.throw(_("No Employee found for the given employee field value. '{}': {}").format(employee_fieldname,employee_field_value))
+ frappe.throw(
+ _("No Employee found for the given employee field value. '{}': {}").format(
+ employee_fieldname, employee_field_value
+ )
+ )
doc = frappe.new_doc("Employee Checkin")
doc.employee = employee.name
@@ -70,13 +98,24 @@
doc.time = timestamp
doc.device_id = device_id
doc.log_type = log_type
- if cint(skip_auto_attendance) == 1: doc.skip_auto_attendance = '1'
+ if cint(skip_auto_attendance) == 1:
+ doc.skip_auto_attendance = "1"
doc.insert()
return doc
-def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, late_entry=False, early_exit=False, in_time=None, out_time=None, shift=None):
+def mark_attendance_and_link_log(
+ logs,
+ attendance_status,
+ attendance_date,
+ working_hours=None,
+ late_entry=False,
+ early_exit=False,
+ in_time=None,
+ out_time=None,
+ shift=None,
+):
"""Creates an attendance and links the attendance to the Employee Checkin.
Note: If attendance is already present for the given date, the logs are marked as skipped and no exception is thrown.
@@ -87,40 +126,52 @@
"""
log_names = [x.name for x in logs]
employee = logs[0].employee
- if attendance_status == 'Skip':
- frappe.db.sql("""update `tabEmployee Checkin`
+ if attendance_status == "Skip":
+ frappe.db.sql(
+ """update `tabEmployee Checkin`
set skip_auto_attendance = %s
- where name in %s""", ('1', log_names))
+ where name in %s""",
+ ("1", log_names),
+ )
return None
- elif attendance_status in ('Present', 'Absent', 'Half Day'):
- employee_doc = frappe.get_doc('Employee', employee)
- if not frappe.db.exists('Attendance', {'employee':employee, 'attendance_date':attendance_date, 'docstatus':('!=', '2')}):
+ elif attendance_status in ("Present", "Absent", "Half Day"):
+ employee_doc = frappe.get_doc("Employee", employee)
+ if not frappe.db.exists(
+ "Attendance",
+ {"employee": employee, "attendance_date": attendance_date, "docstatus": ("!=", "2")},
+ ):
doc_dict = {
- 'doctype': 'Attendance',
- 'employee': employee,
- 'attendance_date': attendance_date,
- 'status': attendance_status,
- 'working_hours': working_hours,
- 'company': employee_doc.company,
- 'shift': shift,
- 'late_entry': late_entry,
- 'early_exit': early_exit,
- 'in_time': in_time,
- 'out_time': out_time
+ "doctype": "Attendance",
+ "employee": employee,
+ "attendance_date": attendance_date,
+ "status": attendance_status,
+ "working_hours": working_hours,
+ "company": employee_doc.company,
+ "shift": shift,
+ "late_entry": late_entry,
+ "early_exit": early_exit,
+ "in_time": in_time,
+ "out_time": out_time,
}
attendance = frappe.get_doc(doc_dict).insert()
attendance.submit()
- frappe.db.sql("""update `tabEmployee Checkin`
+ frappe.db.sql(
+ """update `tabEmployee Checkin`
set attendance = %s
- where name in %s""", (attendance.name, log_names))
+ where name in %s""",
+ (attendance.name, log_names),
+ )
return attendance
else:
- frappe.db.sql("""update `tabEmployee Checkin`
+ frappe.db.sql(
+ """update `tabEmployee Checkin`
set skip_auto_attendance = %s
- where name in %s""", ('1', log_names))
+ where name in %s""",
+ ("1", log_names),
+ )
return None
else:
- frappe.throw(_('{} is an invalid Attendance Status.').format(attendance_status))
+ frappe.throw(_("{} is an invalid Attendance Status.").format(attendance_status))
def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
@@ -133,29 +184,35 @@
"""
total_hours = 0
in_time = out_time = None
- if check_in_out_type == 'Alternating entries as IN and OUT during the same shift':
+ if check_in_out_type == "Alternating entries as IN and OUT during the same shift":
in_time = logs[0].time
if len(logs) >= 2:
out_time = logs[-1].time
- if working_hours_calc_type == 'First Check-in and Last Check-out':
+ if working_hours_calc_type == "First Check-in and Last Check-out":
# assumption in this case: First log always taken as IN, Last log always taken as OUT
total_hours = time_diff_in_hours(in_time, logs[-1].time)
- elif working_hours_calc_type == 'Every Valid Check-in and Check-out':
+ elif working_hours_calc_type == "Every Valid Check-in and Check-out":
logs = logs[:]
while len(logs) >= 2:
total_hours += time_diff_in_hours(logs[0].time, logs[1].time)
del logs[:2]
- elif check_in_out_type == 'Strictly based on Log Type in Employee Checkin':
- if working_hours_calc_type == 'First Check-in and Last Check-out':
- first_in_log_index = find_index_in_dict(logs, 'log_type', 'IN')
- first_in_log = logs[first_in_log_index] if first_in_log_index or first_in_log_index == 0 else None
- last_out_log_index = find_index_in_dict(reversed(logs), 'log_type', 'OUT')
- last_out_log = logs[len(logs)-1-last_out_log_index] if last_out_log_index or last_out_log_index == 0 else None
+ elif check_in_out_type == "Strictly based on Log Type in Employee Checkin":
+ if working_hours_calc_type == "First Check-in and Last Check-out":
+ first_in_log_index = find_index_in_dict(logs, "log_type", "IN")
+ first_in_log = (
+ logs[first_in_log_index] if first_in_log_index or first_in_log_index == 0 else None
+ )
+ last_out_log_index = find_index_in_dict(reversed(logs), "log_type", "OUT")
+ last_out_log = (
+ logs[len(logs) - 1 - last_out_log_index]
+ if last_out_log_index or last_out_log_index == 0
+ else None
+ )
if first_in_log and last_out_log:
in_time, out_time = first_in_log.time, last_out_log.time
total_hours = time_diff_in_hours(in_time, out_time)
- elif working_hours_calc_type == 'Every Valid Check-in and Check-out':
+ elif working_hours_calc_type == "Every Valid Check-in and Check-out":
in_log = out_log = None
for log in logs:
if in_log and out_log:
@@ -165,16 +222,18 @@
total_hours += time_diff_in_hours(in_log.time, out_log.time)
in_log = out_log = None
if not in_log:
- in_log = log if log.log_type == 'IN' else None
+ in_log = log if log.log_type == "IN" else None
elif not out_log:
- out_log = log if log.log_type == 'OUT' else None
+ out_log = log if log.log_type == "OUT" else None
if in_log and out_log:
out_time = out_log.time
total_hours += time_diff_in_hours(in_log.time, out_log.time)
return total_hours, in_time, out_time
+
def time_diff_in_hours(start, end):
- return round((end-start).total_seconds() / 3600, 1)
+ return round((end - start).total_seconds() / 3600, 1)
+
def find_index_in_dict(dict_list, key, value):
return next((index for (index, d) in enumerate(dict_list) if d[key] == value), None)
diff --git a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py
index 254bf9e..97f76b0 100644
--- a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py
+++ b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py
@@ -19,85 +19,108 @@
def test_add_log_based_on_employee_field(self):
employee = make_employee("test_add_log_based_on_employee_field@example.com")
employee = frappe.get_doc("Employee", employee)
- employee.attendance_device_id = '3344'
+ employee.attendance_device_id = "3344"
employee.save()
time_now = now_datetime().__str__()[:-7]
- employee_checkin = add_log_based_on_employee_field('3344', time_now, 'mumbai_first_floor', 'IN')
+ employee_checkin = add_log_based_on_employee_field("3344", time_now, "mumbai_first_floor", "IN")
self.assertEqual(employee_checkin.employee, employee.name)
self.assertEqual(employee_checkin.time, time_now)
- self.assertEqual(employee_checkin.device_id, 'mumbai_first_floor')
- self.assertEqual(employee_checkin.log_type, 'IN')
+ self.assertEqual(employee_checkin.device_id, "mumbai_first_floor")
+ self.assertEqual(employee_checkin.log_type, "IN")
def test_mark_attendance_and_link_log(self):
employee = make_employee("test_mark_attendance_and_link_log@example.com")
logs = make_n_checkins(employee, 3)
- mark_attendance_and_link_log(logs, 'Skip', nowdate())
+ mark_attendance_and_link_log(logs, "Skip", nowdate())
log_names = [log.name for log in logs]
- logs_count = frappe.db.count('Employee Checkin', {'name':['in', log_names], 'skip_auto_attendance':1})
+ logs_count = frappe.db.count(
+ "Employee Checkin", {"name": ["in", log_names], "skip_auto_attendance": 1}
+ )
self.assertEqual(logs_count, 3)
logs = make_n_checkins(employee, 4, 2)
now_date = nowdate()
- frappe.db.delete('Attendance', {'employee':employee})
- attendance = mark_attendance_and_link_log(logs, 'Present', now_date, 8.2)
+ frappe.db.delete("Attendance", {"employee": employee})
+ attendance = mark_attendance_and_link_log(logs, "Present", now_date, 8.2)
log_names = [log.name for log in logs]
- logs_count = frappe.db.count('Employee Checkin', {'name':['in', log_names], 'attendance':attendance.name})
+ logs_count = frappe.db.count(
+ "Employee Checkin", {"name": ["in", log_names], "attendance": attendance.name}
+ )
self.assertEqual(logs_count, 4)
- attendance_count = frappe.db.count('Attendance', {'status':'Present', 'working_hours':8.2,
- 'employee':employee, 'attendance_date':now_date})
+ attendance_count = frappe.db.count(
+ "Attendance",
+ {"status": "Present", "working_hours": 8.2, "employee": employee, "attendance_date": now_date},
+ )
self.assertEqual(attendance_count, 1)
def test_calculate_working_hours(self):
- check_in_out_type = ['Alternating entries as IN and OUT during the same shift',
- 'Strictly based on Log Type in Employee Checkin']
- working_hours_calc_type = ['First Check-in and Last Check-out',
- 'Every Valid Check-in and Check-out']
+ check_in_out_type = [
+ "Alternating entries as IN and OUT during the same shift",
+ "Strictly based on Log Type in Employee Checkin",
+ ]
+ working_hours_calc_type = [
+ "First Check-in and Last Check-out",
+ "Every Valid Check-in and Check-out",
+ ]
logs_type_1 = [
- {'time':now_datetime()-timedelta(minutes=390)},
- {'time':now_datetime()-timedelta(minutes=300)},
- {'time':now_datetime()-timedelta(minutes=270)},
- {'time':now_datetime()-timedelta(minutes=90)},
- {'time':now_datetime()-timedelta(minutes=0)}
- ]
+ {"time": now_datetime() - timedelta(minutes=390)},
+ {"time": now_datetime() - timedelta(minutes=300)},
+ {"time": now_datetime() - timedelta(minutes=270)},
+ {"time": now_datetime() - timedelta(minutes=90)},
+ {"time": now_datetime() - timedelta(minutes=0)},
+ ]
logs_type_2 = [
- {'time':now_datetime()-timedelta(minutes=390),'log_type':'OUT'},
- {'time':now_datetime()-timedelta(minutes=360),'log_type':'IN'},
- {'time':now_datetime()-timedelta(minutes=300),'log_type':'OUT'},
- {'time':now_datetime()-timedelta(minutes=290),'log_type':'IN'},
- {'time':now_datetime()-timedelta(minutes=260),'log_type':'OUT'},
- {'time':now_datetime()-timedelta(minutes=240),'log_type':'IN'},
- {'time':now_datetime()-timedelta(minutes=150),'log_type':'IN'},
- {'time':now_datetime()-timedelta(minutes=60),'log_type':'OUT'}
- ]
+ {"time": now_datetime() - timedelta(minutes=390), "log_type": "OUT"},
+ {"time": now_datetime() - timedelta(minutes=360), "log_type": "IN"},
+ {"time": now_datetime() - timedelta(minutes=300), "log_type": "OUT"},
+ {"time": now_datetime() - timedelta(minutes=290), "log_type": "IN"},
+ {"time": now_datetime() - timedelta(minutes=260), "log_type": "OUT"},
+ {"time": now_datetime() - timedelta(minutes=240), "log_type": "IN"},
+ {"time": now_datetime() - timedelta(minutes=150), "log_type": "IN"},
+ {"time": now_datetime() - timedelta(minutes=60), "log_type": "OUT"},
+ ]
logs_type_1 = [frappe._dict(x) for x in logs_type_1]
logs_type_2 = [frappe._dict(x) for x in logs_type_2]
- working_hours = calculate_working_hours(logs_type_1,check_in_out_type[0],working_hours_calc_type[0])
+ working_hours = calculate_working_hours(
+ logs_type_1, check_in_out_type[0], working_hours_calc_type[0]
+ )
self.assertEqual(working_hours, (6.5, logs_type_1[0].time, logs_type_1[-1].time))
- working_hours = calculate_working_hours(logs_type_1,check_in_out_type[0],working_hours_calc_type[1])
+ working_hours = calculate_working_hours(
+ logs_type_1, check_in_out_type[0], working_hours_calc_type[1]
+ )
self.assertEqual(working_hours, (4.5, logs_type_1[0].time, logs_type_1[-1].time))
- working_hours = calculate_working_hours(logs_type_2,check_in_out_type[1],working_hours_calc_type[0])
+ working_hours = calculate_working_hours(
+ logs_type_2, check_in_out_type[1], working_hours_calc_type[0]
+ )
self.assertEqual(working_hours, (5, logs_type_2[1].time, logs_type_2[-1].time))
- working_hours = calculate_working_hours(logs_type_2,check_in_out_type[1],working_hours_calc_type[1])
+ working_hours = calculate_working_hours(
+ logs_type_2, check_in_out_type[1], working_hours_calc_type[1]
+ )
self.assertEqual(working_hours, (4.5, logs_type_2[1].time, logs_type_2[-1].time))
+
def make_n_checkins(employee, n, hours_to_reverse=1):
- logs = [make_checkin(employee, now_datetime() - timedelta(hours=hours_to_reverse, minutes=n+1))]
- for i in range(n-1):
- logs.append(make_checkin(employee, now_datetime() - timedelta(hours=hours_to_reverse, minutes=n-i)))
+ logs = [make_checkin(employee, now_datetime() - timedelta(hours=hours_to_reverse, minutes=n + 1))]
+ for i in range(n - 1):
+ logs.append(
+ make_checkin(employee, now_datetime() - timedelta(hours=hours_to_reverse, minutes=n - i))
+ )
return logs
def make_checkin(employee, time=now_datetime()):
- log = frappe.get_doc({
- "doctype": "Employee Checkin",
- "employee" : employee,
- "time" : time,
- "device_id" : "device1",
- "log_type" : "IN"
- }).insert()
+ log = frappe.get_doc(
+ {
+ "doctype": "Employee Checkin",
+ "employee": employee,
+ "time": time,
+ "device_id": "device1",
+ "log_type": "IN",
+ }
+ ).insert()
return log
diff --git a/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py b/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py
index 6825dae..efc68ce 100644
--- a/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py
+++ b/erpnext/hr/doctype/employee_grade/employee_grade_dashboard.py
@@ -1,11 +1,9 @@
def get_data():
return {
- 'transactions': [
+ "transactions": [
{
- 'items': ['Employee', 'Leave Period'],
+ "items": ["Employee", "Leave Period"],
},
- {
- 'items': ['Employee Onboarding Template', 'Employee Separation Template']
- }
+ {"items": ["Employee Onboarding Template", "Employee Separation Template"]},
]
}
diff --git a/erpnext/hr/doctype/employee_grievance/employee_grievance.py b/erpnext/hr/doctype/employee_grievance/employee_grievance.py
index fd9a33b..45de79f 100644
--- a/erpnext/hr/doctype/employee_grievance/employee_grievance.py
+++ b/erpnext/hr/doctype/employee_grievance/employee_grievance.py
@@ -9,7 +9,8 @@
class EmployeeGrievance(Document):
def on_submit(self):
if self.status not in ["Invalid", "Resolved"]:
- frappe.throw(_("Only Employee Grievance with status {0} or {1} can be submitted").format(
- bold("Invalid"),
- bold("Resolved"))
+ frappe.throw(
+ _("Only Employee Grievance with status {0} or {1} can be submitted").format(
+ bold("Invalid"), bold("Resolved")
+ )
)
diff --git a/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py b/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py
index e2d0002..910d882 100644
--- a/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py
+++ b/erpnext/hr/doctype/employee_grievance/test_employee_grievance.py
@@ -13,6 +13,7 @@
def test_create_employee_grievance(self):
create_employee_grievance()
+
def create_employee_grievance():
grievance_type = create_grievance_type()
emp_1 = make_employee("test_emp_grievance_@example.com", company="_Test Company")
@@ -27,10 +28,10 @@
grievance.grievance_against = emp_2
grievance.description = "test descrip"
- #set cause
+ # set cause
grievance.cause_of_grievance = "test cause"
- #resolution details
+ # resolution details
grievance.resolution_date = today()
grievance.resolution_detail = "test resolution detail"
grievance.resolved_by = "test_emp_grievance_@example.com"
diff --git a/erpnext/hr/doctype/employee_group/test_employee_group.py b/erpnext/hr/doctype/employee_group/test_employee_group.py
index a87f400..3922f54 100644
--- a/erpnext/hr/doctype/employee_group/test_employee_group.py
+++ b/erpnext/hr/doctype/employee_group/test_employee_group.py
@@ -11,17 +11,16 @@
class TestEmployeeGroup(unittest.TestCase):
pass
+
def make_employee_group():
employee = make_employee("testemployee@example.com")
- employee_group = frappe.get_doc({
- "doctype": "Employee Group",
- "employee_group_name": "_Test Employee Group",
- "employee_list": [
- {
- "employee": employee
- }
- ]
- })
+ employee_group = frappe.get_doc(
+ {
+ "doctype": "Employee Group",
+ "employee_group_name": "_Test Employee Group",
+ "employee_list": [{"employee": employee}],
+ }
+ )
employee_group_exist = frappe.db.exists("Employee Group", "_Test Employee Group")
if not employee_group_exist:
employee_group.insert()
@@ -29,6 +28,7 @@
else:
return employee_group_exist
+
def get_employee_group():
employee_group = frappe.db.exists("Employee Group", "_Test Employee Group")
return employee_group
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
index a0939a8..059f83a 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
@@ -9,7 +9,9 @@
from erpnext.controllers.employee_boarding_controller import EmployeeBoardingController
-class IncompleteTaskError(frappe.ValidationError): pass
+class IncompleteTaskError(frappe.ValidationError):
+ pass
+
class EmployeeOnboarding(EmployeeBoardingController):
def validate(self):
@@ -19,12 +21,18 @@
def set_employee(self):
if not self.employee:
- self.employee = frappe.db.get_value('Employee', {'job_applicant': self.job_applicant}, 'name')
+ self.employee = frappe.db.get_value("Employee", {"job_applicant": self.job_applicant}, "name")
def validate_duplicate_employee_onboarding(self):
- emp_onboarding = frappe.db.exists("Employee Onboarding", {"job_applicant": self.job_applicant, "docstatus": ("!=", 2)})
+ emp_onboarding = frappe.db.exists(
+ "Employee Onboarding", {"job_applicant": self.job_applicant, "docstatus": ("!=", 2)}
+ )
if emp_onboarding and emp_onboarding != self.name:
- frappe.throw(_("Employee Onboarding: {0} already exists for Job Applicant: {1}").format(frappe.bold(emp_onboarding), frappe.bold(self.job_applicant)))
+ frappe.throw(
+ _("Employee Onboarding: {0} already exists for Job Applicant: {1}").format(
+ frappe.bold(emp_onboarding), frappe.bold(self.job_applicant)
+ )
+ )
def validate_employee_creation(self):
if self.docstatus != 1:
@@ -36,7 +44,10 @@
else:
task_status = frappe.db.get_value("Task", activity.task, "status")
if task_status not in ["Completed", "Cancelled"]:
- frappe.throw(_("All the mandatory tasks for employee creation are not completed yet."), IncompleteTaskError)
+ frappe.throw(
+ _("All the mandatory tasks for employee creation are not completed yet."),
+ IncompleteTaskError,
+ )
def on_submit(self):
super(EmployeeOnboarding, self).on_submit()
@@ -47,19 +58,29 @@
def on_cancel(self):
super(EmployeeOnboarding, self).on_cancel()
+
@frappe.whitelist()
def make_employee(source_name, target_doc=None):
doc = frappe.get_doc("Employee Onboarding", source_name)
doc.validate_employee_creation()
+
def set_missing_values(source, target):
target.personal_email = frappe.db.get_value("Job Applicant", source.job_applicant, "email_id")
target.status = "Active"
- doc = get_mapped_doc("Employee Onboarding", source_name, {
+
+ doc = get_mapped_doc(
+ "Employee Onboarding",
+ source_name,
+ {
"Employee Onboarding": {
"doctype": "Employee",
"field_map": {
"first_name": "employee_name",
"employee_grade": "grade",
- }}
- }, target_doc, set_missing_values)
+ },
+ }
+ },
+ target_doc,
+ set_missing_values,
+ )
return doc
diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
index 0fb821d..9d91e4b 100644
--- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
+++ b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
@@ -16,8 +16,8 @@
class TestEmployeeOnboarding(unittest.TestCase):
def setUp(self):
- if frappe.db.exists('Employee Onboarding', {'employee_name': 'Test Researcher'}):
- frappe.delete_doc('Employee Onboarding', {'employee_name': 'Test Researcher'})
+ if frappe.db.exists("Employee Onboarding", {"employee_name": "Test Researcher"}):
+ frappe.delete_doc("Employee Onboarding", {"employee_name": "Test Researcher"})
project = "Employee Onboarding : test@researcher.com"
frappe.db.sql("delete from tabProject where name=%s", project)
@@ -26,100 +26,109 @@
def test_employee_onboarding_incomplete_task(self):
onboarding = create_employee_onboarding()
- project_name = frappe.db.get_value('Project', onboarding.project, 'project_name')
- self.assertEqual(project_name, 'Employee Onboarding : test@researcher.com')
+ project_name = frappe.db.get_value("Project", onboarding.project, "project_name")
+ self.assertEqual(project_name, "Employee Onboarding : test@researcher.com")
# don't allow making employee if onboarding is not complete
self.assertRaises(IncompleteTaskError, make_employee, onboarding.name)
# boarding status
- self.assertEqual(onboarding.boarding_status, 'Pending')
+ self.assertEqual(onboarding.boarding_status, "Pending")
# start and end dates
- start_date, end_date = frappe.db.get_value('Task', onboarding.activities[0].task, ['exp_start_date', 'exp_end_date'])
+ start_date, end_date = frappe.db.get_value(
+ "Task", onboarding.activities[0].task, ["exp_start_date", "exp_end_date"]
+ )
self.assertEqual(getdate(start_date), getdate(onboarding.boarding_begins_on))
self.assertEqual(getdate(end_date), add_days(start_date, onboarding.activities[0].duration))
- start_date, end_date = frappe.db.get_value('Task', onboarding.activities[1].task, ['exp_start_date', 'exp_end_date'])
- self.assertEqual(getdate(start_date), add_days(onboarding.boarding_begins_on, onboarding.activities[0].duration))
+ start_date, end_date = frappe.db.get_value(
+ "Task", onboarding.activities[1].task, ["exp_start_date", "exp_end_date"]
+ )
+ self.assertEqual(
+ getdate(start_date), add_days(onboarding.boarding_begins_on, onboarding.activities[0].duration)
+ )
self.assertEqual(getdate(end_date), add_days(start_date, onboarding.activities[1].duration))
# complete the task
- project = frappe.get_doc('Project', onboarding.project)
- for task in frappe.get_all('Task', dict(project=project.name)):
- task = frappe.get_doc('Task', task.name)
- task.status = 'Completed'
+ project = frappe.get_doc("Project", onboarding.project)
+ for task in frappe.get_all("Task", dict(project=project.name)):
+ task = frappe.get_doc("Task", task.name)
+ task.status = "Completed"
task.save()
# boarding status
onboarding.reload()
- self.assertEqual(onboarding.boarding_status, 'Completed')
+ self.assertEqual(onboarding.boarding_status, "Completed")
# make employee
onboarding.reload()
employee = make_employee(onboarding.name)
employee.first_name = employee.employee_name
employee.date_of_joining = getdate()
- employee.date_of_birth = '1990-05-08'
- employee.gender = 'Female'
+ employee.date_of_birth = "1990-05-08"
+ employee.gender = "Female"
employee.insert()
- self.assertEqual(employee.employee_name, 'Test Researcher')
+ self.assertEqual(employee.employee_name, "Test Researcher")
def tearDown(self):
frappe.db.rollback()
def get_job_applicant():
- if frappe.db.exists('Job Applicant', 'test@researcher.com'):
- return frappe.get_doc('Job Applicant', 'test@researcher.com')
- applicant = frappe.new_doc('Job Applicant')
- applicant.applicant_name = 'Test Researcher'
- applicant.email_id = 'test@researcher.com'
- applicant.designation = 'Researcher'
- applicant.status = 'Open'
- applicant.cover_letter = 'I am a great Researcher.'
+ if frappe.db.exists("Job Applicant", "test@researcher.com"):
+ return frappe.get_doc("Job Applicant", "test@researcher.com")
+ applicant = frappe.new_doc("Job Applicant")
+ applicant.applicant_name = "Test Researcher"
+ applicant.email_id = "test@researcher.com"
+ applicant.designation = "Researcher"
+ applicant.status = "Open"
+ applicant.cover_letter = "I am a great Researcher."
applicant.insert()
return applicant
+
def get_job_offer(applicant_name):
- job_offer = frappe.db.exists('Job Offer', {'job_applicant': applicant_name})
+ job_offer = frappe.db.exists("Job Offer", {"job_applicant": applicant_name})
if job_offer:
- return frappe.get_doc('Job Offer', job_offer)
+ return frappe.get_doc("Job Offer", job_offer)
job_offer = create_job_offer(job_applicant=applicant_name)
job_offer.submit()
return job_offer
+
def create_employee_onboarding():
applicant = get_job_applicant()
job_offer = get_job_offer(applicant.name)
- holiday_list = make_holiday_list('_Test Employee Boarding')
- holiday_list = frappe.get_doc('Holiday List', holiday_list)
+ holiday_list = make_holiday_list("_Test Employee Boarding")
+ holiday_list = frappe.get_doc("Holiday List", holiday_list)
holiday_list.holidays = []
holiday_list.save()
- onboarding = frappe.new_doc('Employee Onboarding')
+ onboarding = frappe.new_doc("Employee Onboarding")
onboarding.job_applicant = applicant.name
onboarding.job_offer = job_offer.name
onboarding.date_of_joining = onboarding.boarding_begins_on = getdate()
- onboarding.company = '_Test Company'
+ onboarding.company = "_Test Company"
onboarding.holiday_list = holiday_list.name
- onboarding.designation = 'Researcher'
- onboarding.append('activities', {
- 'activity_name': 'Assign ID Card',
- 'role': 'HR User',
- 'required_for_employee_creation': 1,
- 'begin_on': 0,
- 'duration': 1
- })
- onboarding.append('activities', {
- 'activity_name': 'Assign a laptop',
- 'role': 'HR User',
- 'begin_on': 1,
- 'duration': 1
- })
- onboarding.status = 'Pending'
+ onboarding.designation = "Researcher"
+ onboarding.append(
+ "activities",
+ {
+ "activity_name": "Assign ID Card",
+ "role": "HR User",
+ "required_for_employee_creation": 1,
+ "begin_on": 0,
+ "duration": 1,
+ },
+ )
+ onboarding.append(
+ "activities",
+ {"activity_name": "Assign a laptop", "role": "HR User", "begin_on": 1, "duration": 1},
+ )
+ onboarding.status = "Pending"
onboarding.insert()
onboarding.submit()
diff --git a/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py b/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py
index 3b846a0..93237ee 100644
--- a/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py
+++ b/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template_dashboard.py
@@ -1,9 +1,7 @@
def get_data():
- return {
- 'fieldname': 'employee_onboarding_template',
- 'transactions': [
- {
- 'items': ['Employee Onboarding']
- },
- ],
- }
+ return {
+ "fieldname": "employee_onboarding_template",
+ "transactions": [
+ {"items": ["Employee Onboarding"]},
+ ],
+ }
diff --git a/erpnext/hr/doctype/employee_promotion/employee_promotion.py b/erpnext/hr/doctype/employee_promotion/employee_promotion.py
index cf6156e..d77c1dd 100644
--- a/erpnext/hr/doctype/employee_promotion/employee_promotion.py
+++ b/erpnext/hr/doctype/employee_promotion/employee_promotion.py
@@ -16,12 +16,16 @@
def before_submit(self):
if getdate(self.promotion_date) > getdate():
- frappe.throw(_("Employee Promotion cannot be submitted before Promotion Date"),
- frappe.DocstatusTransitionError)
+ frappe.throw(
+ _("Employee Promotion cannot be submitted before Promotion Date"),
+ frappe.DocstatusTransitionError,
+ )
def on_submit(self):
employee = frappe.get_doc("Employee", self.employee)
- employee = update_employee_work_history(employee, self.promotion_details, date=self.promotion_date)
+ employee = update_employee_work_history(
+ employee, self.promotion_details, date=self.promotion_date
+ )
employee.save()
def on_cancel(self):
diff --git a/erpnext/hr/doctype/employee_promotion/test_employee_promotion.py b/erpnext/hr/doctype/employee_promotion/test_employee_promotion.py
index fc9d195..06825ec 100644
--- a/erpnext/hr/doctype/employee_promotion/test_employee_promotion.py
+++ b/erpnext/hr/doctype/employee_promotion/test_employee_promotion.py
@@ -15,18 +15,20 @@
frappe.db.sql("""delete from `tabEmployee Promotion`""")
def test_submit_before_promotion_date(self):
- promotion_obj = frappe.get_doc({
- "doctype": "Employee Promotion",
- "employee": self.employee,
- "promotion_details" :[
- {
- "property": "Designation",
- "current": "Software Developer",
- "new": "Project Manager",
- "fieldname": "designation"
- }
- ]
- })
+ promotion_obj = frappe.get_doc(
+ {
+ "doctype": "Employee Promotion",
+ "employee": self.employee,
+ "promotion_details": [
+ {
+ "property": "Designation",
+ "current": "Software Developer",
+ "new": "Project Manager",
+ "fieldname": "designation",
+ }
+ ],
+ }
+ )
promotion_obj.promotion_date = add_days(getdate(), 1)
promotion_obj.save()
self.assertRaises(frappe.DocstatusTransitionError, promotion_obj.submit)
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.py b/erpnext/hr/doctype/employee_referral/employee_referral.py
index eaa42c7..47cbfbc 100644
--- a/erpnext/hr/doctype/employee_referral/employee_referral.py
+++ b/erpnext/hr/doctype/employee_referral/employee_referral.py
@@ -30,7 +30,7 @@
@frappe.whitelist()
def create_job_applicant(source_name, target_doc=None):
emp_ref = frappe.get_doc("Employee Referral", source_name)
- #just for Api call if some set status apart from default Status
+ # just for Api call if some set status apart from default Status
status = emp_ref.status
if emp_ref.status in ["Pending", "In process"]:
status = "Open"
@@ -47,9 +47,13 @@
job_applicant.resume_link = emp_ref.resume_link
job_applicant.save()
- frappe.msgprint(_("Job Applicant {0} created successfully.").format(
- get_link_to_form("Job Applicant", job_applicant.name)),
- title=_("Success"), indicator="green")
+ frappe.msgprint(
+ _("Job Applicant {0} created successfully.").format(
+ get_link_to_form("Job Applicant", job_applicant.name)
+ ),
+ title=_("Success"),
+ indicator="green",
+ )
emp_ref.db_set("status", "In Process")
@@ -60,7 +64,6 @@
def create_additional_salary(doc):
import json
-
if isinstance(doc, str):
doc = frappe._dict(json.loads(doc))
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py
index 07c2402..4d683fb 100644
--- a/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py
+++ b/erpnext/hr/doctype/employee_referral/employee_referral_dashboard.py
@@ -1,13 +1,8 @@
def get_data():
return {
- 'fieldname': 'employee_referral',
- 'non_standard_fieldnames': {
- 'Additional Salary': 'ref_docname'
- },
- 'transactions': [
- {
- 'items': ['Job Applicant', 'Additional Salary']
- },
-
- ]
+ "fieldname": "employee_referral",
+ "non_standard_fieldnames": {"Additional Salary": "ref_docname"},
+ "transactions": [
+ {"items": ["Job Applicant", "Additional Salary"]},
+ ],
}
diff --git a/erpnext/hr/doctype/employee_referral/test_employee_referral.py b/erpnext/hr/doctype/employee_referral/test_employee_referral.py
index 529e355..475a935 100644
--- a/erpnext/hr/doctype/employee_referral/test_employee_referral.py
+++ b/erpnext/hr/doctype/employee_referral/test_employee_referral.py
@@ -15,7 +15,6 @@
class TestEmployeeReferral(unittest.TestCase):
-
def setUp(self):
frappe.db.sql("DELETE FROM `tabJob Applicant`")
frappe.db.sql("DELETE FROM `tabEmployee Referral`")
@@ -23,13 +22,12 @@
def test_workflow_and_status_sync(self):
emp_ref = create_employee_referral()
- #Check Initial status
+ # Check Initial status
self.assertTrue(emp_ref.status, "Pending")
job_applicant = create_job_applicant(emp_ref.name)
-
- #Check status sync
+ # Check status sync
emp_ref.reload()
self.assertTrue(emp_ref.status, "In Process")
@@ -47,7 +45,6 @@
emp_ref.reload()
self.assertTrue(emp_ref.status, "Accepted")
-
# Check for Referral reference in additional salary
add_sal = create_additional_salary(emp_ref)
diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.py b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
index f83c1e5..df31d09 100644
--- a/erpnext/hr/doctype/employee_separation/test_employee_separation.py
+++ b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
@@ -6,44 +6,43 @@
import frappe
from frappe.utils import getdate
-test_dependencies = ['Employee Onboarding']
+test_dependencies = ["Employee Onboarding"]
+
class TestEmployeeSeparation(unittest.TestCase):
def test_employee_separation(self):
separation = create_employee_separation()
self.assertEqual(separation.docstatus, 1)
- self.assertEqual(separation.boarding_status, 'Pending')
+ self.assertEqual(separation.boarding_status, "Pending")
- project = frappe.get_doc('Project', separation.project)
- project.percent_complete_method = 'Manual'
- project.status = 'Completed'
+ project = frappe.get_doc("Project", separation.project)
+ project.percent_complete_method = "Manual"
+ project.status = "Completed"
project.save()
separation.reload()
- self.assertEqual(separation.boarding_status, 'Completed')
+ self.assertEqual(separation.boarding_status, "Completed")
separation.cancel()
- self.assertEqual(separation.project, '')
+ self.assertEqual(separation.project, "")
def tearDown(self):
- for entry in frappe.get_all('Employee Separation'):
- doc = frappe.get_doc('Employee Separation', entry.name)
+ for entry in frappe.get_all("Employee Separation"):
+ doc = frappe.get_doc("Employee Separation", entry.name)
if doc.docstatus == 1:
doc.cancel()
doc.delete()
+
def create_employee_separation():
- employee = frappe.db.get_value('Employee', {'status': 'Active', 'company': '_Test Company'})
- separation = frappe.new_doc('Employee Separation')
+ employee = frappe.db.get_value("Employee", {"status": "Active", "company": "_Test Company"})
+ separation = frappe.new_doc("Employee Separation")
separation.employee = employee
separation.boarding_begins_on = getdate()
- separation.company = '_Test Company'
- separation.append('activities', {
- 'activity_name': 'Deactivate Employee',
- 'role': 'HR User'
- })
- separation.boarding_status = 'Pending'
+ separation.company = "_Test Company"
+ separation.append("activities", {"activity_name": "Deactivate Employee", "role": "HR User"})
+ separation.boarding_status = "Pending"
separation.insert()
separation.submit()
return separation
diff --git a/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py b/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py
index 6e2a83e..3ffd8dd 100644
--- a/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py
+++ b/erpnext/hr/doctype/employee_separation_template/employee_separation_template_dashboard.py
@@ -1,9 +1,7 @@
def get_data():
- return {
- 'fieldname': 'employee_separation_template',
- 'transactions': [
- {
- 'items': ['Employee Separation']
- },
- ],
- }
+ return {
+ "fieldname": "employee_separation_template",
+ "transactions": [
+ {"items": ["Employee Separation"]},
+ ],
+ }
diff --git a/erpnext/hr/doctype/employee_transfer/employee_transfer.py b/erpnext/hr/doctype/employee_transfer/employee_transfer.py
index f927d41..6dbefe5 100644
--- a/erpnext/hr/doctype/employee_transfer/employee_transfer.py
+++ b/erpnext/hr/doctype/employee_transfer/employee_transfer.py
@@ -13,8 +13,10 @@
class EmployeeTransfer(Document):
def before_submit(self):
if getdate(self.transfer_date) > getdate():
- frappe.throw(_("Employee Transfer cannot be submitted before Transfer Date"),
- frappe.DocstatusTransitionError)
+ frappe.throw(
+ _("Employee Transfer cannot be submitted before Transfer Date"),
+ frappe.DocstatusTransitionError,
+ )
def on_submit(self):
employee = frappe.get_doc("Employee", self.employee)
@@ -22,22 +24,26 @@
new_employee = frappe.copy_doc(employee)
new_employee.name = None
new_employee.employee_number = None
- new_employee = update_employee_work_history(new_employee, self.transfer_details, date=self.transfer_date)
+ new_employee = update_employee_work_history(
+ new_employee, self.transfer_details, date=self.transfer_date
+ )
if self.new_company and self.company != self.new_company:
new_employee.internal_work_history = []
new_employee.date_of_joining = self.transfer_date
new_employee.company = self.new_company
- #move user_id to new employee before insert
+ # move user_id to new employee before insert
if employee.user_id and not self.validate_user_in_details():
new_employee.user_id = employee.user_id
employee.db_set("user_id", "")
new_employee.insert()
self.db_set("new_employee_id", new_employee.name)
- #relieve the old employee
+ # relieve the old employee
employee.db_set("relieving_date", self.transfer_date)
employee.db_set("status", "Left")
else:
- employee = update_employee_work_history(employee, self.transfer_details, date=self.transfer_date)
+ employee = update_employee_work_history(
+ employee, self.transfer_details, date=self.transfer_date
+ )
if self.new_company and self.company != self.new_company:
employee.company = self.new_company
employee.date_of_joining = self.transfer_date
@@ -47,14 +53,18 @@
employee = frappe.get_doc("Employee", self.employee)
if self.create_new_employee_id:
if self.new_employee_id:
- frappe.throw(_("Please delete the Employee {0} to cancel this document").format(
- "<a href='/app/Form/Employee/{0}'>{0}</a>".format(self.new_employee_id)
- ))
- #mark the employee as active
+ frappe.throw(
+ _("Please delete the Employee {0} to cancel this document").format(
+ "<a href='/app/Form/Employee/{0}'>{0}</a>".format(self.new_employee_id)
+ )
+ )
+ # mark the employee as active
employee.status = "Active"
- employee.relieving_date = ''
+ employee.relieving_date = ""
else:
- employee = update_employee_work_history(employee, self.transfer_details, date=self.transfer_date, cancel=True)
+ employee = update_employee_work_history(
+ employee, self.transfer_details, date=self.transfer_date, cancel=True
+ )
if self.new_company != self.company:
employee.company = self.company
employee.save()
diff --git a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py b/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py
index 64eee40..37a190a 100644
--- a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py
+++ b/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py
@@ -19,18 +19,20 @@
def test_submit_before_transfer_date(self):
make_employee("employee2@transfers.com")
- transfer_obj = frappe.get_doc({
- "doctype": "Employee Transfer",
- "employee": frappe.get_value("Employee", {"user_id":"employee2@transfers.com"}, "name"),
- "transfer_details" :[
- {
- "property": "Designation",
- "current": "Software Developer",
- "new": "Project Manager",
- "fieldname": "designation"
- }
- ]
- })
+ transfer_obj = frappe.get_doc(
+ {
+ "doctype": "Employee Transfer",
+ "employee": frappe.get_value("Employee", {"user_id": "employee2@transfers.com"}, "name"),
+ "transfer_details": [
+ {
+ "property": "Designation",
+ "current": "Software Developer",
+ "new": "Project Manager",
+ "fieldname": "designation",
+ }
+ ],
+ }
+ )
transfer_obj.transfer_date = add_days(getdate(), 1)
transfer_obj.save()
self.assertRaises(frappe.DocstatusTransitionError, transfer_obj.submit)
@@ -42,32 +44,35 @@
def test_new_employee_creation(self):
make_employee("employee3@transfers.com")
- transfer = frappe.get_doc({
- "doctype": "Employee Transfer",
- "employee": frappe.get_value("Employee", {"user_id":"employee3@transfers.com"}, "name"),
- "create_new_employee_id": 1,
- "transfer_date": getdate(),
- "transfer_details" :[
- {
- "property": "Designation",
- "current": "Software Developer",
- "new": "Project Manager",
- "fieldname": "designation"
- }
- ]
- }).insert()
+ transfer = frappe.get_doc(
+ {
+ "doctype": "Employee Transfer",
+ "employee": frappe.get_value("Employee", {"user_id": "employee3@transfers.com"}, "name"),
+ "create_new_employee_id": 1,
+ "transfer_date": getdate(),
+ "transfer_details": [
+ {
+ "property": "Designation",
+ "current": "Software Developer",
+ "new": "Project Manager",
+ "fieldname": "designation",
+ }
+ ],
+ }
+ ).insert()
transfer.submit()
self.assertTrue(transfer.new_employee_id)
self.assertEqual(frappe.get_value("Employee", transfer.new_employee_id, "status"), "Active")
self.assertEqual(frappe.get_value("Employee", transfer.employee, "status"), "Left")
def test_employee_history(self):
- employee = make_employee("employee4@transfers.com",
+ employee = make_employee(
+ "employee4@transfers.com",
company="Test Company",
date_of_birth=getdate("30-09-1980"),
date_of_joining=getdate("01-10-2021"),
department="Accounts - TC",
- designation="Accountant"
+ designation="Accountant",
)
transfer = create_employee_transfer(employee)
@@ -94,36 +99,40 @@
def create_company():
if not frappe.db.exists("Company", "Test Company"):
- frappe.get_doc({
- "doctype": "Company",
- "company_name": "Test Company",
- "default_currency": "INR",
- "country": "India"
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Company",
+ "company_name": "Test Company",
+ "default_currency": "INR",
+ "country": "India",
+ }
+ ).insert()
def create_employee_transfer(employee):
- doc = frappe.get_doc({
- "doctype": "Employee Transfer",
- "employee": employee,
- "transfer_date": getdate(),
- "transfer_details": [
- {
- "property": "Designation",
- "current": "Accountant",
- "new": "Manager",
- "fieldname": "designation"
- },
- {
- "property": "Department",
- "current": "Accounts - TC",
- "new": "Management - TC",
- "fieldname": "department"
- }
- ]
- })
+ doc = frappe.get_doc(
+ {
+ "doctype": "Employee Transfer",
+ "employee": employee,
+ "transfer_date": getdate(),
+ "transfer_details": [
+ {
+ "property": "Designation",
+ "current": "Accountant",
+ "new": "Manager",
+ "fieldname": "designation",
+ },
+ {
+ "property": "Department",
+ "current": "Accounts - TC",
+ "new": "Management - TC",
+ "fieldname": "department",
+ },
+ ],
+ }
+ )
doc.save()
doc.submit()
- return doc
\ No newline at end of file
+ return doc
diff --git a/erpnext/hr/doctype/employment_type/test_employment_type.py b/erpnext/hr/doctype/employment_type/test_employment_type.py
index c43f963..fdf6965 100644
--- a/erpnext/hr/doctype/employment_type/test_employment_type.py
+++ b/erpnext/hr/doctype/employment_type/test_employment_type.py
@@ -3,4 +3,4 @@
import frappe
-test_records = frappe.get_test_records('Employment Type')
+test_records = frappe.get_test_records("Employment Type")
diff --git a/erpnext/hr/doctype/exit_interview/exit_interview.py b/erpnext/hr/doctype/exit_interview/exit_interview.py
index 59fb2fd..8317310 100644
--- a/erpnext/hr/doctype/exit_interview/exit_interview.py
+++ b/erpnext/hr/doctype/exit_interview/exit_interview.py
@@ -16,41 +16,45 @@
self.set_employee_email()
def validate_relieving_date(self):
- if not frappe.db.get_value('Employee', self.employee, 'relieving_date'):
- frappe.throw(_('Please set the relieving date for employee {0}').format(
- get_link_to_form('Employee', self.employee)),
- title=_('Relieving Date Missing'))
+ if not frappe.db.get_value("Employee", self.employee, "relieving_date"):
+ frappe.throw(
+ _("Please set the relieving date for employee {0}").format(
+ get_link_to_form("Employee", self.employee)
+ ),
+ title=_("Relieving Date Missing"),
+ )
def validate_duplicate_interview(self):
- doc = frappe.db.exists('Exit Interview', {
- 'employee': self.employee,
- 'name': ('!=', self.name),
- 'docstatus': ('!=', 2)
- })
+ doc = frappe.db.exists(
+ "Exit Interview", {"employee": self.employee, "name": ("!=", self.name), "docstatus": ("!=", 2)}
+ )
if doc:
- frappe.throw(_('Exit Interview {0} already exists for Employee: {1}').format(
- get_link_to_form('Exit Interview', doc), frappe.bold(self.employee)),
- frappe.DuplicateEntryError)
+ frappe.throw(
+ _("Exit Interview {0} already exists for Employee: {1}").format(
+ get_link_to_form("Exit Interview", doc), frappe.bold(self.employee)
+ ),
+ frappe.DuplicateEntryError,
+ )
def set_employee_email(self):
- employee = frappe.get_doc('Employee', self.employee)
+ employee = frappe.get_doc("Employee", self.employee)
self.email = get_employee_email(employee)
def on_submit(self):
- if self.status != 'Completed':
- frappe.throw(_('Only Completed documents can be submitted'))
+ if self.status != "Completed":
+ frappe.throw(_("Only Completed documents can be submitted"))
self.update_interview_date_in_employee()
def on_cancel(self):
self.update_interview_date_in_employee()
- self.db_set('status', 'Cancelled')
+ self.db_set("status", "Cancelled")
def update_interview_date_in_employee(self):
if self.docstatus == 1:
- frappe.db.set_value('Employee', self.employee, 'held_on', self.date)
+ frappe.db.set_value("Employee", self.employee, "held_on", self.date)
elif self.docstatus == 2:
- frappe.db.set_value('Employee', self.employee, 'held_on', None)
+ frappe.db.set_value("Employee", self.employee, "held_on", None)
@frappe.whitelist()
@@ -62,17 +66,19 @@
email_failure = []
for exit_interview in interviews:
- interview = frappe.get_doc('Exit Interview', exit_interview.get('name'))
- if interview.get('questionnaire_email_sent'):
+ interview = frappe.get_doc("Exit Interview", exit_interview.get("name"))
+ if interview.get("questionnaire_email_sent"):
continue
- employee = frappe.get_doc('Employee', interview.employee)
+ employee = frappe.get_doc("Employee", interview.employee)
email = get_employee_email(employee)
context = interview.as_dict()
context.update(employee.as_dict())
- template_name = frappe.db.get_single_value('HR Settings', 'exit_questionnaire_notification_template')
- template = frappe.get_doc('Email Template', template_name)
+ template_name = frappe.db.get_single_value(
+ "HR Settings", "exit_questionnaire_notification_template"
+ )
+ template = frappe.get_doc("Email Template", template_name)
if email:
frappe.sendmail(
@@ -80,13 +86,13 @@
subject=template.subject,
message=frappe.render_template(template.response, context),
reference_doctype=interview.doctype,
- reference_name=interview.name
+ reference_name=interview.name,
)
- interview.db_set('questionnaire_email_sent', True)
+ interview.db_set("questionnaire_email_sent", True)
interview.notify_update()
email_success.append(email)
else:
- email_failure.append(get_link_to_form('Employee', employee.name))
+ email_failure.append(get_link_to_form("Employee", employee.name))
show_email_summary(email_success, email_failure)
@@ -98,34 +104,43 @@
interviews = json.loads(interviews)
if not len(interviews):
- frappe.throw(_('Atleast one interview has to be selected.'))
+ frappe.throw(_("Atleast one interview has to be selected."))
return interviews
def validate_questionnaire_settings():
- settings = frappe.db.get_value('HR Settings', 'HR Settings',
- ['exit_questionnaire_web_form', 'exit_questionnaire_notification_template'], as_dict=True)
+ settings = frappe.db.get_value(
+ "HR Settings",
+ "HR Settings",
+ ["exit_questionnaire_web_form", "exit_questionnaire_notification_template"],
+ as_dict=True,
+ )
- if not settings.exit_questionnaire_web_form or not settings.exit_questionnaire_notification_template:
+ if (
+ not settings.exit_questionnaire_web_form or not settings.exit_questionnaire_notification_template
+ ):
frappe.throw(
- _('Please set {0} and {1} in {2}.').format(
- frappe.bold('Exit Questionnaire Web Form'),
- frappe.bold('Notification Template'),
- get_link_to_form('HR Settings', 'HR Settings')),
- title=_('Settings Missing')
+ _("Please set {0} and {1} in {2}.").format(
+ frappe.bold("Exit Questionnaire Web Form"),
+ frappe.bold("Notification Template"),
+ get_link_to_form("HR Settings", "HR Settings"),
+ ),
+ title=_("Settings Missing"),
)
def show_email_summary(email_success, email_failure):
- message = ''
+ message = ""
if email_success:
- message += _('{0}: {1}').format(
- frappe.bold('Sent Successfully'), ', '.join(email_success))
+ message += _("{0}: {1}").format(frappe.bold("Sent Successfully"), ", ".join(email_success))
if message and email_failure:
- message += '<br><br>'
+ message += "<br><br>"
if email_failure:
- message += _('{0} due to missing email information for employee(s): {1}').format(
- frappe.bold('Sending Failed'), ', '.join(email_failure))
+ message += _("{0} due to missing email information for employee(s): {1}").format(
+ frappe.bold("Sending Failed"), ", ".join(email_failure)
+ )
- frappe.msgprint(message, title=_('Exit Questionnaire'), indicator='blue', is_minimizable=True, wide=True)
+ frappe.msgprint(
+ message, title=_("Exit Questionnaire"), indicator="blue", is_minimizable=True, wide=True
+ )
diff --git a/erpnext/hr/doctype/exit_interview/test_exit_interview.py b/erpnext/hr/doctype/exit_interview/test_exit_interview.py
index 8e076ed..9c2c644 100644
--- a/erpnext/hr/doctype/exit_interview/test_exit_interview.py
+++ b/erpnext/hr/doctype/exit_interview/test_exit_interview.py
@@ -16,84 +16,88 @@
class TestExitInterview(unittest.TestCase):
def setUp(self):
- frappe.db.sql('delete from `tabExit Interview`')
+ frappe.db.sql("delete from `tabExit Interview`")
def test_duplicate_interview(self):
- employee = make_employee('employeeexitint1@example.com')
- frappe.db.set_value('Employee', employee, 'relieving_date', getdate())
+ employee = make_employee("employeeexitint1@example.com")
+ frappe.db.set_value("Employee", employee, "relieving_date", getdate())
interview = create_exit_interview(employee)
doc = frappe.copy_doc(interview)
self.assertRaises(frappe.DuplicateEntryError, doc.save)
def test_relieving_date_validation(self):
- employee = make_employee('employeeexitint2@example.com')
+ employee = make_employee("employeeexitint2@example.com")
# unset relieving date
- frappe.db.set_value('Employee', employee, 'relieving_date', None)
+ frappe.db.set_value("Employee", employee, "relieving_date", None)
interview = create_exit_interview(employee, save=False)
self.assertRaises(frappe.ValidationError, interview.save)
# set relieving date
- frappe.db.set_value('Employee', employee, 'relieving_date', getdate())
+ frappe.db.set_value("Employee", employee, "relieving_date", getdate())
interview = create_exit_interview(employee)
self.assertTrue(interview.name)
def test_interview_date_updated_in_employee_master(self):
- employee = make_employee('employeeexit3@example.com')
- frappe.db.set_value('Employee', employee, 'relieving_date', getdate())
+ employee = make_employee("employeeexit3@example.com")
+ frappe.db.set_value("Employee", employee, "relieving_date", getdate())
interview = create_exit_interview(employee)
- interview.status = 'Completed'
- interview.employee_status = 'Exit Confirmed'
+ interview.status = "Completed"
+ interview.employee_status = "Exit Confirmed"
# exit interview date updated on submit
interview.submit()
- self.assertEqual(frappe.db.get_value('Employee', employee, 'held_on'), interview.date)
+ self.assertEqual(frappe.db.get_value("Employee", employee, "held_on"), interview.date)
# exit interview reset on cancel
interview.reload()
interview.cancel()
- self.assertEqual(frappe.db.get_value('Employee', employee, 'held_on'), None)
+ self.assertEqual(frappe.db.get_value("Employee", employee, "held_on"), None)
def test_send_exit_questionnaire(self):
create_custom_doctype()
create_webform()
template = create_notification_template()
- webform = frappe.db.get_all('Web Form', limit=1)
- frappe.db.set_value('HR Settings', 'HR Settings', {
- 'exit_questionnaire_web_form': webform[0].name,
- 'exit_questionnaire_notification_template': template
- })
+ webform = frappe.db.get_all("Web Form", limit=1)
+ frappe.db.set_value(
+ "HR Settings",
+ "HR Settings",
+ {
+ "exit_questionnaire_web_form": webform[0].name,
+ "exit_questionnaire_notification_template": template,
+ },
+ )
- employee = make_employee('employeeexit3@example.com')
- frappe.db.set_value('Employee', employee, 'relieving_date', getdate())
+ employee = make_employee("employeeexit3@example.com")
+ frappe.db.set_value("Employee", employee, "relieving_date", getdate())
interview = create_exit_interview(employee)
send_exit_questionnaire([interview])
- email_queue = frappe.db.get_all('Email Queue', ['name', 'message'], limit=1)
- self.assertTrue('Subject: Exit Questionnaire Notification' in email_queue[0].message)
+ email_queue = frappe.db.get_all("Email Queue", ["name", "message"], limit=1)
+ self.assertTrue("Subject: Exit Questionnaire Notification" in email_queue[0].message)
def tearDown(self):
frappe.db.rollback()
def create_exit_interview(employee, save=True):
- interviewer = create_user('test_exit_interviewer@example.com')
+ interviewer = create_user("test_exit_interviewer@example.com")
- doc = frappe.get_doc({
- 'doctype': 'Exit Interview',
- 'employee': employee,
- 'company': '_Test Company',
- 'status': 'Pending',
- 'date': getdate(),
- 'interviewers': [{
- 'interviewer': interviewer.name
- }],
- 'interview_summary': 'Test'
- })
+ doc = frappe.get_doc(
+ {
+ "doctype": "Exit Interview",
+ "employee": employee,
+ "company": "_Test Company",
+ "status": "Pending",
+ "date": getdate(),
+ "interviewers": [{"interviewer": interviewer.name}],
+ "interview_summary": "Test",
+ }
+ )
if save:
return doc.insert()
@@ -101,18 +105,22 @@
def create_notification_template():
- template = frappe.db.exists('Email Template', _('Exit Questionnaire Notification'))
+ template = frappe.db.exists("Email Template", _("Exit Questionnaire Notification"))
if not template:
- base_path = frappe.get_app_path('erpnext', 'hr', 'doctype')
- response = frappe.read_file(os.path.join(base_path, 'exit_interview/exit_questionnaire_notification_template.html'))
+ base_path = frappe.get_app_path("erpnext", "hr", "doctype")
+ response = frappe.read_file(
+ os.path.join(base_path, "exit_interview/exit_questionnaire_notification_template.html")
+ )
- template = frappe.get_doc({
- 'doctype': 'Email Template',
- 'name': _('Exit Questionnaire Notification'),
- 'response': response,
- 'subject': _('Exit Questionnaire Notification'),
- 'owner': frappe.session.user,
- }).insert(ignore_permissions=True)
+ template = frappe.get_doc(
+ {
+ "doctype": "Email Template",
+ "name": _("Exit Questionnaire Notification"),
+ "response": response,
+ "subject": _("Exit Questionnaire Notification"),
+ "owner": frappe.session.user,
+ }
+ ).insert(ignore_permissions=True)
template = template.name
- return template
\ No newline at end of file
+ return template
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index 12a3112..89d86c1 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -13,13 +13,19 @@
from erpnext.hr.utils import set_employee_name, share_doc_with_approver, validate_active_employee
-class InvalidExpenseApproverError(frappe.ValidationError): pass
-class ExpenseApproverIdentityError(frappe.ValidationError): pass
+class InvalidExpenseApproverError(frappe.ValidationError):
+ pass
+
+
+class ExpenseApproverIdentityError(frappe.ValidationError):
+ pass
+
class ExpenseClaim(AccountsController):
def onload(self):
- self.get("__onload").make_payment_via_journal_entry = frappe.db.get_single_value('Accounts Settings',
- 'make_payment_via_journal_entry')
+ self.get("__onload").make_payment_via_journal_entry = frappe.db.get_single_value(
+ "Accounts Settings", "make_payment_via_journal_entry"
+ )
def validate(self):
validate_active_employee(self.employee)
@@ -36,29 +42,35 @@
self.project = frappe.db.get_value("Task", self.task, "project")
def set_status(self, update=False):
- status = {
- "0": "Draft",
- "1": "Submitted",
- "2": "Cancelled"
- }[cstr(self.docstatus or 0)]
+ status = {"0": "Draft", "1": "Submitted", "2": "Cancelled"}[cstr(self.docstatus or 0)]
precision = self.precision("grand_total")
if (
# set as paid
self.is_paid
- or (flt(self.total_sanctioned_amount > 0) and (
- # grand total is reimbursed
- (self.docstatus == 1 and flt(self.grand_total, precision) == flt(self.total_amount_reimbursed, precision))
- # grand total (to be paid) is 0 since linked advances already cover the claimed amount
- or (flt(self.grand_total, precision) == 0)
- ))
+ or (
+ flt(self.total_sanctioned_amount > 0)
+ and (
+ # grand total is reimbursed
+ (
+ self.docstatus == 1
+ and flt(self.grand_total, precision) == flt(self.total_amount_reimbursed, precision)
+ )
+ # grand total (to be paid) is 0 since linked advances already cover the claimed amount
+ or (flt(self.grand_total, precision) == 0)
+ )
+ )
) and self.approval_status == "Approved":
status = "Paid"
- elif flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1 and self.approval_status == 'Approved':
+ elif (
+ flt(self.total_sanctioned_amount) > 0
+ and self.docstatus == 1
+ and self.approval_status == "Approved"
+ ):
status = "Unpaid"
- elif self.docstatus == 1 and self.approval_status == 'Rejected':
- status = 'Rejected'
+ elif self.docstatus == 1 and self.approval_status == "Rejected":
+ status = "Rejected"
if update:
self.db_set("status", status)
@@ -70,14 +82,16 @@
def set_payable_account(self):
if not self.payable_account and not self.is_paid:
- self.payable_account = frappe.get_cached_value('Company', self.company, 'default_expense_claim_payable_account')
+ self.payable_account = frappe.get_cached_value(
+ "Company", self.company, "default_expense_claim_payable_account"
+ )
def set_cost_center(self):
if not self.cost_center:
- self.cost_center = frappe.get_cached_value('Company', self.company, 'cost_center')
+ self.cost_center = frappe.get_cached_value("Company", self.company, "cost_center")
def on_submit(self):
- if self.approval_status=="Draft":
+ if self.approval_status == "Draft":
frappe.throw(_("""Approval Status must be 'Approved' or 'Rejected'"""))
self.update_task_and_project()
@@ -91,7 +105,7 @@
def on_cancel(self):
self.update_task_and_project()
- self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
+ self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
if self.payable_account:
self.make_gl_entries(cancel=True)
@@ -122,43 +136,51 @@
# payable entry
if self.grand_total:
gl_entry.append(
- self.get_gl_dict({
- "account": self.payable_account,
- "credit": self.grand_total,
- "credit_in_account_currency": self.grand_total,
- "against": ",".join([d.default_account for d in self.expenses]),
- "party_type": "Employee",
- "party": self.employee,
- "against_voucher_type": self.doctype,
- "against_voucher": self.name,
- "cost_center": self.cost_center
- }, item=self)
+ self.get_gl_dict(
+ {
+ "account": self.payable_account,
+ "credit": self.grand_total,
+ "credit_in_account_currency": self.grand_total,
+ "against": ",".join([d.default_account for d in self.expenses]),
+ "party_type": "Employee",
+ "party": self.employee,
+ "against_voucher_type": self.doctype,
+ "against_voucher": self.name,
+ "cost_center": self.cost_center,
+ },
+ item=self,
+ )
)
# expense entries
for data in self.expenses:
gl_entry.append(
- self.get_gl_dict({
- "account": data.default_account,
- "debit": data.sanctioned_amount,
- "debit_in_account_currency": data.sanctioned_amount,
- "against": self.employee,
- "cost_center": data.cost_center or self.cost_center
- }, item=data)
+ self.get_gl_dict(
+ {
+ "account": data.default_account,
+ "debit": data.sanctioned_amount,
+ "debit_in_account_currency": data.sanctioned_amount,
+ "against": self.employee,
+ "cost_center": data.cost_center or self.cost_center,
+ },
+ item=data,
+ )
)
for data in self.advances:
gl_entry.append(
- self.get_gl_dict({
- "account": data.advance_account,
- "credit": data.allocated_amount,
- "credit_in_account_currency": data.allocated_amount,
- "against": ",".join([d.default_account for d in self.expenses]),
- "party_type": "Employee",
- "party": self.employee,
- "against_voucher_type": "Employee Advance",
- "against_voucher": data.employee_advance
- })
+ self.get_gl_dict(
+ {
+ "account": data.advance_account,
+ "credit": data.allocated_amount,
+ "credit_in_account_currency": data.allocated_amount,
+ "against": ",".join([d.default_account for d in self.expenses]),
+ "party_type": "Employee",
+ "party": self.employee,
+ "against_voucher_type": "Employee Advance",
+ "against_voucher": data.employee_advance,
+ }
+ )
)
self.add_tax_gl_entries(gl_entry)
@@ -167,25 +189,31 @@
# payment entry
payment_account = get_bank_cash_account(self.mode_of_payment, self.company).get("account")
gl_entry.append(
- self.get_gl_dict({
- "account": payment_account,
- "credit": self.grand_total,
- "credit_in_account_currency": self.grand_total,
- "against": self.employee
- }, item=self)
+ self.get_gl_dict(
+ {
+ "account": payment_account,
+ "credit": self.grand_total,
+ "credit_in_account_currency": self.grand_total,
+ "against": self.employee,
+ },
+ item=self,
+ )
)
gl_entry.append(
- self.get_gl_dict({
- "account": self.payable_account,
- "party_type": "Employee",
- "party": self.employee,
- "against": payment_account,
- "debit": self.grand_total,
- "debit_in_account_currency": self.grand_total,
- "against_voucher": self.name,
- "against_voucher_type": self.doctype,
- }, item=self)
+ self.get_gl_dict(
+ {
+ "account": self.payable_account,
+ "party_type": "Employee",
+ "party": self.employee,
+ "against": payment_account,
+ "debit": self.grand_total,
+ "debit_in_account_currency": self.grand_total,
+ "against_voucher": self.name,
+ "against_voucher_type": self.doctype,
+ },
+ item=self,
+ )
)
return gl_entry
@@ -194,22 +222,28 @@
# tax table gl entries
for tax in self.get("taxes"):
gl_entries.append(
- self.get_gl_dict({
- "account": tax.account_head,
- "debit": tax.tax_amount,
- "debit_in_account_currency": tax.tax_amount,
- "against": self.employee,
- "cost_center": self.cost_center,
- "against_voucher_type": self.doctype,
- "against_voucher": self.name
- }, item=tax)
+ self.get_gl_dict(
+ {
+ "account": tax.account_head,
+ "debit": tax.tax_amount,
+ "debit_in_account_currency": tax.tax_amount,
+ "against": self.employee,
+ "cost_center": self.cost_center,
+ "against_voucher_type": self.doctype,
+ "against_voucher": self.name,
+ },
+ item=tax,
+ )
)
def validate_account_details(self):
for data in self.expenses:
if not data.cost_center:
- frappe.throw(_("Row {0}: {1} is required in the expenses table to book an expense claim.")
- .format(data.idx, frappe.bold("Cost Center")))
+ frappe.throw(
+ _("Row {0}: {1} is required in the expenses table to book an expense claim.").format(
+ data.idx, frappe.bold("Cost Center")
+ )
+ )
if self.is_paid:
if not self.mode_of_payment:
@@ -218,8 +252,8 @@
def calculate_total_amount(self):
self.total_claimed_amount = 0
self.total_sanctioned_amount = 0
- for d in self.get('expenses'):
- if self.approval_status == 'Rejected':
+ for d in self.get("expenses"):
+ if self.approval_status == "Rejected":
d.sanctioned_amount = 0.0
self.total_claimed_amount += flt(d.amount)
@@ -230,12 +264,16 @@
self.total_taxes_and_charges = 0
for tax in self.taxes:
if tax.rate:
- tax.tax_amount = flt(self.total_sanctioned_amount) * flt(tax.rate/100)
+ tax.tax_amount = flt(self.total_sanctioned_amount) * flt(tax.rate / 100)
tax.total = flt(tax.tax_amount) + flt(self.total_sanctioned_amount)
self.total_taxes_and_charges += flt(tax.tax_amount)
- self.grand_total = flt(self.total_sanctioned_amount) + flt(self.total_taxes_and_charges) - flt(self.total_advance_amount)
+ self.grand_total = (
+ flt(self.total_sanctioned_amount)
+ + flt(self.total_taxes_and_charges)
+ - flt(self.total_advance_amount)
+ )
def update_task(self):
task = frappe.get_doc("Task", self.task)
@@ -245,16 +283,23 @@
def validate_advances(self):
self.total_advance_amount = 0
for d in self.get("advances"):
- ref_doc = frappe.db.get_value("Employee Advance", d.employee_advance,
- ["posting_date", "paid_amount", "claimed_amount", "advance_account"], as_dict=1)
+ ref_doc = frappe.db.get_value(
+ "Employee Advance",
+ d.employee_advance,
+ ["posting_date", "paid_amount", "claimed_amount", "advance_account"],
+ as_dict=1,
+ )
d.posting_date = ref_doc.posting_date
d.advance_account = ref_doc.advance_account
d.advance_paid = ref_doc.paid_amount
d.unclaimed_amount = flt(ref_doc.paid_amount) - flt(ref_doc.claimed_amount)
if d.allocated_amount and flt(d.allocated_amount) > flt(d.unclaimed_amount):
- frappe.throw(_("Row {0}# Allocated amount {1} cannot be greater than unclaimed amount {2}")
- .format(d.idx, d.allocated_amount, d.unclaimed_amount))
+ frappe.throw(
+ _("Row {0}# Allocated amount {1} cannot be greater than unclaimed amount {2}").format(
+ d.idx, d.allocated_amount, d.unclaimed_amount
+ )
+ )
self.total_advance_amount += flt(d.allocated_amount)
@@ -263,27 +308,36 @@
if flt(self.total_advance_amount, precision) > flt(self.total_claimed_amount, precision):
frappe.throw(_("Total advance amount cannot be greater than total claimed amount"))
- if self.total_sanctioned_amount \
- and flt(self.total_advance_amount, precision) > flt(self.total_sanctioned_amount, precision):
+ if self.total_sanctioned_amount and flt(self.total_advance_amount, precision) > flt(
+ self.total_sanctioned_amount, precision
+ ):
frappe.throw(_("Total advance amount cannot be greater than total sanctioned amount"))
def validate_sanctioned_amount(self):
- for d in self.get('expenses'):
+ for d in self.get("expenses"):
if flt(d.sanctioned_amount) > flt(d.amount):
- frappe.throw(_("Sanctioned Amount cannot be greater than Claim Amount in Row {0}.").format(d.idx))
+ frappe.throw(
+ _("Sanctioned Amount cannot be greater than Claim Amount in Row {0}.").format(d.idx)
+ )
def set_expense_account(self, validate=False):
for expense in self.expenses:
if not expense.default_account or not validate:
- expense.default_account = get_expense_claim_account(expense.expense_type, self.company)["account"]
+ expense.default_account = get_expense_claim_account(expense.expense_type, self.company)[
+ "account"
+ ]
+
def update_reimbursed_amount(doc, amount):
doc.total_amount_reimbursed += amount
- frappe.db.set_value("Expense Claim", doc.name , "total_amount_reimbursed", doc.total_amount_reimbursed)
+ frappe.db.set_value(
+ "Expense Claim", doc.name, "total_amount_reimbursed", doc.total_amount_reimbursed
+ )
doc.set_status()
- frappe.db.set_value("Expense Claim", doc.name , "status", doc.status)
+ frappe.db.set_value("Expense Claim", doc.name, "status", doc.status)
+
@frappe.whitelist()
def make_bank_entry(dt, dn):
@@ -294,69 +348,80 @@
if not default_bank_cash_account:
default_bank_cash_account = get_default_bank_cash_account(expense_claim.company, "Cash")
- payable_amount = flt(expense_claim.total_sanctioned_amount) \
- - flt(expense_claim.total_amount_reimbursed) - flt(expense_claim.total_advance_amount)
+ payable_amount = (
+ flt(expense_claim.total_sanctioned_amount)
+ - flt(expense_claim.total_amount_reimbursed)
+ - flt(expense_claim.total_advance_amount)
+ )
je = frappe.new_doc("Journal Entry")
- je.voucher_type = 'Bank Entry'
+ je.voucher_type = "Bank Entry"
je.company = expense_claim.company
- je.remark = 'Payment against Expense Claim: ' + dn
+ je.remark = "Payment against Expense Claim: " + dn
- je.append("accounts", {
- "account": expense_claim.payable_account,
- "debit_in_account_currency": payable_amount,
- "reference_type": "Expense Claim",
- "party_type": "Employee",
- "party": expense_claim.employee,
- "cost_center": erpnext.get_default_cost_center(expense_claim.company),
- "reference_name": expense_claim.name
- })
+ je.append(
+ "accounts",
+ {
+ "account": expense_claim.payable_account,
+ "debit_in_account_currency": payable_amount,
+ "reference_type": "Expense Claim",
+ "party_type": "Employee",
+ "party": expense_claim.employee,
+ "cost_center": erpnext.get_default_cost_center(expense_claim.company),
+ "reference_name": expense_claim.name,
+ },
+ )
- je.append("accounts", {
- "account": default_bank_cash_account.account,
- "credit_in_account_currency": payable_amount,
- "reference_type": "Expense Claim",
- "reference_name": expense_claim.name,
- "balance": default_bank_cash_account.balance,
- "account_currency": default_bank_cash_account.account_currency,
- "cost_center": erpnext.get_default_cost_center(expense_claim.company),
- "account_type": default_bank_cash_account.account_type
- })
+ je.append(
+ "accounts",
+ {
+ "account": default_bank_cash_account.account,
+ "credit_in_account_currency": payable_amount,
+ "reference_type": "Expense Claim",
+ "reference_name": expense_claim.name,
+ "balance": default_bank_cash_account.balance,
+ "account_currency": default_bank_cash_account.account_currency,
+ "cost_center": erpnext.get_default_cost_center(expense_claim.company),
+ "account_type": default_bank_cash_account.account_type,
+ },
+ )
return je.as_dict()
+
@frappe.whitelist()
def get_expense_claim_account_and_cost_center(expense_claim_type, company):
data = get_expense_claim_account(expense_claim_type, company)
cost_center = erpnext.get_default_cost_center(company)
- return {
- "account": data.get("account"),
- "cost_center": cost_center
- }
+ return {"account": data.get("account"), "cost_center": cost_center}
+
@frappe.whitelist()
def get_expense_claim_account(expense_claim_type, company):
- account = frappe.db.get_value("Expense Claim Account",
- {"parent": expense_claim_type, "company": company}, "default_account")
+ account = frappe.db.get_value(
+ "Expense Claim Account", {"parent": expense_claim_type, "company": company}, "default_account"
+ )
if not account:
- frappe.throw(_("Set the default account for the {0} {1}")
- .format(frappe.bold("Expense Claim Type"), get_link_to_form("Expense Claim Type", expense_claim_type)))
+ frappe.throw(
+ _("Set the default account for the {0} {1}").format(
+ frappe.bold("Expense Claim Type"), get_link_to_form("Expense Claim Type", expense_claim_type)
+ )
+ )
- return {
- "account": account
- }
+ return {"account": account}
+
@frappe.whitelist()
def get_advances(employee, advance_id=None):
advance = frappe.qb.DocType("Employee Advance")
- query = (
- frappe.qb.from_(advance)
- .select(
- advance.name, advance.posting_date, advance.paid_amount,
- advance.claimed_amount, advance.advance_account
- )
+ query = frappe.qb.from_(advance).select(
+ advance.name,
+ advance.posting_date,
+ advance.paid_amount,
+ advance.claimed_amount,
+ advance.advance_account,
)
if not advance_id:
@@ -374,25 +439,26 @@
@frappe.whitelist()
def get_expense_claim(
- employee_name, company, employee_advance_name, posting_date, paid_amount, claimed_amount):
- default_payable_account = frappe.get_cached_value('Company', company, "default_payable_account")
- default_cost_center = frappe.get_cached_value('Company', company, 'cost_center')
+ employee_name, company, employee_advance_name, posting_date, paid_amount, claimed_amount
+):
+ default_payable_account = frappe.get_cached_value("Company", company, "default_payable_account")
+ default_cost_center = frappe.get_cached_value("Company", company, "cost_center")
- expense_claim = frappe.new_doc('Expense Claim')
+ expense_claim = frappe.new_doc("Expense Claim")
expense_claim.company = company
expense_claim.employee = employee_name
expense_claim.payable_account = default_payable_account
expense_claim.cost_center = default_cost_center
expense_claim.is_paid = 1 if flt(paid_amount) else 0
expense_claim.append(
- 'advances',
+ "advances",
{
- 'employee_advance': employee_advance_name,
- 'posting_date': posting_date,
- 'advance_paid': flt(paid_amount),
- 'unclaimed_amount': flt(paid_amount) - flt(claimed_amount),
- 'allocated_amount': flt(paid_amount) - flt(claimed_amount)
- }
+ "employee_advance": employee_advance_name,
+ "posting_date": posting_date,
+ "advance_paid": flt(paid_amount),
+ "unclaimed_amount": flt(paid_amount) - flt(claimed_amount),
+ "allocated_amount": flt(paid_amount) - flt(claimed_amount),
+ },
)
return expense_claim
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py b/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py
index 7539c71..8b1acc6 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim_dashboard.py
@@ -3,18 +3,10 @@
def get_data():
return {
- 'fieldname': 'reference_name',
- 'internal_links': {
- 'Employee Advance': ['advances', 'employee_advance']
- },
- 'transactions': [
- {
- 'label': _('Payment'),
- 'items': ['Payment Entry', 'Journal Entry']
- },
- {
- 'label': _('Reference'),
- 'items': ['Employee Advance']
- },
- ]
+ "fieldname": "reference_name",
+ "internal_links": {"Employee Advance": ["advances", "employee_advance"]},
+ "transactions": [
+ {"label": _("Payment"), "items": ["Payment Entry", "Journal Entry"]},
+ {"label": _("Reference"), "items": ["Employee Advance"]},
+ ],
}
diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
index 1244cc4..9b3d53a 100644
--- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
@@ -10,8 +10,8 @@
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.expense_claim.expense_claim import make_bank_entry
-test_dependencies = ['Employee']
-company_name = '_Test Company 3'
+test_dependencies = ["Employee"]
+company_name = "_Test Company 3"
class TestExpenseClaim(unittest.TestCase):
@@ -23,28 +23,26 @@
frappe.db.sql("""delete from `tabProject`""")
frappe.db.sql("update `tabExpense Claim` set project = '', task = ''")
- project = frappe.get_doc({
- "project_name": "_Test Project 1",
- "doctype": "Project"
- })
+ project = frappe.get_doc({"project_name": "_Test Project 1", "doctype": "Project"})
project.save()
- task = frappe.get_doc(dict(
- doctype = 'Task',
- subject = '_Test Project Task 1',
- status = 'Open',
- project = project.name
- )).insert()
+ task = frappe.get_doc(
+ dict(doctype="Task", subject="_Test Project Task 1", status="Open", project=project.name)
+ ).insert()
task_name = task.name
payable_account = get_payable_account(company_name)
- make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC3", project.name, task_name)
+ make_expense_claim(
+ payable_account, 300, 200, company_name, "Travel Expenses - _TC3", project.name, task_name
+ )
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200)
self.assertEqual(frappe.db.get_value("Project", project.name, "total_expense_claim"), 200)
- expense_claim2 = make_expense_claim(payable_account, 600, 500, company_name, "Travel Expenses - _TC3", project.name, task_name)
+ expense_claim2 = make_expense_claim(
+ payable_account, 600, 500, company_name, "Travel Expenses - _TC3", project.name, task_name
+ )
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 700)
self.assertEqual(frappe.db.get_value("Project", project.name, "total_expense_claim"), 700)
@@ -56,7 +54,9 @@
def test_expense_claim_status(self):
payable_account = get_payable_account(company_name)
- expense_claim = make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC3")
+ expense_claim = make_expense_claim(
+ payable_account, 300, 200, company_name, "Travel Expenses - _TC3"
+ )
je_dict = make_bank_entry("Expense Claim", expense_claim.name)
je = frappe.get_doc(je_dict)
@@ -78,7 +78,9 @@
self.assertEqual(claim.status, "Submitted")
# no gl entries created
- gl_entry = frappe.get_all('GL Entry', {'voucher_type': 'Expense Claim', 'voucher_no': claim.name})
+ gl_entry = frappe.get_all(
+ "GL Entry", {"voucher_type": "Expense Claim", "voucher_no": claim.name}
+ )
self.assertEqual(len(gl_entry), 0)
def test_expense_claim_against_fully_paid_advances(self):
@@ -91,7 +93,9 @@
frappe.db.delete("Employee Advance")
payable_account = get_payable_account("_Test Company")
- claim = make_expense_claim(payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+ claim = make_expense_claim(
+ payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True
+ )
advance = make_employee_advance(claim.employee)
pe = make_payment_entry(advance)
@@ -117,10 +121,12 @@
frappe.db.delete("Employee Advance")
payable_account = get_payable_account("_Test Company")
- claim = make_expense_claim(payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+ claim = make_expense_claim(
+ payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True
+ )
# link advance for partial amount
- advance = make_employee_advance(claim.employee, {'advance_amount': 500})
+ advance = make_employee_advance(claim.employee, {"advance_amount": 500})
pe = make_advance_payment(advance)
pe.submit()
@@ -141,21 +147,35 @@
def test_expense_claim_gl_entry(self):
payable_account = get_payable_account(company_name)
taxes = generate_taxes()
- expense_claim = make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC3",
- do_not_submit=True, taxes=taxes)
+ expense_claim = make_expense_claim(
+ payable_account,
+ 300,
+ 200,
+ company_name,
+ "Travel Expenses - _TC3",
+ do_not_submit=True,
+ taxes=taxes,
+ )
expense_claim.submit()
- gl_entries = frappe.db.sql("""select account, debit, credit
+ gl_entries = frappe.db.sql(
+ """select account, debit, credit
from `tabGL Entry` where voucher_type='Expense Claim' and voucher_no=%s
- order by account asc""", expense_claim.name, as_dict=1)
+ order by account asc""",
+ expense_claim.name,
+ as_dict=1,
+ )
self.assertTrue(gl_entries)
- expected_values = dict((d[0], d) for d in [
- ['Output Tax CGST - _TC3',18.0, 0.0],
- [payable_account, 0.0, 218.0],
- ["Travel Expenses - _TC3", 200.0, 0.0]
- ])
+ expected_values = dict(
+ (d[0], d)
+ for d in [
+ ["Output Tax CGST - _TC3", 18.0, 0.0],
+ [payable_account, 0.0, 218.0],
+ ["Travel Expenses - _TC3", 200.0, 0.0],
+ ]
+ )
for gle in gl_entries:
self.assertEqual(expected_values[gle.account][0], gle.account)
@@ -164,20 +184,30 @@
def test_rejected_expense_claim(self):
payable_account = get_payable_account(company_name)
- expense_claim = frappe.get_doc({
- "doctype": "Expense Claim",
- "employee": "_T-Employee-00001",
- "payable_account": payable_account,
- "approval_status": "Rejected",
- "expenses":
- [{"expense_type": "Travel", "default_account": "Travel Expenses - _TC3", "amount": 300, "sanctioned_amount": 200}]
- })
+ expense_claim = frappe.get_doc(
+ {
+ "doctype": "Expense Claim",
+ "employee": "_T-Employee-00001",
+ "payable_account": payable_account,
+ "approval_status": "Rejected",
+ "expenses": [
+ {
+ "expense_type": "Travel",
+ "default_account": "Travel Expenses - _TC3",
+ "amount": 300,
+ "sanctioned_amount": 200,
+ }
+ ],
+ }
+ )
expense_claim.submit()
- self.assertEqual(expense_claim.status, 'Rejected')
+ self.assertEqual(expense_claim.status, "Rejected")
self.assertEqual(expense_claim.total_sanctioned_amount, 0.0)
- gl_entry = frappe.get_all('GL Entry', {'voucher_type': 'Expense Claim', 'voucher_no': expense_claim.name})
+ gl_entry = frappe.get_all(
+ "GL Entry", {"voucher_type": "Expense Claim", "voucher_no": expense_claim.name}
+ )
self.assertEqual(len(gl_entry), 0)
def test_expense_approver_perms(self):
@@ -186,7 +216,9 @@
# check doc shared
payable_account = get_payable_account("_Test Company")
- expense_claim = make_expense_claim(payable_account, 300, 200, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+ expense_claim = make_expense_claim(
+ payable_account, 300, 200, "_Test Company", "Travel Expenses - _TC", do_not_submit=True
+ )
expense_claim.expense_approver = user
expense_claim.save()
self.assertTrue(expense_claim.name in frappe.share.get_shared("Expense Claim", user))
@@ -210,51 +242,76 @@
def test_multiple_payment_entries_against_expense(self):
# Creating expense claim
payable_account = get_payable_account("_Test Company")
- expense_claim = make_expense_claim(payable_account, 5500, 5500, "_Test Company", "Travel Expenses - _TC")
+ expense_claim = make_expense_claim(
+ payable_account, 5500, 5500, "_Test Company", "Travel Expenses - _TC"
+ )
expense_claim.save()
expense_claim.submit()
# Payment entry 1: paying 500
- make_payment_entry(expense_claim, payable_account,500)
- outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(expense_claim)
+ make_payment_entry(expense_claim, payable_account, 500)
+ outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(
+ expense_claim
+ )
self.assertEqual(outstanding_amount, 5000)
self.assertEqual(total_amount_reimbursed, 500)
# Payment entry 1: paying 2000
- make_payment_entry(expense_claim, payable_account,2000)
- outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(expense_claim)
+ make_payment_entry(expense_claim, payable_account, 2000)
+ outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(
+ expense_claim
+ )
self.assertEqual(outstanding_amount, 3000)
self.assertEqual(total_amount_reimbursed, 2500)
# Payment entry 1: paying 3000
- make_payment_entry(expense_claim, payable_account,3000)
- outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(expense_claim)
+ make_payment_entry(expense_claim, payable_account, 3000)
+ outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(
+ expense_claim
+ )
self.assertEqual(outstanding_amount, 0)
self.assertEqual(total_amount_reimbursed, 5500)
def get_payable_account(company):
- return frappe.get_cached_value('Company', company, 'default_payable_account')
+ return frappe.get_cached_value("Company", company, "default_payable_account")
+
def generate_taxes():
- parent_account = frappe.db.get_value('Account',
- {'company': company_name, 'is_group':1, 'account_type': 'Tax'},
- 'name')
- account = create_account(company=company_name, account_name="Output Tax CGST", account_type="Tax", parent_account=parent_account)
- return {'taxes':[{
- "account_head": account,
- "rate": 9,
- "description": "CGST",
- "tax_amount": 10,
- "total": 210
- }]}
+ parent_account = frappe.db.get_value(
+ "Account", {"company": company_name, "is_group": 1, "account_type": "Tax"}, "name"
+ )
+ account = create_account(
+ company=company_name,
+ account_name="Output Tax CGST",
+ account_type="Tax",
+ parent_account=parent_account,
+ )
+ return {
+ "taxes": [
+ {"account_head": account, "rate": 9, "description": "CGST", "tax_amount": 10, "total": 210}
+ ]
+ }
-def make_expense_claim(payable_account, amount, sanctioned_amount, company, account, project=None, task_name=None, do_not_submit=False, taxes=None):
+
+def make_expense_claim(
+ payable_account,
+ amount,
+ sanctioned_amount,
+ company,
+ account,
+ project=None,
+ task_name=None,
+ do_not_submit=False,
+ taxes=None,
+):
employee = frappe.db.get_value("Employee", {"status": "Active"})
if not employee:
employee = make_employee("test_employee@expense_claim.com", company=company)
- currency, cost_center = frappe.db.get_value('Company', company, ['default_currency', 'cost_center'])
+ currency, cost_center = frappe.db.get_value(
+ "Company", company, ["default_currency", "cost_center"]
+ )
expense_claim = {
"doctype": "Expense Claim",
"employee": employee,
@@ -262,14 +319,16 @@
"approval_status": "Approved",
"company": company,
"currency": currency,
- "expenses": [{
- "expense_type": "Travel",
- "default_account": account,
- "currency": currency,
- "amount": amount,
- "sanctioned_amount": sanctioned_amount,
- "cost_center": cost_center
- }]
+ "expenses": [
+ {
+ "expense_type": "Travel",
+ "default_account": account,
+ "currency": currency,
+ "amount": amount,
+ "sanctioned_amount": sanctioned_amount,
+ "cost_center": cost_center,
+ }
+ ],
}
if taxes:
expense_claim.update(taxes)
@@ -286,17 +345,24 @@
expense_claim.submit()
return expense_claim
-def get_outstanding_and_total_reimbursed_amounts(expense_claim):
- outstanding_amount = flt(frappe.db.get_value("Expense Claim", expense_claim.name, "total_sanctioned_amount")) - \
- flt(frappe.db.get_value("Expense Claim", expense_claim.name, "total_amount_reimbursed"))
- total_amount_reimbursed = flt(frappe.db.get_value("Expense Claim", expense_claim.name, "total_amount_reimbursed"))
- return outstanding_amount,total_amount_reimbursed
+def get_outstanding_and_total_reimbursed_amounts(expense_claim):
+ outstanding_amount = flt(
+ frappe.db.get_value("Expense Claim", expense_claim.name, "total_sanctioned_amount")
+ ) - flt(frappe.db.get_value("Expense Claim", expense_claim.name, "total_amount_reimbursed"))
+ total_amount_reimbursed = flt(
+ frappe.db.get_value("Expense Claim", expense_claim.name, "total_amount_reimbursed")
+ )
+
+ return outstanding_amount, total_amount_reimbursed
+
def make_payment_entry(expense_claim, payable_account, amt):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
- pe = get_payment_entry("Expense Claim", expense_claim.name, bank_account="_Test Bank USD - _TC", bank_amount=amt)
+ pe = get_payment_entry(
+ "Expense Claim", expense_claim.name, bank_account="_Test Bank USD - _TC", bank_amount=amt
+ )
pe.reference_no = "1"
pe.reference_date = nowdate()
pe.source_exchange_rate = 1
diff --git a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py
index 570b2c1..6d29f7d 100644
--- a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py
+++ b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.py
@@ -18,12 +18,13 @@
for entry in self.accounts:
accounts_list.append(entry.company)
- if len(accounts_list)!= len(set(accounts_list)):
+ if len(accounts_list) != len(set(accounts_list)):
frappe.throw(_("Same Company is entered more than once"))
def validate_accounts(self):
for entry in self.accounts:
"""Error when Company of Ledger account doesn't match with Company Selected"""
if frappe.db.get_value("Account", entry.default_account, "company") != entry.company:
- frappe.throw(_("Account {0} does not match with Company {1}"
- ).format(entry.default_account, entry.company))
+ frappe.throw(
+ _("Account {0} does not match with Company {1}").format(entry.default_account, entry.company)
+ )
diff --git a/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.py b/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.py
index a2403b6..62348e2 100644
--- a/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.py
+++ b/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.py
@@ -5,5 +5,6 @@
# test_records = frappe.get_test_records('Expense Claim Type')
+
class TestExpenseClaimType(unittest.TestCase):
pass
diff --git a/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.py b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.py
index f539537..8137a0d 100644
--- a/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.py
+++ b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.py
@@ -39,16 +39,20 @@
for data in self.get_assets_movement():
self.append("assets_allocated", data)
else:
- frappe.throw(_("Set Relieving Date for Employee: {0}").format(get_link_to_form("Employee", self.employee)))
+ frappe.throw(
+ _("Set Relieving Date for Employee: {0}").format(get_link_to_form("Employee", self.employee))
+ )
def create_component_row(self, components, component_type):
for component in components:
- self.append(component_type, {
- "status": "Unsettled",
- "reference_document_type": component if component != "Bonus" else "Additional Salary",
- "component": component
- })
-
+ self.append(
+ component_type,
+ {
+ "status": "Unsettled",
+ "reference_document_type": component if component != "Bonus" else "Additional Salary",
+ "component": component,
+ },
+ )
def get_payable_component(self):
return [
@@ -66,13 +70,11 @@
]
def get_assets_movement(self):
- asset_movements = frappe.get_all("Asset Movement Item",
- filters = {"docstatus": 1},
- fields = ["asset", "from_employee", "to_employee", "parent", "asset_name"],
- or_filters = {
- "from_employee": self.employee,
- "to_employee": self.employee
- }
+ asset_movements = frappe.get_all(
+ "Asset Movement Item",
+ filters={"docstatus": 1},
+ fields=["asset", "from_employee", "to_employee", "parent", "asset_name"],
+ or_filters={"from_employee": self.employee, "to_employee": self.employee},
)
data = []
@@ -90,12 +92,14 @@
inwards_counts = [movement.asset for movement in inward_movements].count(movement.asset)
if inwards_counts > outwards_count:
- data.append({
- "reference": movement.parent,
- "asset_name": movement.asset_name,
- "date": frappe.db.get_value("Asset Movement", movement.parent, "transaction_date"),
- "status": "Owned"
- })
+ data.append(
+ {
+ "reference": movement.parent,
+ "asset_name": movement.asset_name,
+ "date": frappe.db.get_value("Asset Movement", movement.parent, "transaction_date"),
+ "status": "Owned",
+ }
+ )
return data
@frappe.whitelist()
@@ -112,7 +116,7 @@
if data.amount > 0 and not data.paid_via_salary_slip:
account_dict = {
"account": data.account,
- "debit_in_account_currency": flt(data.amount, precision)
+ "debit_in_account_currency": flt(data.amount, precision),
}
if data.reference_document_type == "Expense Claim":
account_dict["party_type"] = "Employee"
@@ -124,7 +128,7 @@
if data.amount > 0:
account_dict = {
"account": data.account,
- "credit_in_account_currency": flt(data.amount, precision)
+ "credit_in_account_currency": flt(data.amount, precision),
}
if data.reference_document_type == "Employee Advance":
account_dict["party_type"] = "Employee"
@@ -132,46 +136,67 @@
jv.append("accounts", account_dict)
- jv.append("accounts", {
- "credit_in_account_currency": difference if difference > 0 else 0,
- "debit_in_account_currency": -(difference) if difference < 0 else 0,
- "reference_type": self.doctype,
- "reference_name": self.name
- })
+ jv.append(
+ "accounts",
+ {
+ "credit_in_account_currency": difference if difference > 0 else 0,
+ "debit_in_account_currency": -(difference) if difference < 0 else 0,
+ "reference_type": self.doctype,
+ "reference_name": self.name,
+ },
+ )
return jv
+
@frappe.whitelist()
def get_account_and_amount(ref_doctype, ref_document):
if not ref_doctype or not ref_document:
return None
if ref_doctype == "Salary Slip":
- salary_details = frappe.db.get_value("Salary Slip", ref_document, ["payroll_entry", "net_pay"], as_dict=1)
+ salary_details = frappe.db.get_value(
+ "Salary Slip", ref_document, ["payroll_entry", "net_pay"], as_dict=1
+ )
amount = salary_details.net_pay
- payable_account = frappe.db.get_value("Payroll Entry", salary_details.payroll_entry, "payroll_payable_account") if salary_details.payroll_entry else None
+ payable_account = (
+ frappe.db.get_value("Payroll Entry", salary_details.payroll_entry, "payroll_payable_account")
+ if salary_details.payroll_entry
+ else None
+ )
return [payable_account, amount]
if ref_doctype == "Gratuity":
- payable_account, amount = frappe.db.get_value("Gratuity", ref_document, ["payable_account", "amount"])
+ payable_account, amount = frappe.db.get_value(
+ "Gratuity", ref_document, ["payable_account", "amount"]
+ )
return [payable_account, amount]
if ref_doctype == "Expense Claim":
- details = frappe.db.get_value("Expense Claim", ref_document,
- ["payable_account", "grand_total", "total_amount_reimbursed", "total_advance_amount"], as_dict=True)
+ details = frappe.db.get_value(
+ "Expense Claim",
+ ref_document,
+ ["payable_account", "grand_total", "total_amount_reimbursed", "total_advance_amount"],
+ as_dict=True,
+ )
payable_account = details.payable_account
amount = details.grand_total - (details.total_amount_reimbursed + details.total_advance_amount)
return [payable_account, amount]
if ref_doctype == "Loan":
- details = frappe.db.get_value("Loan", ref_document,
- ["payment_account", "total_payment", "total_amount_paid"], as_dict=1)
+ details = frappe.db.get_value(
+ "Loan", ref_document, ["payment_account", "total_payment", "total_amount_paid"], as_dict=1
+ )
payment_account = details.payment_account
amount = details.total_payment - details.total_amount_paid
return [payment_account, amount]
if ref_doctype == "Employee Advance":
- details = frappe.db.get_value("Employee Advance", ref_document,
- ["advance_account","paid_amount", "claimed_amount", "return_amount"], as_dict = 1)
+ details = frappe.db.get_value(
+ "Employee Advance",
+ ref_document,
+ ["advance_account", "paid_amount", "claimed_amount", "return_amount"],
+ as_dict=1,
+ )
payment_account = details.advance_account
amount = details.paid_amount - (details.claimed_amount + details.return_amount)
return [payment_account, amount]
diff --git a/erpnext/hr/doctype/full_and_final_statement/test_full_and_final_statement.py b/erpnext/hr/doctype/full_and_final_statement/test_full_and_final_statement.py
index f6c1d15..8c6723f 100644
--- a/erpnext/hr/doctype/full_and_final_statement/test_full_and_final_statement.py
+++ b/erpnext/hr/doctype/full_and_final_statement/test_full_and_final_statement.py
@@ -12,7 +12,6 @@
class TestFullandFinalStatement(unittest.TestCase):
-
def setUp(self):
create_asset_data()
@@ -27,18 +26,26 @@
frappe.db.set_value("Employee", employee, "relieving_date", add_days(today(), 30))
fnf = create_full_and_final_statement(employee)
- payables_bootstraped_component = ["Salary Slip", "Gratuity",
- "Expense Claim", "Bonus", "Leave Encashment"]
+ payables_bootstraped_component = [
+ "Salary Slip",
+ "Gratuity",
+ "Expense Claim",
+ "Bonus",
+ "Leave Encashment",
+ ]
receivable_bootstraped_component = ["Loan", "Employee Advance"]
- #checking payable s and receivables bootstraped value
+ # checking payable s and receivables bootstraped value
self.assertEqual([payable.component for payable in fnf.payables], payables_bootstraped_component)
- self.assertEqual([receivable.component for receivable in fnf.receivables], receivable_bootstraped_component)
+ self.assertEqual(
+ [receivable.component for receivable in fnf.receivables], receivable_bootstraped_component
+ )
- #checking allocated asset
+ # checking allocated asset
self.assertIn(movement, [asset.reference for asset in fnf.assets_allocated])
+
def create_full_and_final_statement(employee):
fnf = frappe.new_doc("Full and Final Statement")
fnf.employee = employee
@@ -46,6 +53,7 @@
fnf.save()
return fnf
+
def create_asset_movement(employee):
asset_name = create_asset()
movement = frappe.new_doc("Asset Movement")
@@ -53,18 +61,17 @@
movement.purpose = "Issue"
movement.transaction_date = today()
- movement.append("assets", {
- "asset": asset_name,
- "to_employee": employee
- })
+ movement.append("assets", {"asset": asset_name, "to_employee": employee})
movement.save()
movement.submit()
return movement.name
+
def create_asset():
- pr = make_purchase_receipt(item_code="Macbook Pro",
- qty=1, rate=100000.0, location="Test Location")
+ pr = make_purchase_receipt(
+ item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location"
+ )
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
asset = frappe.get_doc("Asset", asset_name)
diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py
index a8c8c16..fad827a 100644
--- a/erpnext/hr/doctype/holiday_list/holiday_list.py
+++ b/erpnext/hr/doctype/holiday_list/holiday_list.py
@@ -10,7 +10,9 @@
from frappe.utils import cint, formatdate, getdate, today
-class OverlapError(frappe.ValidationError): pass
+class OverlapError(frappe.ValidationError):
+ pass
+
class HolidayList(Document):
def validate(self):
@@ -21,9 +23,14 @@
def get_weekly_off_dates(self):
self.validate_values()
date_list = self.get_weekly_off_date_list(self.from_date, self.to_date)
- last_idx = max([cint(d.idx) for d in self.get("holidays")] or [0,])
+ last_idx = max(
+ [cint(d.idx) for d in self.get("holidays")]
+ or [
+ 0,
+ ]
+ )
for i, d in enumerate(date_list):
- ch = self.append('holidays', {})
+ ch = self.append("holidays", {})
ch.description = _(self.weekly_off)
ch.holiday_date = d
ch.weekly_off = 1
@@ -33,14 +40,17 @@
if not self.weekly_off:
throw(_("Please select weekly off day"))
-
def validate_days(self):
if getdate(self.from_date) > getdate(self.to_date):
throw(_("To Date cannot be before From Date"))
for day in self.get("holidays"):
if not (getdate(self.from_date) <= getdate(day.holiday_date) <= getdate(self.to_date)):
- frappe.throw(_("The holiday on {0} is not between From Date and To Date").format(formatdate(day.holiday_date)))
+ frappe.throw(
+ _("The holiday on {0} is not between From Date and To Date").format(
+ formatdate(day.holiday_date)
+ )
+ )
def get_weekly_off_date_list(self, start_date, end_date):
start_date, end_date = getdate(start_date), getdate(end_date)
@@ -66,7 +76,8 @@
@frappe.whitelist()
def clear_table(self):
- self.set('holidays', [])
+ self.set("holidays", [])
+
@frappe.whitelist()
def get_events(start, end, filters=None):
@@ -82,23 +93,28 @@
filters = []
if start:
- filters.append(['Holiday', 'holiday_date', '>', getdate(start)])
+ filters.append(["Holiday", "holiday_date", ">", getdate(start)])
if end:
- filters.append(['Holiday', 'holiday_date', '<', getdate(end)])
+ filters.append(["Holiday", "holiday_date", "<", getdate(end)])
- return frappe.get_list('Holiday List',
- fields=['name', '`tabHoliday`.holiday_date', '`tabHoliday`.description', '`tabHoliday List`.color'],
- filters = filters,
- update={"allDay": 1})
+ return frappe.get_list(
+ "Holiday List",
+ fields=[
+ "name",
+ "`tabHoliday`.holiday_date",
+ "`tabHoliday`.description",
+ "`tabHoliday List`.color",
+ ],
+ filters=filters,
+ update={"allDay": 1},
+ )
def is_holiday(holiday_list, date=None):
- """Returns true if the given date is a holiday in the given holiday list
- """
+ """Returns true if the given date is a holiday in the given holiday list"""
if date is None:
date = today()
if holiday_list:
- return bool(frappe.get_all('Holiday List',
- dict(name=holiday_list, holiday_date=date)))
+ return bool(frappe.get_all("Holiday List", dict(name=holiday_list, holiday_date=date)))
else:
return False
diff --git a/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py b/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py
index 4a540ce..0cbf094 100644
--- a/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py
+++ b/erpnext/hr/doctype/holiday_list/holiday_list_dashboard.py
@@ -1,19 +1,15 @@
def get_data():
return {
- 'fieldname': 'holiday_list',
- 'non_standard_fieldnames': {
- 'Company': 'default_holiday_list',
- 'Leave Period': 'optional_holiday_list'
+ "fieldname": "holiday_list",
+ "non_standard_fieldnames": {
+ "Company": "default_holiday_list",
+ "Leave Period": "optional_holiday_list",
},
- 'transactions': [
+ "transactions": [
{
- 'items': ['Company', 'Employee', 'Workstation'],
+ "items": ["Company", "Employee", "Workstation"],
},
- {
- 'items': ['Leave Period', 'Shift Type']
- },
- {
- 'items': ['Service Level', 'Service Level Agreement']
- }
- ]
+ {"items": ["Leave Period", "Shift Type"]},
+ {"items": ["Service Level", "Service Level Agreement"]},
+ ],
}
diff --git a/erpnext/hr/doctype/holiday_list/test_holiday_list.py b/erpnext/hr/doctype/holiday_list/test_holiday_list.py
index aed901a..d32cfe8 100644
--- a/erpnext/hr/doctype/holiday_list/test_holiday_list.py
+++ b/erpnext/hr/doctype/holiday_list/test_holiday_list.py
@@ -12,24 +12,31 @@
class TestHolidayList(unittest.TestCase):
def test_holiday_list(self):
today_date = getdate()
- test_holiday_dates = [today_date-timedelta(days=5), today_date-timedelta(days=4)]
- holiday_list = make_holiday_list("test_holiday_list",
+ test_holiday_dates = [today_date - timedelta(days=5), today_date - timedelta(days=4)]
+ holiday_list = make_holiday_list(
+ "test_holiday_list",
holiday_dates=[
- {'holiday_date': test_holiday_dates[0], 'description': 'test holiday'},
- {'holiday_date': test_holiday_dates[1], 'description': 'test holiday2'}
- ])
- fetched_holiday_list = frappe.get_value('Holiday List', holiday_list.name)
+ {"holiday_date": test_holiday_dates[0], "description": "test holiday"},
+ {"holiday_date": test_holiday_dates[1], "description": "test holiday2"},
+ ],
+ )
+ fetched_holiday_list = frappe.get_value("Holiday List", holiday_list.name)
self.assertEqual(holiday_list.name, fetched_holiday_list)
-def make_holiday_list(name, from_date=getdate()-timedelta(days=10), to_date=getdate(), holiday_dates=None):
+
+def make_holiday_list(
+ name, from_date=getdate() - timedelta(days=10), to_date=getdate(), holiday_dates=None
+):
frappe.delete_doc_if_exists("Holiday List", name, force=1)
- doc = frappe.get_doc({
- "doctype": "Holiday List",
- "holiday_list_name": name,
- "from_date" : from_date,
- "to_date" : to_date,
- "holidays" : holiday_dates
- }).insert()
+ doc = frappe.get_doc(
+ {
+ "doctype": "Holiday List",
+ "holiday_list_name": name,
+ "from_date": from_date,
+ "to_date": to_date,
+ "holidays": holiday_dates,
+ }
+ ).insert()
return doc
@@ -39,7 +46,7 @@
Context manager for setting holiday list in tests
"""
try:
- company = frappe.get_doc('Company', company_name)
+ company = frappe.get_doc("Company", company_name)
previous_holiday_list = company.default_holiday_list
company.default_holiday_list = holiday_list
@@ -49,6 +56,6 @@
finally:
# restore holiday list setup
- company = frappe.get_doc('Company', company_name)
+ company = frappe.get_doc("Company", company_name)
company.default_holiday_list = previous_holiday_list
company.save()
diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.py b/erpnext/hr/doctype/hr_settings/hr_settings.py
index c295bcb..72a49e2 100644
--- a/erpnext/hr/doctype/hr_settings/hr_settings.py
+++ b/erpnext/hr/doctype/hr_settings/hr_settings.py
@@ -10,6 +10,7 @@
# Wether to proceed with frequency change
PROCEED_WITH_FREQUENCY_CHANGE = False
+
class HRSettings(Document):
def validate(self):
self.set_naming_series()
@@ -22,21 +23,24 @@
def set_naming_series(self):
from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series
- set_by_naming_series("Employee", "employee_number",
- self.get("emp_created_by")=="Naming Series", hide_name_field=True)
+
+ set_by_naming_series(
+ "Employee",
+ "employee_number",
+ self.get("emp_created_by") == "Naming Series",
+ hide_name_field=True,
+ )
def validate_frequency_change(self):
weekly_job, monthly_job = None, None
try:
weekly_job = frappe.get_doc(
- 'Scheduled Job Type',
- 'employee_reminders.send_reminders_in_advance_weekly'
+ "Scheduled Job Type", "employee_reminders.send_reminders_in_advance_weekly"
)
monthly_job = frappe.get_doc(
- 'Scheduled Job Type',
- 'employee_reminders.send_reminders_in_advance_monthly'
+ "Scheduled Job Type", "employee_reminders.send_reminders_in_advance_monthly"
)
except frappe.DoesNotExistError:
return
@@ -62,17 +66,20 @@
from_date = frappe.bold(format_date(from_date))
to_date = frappe.bold(format_date(to_date))
frappe.msgprint(
- msg=frappe._('Employees will miss holiday reminders from {} until {}. <br> Do you want to proceed with this change?').format(from_date, to_date),
- title='Confirm change in Frequency',
+ msg=frappe._(
+ "Employees will miss holiday reminders from {} until {}. <br> Do you want to proceed with this change?"
+ ).format(from_date, to_date),
+ title="Confirm change in Frequency",
primary_action={
- 'label': frappe._('Yes, Proceed'),
- 'client_action': 'erpnext.proceed_save_with_reminders_frequency_change'
+ "label": frappe._("Yes, Proceed"),
+ "client_action": "erpnext.proceed_save_with_reminders_frequency_change",
},
- raise_exception=frappe.ValidationError
+ raise_exception=frappe.ValidationError,
)
+
@frappe.whitelist()
def set_proceed_with_frequency_change():
- '''Enables proceed with frequency change'''
+ """Enables proceed with frequency change"""
global PROCEED_WITH_FREQUENCY_CHANGE
PROCEED_WITH_FREQUENCY_CHANGE = True
diff --git a/erpnext/hr/doctype/interest/test_interest.py b/erpnext/hr/doctype/interest/test_interest.py
index d4ecd9b..eacb57f 100644
--- a/erpnext/hr/doctype/interest/test_interest.py
+++ b/erpnext/hr/doctype/interest/test_interest.py
@@ -5,5 +5,6 @@
# test_records = frappe.get_test_records('Interest')
+
class TestInterest(unittest.TestCase):
pass
diff --git a/erpnext/hr/doctype/interview/interview.py b/erpnext/hr/doctype/interview/interview.py
index a3b111c..a6e9af2 100644
--- a/erpnext/hr/doctype/interview/interview.py
+++ b/erpnext/hr/doctype/interview/interview.py
@@ -13,6 +13,7 @@
class DuplicateInterviewRoundError(frappe.ValidationError):
pass
+
class Interview(Document):
def validate(self):
self.validate_duplicate_interview()
@@ -21,37 +22,47 @@
self.set_average_rating()
def on_submit(self):
- if self.status not in ['Cleared', 'Rejected']:
- frappe.throw(_('Only Interviews with Cleared or Rejected status can be submitted.'), title=_('Not Allowed'))
+ if self.status not in ["Cleared", "Rejected"]:
+ frappe.throw(
+ _("Only Interviews with Cleared or Rejected status can be submitted."), title=_("Not Allowed")
+ )
def validate_duplicate_interview(self):
- duplicate_interview = frappe.db.exists('Interview', {
- 'job_applicant': self.job_applicant,
- 'interview_round': self.interview_round,
- 'docstatus': 1
- }
+ duplicate_interview = frappe.db.exists(
+ "Interview",
+ {"job_applicant": self.job_applicant, "interview_round": self.interview_round, "docstatus": 1},
)
if duplicate_interview:
- frappe.throw(_('Job Applicants are not allowed to appear twice for the same Interview round. Interview {0} already scheduled for Job Applicant {1}').format(
- frappe.bold(get_link_to_form('Interview', duplicate_interview)),
- frappe.bold(self.job_applicant)
- ))
+ frappe.throw(
+ _(
+ "Job Applicants are not allowed to appear twice for the same Interview round. Interview {0} already scheduled for Job Applicant {1}"
+ ).format(
+ frappe.bold(get_link_to_form("Interview", duplicate_interview)),
+ frappe.bold(self.job_applicant),
+ )
+ )
def validate_designation(self):
- applicant_designation = frappe.db.get_value('Job Applicant', self.job_applicant, 'designation')
- if self.designation :
+ applicant_designation = frappe.db.get_value("Job Applicant", self.job_applicant, "designation")
+ if self.designation:
if self.designation != applicant_designation:
- frappe.throw(_('Interview Round {0} is only for Designation {1}. Job Applicant has applied for the role {2}').format(
- self.interview_round, frappe.bold(self.designation), applicant_designation),
- exc=DuplicateInterviewRoundError)
+ frappe.throw(
+ _(
+ "Interview Round {0} is only for Designation {1}. Job Applicant has applied for the role {2}"
+ ).format(
+ self.interview_round, frappe.bold(self.designation), applicant_designation
+ ),
+ exc=DuplicateInterviewRoundError,
+ )
else:
self.designation = applicant_designation
def validate_overlap(self):
- interviewers = [entry.interviewer for entry in self.interview_details] or ['']
+ interviewers = [entry.interviewer for entry in self.interview_details] or [""]
- overlaps = frappe.db.sql("""
+ overlaps = frappe.db.sql(
+ """
SELECT interview.name
FROM `tabInterview` as interview
INNER JOIN `tabInterview Detail` as detail
@@ -61,12 +72,25 @@
((from_time < %s and to_time > %s) or
(from_time > %s and to_time < %s) or
(from_time = %s))
- """, (self.scheduled_on, self.name, self.job_applicant, interviewers,
- self.from_time, self.to_time, self.from_time, self.to_time, self.from_time))
+ """,
+ (
+ self.scheduled_on,
+ self.name,
+ self.job_applicant,
+ interviewers,
+ self.from_time,
+ self.to_time,
+ self.from_time,
+ self.to_time,
+ self.from_time,
+ ),
+ )
if overlaps:
- overlapping_details = _('Interview overlaps with {0}').format(get_link_to_form('Interview', overlaps[0][0]))
- frappe.throw(overlapping_details, title=_('Overlap'))
+ overlapping_details = _("Interview overlaps with {0}").format(
+ get_link_to_form("Interview", overlaps[0][0])
+ )
+ frappe.throw(overlapping_details, title=_("Overlap"))
def set_average_rating(self):
total_rating = 0
@@ -74,7 +98,9 @@
if entry.average_rating:
total_rating += entry.average_rating
- self.average_rating = flt(total_rating / len(self.interview_details) if len(self.interview_details) else 0)
+ self.average_rating = flt(
+ total_rating / len(self.interview_details) if len(self.interview_details) else 0
+ )
@frappe.whitelist()
def reschedule_interview(self, scheduled_on, from_time, to_time):
@@ -82,137 +108,155 @@
from_time = self.from_time
to_time = self.to_time
- self.db_set({
- 'scheduled_on': scheduled_on,
- 'from_time': from_time,
- 'to_time': to_time
- })
+ self.db_set({"scheduled_on": scheduled_on, "from_time": from_time, "to_time": to_time})
self.notify_update()
recipients = get_recipients(self.name)
try:
frappe.sendmail(
- recipients= recipients,
- subject=_('Interview: {0} Rescheduled').format(self.name),
- message=_('Your Interview session is rescheduled from {0} {1} - {2} to {3} {4} - {5}').format(
- original_date, from_time, to_time, self.scheduled_on, self.from_time, self.to_time),
+ recipients=recipients,
+ subject=_("Interview: {0} Rescheduled").format(self.name),
+ message=_("Your Interview session is rescheduled from {0} {1} - {2} to {3} {4} - {5}").format(
+ original_date, from_time, to_time, self.scheduled_on, self.from_time, self.to_time
+ ),
reference_doctype=self.doctype,
- reference_name=self.name
+ reference_name=self.name,
)
except Exception:
- frappe.msgprint(_('Failed to send the Interview Reschedule notification. Please configure your email account.'))
+ frappe.msgprint(
+ _("Failed to send the Interview Reschedule notification. Please configure your email account.")
+ )
- frappe.msgprint(_('Interview Rescheduled successfully'), indicator='green')
+ frappe.msgprint(_("Interview Rescheduled successfully"), indicator="green")
def get_recipients(name, for_feedback=0):
- interview = frappe.get_doc('Interview', name)
+ interview = frappe.get_doc("Interview", name)
if for_feedback:
recipients = [d.interviewer for d in interview.interview_details if not d.interview_feedback]
else:
recipients = [d.interviewer for d in interview.interview_details]
- recipients.append(frappe.db.get_value('Job Applicant', interview.job_applicant, 'email_id'))
+ recipients.append(frappe.db.get_value("Job Applicant", interview.job_applicant, "email_id"))
return recipients
@frappe.whitelist()
def get_interviewers(interview_round):
- return frappe.get_all('Interviewer', filters={'parent': interview_round}, fields=['user as interviewer'])
+ return frappe.get_all(
+ "Interviewer", filters={"parent": interview_round}, fields=["user as interviewer"]
+ )
def send_interview_reminder():
- reminder_settings = frappe.db.get_value('HR Settings', 'HR Settings',
- ['send_interview_reminder', 'interview_reminder_template'], as_dict=True)
+ reminder_settings = frappe.db.get_value(
+ "HR Settings",
+ "HR Settings",
+ ["send_interview_reminder", "interview_reminder_template"],
+ as_dict=True,
+ )
if not reminder_settings.send_interview_reminder:
return
- remind_before = cstr(frappe.db.get_single_value('HR Settings', 'remind_before')) or '01:00:00'
- remind_before = datetime.datetime.strptime(remind_before, '%H:%M:%S')
+ remind_before = cstr(frappe.db.get_single_value("HR Settings", "remind_before")) or "01:00:00"
+ remind_before = datetime.datetime.strptime(remind_before, "%H:%M:%S")
reminder_date_time = datetime.datetime.now() + datetime.timedelta(
- hours=remind_before.hour, minutes=remind_before.minute, seconds=remind_before.second)
+ hours=remind_before.hour, minutes=remind_before.minute, seconds=remind_before.second
+ )
- interviews = frappe.get_all('Interview', filters={
- 'scheduled_on': ['between', (datetime.datetime.now(), reminder_date_time)],
- 'status': 'Pending',
- 'reminded': 0,
- 'docstatus': ['!=', 2]
- })
+ interviews = frappe.get_all(
+ "Interview",
+ filters={
+ "scheduled_on": ["between", (datetime.datetime.now(), reminder_date_time)],
+ "status": "Pending",
+ "reminded": 0,
+ "docstatus": ["!=", 2],
+ },
+ )
- interview_template = frappe.get_doc('Email Template', reminder_settings.interview_reminder_template)
+ interview_template = frappe.get_doc(
+ "Email Template", reminder_settings.interview_reminder_template
+ )
for d in interviews:
- doc = frappe.get_doc('Interview', d.name)
+ doc = frappe.get_doc("Interview", d.name)
context = doc.as_dict()
message = frappe.render_template(interview_template.response, context)
recipients = get_recipients(doc.name)
frappe.sendmail(
- recipients= recipients,
+ recipients=recipients,
subject=interview_template.subject,
message=message,
reference_doctype=doc.doctype,
- reference_name=doc.name
+ reference_name=doc.name,
)
- doc.db_set('reminded', 1)
+ doc.db_set("reminded", 1)
def send_daily_feedback_reminder():
- reminder_settings = frappe.db.get_value('HR Settings', 'HR Settings',
- ['send_interview_feedback_reminder', 'feedback_reminder_notification_template'], as_dict=True)
+ reminder_settings = frappe.db.get_value(
+ "HR Settings",
+ "HR Settings",
+ ["send_interview_feedback_reminder", "feedback_reminder_notification_template"],
+ as_dict=True,
+ )
if not reminder_settings.send_interview_feedback_reminder:
return
- interview_feedback_template = frappe.get_doc('Email Template', reminder_settings.feedback_reminder_notification_template)
- interviews = frappe.get_all('Interview', filters={'status': ['in', ['Under Review', 'Pending']], 'docstatus': ['!=', 2]})
+ interview_feedback_template = frappe.get_doc(
+ "Email Template", reminder_settings.feedback_reminder_notification_template
+ )
+ interviews = frappe.get_all(
+ "Interview", filters={"status": ["in", ["Under Review", "Pending"]], "docstatus": ["!=", 2]}
+ )
for entry in interviews:
recipients = get_recipients(entry.name, for_feedback=1)
- doc = frappe.get_doc('Interview', entry.name)
+ doc = frappe.get_doc("Interview", entry.name)
context = doc.as_dict()
message = frappe.render_template(interview_feedback_template.response, context)
if len(recipients):
frappe.sendmail(
- recipients= recipients,
+ recipients=recipients,
subject=interview_feedback_template.subject,
message=message,
- reference_doctype='Interview',
- reference_name=entry.name
+ reference_doctype="Interview",
+ reference_name=entry.name,
)
@frappe.whitelist()
def get_expected_skill_set(interview_round):
- return frappe.get_all('Expected Skill Set', filters ={'parent': interview_round}, fields=['skill'])
+ return frappe.get_all("Expected Skill Set", filters={"parent": interview_round}, fields=["skill"])
@frappe.whitelist()
def create_interview_feedback(data, interview_name, interviewer, job_applicant):
import json
-
if isinstance(data, str):
data = frappe._dict(json.loads(data))
if frappe.session.user != interviewer:
- frappe.throw(_('Only Interviewer Are allowed to submit Interview Feedback'))
+ frappe.throw(_("Only Interviewer Are allowed to submit Interview Feedback"))
- interview_feedback = frappe.new_doc('Interview Feedback')
+ interview_feedback = frappe.new_doc("Interview Feedback")
interview_feedback.interview = interview_name
interview_feedback.interviewer = interviewer
interview_feedback.job_applicant = job_applicant
for d in data.skill_set:
d = frappe._dict(d)
- interview_feedback.append('skill_assessment', {'skill': d.skill, 'rating': d.rating})
+ interview_feedback.append("skill_assessment", {"skill": d.skill, "rating": d.rating})
interview_feedback.feedback = data.feedback
interview_feedback.result = data.result
@@ -220,24 +264,33 @@
interview_feedback.save()
interview_feedback.submit()
- frappe.msgprint(_('Interview Feedback {0} submitted successfully').format(
- get_link_to_form('Interview Feedback', interview_feedback.name)))
+ frappe.msgprint(
+ _("Interview Feedback {0} submitted successfully").format(
+ get_link_to_form("Interview Feedback", interview_feedback.name)
+ )
+ )
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_interviewer_list(doctype, txt, searchfield, start, page_len, filters):
filters = [
- ['Has Role', 'parent', 'like', '%{}%'.format(txt)],
- ['Has Role', 'role', '=', 'interviewer'],
- ['Has Role', 'parenttype', '=', 'User']
+ ["Has Role", "parent", "like", "%{}%".format(txt)],
+ ["Has Role", "role", "=", "interviewer"],
+ ["Has Role", "parenttype", "=", "User"],
]
if filters and isinstance(filters, list):
filters.extend(filters)
- return frappe.get_all('Has Role', limit_start=start, limit_page_length=page_len,
- filters=filters, fields = ['parent'], as_list=1)
+ return frappe.get_all(
+ "Has Role",
+ limit_start=start,
+ limit_page_length=page_len,
+ filters=filters,
+ fields=["parent"],
+ as_list=1,
+ )
@frappe.whitelist()
@@ -256,12 +309,13 @@
"Pending": "#fff4f0",
"Under Review": "#d3e8fc",
"Cleared": "#eaf5ed",
- "Rejected": "#fce7e7"
+ "Rejected": "#fce7e7",
}
- conditions = get_event_conditions('Interview', filters)
+ conditions = get_event_conditions("Interview", filters)
- interviews = frappe.db.sql("""
+ interviews = frappe.db.sql(
+ """
SELECT DISTINCT
`tabInterview`.name, `tabInterview`.job_applicant, `tabInterview`.interview_round,
`tabInterview`.scheduled_on, `tabInterview`.status, `tabInterview`.from_time as from_time,
@@ -272,10 +326,13 @@
(`tabInterview`.scheduled_on between %(start)s and %(end)s)
and docstatus != 2
{conditions}
- """.format(conditions=conditions), {
- "start": start,
- "end": end
- }, as_dict=True, update={"allDay": 0})
+ """.format(
+ conditions=conditions
+ ),
+ {"start": start, "end": end},
+ as_dict=True,
+ update={"allDay": 0},
+ )
for d in interviews:
subject_data = []
@@ -286,11 +343,11 @@
color = event_color.get(d.status)
interview_data = {
- 'from': get_datetime('%s %s' % (d.scheduled_on, d.from_time or '00:00:00')),
- 'to': get_datetime('%s %s' % (d.scheduled_on, d.to_time or '00:00:00')),
- 'name': d.name,
- 'subject': '\n'.join(subject_data),
- 'color': color if color else "#89bcde"
+ "from": get_datetime("%s %s" % (d.scheduled_on, d.from_time or "00:00:00")),
+ "to": get_datetime("%s %s" % (d.scheduled_on, d.to_time or "00:00:00")),
+ "name": d.name,
+ "subject": "\n".join(subject_data),
+ "color": color if color else "#89bcde",
}
events.append(interview_data)
diff --git a/erpnext/hr/doctype/interview/test_interview.py b/erpnext/hr/doctype/interview/test_interview.py
index fdb11af..ae8493a 100644
--- a/erpnext/hr/doctype/interview/test_interview.py
+++ b/erpnext/hr/doctype/interview/test_interview.py
@@ -19,23 +19,30 @@
class TestInterview(unittest.TestCase):
def test_validations_for_designation(self):
job_applicant = create_job_applicant()
- interview = create_interview_and_dependencies(job_applicant.name, designation='_Test_Sales_manager', save=0)
+ interview = create_interview_and_dependencies(
+ job_applicant.name, designation="_Test_Sales_manager", save=0
+ )
self.assertRaises(DuplicateInterviewRoundError, interview.save)
def test_notification_on_rescheduling(self):
job_applicant = create_job_applicant()
- interview = create_interview_and_dependencies(job_applicant.name, scheduled_on=add_days(getdate(), -4))
+ interview = create_interview_and_dependencies(
+ job_applicant.name, scheduled_on=add_days(getdate(), -4)
+ )
previous_scheduled_date = interview.scheduled_on
frappe.db.sql("DELETE FROM `tabEmail Queue`")
- interview.reschedule_interview(add_days(getdate(previous_scheduled_date), 2),
- from_time=nowtime(), to_time=nowtime())
+ interview.reschedule_interview(
+ add_days(getdate(previous_scheduled_date), 2), from_time=nowtime(), to_time=nowtime()
+ )
interview.reload()
self.assertEqual(interview.scheduled_on, add_days(getdate(previous_scheduled_date), 2))
- notification = frappe.get_all("Email Queue", filters={"message": ("like", "%Your Interview session is rescheduled from%")})
+ notification = frappe.get_all(
+ "Email Queue", filters={"message": ("like", "%Your Interview session is rescheduled from%")}
+ )
self.assertIsNotNone(notification)
def test_notification_for_scheduling(self):
@@ -76,29 +83,33 @@
interview = create_interview_and_dependencies(job_applicant.name)
details = get_interview_details(job_applicant.name)
- self.assertEqual(details.get('stars'), 5)
- self.assertEqual(details.get('interviews').get(interview.name), {
- 'name': interview.name,
- 'interview_round': interview.interview_round,
- 'expected_average_rating': interview.expected_average_rating * 5,
- 'average_rating': interview.average_rating * 5,
- 'status': 'Pending'
- })
+ self.assertEqual(details.get("stars"), 5)
+ self.assertEqual(
+ details.get("interviews").get(interview.name),
+ {
+ "name": interview.name,
+ "interview_round": interview.interview_round,
+ "expected_average_rating": interview.expected_average_rating * 5,
+ "average_rating": interview.average_rating * 5,
+ "status": "Pending",
+ },
+ )
def tearDown(self):
frappe.db.rollback()
-def create_interview_and_dependencies(job_applicant, scheduled_on=None, from_time=None, to_time=None, designation=None, save=1):
+def create_interview_and_dependencies(
+ job_applicant, scheduled_on=None, from_time=None, to_time=None, designation=None, save=1
+):
if designation:
- designation=create_designation(designation_name = "_Test_Sales_manager").name
+ designation = create_designation(designation_name="_Test_Sales_manager").name
interviewer_1 = create_user("test_interviewer1@example.com", "Interviewer")
interviewer_2 = create_user("test_interviewer2@example.com", "Interviewer")
interview_round = create_interview_round(
- "Technical Round", ["Python", "JS"],
- designation=designation, save=True
+ "Technical Round", ["Python", "JS"], designation=designation, save=True
)
interview = frappe.new_doc("Interview")
@@ -116,6 +127,7 @@
return interview
+
def create_interview_round(name, skill_set, interviewers=[], designation=None, save=True):
create_skill_set(skill_set)
interview_round = frappe.new_doc("Interview Round")
@@ -130,15 +142,14 @@
interview_round.append("expected_skill_set", {"skill": skill})
for interviewer in interviewers:
- interview_round.append("interviewer", {
- "user": interviewer
- })
+ interview_round.append("interviewer", {"user": interviewer})
if save:
interview_round.save()
return interview_round
+
def create_skill_set(skill_set):
for skill in skill_set:
if not frappe.db.exists("Skill", skill):
@@ -146,6 +157,7 @@
doc.skill_name = skill
doc.save()
+
def create_interview_type(name="test_interview_type"):
if frappe.db.exists("Interview Type", name):
return frappe.get_doc("Interview Type", name).name
@@ -157,32 +169,41 @@
return doc.name
+
def setup_reminder_settings():
- if not frappe.db.exists('Email Template', _('Interview Reminder')):
- base_path = frappe.get_app_path('erpnext', 'hr', 'doctype')
- response = frappe.read_file(os.path.join(base_path, 'interview/interview_reminder_notification_template.html'))
+ if not frappe.db.exists("Email Template", _("Interview Reminder")):
+ base_path = frappe.get_app_path("erpnext", "hr", "doctype")
+ response = frappe.read_file(
+ os.path.join(base_path, "interview/interview_reminder_notification_template.html")
+ )
- frappe.get_doc({
- 'doctype': 'Email Template',
- 'name': _('Interview Reminder'),
- 'response': response,
- 'subject': _('Interview Reminder'),
- 'owner': frappe.session.user,
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Email Template",
+ "name": _("Interview Reminder"),
+ "response": response,
+ "subject": _("Interview Reminder"),
+ "owner": frappe.session.user,
+ }
+ ).insert(ignore_permissions=True)
- if not frappe.db.exists('Email Template', _('Interview Feedback Reminder')):
- base_path = frappe.get_app_path('erpnext', 'hr', 'doctype')
- response = frappe.read_file(os.path.join(base_path, 'interview/interview_feedback_reminder_template.html'))
+ if not frappe.db.exists("Email Template", _("Interview Feedback Reminder")):
+ base_path = frappe.get_app_path("erpnext", "hr", "doctype")
+ response = frappe.read_file(
+ os.path.join(base_path, "interview/interview_feedback_reminder_template.html")
+ )
- frappe.get_doc({
- 'doctype': 'Email Template',
- 'name': _('Interview Feedback Reminder'),
- 'response': response,
- 'subject': _('Interview Feedback Reminder'),
- 'owner': frappe.session.user,
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Email Template",
+ "name": _("Interview Feedback Reminder"),
+ "response": response,
+ "subject": _("Interview Feedback Reminder"),
+ "owner": frappe.session.user,
+ }
+ ).insert(ignore_permissions=True)
- hr_settings = frappe.get_doc('HR Settings')
- hr_settings.interview_reminder_template = _('Interview Reminder')
- hr_settings.feedback_reminder_notification_template = _('Interview Feedback Reminder')
+ hr_settings = frappe.get_doc("HR Settings")
+ hr_settings.interview_reminder_template = _("Interview Reminder")
+ hr_settings.feedback_reminder_notification_template = _("Interview Feedback Reminder")
hr_settings.save()
diff --git a/erpnext/hr/doctype/interview_feedback/interview_feedback.py b/erpnext/hr/doctype/interview_feedback/interview_feedback.py
index 2ff00c1..5bb498f 100644
--- a/erpnext/hr/doctype/interview_feedback/interview_feedback.py
+++ b/erpnext/hr/doctype/interview_feedback/interview_feedback.py
@@ -24,28 +24,36 @@
def validate_interviewer(self):
applicable_interviewers = get_applicable_interviewers(self.interview)
if self.interviewer not in applicable_interviewers:
- frappe.throw(_('{0} is not allowed to submit Interview Feedback for the Interview: {1}').format(
- frappe.bold(self.interviewer), frappe.bold(self.interview)))
+ frappe.throw(
+ _("{0} is not allowed to submit Interview Feedback for the Interview: {1}").format(
+ frappe.bold(self.interviewer), frappe.bold(self.interview)
+ )
+ )
def validate_interview_date(self):
- scheduled_date = frappe.db.get_value('Interview', self.interview, 'scheduled_on')
+ scheduled_date = frappe.db.get_value("Interview", self.interview, "scheduled_on")
if getdate() < getdate(scheduled_date) and self.docstatus == 1:
- frappe.throw(_('{0} submission before {1} is not allowed').format(
- frappe.bold('Interview Feedback'),
- frappe.bold('Interview Scheduled Date')
- ))
+ frappe.throw(
+ _("{0} submission before {1} is not allowed").format(
+ frappe.bold("Interview Feedback"), frappe.bold("Interview Scheduled Date")
+ )
+ )
def validate_duplicate(self):
- duplicate_feedback = frappe.db.exists('Interview Feedback', {
- 'interviewer': self.interviewer,
- 'interview': self.interview,
- 'docstatus': 1
- })
+ duplicate_feedback = frappe.db.exists(
+ "Interview Feedback",
+ {"interviewer": self.interviewer, "interview": self.interview, "docstatus": 1},
+ )
if duplicate_feedback:
- frappe.throw(_('Feedback already submitted for the Interview {0}. Please cancel the previous Interview Feedback {1} to continue.').format(
- self.interview, get_link_to_form('Interview Feedback', duplicate_feedback)))
+ frappe.throw(
+ _(
+ "Feedback already submitted for the Interview {0}. Please cancel the previous Interview Feedback {1} to continue."
+ ).format(
+ self.interview, get_link_to_form("Interview Feedback", duplicate_feedback)
+ )
+ )
def calculate_average_rating(self):
total_rating = 0
@@ -53,10 +61,12 @@
if d.rating:
total_rating += d.rating
- self.average_rating = flt(total_rating / len(self.skill_assessment) if len(self.skill_assessment) else 0)
+ self.average_rating = flt(
+ total_rating / len(self.skill_assessment) if len(self.skill_assessment) else 0
+ )
def update_interview_details(self):
- doc = frappe.get_doc('Interview', self.interview)
+ doc = frappe.get_doc("Interview", self.interview)
if self.docstatus == 2:
for entry in doc.interview_details:
@@ -77,5 +87,5 @@
@frappe.whitelist()
def get_applicable_interviewers(interview):
- data = frappe.get_all('Interview Detail', filters={'parent': interview}, fields=['interviewer'])
+ data = frappe.get_all("Interview Detail", filters={"parent": interview}, fields=["interviewer"])
return [d.interviewer for d in data]
diff --git a/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py b/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py
index 19c4642..63d4775 100644
--- a/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py
+++ b/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py
@@ -17,14 +17,16 @@
def test_validation_for_skill_set(self):
frappe.set_user("Administrator")
job_applicant = create_job_applicant()
- interview = create_interview_and_dependencies(job_applicant.name, scheduled_on=add_days(getdate(), -1))
+ interview = create_interview_and_dependencies(
+ job_applicant.name, scheduled_on=add_days(getdate(), -1)
+ )
skill_ratings = get_skills_rating(interview.interview_round)
interviewer = interview.interview_details[0].interviewer
- create_skill_set(['Leadership'])
+ create_skill_set(["Leadership"])
interview_feedback = create_interview_feedback(interview.name, interviewer, skill_ratings)
- interview_feedback.append("skill_assessment", {"skill": 'Leadership', 'rating': 0.8})
+ interview_feedback.append("skill_assessment", {"skill": "Leadership", "rating": 0.8})
frappe.set_user(interviewer)
self.assertRaises(frappe.ValidationError, interview_feedback.save)
@@ -33,7 +35,9 @@
def test_average_ratings_on_feedback_submission_and_cancellation(self):
job_applicant = create_job_applicant()
- interview = create_interview_and_dependencies(job_applicant.name, scheduled_on=add_days(getdate(), -1))
+ interview = create_interview_and_dependencies(
+ job_applicant.name, scheduled_on=add_days(getdate(), -1)
+ )
skill_ratings = get_skills_rating(interview.interview_round)
# For First Interviewer Feedback
@@ -48,20 +52,26 @@
if d.rating:
total_rating += d.rating
- avg_rating = flt(total_rating / len(feedback_1.skill_assessment) if len(feedback_1.skill_assessment) else 0)
+ avg_rating = flt(
+ total_rating / len(feedback_1.skill_assessment) if len(feedback_1.skill_assessment) else 0
+ )
self.assertEqual(flt(avg_rating, 2), flt(feedback_1.average_rating, 2))
- avg_on_interview_detail = frappe.db.get_value('Interview Detail', {
- 'parent': feedback_1.interview,
- 'interviewer': feedback_1.interviewer,
- 'interview_feedback': feedback_1.name
- }, 'average_rating')
+ avg_on_interview_detail = frappe.db.get_value(
+ "Interview Detail",
+ {
+ "parent": feedback_1.interview,
+ "interviewer": feedback_1.interviewer,
+ "interview_feedback": feedback_1.name,
+ },
+ "average_rating",
+ )
# 1. average should be reflected in Interview Detail.
self.assertEqual(flt(avg_on_interview_detail, 2), flt(feedback_1.average_rating, 2))
- '''For Second Interviewer Feedback'''
+ """For Second Interviewer Feedback"""
interviewer = interview.interview_details[1].interviewer
frappe.set_user(interviewer)
@@ -95,7 +105,9 @@
def get_skills_rating(interview_round):
import random
- skills = frappe.get_all("Expected Skill Set", filters={"parent": interview_round}, fields = ["skill"])
+ skills = frappe.get_all(
+ "Expected Skill Set", filters={"parent": interview_round}, fields=["skill"]
+ )
for d in skills:
d["rating"] = random.random()
return skills
diff --git a/erpnext/hr/doctype/interview_round/interview_round.py b/erpnext/hr/doctype/interview_round/interview_round.py
index 0f442c3..83dbf0e 100644
--- a/erpnext/hr/doctype/interview_round/interview_round.py
+++ b/erpnext/hr/doctype/interview_round/interview_round.py
@@ -11,6 +11,7 @@
class InterviewRound(Document):
pass
+
@frappe.whitelist()
def create_interview(doc):
if isinstance(doc, str):
@@ -24,10 +25,5 @@
if doc.interviewers:
interview.interview_details = []
for data in doc.interviewers:
- interview.append("interview_details", {
- "interviewer": data.user
- })
+ interview.append("interview_details", {"interviewer": data.user})
return interview
-
-
-
diff --git a/erpnext/hr/doctype/interview_round/test_interview_round.py b/erpnext/hr/doctype/interview_round/test_interview_round.py
index dcec941..9568165 100644
--- a/erpnext/hr/doctype/interview_round/test_interview_round.py
+++ b/erpnext/hr/doctype/interview_round/test_interview_round.py
@@ -8,4 +8,3 @@
class TestInterviewRound(unittest.TestCase):
pass
-
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py
index ccc21ce..5b0a4ca 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.py
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.py
@@ -13,7 +13,9 @@
from erpnext.hr.doctype.interview.interview import get_interviewers
-class DuplicationError(frappe.ValidationError): pass
+class DuplicationError(frappe.ValidationError):
+ pass
+
class JobApplicant(Document):
def onload(self):
@@ -36,8 +38,8 @@
self.set_status_for_employee_referral()
if not self.applicant_name and self.email_id:
- guess = self.email_id.split('@')[0]
- self.applicant_name = ' '.join([p.capitalize() for p in guess.split('.')])
+ guess = self.email_id.split("@")[0]
+ self.applicant_name = " ".join([p.capitalize() for p in guess.split(".")])
def set_status_for_employee_referral(self):
emp_ref = frappe.get_doc("Employee Referral", self.employee_referral)
@@ -46,11 +48,11 @@
elif self.status in ["Accepted", "Rejected"]:
emp_ref.db_set("status", self.status)
+
@frappe.whitelist()
def create_interview(doc, interview_round):
import json
-
if isinstance(doc, str):
doc = json.loads(doc)
doc = frappe.get_doc(doc)
@@ -58,7 +60,11 @@
round_designation = frappe.db.get_value("Interview Round", interview_round, "designation")
if round_designation and doc.designation and round_designation != doc.designation:
- frappe.throw(_("Interview Round {0} is only applicable for the Designation {1}").format(interview_round, round_designation))
+ frappe.throw(
+ _("Interview Round {0} is only applicable for the Designation {1}").format(
+ interview_round, round_designation
+ )
+ )
interview = frappe.new_doc("Interview")
interview.interview_round = interview_round
@@ -69,23 +75,25 @@
interviewer_detail = get_interviewers(interview_round)
for d in interviewer_detail:
- interview.append("interview_details", {
- "interviewer": d.interviewer
- })
+ interview.append("interview_details", {"interviewer": d.interviewer})
return interview
+
@frappe.whitelist()
def get_interview_details(job_applicant):
- interview_details = frappe.db.get_all("Interview",
- filters={"job_applicant":job_applicant, "docstatus": ["!=", 2]},
- fields=["name", "interview_round", "expected_average_rating", "average_rating", "status"]
+ interview_details = frappe.db.get_all(
+ "Interview",
+ filters={"job_applicant": job_applicant, "docstatus": ["!=", 2]},
+ fields=["name", "interview_round", "expected_average_rating", "average_rating", "status"],
)
interview_detail_map = {}
meta = frappe.get_meta("Interview")
number_of_stars = meta.get_options("expected_average_rating") or 5
for detail in interview_details:
- detail.expected_average_rating = detail.expected_average_rating * number_of_stars if detail.expected_average_rating else 0
+ detail.expected_average_rating = (
+ detail.expected_average_rating * number_of_stars if detail.expected_average_rating else 0
+ )
detail.average_rating = detail.average_rating * number_of_stars if detail.average_rating else 0
interview_detail_map[detail.name] = detail
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py
index 56331ac..14b944a 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py
+++ b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py
@@ -1,15 +1,9 @@
def get_data():
return {
- 'fieldname': 'job_applicant',
- 'transactions': [
- {
- 'items': ['Employee', 'Employee Onboarding']
- },
- {
- 'items': ['Job Offer', 'Appointment Letter']
- },
- {
- 'items': ['Interview']
- }
+ "fieldname": "job_applicant",
+ "transactions": [
+ {"items": ["Employee", "Employee Onboarding"]},
+ {"items": ["Job Offer", "Appointment Letter"]},
+ {"items": ["Interview"]},
],
}
diff --git a/erpnext/hr/doctype/job_applicant/test_job_applicant.py b/erpnext/hr/doctype/job_applicant/test_job_applicant.py
index bf16220..99d1161 100644
--- a/erpnext/hr/doctype/job_applicant/test_job_applicant.py
+++ b/erpnext/hr/doctype/job_applicant/test_job_applicant.py
@@ -10,21 +10,25 @@
class TestJobApplicant(unittest.TestCase):
def test_job_applicant_naming(self):
- applicant = frappe.get_doc({
- "doctype": "Job Applicant",
- "status": "Open",
- "applicant_name": "_Test Applicant",
- "email_id": "job_applicant_naming@example.com"
- }).insert()
- self.assertEqual(applicant.name, 'job_applicant_naming@example.com')
+ applicant = frappe.get_doc(
+ {
+ "doctype": "Job Applicant",
+ "status": "Open",
+ "applicant_name": "_Test Applicant",
+ "email_id": "job_applicant_naming@example.com",
+ }
+ ).insert()
+ self.assertEqual(applicant.name, "job_applicant_naming@example.com")
- applicant = frappe.get_doc({
- "doctype": "Job Applicant",
- "status": "Open",
- "applicant_name": "_Test Applicant",
- "email_id": "job_applicant_naming@example.com"
- }).insert()
- self.assertEqual(applicant.name, 'job_applicant_naming@example.com-1')
+ applicant = frappe.get_doc(
+ {
+ "doctype": "Job Applicant",
+ "status": "Open",
+ "applicant_name": "_Test Applicant",
+ "email_id": "job_applicant_naming@example.com",
+ }
+ ).insert()
+ self.assertEqual(applicant.name, "job_applicant_naming@example.com-1")
def tearDown(self):
frappe.db.rollback()
@@ -41,11 +45,13 @@
if frappe.db.exists("Job Applicant", filters):
return frappe.get_doc("Job Applicant", filters)
- job_applicant = frappe.get_doc({
- "doctype": "Job Applicant",
- "status": args.status or "Open",
- "designation": create_designation().name
- })
+ job_applicant = frappe.get_doc(
+ {
+ "doctype": "Job Applicant",
+ "status": args.status or "Open",
+ "designation": create_designation().name,
+ }
+ )
job_applicant.update(filters)
job_applicant.save()
diff --git a/erpnext/hr/doctype/job_offer/job_offer.py b/erpnext/hr/doctype/job_offer/job_offer.py
index 072fc73..b46930a 100644
--- a/erpnext/hr/doctype/job_offer/job_offer.py
+++ b/erpnext/hr/doctype/job_offer/job_offer.py
@@ -17,9 +17,15 @@
def validate(self):
self.validate_vacancies()
- job_offer = frappe.db.exists("Job Offer",{"job_applicant": self.job_applicant, "docstatus": ["!=", 2]})
+ job_offer = frappe.db.exists(
+ "Job Offer", {"job_applicant": self.job_applicant, "docstatus": ["!=", 2]}
+ )
if job_offer and job_offer != self.name:
- frappe.throw(_("Job Offer: {0} is already for Job Applicant: {1}").format(frappe.bold(job_offer), frappe.bold(self.job_applicant)))
+ frappe.throw(
+ _("Job Offer: {0} is already for Job Applicant: {1}").format(
+ frappe.bold(job_offer), frappe.bold(self.job_applicant)
+ )
+ )
def validate_vacancies(self):
staffing_plan = get_staffing_plan_detail(self.designation, self.company, self.offer_date)
@@ -27,7 +33,7 @@
if staffing_plan and check_vacancies:
job_offers = self.get_job_offer(staffing_plan.from_date, staffing_plan.to_date)
if not staffing_plan.get("vacancies") or cint(staffing_plan.vacancies) - len(job_offers) <= 0:
- error_variable = 'for ' + frappe.bold(self.designation)
+ error_variable = "for " + frappe.bold(self.designation)
if staffing_plan.get("parent"):
error_variable = frappe.bold(get_link_to_form("Staffing Plan", staffing_plan.parent))
@@ -37,20 +43,27 @@
update_job_applicant(self.status, self.job_applicant)
def get_job_offer(self, from_date, to_date):
- ''' Returns job offer created during a time period '''
- return frappe.get_all("Job Offer", filters={
- "offer_date": ['between', (from_date, to_date)],
+ """Returns job offer created during a time period"""
+ return frappe.get_all(
+ "Job Offer",
+ filters={
+ "offer_date": ["between", (from_date, to_date)],
"designation": self.designation,
"company": self.company,
- "docstatus": 1
- }, fields=['name'])
+ "docstatus": 1,
+ },
+ fields=["name"],
+ )
+
def update_job_applicant(status, job_applicant):
if status in ("Accepted", "Rejected"):
frappe.set_value("Job Applicant", job_applicant, "status", status)
+
def get_staffing_plan_detail(designation, company, offer_date):
- detail = frappe.db.sql("""
+ detail = frappe.db.sql(
+ """
SELECT DISTINCT spd.parent,
sp.from_date as from_date,
sp.to_date as to_date,
@@ -64,21 +77,31 @@
AND sp.company=%s
AND spd.parent = sp.name
AND %s between sp.from_date and sp.to_date
- """, (designation, company, offer_date), as_dict=1)
+ """,
+ (designation, company, offer_date),
+ as_dict=1,
+ )
return frappe._dict(detail[0]) if (detail and detail[0].parent) else None
+
@frappe.whitelist()
def make_employee(source_name, target_doc=None):
def set_missing_values(source, target):
- target.personal_email, target.first_name = frappe.db.get_value("Job Applicant", \
- source.job_applicant, ["email_id", "applicant_name"])
- doc = get_mapped_doc("Job Offer", source_name, {
+ target.personal_email, target.first_name = frappe.db.get_value(
+ "Job Applicant", source.job_applicant, ["email_id", "applicant_name"]
+ )
+
+ doc = get_mapped_doc(
+ "Job Offer",
+ source_name,
+ {
"Job Offer": {
"doctype": "Employee",
- "field_map": {
- "applicant_name": "employee_name",
- "offer_date": "scheduled_confirmation_date"
- }}
- }, target_doc, set_missing_values)
+ "field_map": {"applicant_name": "employee_name", "offer_date": "scheduled_confirmation_date"},
+ }
+ },
+ target_doc,
+ set_missing_values,
+ )
return doc
diff --git a/erpnext/hr/doctype/job_offer/test_job_offer.py b/erpnext/hr/doctype/job_offer/test_job_offer.py
index d94e03c..7d8ef11 100644
--- a/erpnext/hr/doctype/job_offer/test_job_offer.py
+++ b/erpnext/hr/doctype/job_offer/test_job_offer.py
@@ -12,17 +12,19 @@
# test_records = frappe.get_test_records('Job Offer')
+
class TestJobOffer(unittest.TestCase):
def test_job_offer_creation_against_vacancies(self):
frappe.db.set_value("HR Settings", None, "check_vacancies", 1)
job_applicant = create_job_applicant(email_id="test_job_offer@example.com")
job_offer = create_job_offer(job_applicant=job_applicant.name, designation="UX Designer")
- create_staffing_plan(name='Test No Vacancies', staffing_details=[{
- "designation": "UX Designer",
- "vacancies": 0,
- "estimated_cost_per_position": 5000
- }])
+ create_staffing_plan(
+ name="Test No Vacancies",
+ staffing_details=[
+ {"designation": "UX Designer", "vacancies": 0, "estimated_cost_per_position": 5000}
+ ],
+ )
self.assertRaises(frappe.ValidationError, job_offer.submit)
# test creation of job offer when vacancies are not present
@@ -49,6 +51,7 @@
def tearDown(self):
frappe.db.sql("DELETE FROM `tabJob Offer` WHERE 1")
+
def create_job_offer(**args):
args = frappe._dict(args)
if not args.job_applicant:
@@ -57,32 +60,34 @@
if not frappe.db.exists("Designation", args.designation):
designation = create_designation(designation_name=args.designation)
- job_offer = frappe.get_doc({
- "doctype": "Job Offer",
- "job_applicant": args.job_applicant or job_applicant.name,
- "offer_date": args.offer_date or nowdate(),
- "designation": args.designation or "Researcher",
- "status": args.status or "Accepted"
- })
+ job_offer = frappe.get_doc(
+ {
+ "doctype": "Job Offer",
+ "job_applicant": args.job_applicant or job_applicant.name,
+ "offer_date": args.offer_date or nowdate(),
+ "designation": args.designation or "Researcher",
+ "status": args.status or "Accepted",
+ }
+ )
return job_offer
+
def create_staffing_plan(**args):
args = frappe._dict(args)
make_company()
frappe.db.set_value("Company", "_Test Company", "is_group", 1)
if frappe.db.exists("Staffing Plan", args.name or "Test"):
return
- staffing_plan = frappe.get_doc({
- "doctype": "Staffing Plan",
- "name": args.name or "Test",
- "from_date": args.from_date or nowdate(),
- "to_date": args.to_date or add_days(nowdate(), 10),
- "staffing_details": args.staffing_details or [{
- "designation": "Researcher",
- "vacancies": 1,
- "estimated_cost_per_position": 50000
- }]
- })
+ staffing_plan = frappe.get_doc(
+ {
+ "doctype": "Staffing Plan",
+ "name": args.name or "Test",
+ "from_date": args.from_date or nowdate(),
+ "to_date": args.to_date or add_days(nowdate(), 10),
+ "staffing_details": args.staffing_details
+ or [{"designation": "Researcher", "vacancies": 1, "estimated_cost_per_position": 50000}],
+ }
+ )
staffing_plan.insert()
staffing_plan.submit()
return staffing_plan
diff --git a/erpnext/hr/doctype/job_opening/job_opening.py b/erpnext/hr/doctype/job_opening/job_opening.py
index d53daf1..c71407d 100644
--- a/erpnext/hr/doctype/job_opening/job_opening.py
+++ b/erpnext/hr/doctype/job_opening/job_opening.py
@@ -16,67 +16,78 @@
class JobOpening(WebsiteGenerator):
website = frappe._dict(
- template = "templates/generators/job_opening.html",
- condition_field = "publish",
- page_title_field = "job_title",
+ template="templates/generators/job_opening.html",
+ condition_field="publish",
+ page_title_field="job_title",
)
def validate(self):
if not self.route:
- self.route = frappe.scrub(self.job_title).replace('_', '-')
+ self.route = frappe.scrub(self.job_title).replace("_", "-")
self.validate_current_vacancies()
def validate_current_vacancies(self):
if not self.staffing_plan:
- staffing_plan = get_active_staffing_plan_details(self.company,
- self.designation)
+ staffing_plan = get_active_staffing_plan_details(self.company, self.designation)
if staffing_plan:
self.staffing_plan = staffing_plan[0].name
self.planned_vacancies = staffing_plan[0].vacancies
elif not self.planned_vacancies:
- planned_vacancies = frappe.db.sql("""
+ planned_vacancies = frappe.db.sql(
+ """
select vacancies from `tabStaffing Plan Detail`
- where parent=%s and designation=%s""", (self.staffing_plan, self.designation))
+ where parent=%s and designation=%s""",
+ (self.staffing_plan, self.designation),
+ )
self.planned_vacancies = planned_vacancies[0][0] if planned_vacancies else None
if self.staffing_plan and self.planned_vacancies:
staffing_plan_company = frappe.db.get_value("Staffing Plan", self.staffing_plan, "company")
- lft, rgt = frappe.get_cached_value('Company', staffing_plan_company, ["lft", "rgt"])
+ lft, rgt = frappe.get_cached_value("Company", staffing_plan_company, ["lft", "rgt"])
designation_counts = get_designation_counts(self.designation, self.company)
- current_count = designation_counts['employee_count'] + designation_counts['job_openings']
+ current_count = designation_counts["employee_count"] + designation_counts["job_openings"]
if self.planned_vacancies <= current_count:
- frappe.throw(_("Job Openings for designation {0} already open or hiring completed as per Staffing Plan {1}").format(
- self.designation, self.staffing_plan))
+ frappe.throw(
+ _(
+ "Job Openings for designation {0} already open or hiring completed as per Staffing Plan {1}"
+ ).format(self.designation, self.staffing_plan)
+ )
def get_context(self, context):
- context.parents = [{'route': 'jobs', 'title': _('All Jobs') }]
+ context.parents = [{"route": "jobs", "title": _("All Jobs")}]
+
def get_list_context(context):
context.title = _("Jobs")
- context.introduction = _('Current Job Openings')
+ context.introduction = _("Current Job Openings")
context.get_list = get_job_openings
-def get_job_openings(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by=None):
- fields = ['name', 'status', 'job_title', 'description', 'publish_salary_range',
- 'lower_range', 'upper_range', 'currency', 'job_application_route']
+
+def get_job_openings(
+ doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by=None
+):
+ fields = [
+ "name",
+ "status",
+ "job_title",
+ "description",
+ "publish_salary_range",
+ "lower_range",
+ "upper_range",
+ "currency",
+ "job_application_route",
+ ]
filters = filters or {}
- filters.update({
- 'status': 'Open'
- })
+ filters.update({"status": "Open"})
if txt:
- filters.update({
- 'job_title': ['like', '%{0}%'.format(txt)],
- 'description': ['like', '%{0}%'.format(txt)]
- })
+ filters.update(
+ {"job_title": ["like", "%{0}%".format(txt)], "description": ["like", "%{0}%".format(txt)]}
+ )
- return frappe.get_all(doctype,
- filters,
- fields,
- start=limit_start,
- page_length=limit_page_length,
- order_by=order_by
+ return frappe.get_all(
+ doctype, filters, fields, start=limit_start, page_length=limit_page_length, order_by=order_by
)
diff --git a/erpnext/hr/doctype/job_opening/job_opening_dashboard.py b/erpnext/hr/doctype/job_opening/job_opening_dashboard.py
index 67600dc..a309328 100644
--- a/erpnext/hr/doctype/job_opening/job_opening_dashboard.py
+++ b/erpnext/hr/doctype/job_opening/job_opening_dashboard.py
@@ -1,9 +1,5 @@
def get_data():
- return {
- 'fieldname': 'job_title',
- 'transactions': [
- {
- 'items': ['Job Applicant']
- }
- ],
- }
+ return {
+ "fieldname": "job_title",
+ "transactions": [{"items": ["Job Applicant"]}],
+ }
diff --git a/erpnext/hr/doctype/job_opening/test_job_opening.py b/erpnext/hr/doctype/job_opening/test_job_opening.py
index a1c3a1d..a72a6eb 100644
--- a/erpnext/hr/doctype/job_opening/test_job_opening.py
+++ b/erpnext/hr/doctype/job_opening/test_job_opening.py
@@ -5,5 +5,6 @@
# test_records = frappe.get_test_records('Job Opening')
+
class TestJobOpening(unittest.TestCase):
pass
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
index 232118f..98408af 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
@@ -15,11 +15,25 @@
from erpnext.hr.utils import get_leave_period, set_employee_name
-class OverlapError(frappe.ValidationError): pass
-class BackDatedAllocationError(frappe.ValidationError): pass
-class OverAllocationError(frappe.ValidationError): pass
-class LessAllocationError(frappe.ValidationError): pass
-class ValueMultiplierError(frappe.ValidationError): pass
+class OverlapError(frappe.ValidationError):
+ pass
+
+
+class BackDatedAllocationError(frappe.ValidationError):
+ pass
+
+
+class OverAllocationError(frappe.ValidationError):
+ pass
+
+
+class LessAllocationError(frappe.ValidationError):
+ pass
+
+
+class ValueMultiplierError(frappe.ValidationError):
+ pass
+
class LeaveAllocation(Document):
def validate(self):
@@ -35,16 +49,22 @@
def validate_leave_allocation_days(self):
company = frappe.db.get_value("Employee", self.employee, "company")
leave_period = get_leave_period(self.from_date, self.to_date, company)
- max_leaves_allowed = flt(frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed"))
+ max_leaves_allowed = flt(
+ frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed")
+ )
if max_leaves_allowed > 0:
leave_allocated = 0
if leave_period:
- leave_allocated = get_leave_allocation_for_period(self.employee, self.leave_type,
- leave_period[0].from_date, leave_period[0].to_date)
+ leave_allocated = get_leave_allocation_for_period(
+ self.employee, self.leave_type, leave_period[0].from_date, leave_period[0].to_date
+ )
leave_allocated += flt(self.new_leaves_allocated)
if leave_allocated > max_leaves_allowed:
- frappe.throw(_("Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period")
- .format(self.leave_type, self.employee))
+ frappe.throw(
+ _(
+ "Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period"
+ ).format(self.leave_type, self.employee)
+ )
def on_submit(self):
self.create_leave_ledger_entry()
@@ -69,20 +89,22 @@
"leaves": leaves_to_be_added,
"from_date": self.from_date,
"to_date": self.to_date,
- "is_carry_forward": 0
+ "is_carry_forward": 0,
}
create_leave_ledger_entry(self, args, True)
def get_existing_leave_count(self):
- ledger_entries = frappe.get_all("Leave Ledger Entry",
- filters={
- "transaction_type": "Leave Allocation",
- "transaction_name": self.name,
- "employee": self.employee,
- "company": self.company,
- "leave_type": self.leave_type
- },
- pluck="leaves")
+ ledger_entries = frappe.get_all(
+ "Leave Ledger Entry",
+ filters={
+ "transaction_type": "Leave Allocation",
+ "transaction_name": self.name,
+ "employee": self.employee,
+ "company": self.company,
+ "leave_type": self.leave_type,
+ },
+ pluck="leaves",
+ )
total_existing_leaves = 0
for entry in ledger_entries:
total_existing_leaves += entry
@@ -90,21 +112,33 @@
return total_existing_leaves
def validate_against_leave_applications(self):
- leaves_taken = get_approved_leaves_for_period(self.employee, self.leave_type,
- self.from_date, self.to_date)
+ leaves_taken = get_approved_leaves_for_period(
+ self.employee, self.leave_type, self.from_date, self.to_date
+ )
if flt(leaves_taken) > flt(self.total_leaves_allocated):
if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"):
- frappe.msgprint(_("Note: Total allocated leaves {0} shouldn't be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken))
+ frappe.msgprint(
+ _(
+ "Note: Total allocated leaves {0} shouldn't be less than already approved leaves {1} for the period"
+ ).format(self.total_leaves_allocated, leaves_taken)
+ )
else:
- frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError)
+ frappe.throw(
+ _(
+ "Total allocated leaves {0} cannot be less than already approved leaves {1} for the period"
+ ).format(self.total_leaves_allocated, leaves_taken),
+ LessAllocationError,
+ )
def update_leave_policy_assignments_when_no_allocations_left(self):
- allocations = frappe.db.get_list("Leave Allocation", filters = {
- "docstatus": 1,
- "leave_policy_assignment": self.leave_policy_assignment
- })
+ allocations = frappe.db.get_list(
+ "Leave Allocation",
+ filters={"docstatus": 1, "leave_policy_assignment": self.leave_policy_assignment},
+ )
if len(allocations) == 0:
- frappe.db.set_value("Leave Policy Assignment", self.leave_policy_assignment ,"leaves_allocated", 0)
+ frappe.db.set_value(
+ "Leave Policy Assignment", self.leave_policy_assignment, "leaves_allocated", 0
+ )
def validate_period(self):
if date_diff(self.to_date, self.from_date) <= 0:
@@ -112,10 +146,13 @@
def validate_lwp(self):
if frappe.db.get_value("Leave Type", self.leave_type, "is_lwp"):
- frappe.throw(_("Leave Type {0} cannot be allocated since it is leave without pay").format(self.leave_type))
+ frappe.throw(
+ _("Leave Type {0} cannot be allocated since it is leave without pay").format(self.leave_type)
+ )
def validate_allocation_overlap(self):
- leave_allocation = frappe.db.sql("""
+ leave_allocation = frappe.db.sql(
+ """
SELECT
name
FROM `tabLeave Allocation`
@@ -123,29 +160,44 @@
employee=%s AND leave_type=%s
AND name <> %s AND docstatus=1
AND to_date >= %s AND from_date <= %s""",
- (self.employee, self.leave_type, self.name, self.from_date, self.to_date))
+ (self.employee, self.leave_type, self.name, self.from_date, self.to_date),
+ )
if leave_allocation:
- frappe.msgprint(_("{0} already allocated for Employee {1} for period {2} to {3}")
- .format(self.leave_type, self.employee, formatdate(self.from_date), formatdate(self.to_date)))
+ frappe.msgprint(
+ _("{0} already allocated for Employee {1} for period {2} to {3}").format(
+ self.leave_type, self.employee, formatdate(self.from_date), formatdate(self.to_date)
+ )
+ )
- frappe.throw(_('Reference') + ': <a href="/app/Form/Leave Allocation/{0}">{0}</a>'
- .format(leave_allocation[0][0]), OverlapError)
+ frappe.throw(
+ _("Reference")
+ + ': <a href="/app/Form/Leave Allocation/{0}">{0}</a>'.format(leave_allocation[0][0]),
+ OverlapError,
+ )
def validate_back_dated_allocation(self):
- future_allocation = frappe.db.sql("""select name, from_date from `tabLeave Allocation`
+ future_allocation = frappe.db.sql(
+ """select name, from_date from `tabLeave Allocation`
where employee=%s and leave_type=%s and docstatus=1 and from_date > %s
- and carry_forward=1""", (self.employee, self.leave_type, self.to_date), as_dict=1)
+ and carry_forward=1""",
+ (self.employee, self.leave_type, self.to_date),
+ as_dict=1,
+ )
if future_allocation:
- frappe.throw(_("Leave cannot be allocated before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}")
- .format(formatdate(future_allocation[0].from_date), future_allocation[0].name),
- BackDatedAllocationError)
+ frappe.throw(
+ _(
+ "Leave cannot be allocated before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}"
+ ).format(formatdate(future_allocation[0].from_date), future_allocation[0].name),
+ BackDatedAllocationError,
+ )
@frappe.whitelist()
def set_total_leaves_allocated(self):
- self.unused_leaves = get_carry_forwarded_leaves(self.employee,
- self.leave_type, self.from_date, self.carry_forward)
+ self.unused_leaves = get_carry_forwarded_leaves(
+ self.employee, self.leave_type, self.from_date, self.carry_forward
+ )
self.total_leaves_allocated = flt(self.unused_leaves) + flt(self.new_leaves_allocated)
@@ -154,11 +206,14 @@
if self.carry_forward:
self.set_carry_forwarded_leaves_in_previous_allocation()
- if not self.total_leaves_allocated \
- and not frappe.db.get_value("Leave Type", self.leave_type, "is_earned_leave") \
- and not frappe.db.get_value("Leave Type", self.leave_type, "is_compensatory"):
- frappe.throw(_("Total leaves allocated is mandatory for Leave Type {0}")
- .format(self.leave_type))
+ if (
+ not self.total_leaves_allocated
+ and not frappe.db.get_value("Leave Type", self.leave_type, "is_earned_leave")
+ and not frappe.db.get_value("Leave Type", self.leave_type, "is_compensatory")
+ ):
+ frappe.throw(
+ _("Total leaves allocated is mandatory for Leave Type {0}").format(self.leave_type)
+ )
def limit_carry_forward_based_on_max_allowed_leaves(self):
max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed")
@@ -167,13 +222,17 @@
self.unused_leaves = max_leaves_allowed - flt(self.new_leaves_allocated)
def set_carry_forwarded_leaves_in_previous_allocation(self, on_cancel=False):
- ''' Set carry forwarded leaves in previous allocation '''
+ """Set carry forwarded leaves in previous allocation"""
previous_allocation = get_previous_allocation(self.from_date, self.leave_type, self.employee)
if on_cancel:
self.unused_leaves = 0.0
if previous_allocation:
- frappe.db.set_value("Leave Allocation", previous_allocation.name,
- 'carry_forwarded_leaves_count', self.unused_leaves)
+ frappe.db.set_value(
+ "Leave Allocation",
+ previous_allocation.name,
+ "carry_forwarded_leaves_count",
+ self.unused_leaves,
+ )
def validate_total_leaves_allocated(self):
# Adding a day to include To Date in the difference
@@ -183,13 +242,15 @@
def create_leave_ledger_entry(self, submit=True):
if self.unused_leaves:
- expiry_days = frappe.db.get_value("Leave Type", self.leave_type, "expire_carry_forwarded_leaves_after_days")
+ expiry_days = frappe.db.get_value(
+ "Leave Type", self.leave_type, "expire_carry_forwarded_leaves_after_days"
+ )
end_date = add_days(self.from_date, expiry_days - 1) if expiry_days else self.to_date
args = dict(
leaves=self.unused_leaves,
from_date=self.from_date,
- to_date= min(getdate(end_date), getdate(self.to_date)),
- is_carry_forward=1
+ to_date=min(getdate(end_date), getdate(self.to_date)),
+ is_carry_forward=1,
)
create_leave_ledger_entry(self, args, submit)
@@ -197,25 +258,31 @@
leaves=self.new_leaves_allocated,
from_date=self.from_date,
to_date=self.to_date,
- is_carry_forward=0
+ is_carry_forward=0,
)
create_leave_ledger_entry(self, args, submit)
+
def get_previous_allocation(from_date, leave_type, employee):
- ''' Returns document properties of previous allocation '''
- return frappe.db.get_value("Leave Allocation",
+ """Returns document properties of previous allocation"""
+ return frappe.db.get_value(
+ "Leave Allocation",
filters={
- 'to_date': ("<", from_date),
- 'leave_type': leave_type,
- 'employee': employee,
- 'docstatus': 1
+ "to_date": ("<", from_date),
+ "leave_type": leave_type,
+ "employee": employee,
+ "docstatus": 1,
},
- order_by='to_date DESC',
- fieldname=['name', 'from_date', 'to_date', 'employee', 'leave_type'], as_dict=1)
+ order_by="to_date DESC",
+ fieldname=["name", "from_date", "to_date", "employee", "leave_type"],
+ as_dict=1,
+ )
+
def get_leave_allocation_for_period(employee, leave_type, from_date, to_date):
leave_allocated = 0
- leave_allocations = frappe.db.sql("""
+ leave_allocations = frappe.db.sql(
+ """
select employee, leave_type, from_date, to_date, total_leaves_allocated
from `tabLeave Allocation`
where employee=%(employee)s and leave_type=%(leave_type)s
@@ -223,12 +290,10 @@
and (from_date between %(from_date)s and %(to_date)s
or to_date between %(from_date)s and %(to_date)s
or (from_date < %(from_date)s and to_date > %(to_date)s))
- """, {
- "from_date": from_date,
- "to_date": to_date,
- "employee": employee,
- "leave_type": leave_type
- }, as_dict=1)
+ """,
+ {"from_date": from_date, "to_date": to_date, "employee": employee, "leave_type": leave_type},
+ as_dict=1,
+ )
if leave_allocations:
for leave_alloc in leave_allocations:
@@ -236,35 +301,42 @@
return leave_allocated
+
@frappe.whitelist()
def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None):
- ''' Returns carry forwarded leaves for the given employee '''
+ """Returns carry forwarded leaves for the given employee"""
unused_leaves = 0.0
previous_allocation = get_previous_allocation(date, leave_type, employee)
if carry_forward and previous_allocation:
validate_carry_forward(leave_type)
- unused_leaves = get_unused_leaves(employee, leave_type,
- previous_allocation.from_date, previous_allocation.to_date)
+ unused_leaves = get_unused_leaves(
+ employee, leave_type, previous_allocation.from_date, previous_allocation.to_date
+ )
if unused_leaves:
- max_carry_forwarded_leaves = frappe.db.get_value("Leave Type",
- leave_type, "maximum_carry_forwarded_leaves")
+ max_carry_forwarded_leaves = frappe.db.get_value(
+ "Leave Type", leave_type, "maximum_carry_forwarded_leaves"
+ )
if max_carry_forwarded_leaves and unused_leaves > flt(max_carry_forwarded_leaves):
unused_leaves = flt(max_carry_forwarded_leaves)
return unused_leaves
+
def get_unused_leaves(employee, leave_type, from_date, to_date):
- ''' Returns unused leaves between the given period while skipping leave allocation expiry '''
- leaves = frappe.get_all("Leave Ledger Entry", filters={
- 'employee': employee,
- 'leave_type': leave_type,
- 'from_date': ('>=', from_date),
- 'to_date': ('<=', to_date)
- }, or_filters={
- 'is_expired': 0,
- 'is_carry_forward': 1
- }, fields=['sum(leaves) as leaves'])
- return flt(leaves[0]['leaves'])
+ """Returns unused leaves between the given period while skipping leave allocation expiry"""
+ leaves = frappe.get_all(
+ "Leave Ledger Entry",
+ filters={
+ "employee": employee,
+ "leave_type": leave_type,
+ "from_date": (">=", from_date),
+ "to_date": ("<=", to_date),
+ },
+ or_filters={"is_expired": 0, "is_carry_forward": 1},
+ fields=["sum(leaves) as leaves"],
+ )
+ return flt(leaves[0]["leaves"])
+
def validate_carry_forward(leave_type):
if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"):
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py b/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py
index 631beef..96e81db 100644
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py
@@ -1,17 +1,6 @@
def get_data():
- return {
- 'fieldname': 'leave_allocation',
- 'transactions': [
- {
- 'items': ['Compensatory Leave Request']
- },
- {
- 'items': ['Leave Encashment']
- }
- ],
- 'reports': [
- {
- 'items': ['Employee Leave Balance']
- }
- ]
- }
+ return {
+ "fieldname": "leave_allocation",
+ "transactions": [{"items": ["Compensatory Leave Request"]}, {"items": ["Leave Encashment"]}],
+ "reports": [{"items": ["Employee Leave Balance"]}],
+ }
diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
index 1fe9139..a53d4a8 100644
--- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
@@ -31,7 +31,7 @@
"from_date": getdate("2015-10-01"),
"to_date": getdate("2015-10-31"),
"new_leaves_allocated": 5,
- "docstatus": 1
+ "docstatus": 1,
},
{
"doctype": "Leave Allocation",
@@ -41,39 +41,43 @@
"leave_type": "_Test Leave Type",
"from_date": getdate("2015-09-01"),
"to_date": getdate("2015-11-30"),
- "new_leaves_allocated": 5
- }
+ "new_leaves_allocated": 5,
+ },
]
frappe.get_doc(leaves[0]).save()
self.assertRaises(frappe.ValidationError, frappe.get_doc(leaves[1]).save)
def test_invalid_period(self):
- doc = frappe.get_doc({
- "doctype": "Leave Allocation",
- "__islocal": 1,
- "employee": self.employee.name,
- "employee_name": self.employee.employee_name,
- "leave_type": "_Test Leave Type",
- "from_date": getdate("2015-09-30"),
- "to_date": getdate("2015-09-1"),
- "new_leaves_allocated": 5
- })
+ doc = frappe.get_doc(
+ {
+ "doctype": "Leave Allocation",
+ "__islocal": 1,
+ "employee": self.employee.name,
+ "employee_name": self.employee.employee_name,
+ "leave_type": "_Test Leave Type",
+ "from_date": getdate("2015-09-30"),
+ "to_date": getdate("2015-09-1"),
+ "new_leaves_allocated": 5,
+ }
+ )
# invalid period
self.assertRaises(frappe.ValidationError, doc.save)
def test_allocated_leave_days_over_period(self):
- doc = frappe.get_doc({
- "doctype": "Leave Allocation",
- "__islocal": 1,
- "employee": self.employee.name,
- "employee_name": self.employee.employee_name,
- "leave_type": "_Test Leave Type",
- "from_date": getdate("2015-09-1"),
- "to_date": getdate("2015-09-30"),
- "new_leaves_allocated": 35
- })
+ doc = frappe.get_doc(
+ {
+ "doctype": "Leave Allocation",
+ "__islocal": 1,
+ "employee": self.employee.name,
+ "employee_name": self.employee.employee_name,
+ "leave_type": "_Test Leave Type",
+ "from_date": getdate("2015-09-1"),
+ "to_date": getdate("2015-09-30"),
+ "new_leaves_allocated": 35,
+ }
+ )
# allocated leave more than period
self.assertRaises(frappe.ValidationError, doc.save)
@@ -91,7 +95,8 @@
leave_type="_Test_CF_leave",
from_date=add_months(nowdate(), -12),
to_date=add_months(nowdate(), -1),
- carry_forward=0)
+ carry_forward=0,
+ )
leave_allocation.submit()
# carry forwarded leaves considering maximum_carry_forwarded_leaves
@@ -100,7 +105,8 @@
employee=self.employee.name,
employee_name=self.employee.employee_name,
leave_type="_Test_CF_leave",
- carry_forward=1)
+ carry_forward=1,
+ )
leave_allocation_1.submit()
self.assertEqual(leave_allocation_1.unused_leaves, 10)
@@ -114,7 +120,8 @@
employee_name=self.employee.employee_name,
leave_type="_Test_CF_leave",
carry_forward=1,
- new_leaves_allocated=25)
+ new_leaves_allocated=25,
+ )
leave_allocation_2.submit()
self.assertEqual(leave_allocation_2.unused_leaves, 5)
@@ -123,7 +130,8 @@
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
- expire_carry_forwarded_leaves_after_days=90)
+ expire_carry_forwarded_leaves_after_days=90,
+ )
leave_type.save()
# initial leave allocation
@@ -133,7 +141,8 @@
leave_type="_Test_CF_leave_expiry",
from_date=add_months(nowdate(), -24),
to_date=add_months(nowdate(), -12),
- carry_forward=0)
+ carry_forward=0,
+ )
leave_allocation.submit()
leave_allocation = create_leave_allocation(
@@ -142,7 +151,8 @@
leave_type="_Test_CF_leave_expiry",
from_date=add_days(nowdate(), -90),
to_date=add_days(nowdate(), 100),
- carry_forward=1)
+ carry_forward=1,
+ )
leave_allocation.submit()
# expires all the carry forwarded leaves after 90 days
@@ -155,19 +165,21 @@
leave_type="_Test_CF_leave_expiry",
carry_forward=1,
from_date=add_months(nowdate(), 6),
- to_date=add_months(nowdate(), 12))
+ to_date=add_months(nowdate(), 12),
+ )
leave_allocation_1.submit()
self.assertEqual(leave_allocation_1.unused_leaves, leave_allocation.new_leaves_allocated)
def test_creation_of_leave_ledger_entry_on_submit(self):
leave_allocation = create_leave_allocation(
- employee=self.employee.name,
- employee_name=self.employee.employee_name
+ employee=self.employee.name, employee_name=self.employee.employee_name
)
leave_allocation.submit()
- leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_allocation.name))
+ leave_ledger_entry = frappe.get_all(
+ "Leave Ledger Entry", fields="*", filters=dict(transaction_name=leave_allocation.name)
+ )
self.assertEqual(len(leave_ledger_entry), 1)
self.assertEqual(leave_ledger_entry[0].employee, leave_allocation.employee)
@@ -176,12 +188,13 @@
# check if leave ledger entry is deleted on cancellation
leave_allocation.cancel()
- self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_allocation.name}))
+ self.assertFalse(
+ frappe.db.exists("Leave Ledger Entry", {"transaction_name": leave_allocation.name})
+ )
def test_leave_addition_after_submit(self):
leave_allocation = create_leave_allocation(
- employee=self.employee.name,
- employee_name=self.employee.employee_name
+ employee=self.employee.name, employee_name=self.employee.employee_name
)
leave_allocation.submit()
self.assertTrue(leave_allocation.total_leaves_allocated, 15)
@@ -191,8 +204,7 @@
def test_leave_subtraction_after_submit(self):
leave_allocation = create_leave_allocation(
- employee=self.employee.name,
- employee_name=self.employee.employee_name
+ employee=self.employee.name, employee_name=self.employee.employee_name
)
leave_allocation.submit()
self.assertTrue(leave_allocation.total_leaves_allocated, 15)
@@ -204,26 +216,29 @@
from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
make_holiday_list()
- frappe.db.set_value("Company", self.employee.company, "default_holiday_list", "Salary Slip Test Holiday List")
+ frappe.db.set_value(
+ "Company", self.employee.company, "default_holiday_list", "Salary Slip Test Holiday List"
+ )
leave_allocation = create_leave_allocation(
- employee=self.employee.name,
- employee_name=self.employee.employee_name
+ employee=self.employee.name, employee_name=self.employee.employee_name
)
leave_allocation.submit()
self.assertTrue(leave_allocation.total_leaves_allocated, 15)
- leave_application = frappe.get_doc({
- "doctype": 'Leave Application',
- "employee": self.employee.name,
- "leave_type": "_Test Leave Type",
- "from_date": add_months(nowdate(), 2),
- "to_date": add_months(add_days(nowdate(), 10), 2),
- "company": self.employee.company,
- "docstatus": 1,
- "status": "Approved",
- "leave_approver": 'test@example.com'
- })
+ leave_application = frappe.get_doc(
+ {
+ "doctype": "Leave Application",
+ "employee": self.employee.name,
+ "leave_type": "_Test Leave Type",
+ "from_date": add_months(nowdate(), 2),
+ "to_date": add_months(add_days(nowdate(), 10), 2),
+ "company": self.employee.company,
+ "docstatus": 1,
+ "status": "Approved",
+ "leave_approver": "test@example.com",
+ }
+ )
leave_application.submit()
leave_application.reload()
@@ -232,22 +247,26 @@
leave_allocation.total_leaves_allocated = leave_application.total_leave_days - 1
self.assertRaises(frappe.ValidationError, leave_allocation.submit)
+
def create_leave_allocation(**args):
args = frappe._dict(args)
emp_id = make_employee("test_emp_leave_allocation@salary.com")
employee = frappe.get_doc("Employee", emp_id)
- return frappe.get_doc({
- "doctype": "Leave Allocation",
- "__islocal": 1,
- "employee": args.employee or employee.name,
- "employee_name": args.employee_name or employee.employee_name,
- "leave_type": args.leave_type or "_Test Leave Type",
- "from_date": args.from_date or nowdate(),
- "new_leaves_allocated": args.new_leaves_allocated or 15,
- "carry_forward": args.carry_forward or 0,
- "to_date": args.to_date or add_months(nowdate(), 12)
- })
+ return frappe.get_doc(
+ {
+ "doctype": "Leave Allocation",
+ "__islocal": 1,
+ "employee": args.employee or employee.name,
+ "employee_name": args.employee_name or employee.employee_name,
+ "leave_type": args.leave_type or "_Test Leave Type",
+ "from_date": args.from_date or nowdate(),
+ "new_leaves_allocated": args.new_leaves_allocated or 15,
+ "carry_forward": args.carry_forward or 0,
+ "to_date": args.to_date or add_months(nowdate(), 12),
+ }
+ )
+
test_dependencies = ["Employee", "Leave Type"]
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 2987c1e..18c69f7 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -32,15 +32,30 @@
)
-class LeaveDayBlockedError(frappe.ValidationError): pass
-class OverlapError(frappe.ValidationError): pass
-class AttendanceAlreadyMarkedError(frappe.ValidationError): pass
-class NotAnOptionalHoliday(frappe.ValidationError): pass
+class LeaveDayBlockedError(frappe.ValidationError):
+ pass
+
+
+class OverlapError(frappe.ValidationError):
+ pass
+
+
+class AttendanceAlreadyMarkedError(frappe.ValidationError):
+ pass
+
+
+class NotAnOptionalHoliday(frappe.ValidationError):
+ pass
+
+
class InsufficientLeaveBalanceError(frappe.ValidationError):
pass
+
+
class LeaveAcrossAllocationsError(frappe.ValidationError):
pass
+
from frappe.model.document import Document
@@ -60,7 +75,7 @@
self.validate_salary_processed_days()
self.validate_attendance()
self.set_half_day_date()
- if frappe.db.get_value("Leave Type", self.leave_type, 'is_optional_leave'):
+ if frappe.db.get_value("Leave Type", self.leave_type, "is_optional_leave"):
self.validate_optional_leave()
self.validate_applicable_after()
@@ -74,7 +89,9 @@
def on_submit(self):
if self.status == "Open":
- frappe.throw(_("Only Leave Applications with status 'Approved' and 'Rejected' can be submitted"))
+ frappe.throw(
+ _("Only Leave Applications with status 'Approved' and 'Rejected' can be submitted")
+ )
self.validate_back_dated_application()
self.update_attendance()
@@ -101,7 +118,9 @@
leave_type = frappe.get_doc("Leave Type", self.leave_type)
if leave_type.applicable_after > 0:
date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
- leave_days = get_approved_leaves_for_period(self.employee, False, date_of_joining, self.from_date)
+ leave_days = get_approved_leaves_for_period(
+ self.employee, False, date_of_joining, self.from_date
+ )
number_of_days = date_diff(getdate(self.from_date), date_of_joining)
if number_of_days >= 0:
holidays = 0
@@ -109,29 +128,48 @@
holidays = get_holidays(self.employee, date_of_joining, self.from_date)
number_of_days = number_of_days - leave_days - holidays
if number_of_days < leave_type.applicable_after:
- frappe.throw(_("{0} applicable after {1} working days").format(self.leave_type, leave_type.applicable_after))
+ frappe.throw(
+ _("{0} applicable after {1} working days").format(
+ self.leave_type, leave_type.applicable_after
+ )
+ )
def validate_dates(self):
if frappe.db.get_single_value("HR Settings", "restrict_backdated_leave_application"):
if self.from_date and getdate(self.from_date) < getdate():
- allowed_role = frappe.db.get_single_value("HR Settings", "role_allowed_to_create_backdated_leave_application")
+ allowed_role = frappe.db.get_single_value(
+ "HR Settings", "role_allowed_to_create_backdated_leave_application"
+ )
user = frappe.get_doc("User", frappe.session.user)
user_roles = [d.role for d in user.roles]
if not allowed_role:
- frappe.throw(_("Backdated Leave Application is restricted. Please set the {} in {}").format(
- frappe.bold("Role Allowed to Create Backdated Leave Application"), get_link_to_form("HR Settings", "HR Settings")))
+ frappe.throw(
+ _("Backdated Leave Application is restricted. Please set the {} in {}").format(
+ frappe.bold("Role Allowed to Create Backdated Leave Application"),
+ get_link_to_form("HR Settings", "HR Settings"),
+ )
+ )
- if (allowed_role and allowed_role not in user_roles):
- frappe.throw(_("Only users with the {0} role can create backdated leave applications").format(allowed_role))
+ if allowed_role and allowed_role not in user_roles:
+ frappe.throw(
+ _("Only users with the {0} role can create backdated leave applications").format(
+ allowed_role
+ )
+ )
if self.from_date and self.to_date and (getdate(self.to_date) < getdate(self.from_date)):
frappe.throw(_("To date cannot be before from date"))
- if self.half_day and self.half_day_date \
- and (getdate(self.half_day_date) < getdate(self.from_date)
- or getdate(self.half_day_date) > getdate(self.to_date)):
+ if (
+ self.half_day
+ and self.half_day_date
+ and (
+ getdate(self.half_day_date) < getdate(self.from_date)
+ or getdate(self.half_day_date) > getdate(self.to_date)
+ )
+ ):
- frappe.throw(_("Half Day Date should be between From Date and To Date"))
+ frappe.throw(_("Half Day Date should be between From Date and To Date"))
if not is_lwp(self.leave_type):
self.validate_dates_across_allocation()
@@ -146,10 +184,14 @@
if not (alloc_on_from_date or alloc_on_to_date):
frappe.throw(_("Application period cannot be outside leave allocation period"))
elif self.is_separate_ledger_entry_required(alloc_on_from_date, alloc_on_to_date):
- frappe.throw(_("Application period cannot be across two allocation records"), exc=LeaveAcrossAllocationsError)
+ frappe.throw(
+ _("Application period cannot be across two allocation records"),
+ exc=LeaveAcrossAllocationsError,
+ )
def get_allocation_based_on_application_dates(self) -> Tuple[Dict, Dict]:
"""Returns allocation name, from and to dates for application dates"""
+
def _get_leave_allocation_record(date):
LeaveAllocation = frappe.qb.DocType("Leave Allocation")
allocation = (
@@ -171,13 +213,20 @@
return allocation_based_on_from_date, allocation_based_on_to_date
def validate_back_dated_application(self):
- future_allocation = frappe.db.sql("""select name, from_date from `tabLeave Allocation`
+ future_allocation = frappe.db.sql(
+ """select name, from_date from `tabLeave Allocation`
where employee=%s and leave_type=%s and docstatus=1 and from_date > %s
- and carry_forward=1""", (self.employee, self.leave_type, self.to_date), as_dict=1)
+ and carry_forward=1""",
+ (self.employee, self.leave_type, self.to_date),
+ as_dict=1,
+ )
if future_allocation:
- frappe.throw(_("Leave cannot be applied/cancelled before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}")
- .format(formatdate(future_allocation[0].from_date), future_allocation[0].name))
+ frappe.throw(
+ _(
+ "Leave cannot be applied/cancelled before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}"
+ ).format(formatdate(future_allocation[0].from_date), future_allocation[0].name)
+ )
def update_attendance(self):
if self.status != "Approved":
@@ -189,8 +238,9 @@
for dt in daterange(getdate(self.from_date), getdate(self.to_date)):
date = dt.strftime("%Y-%m-%d")
- attendance_name = frappe.db.exists("Attendance", dict(employee = self.employee,
- attendance_date = date, docstatus = ('!=', 2)))
+ attendance_name = frappe.db.exists(
+ "Attendance", dict(employee=self.employee, attendance_date=date, docstatus=("!=", 2))
+ )
# don't mark attendance for holidays
# if leave type does not include holidays within leaves as leaves
@@ -207,17 +257,17 @@
self.create_or_update_attendance(attendance_name, date)
def create_or_update_attendance(self, attendance_name, date):
- status = "Half Day" if self.half_day_date and getdate(date) == getdate(self.half_day_date) else "On Leave"
+ status = (
+ "Half Day"
+ if self.half_day_date and getdate(date) == getdate(self.half_day_date)
+ else "On Leave"
+ )
if attendance_name:
# update existing attendance, change absent to on leave
- doc = frappe.get_doc('Attendance', attendance_name)
+ doc = frappe.get_doc("Attendance", attendance_name)
if doc.status != status:
- doc.db_set({
- 'status': status,
- 'leave_type': self.leave_type,
- 'leave_application': self.name
- })
+ doc.db_set({"status": status, "leave_type": self.leave_type, "leave_application": self.name})
else:
# make new attendance and submit it
doc = frappe.new_doc("Attendance")
@@ -234,8 +284,12 @@
def cancel_attendance(self):
if self.docstatus == 2:
- attendance = frappe.db.sql("""select name from `tabAttendance` where employee = %s\
- and (attendance_date between %s and %s) and docstatus < 2 and status in ('On Leave', 'Half Day')""",(self.employee, self.from_date, self.to_date), as_dict=1)
+ attendance = frappe.db.sql(
+ """select name from `tabAttendance` where employee = %s\
+ and (attendance_date between %s and %s) and docstatus < 2 and status in ('On Leave', 'Half Day')""",
+ (self.employee, self.from_date, self.to_date),
+ as_dict=1,
+ )
for name in attendance:
frappe.db.set_value("Attendance", name, "docstatus", 2)
@@ -243,21 +297,29 @@
if not frappe.db.get_value("Leave Type", self.leave_type, "is_lwp"):
return
- last_processed_pay_slip = frappe.db.sql("""
+ last_processed_pay_slip = frappe.db.sql(
+ """
select start_date, end_date from `tabSalary Slip`
where docstatus = 1 and employee = %s
and ((%s between start_date and end_date) or (%s between start_date and end_date))
order by modified desc limit 1
- """,(self.employee, self.to_date, self.from_date))
+ """,
+ (self.employee, self.to_date, self.from_date),
+ )
if last_processed_pay_slip:
- frappe.throw(_("Salary already processed for period between {0} and {1}, Leave application period cannot be between this date range.").format(formatdate(last_processed_pay_slip[0][0]),
- formatdate(last_processed_pay_slip[0][1])))
-
+ frappe.throw(
+ _(
+ "Salary already processed for period between {0} and {1}, Leave application period cannot be between this date range."
+ ).format(
+ formatdate(last_processed_pay_slip[0][0]), formatdate(last_processed_pay_slip[0][1])
+ )
+ )
def show_block_day_warning(self):
- block_dates = get_applicable_block_dates(self.from_date, self.to_date,
- self.employee, self.company, all_lists=True)
+ block_dates = get_applicable_block_dates(
+ self.from_date, self.to_date, self.employee, self.company, all_lists=True
+ )
if block_dates:
frappe.msgprint(_("Warning: Leave application contains following block dates") + ":")
@@ -265,27 +327,41 @@
frappe.msgprint(formatdate(d.block_date) + ": " + d.reason)
def validate_block_days(self):
- block_dates = get_applicable_block_dates(self.from_date, self.to_date,
- self.employee, self.company)
+ block_dates = get_applicable_block_dates(
+ self.from_date, self.to_date, self.employee, self.company
+ )
if block_dates and self.status == "Approved":
frappe.throw(_("You are not authorized to approve leaves on Block Dates"), LeaveDayBlockedError)
def validate_balance_leaves(self):
if self.from_date and self.to_date:
- self.total_leave_days = get_number_of_leave_days(self.employee, self.leave_type,
- self.from_date, self.to_date, self.half_day, self.half_day_date)
+ self.total_leave_days = get_number_of_leave_days(
+ self.employee, self.leave_type, self.from_date, self.to_date, self.half_day, self.half_day_date
+ )
if self.total_leave_days <= 0:
- frappe.throw(_("The day(s) on which you are applying for leave are holidays. You need not apply for leave."))
+ frappe.throw(
+ _(
+ "The day(s) on which you are applying for leave are holidays. You need not apply for leave."
+ )
+ )
if not is_lwp(self.leave_type):
- leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date, self.to_date,
- consider_all_leaves_in_the_allocation_period=True, for_consumption=True)
+ leave_balance = get_leave_balance_on(
+ self.employee,
+ self.leave_type,
+ self.from_date,
+ self.to_date,
+ consider_all_leaves_in_the_allocation_period=True,
+ for_consumption=True,
+ )
self.leave_balance = leave_balance.get("leave_balance")
leave_balance_for_consumption = leave_balance.get("leave_balance_for_consumption")
- if self.status != "Rejected" and (leave_balance_for_consumption < self.total_leave_days or not leave_balance_for_consumption):
+ if self.status != "Rejected" and (
+ leave_balance_for_consumption < self.total_leave_days or not leave_balance_for_consumption
+ ):
self.show_insufficient_balance_message(leave_balance_for_consumption)
def show_insufficient_balance_message(self, leave_balance_for_consumption: float) -> None:
@@ -293,39 +369,57 @@
if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"):
if leave_balance_for_consumption != self.leave_balance:
- msg = _("Warning: Insufficient leave balance for Leave Type {0} in this allocation.").format(frappe.bold(self.leave_type))
+ msg = _("Warning: Insufficient leave balance for Leave Type {0} in this allocation.").format(
+ frappe.bold(self.leave_type)
+ )
msg += "<br><br>"
- msg += _("Actual balances aren't available because the leave application spans over different leave allocations. You can still apply for leaves which would be compensated during the next allocation.")
+ msg += _(
+ "Actual balances aren't available because the leave application spans over different leave allocations. You can still apply for leaves which would be compensated during the next allocation."
+ )
else:
- msg = _("Warning: Insufficient leave balance for Leave Type {0}.").format(frappe.bold(self.leave_type))
+ msg = _("Warning: Insufficient leave balance for Leave Type {0}.").format(
+ frappe.bold(self.leave_type)
+ )
frappe.msgprint(msg, title=_("Warning"), indicator="orange")
else:
- frappe.throw(_("Insufficient leave balance for Leave Type {0}").format(frappe.bold(self.leave_type)),
- exc=InsufficientLeaveBalanceError, title=_("Insufficient Balance"))
+ frappe.throw(
+ _("Insufficient leave balance for Leave Type {0}").format(frappe.bold(self.leave_type)),
+ exc=InsufficientLeaveBalanceError,
+ title=_("Insufficient Balance"),
+ )
def validate_leave_overlap(self):
if not self.name:
# hack! if name is null, it could cause problems with !=
self.name = "New Leave Application"
- for d in frappe.db.sql("""
+ for d in frappe.db.sql(
+ """
select
name, leave_type, posting_date, from_date, to_date, total_leave_days, half_day_date
from `tabLeave Application`
where employee = %(employee)s and docstatus < 2 and status in ("Open", "Approved")
and to_date >= %(from_date)s and from_date <= %(to_date)s
- and name != %(name)s""", {
+ and name != %(name)s""",
+ {
"employee": self.employee,
"from_date": self.from_date,
"to_date": self.to_date,
- "name": self.name
- }, as_dict = 1):
+ "name": self.name,
+ },
+ as_dict=1,
+ ):
- if cint(self.half_day)==1 and getdate(self.half_day_date) == getdate(d.half_day_date) and (
- flt(self.total_leave_days)==0.5
- or getdate(self.from_date) == getdate(d.to_date)
- or getdate(self.to_date) == getdate(d.from_date)):
+ if (
+ cint(self.half_day) == 1
+ and getdate(self.half_day_date) == getdate(d.half_day_date)
+ and (
+ flt(self.total_leave_days) == 0.5
+ or getdate(self.from_date) == getdate(d.to_date)
+ or getdate(self.to_date) == getdate(d.from_date)
+ )
+ ):
total_leaves_on_half_day = self.get_total_leaves_on_half_day()
if total_leaves_on_half_day >= 1:
@@ -335,22 +429,22 @@
def throw_overlap_error(self, d):
form_link = get_link_to_form("Leave Application", d.name)
- msg = _("Employee {0} has already applied for {1} between {2} and {3} : {4}").format(self.employee,
- d['leave_type'], formatdate(d['from_date']), formatdate(d['to_date']), form_link)
+ msg = _("Employee {0} has already applied for {1} between {2} and {3} : {4}").format(
+ self.employee, d["leave_type"], formatdate(d["from_date"]), formatdate(d["to_date"]), form_link
+ )
frappe.throw(msg, OverlapError)
def get_total_leaves_on_half_day(self):
- leave_count_on_half_day_date = frappe.db.sql("""select count(name) from `tabLeave Application`
+ leave_count_on_half_day_date = frappe.db.sql(
+ """select count(name) from `tabLeave Application`
where employee = %(employee)s
and docstatus < 2
and status in ("Open", "Approved")
and half_day = 1
and half_day_date = %(half_day_date)s
- and name != %(name)s""", {
- "employee": self.employee,
- "half_day_date": self.half_day_date,
- "name": self.name
- })[0][0]
+ and name != %(name)s""",
+ {"employee": self.employee, "half_day_date": self.half_day_date, "name": self.name},
+ )[0][0]
return leave_count_on_half_day_date * 0.5
@@ -360,24 +454,36 @@
frappe.throw(_("Leave of type {0} cannot be longer than {1}").format(self.leave_type, max_days))
def validate_attendance(self):
- attendance = frappe.db.sql("""select name from `tabAttendance` where employee = %s and (attendance_date between %s and %s)
+ attendance = frappe.db.sql(
+ """select name from `tabAttendance` where employee = %s and (attendance_date between %s and %s)
and status = "Present" and docstatus = 1""",
- (self.employee, self.from_date, self.to_date))
+ (self.employee, self.from_date, self.to_date),
+ )
if attendance:
- frappe.throw(_("Attendance for employee {0} is already marked for this day").format(self.employee),
- AttendanceAlreadyMarkedError)
+ frappe.throw(
+ _("Attendance for employee {0} is already marked for this day").format(self.employee),
+ AttendanceAlreadyMarkedError,
+ )
def validate_optional_leave(self):
leave_period = get_leave_period(self.from_date, self.to_date, self.company)
if not leave_period:
frappe.throw(_("Cannot find active Leave Period"))
- optional_holiday_list = frappe.db.get_value("Leave Period", leave_period[0]["name"], "optional_holiday_list")
+ optional_holiday_list = frappe.db.get_value(
+ "Leave Period", leave_period[0]["name"], "optional_holiday_list"
+ )
if not optional_holiday_list:
- frappe.throw(_("Optional Holiday List not set for leave period {0}").format(leave_period[0]["name"]))
+ frappe.throw(
+ _("Optional Holiday List not set for leave period {0}").format(leave_period[0]["name"])
+ )
day = getdate(self.from_date)
while day <= getdate(self.to_date):
- if not frappe.db.exists({"doctype": "Holiday", "parent": optional_holiday_list, "holiday_date": day}):
- frappe.throw(_("{0} is not in Optional Holiday List").format(formatdate(day)), NotAnOptionalHoliday)
+ if not frappe.db.exists(
+ {"doctype": "Holiday", "parent": optional_holiday_list, "holiday_date": day}
+ ):
+ frappe.throw(
+ _("{0} is not in Optional Holiday List").format(formatdate(day)), NotAnOptionalHoliday
+ )
day = add_days(day, 1)
def set_half_day_date(self):
@@ -392,44 +498,50 @@
if not employee.user_id:
return
- parent_doc = frappe.get_doc('Leave Application', self.name)
+ parent_doc = frappe.get_doc("Leave Application", self.name)
args = parent_doc.as_dict()
- template = frappe.db.get_single_value('HR Settings', 'leave_status_notification_template')
+ template = frappe.db.get_single_value("HR Settings", "leave_status_notification_template")
if not template:
frappe.msgprint(_("Please set default template for Leave Status Notification in HR Settings."))
return
email_template = frappe.get_doc("Email Template", template)
message = frappe.render_template(email_template.response, args)
- self.notify({
- # for post in messages
- "message": message,
- "message_to": employee.user_id,
- # for email
- "subject": email_template.subject,
- "notify": "employee"
- })
+ self.notify(
+ {
+ # for post in messages
+ "message": message,
+ "message_to": employee.user_id,
+ # for email
+ "subject": email_template.subject,
+ "notify": "employee",
+ }
+ )
def notify_leave_approver(self):
if self.leave_approver:
- parent_doc = frappe.get_doc('Leave Application', self.name)
+ parent_doc = frappe.get_doc("Leave Application", self.name)
args = parent_doc.as_dict()
- template = frappe.db.get_single_value('HR Settings', 'leave_approval_notification_template')
+ template = frappe.db.get_single_value("HR Settings", "leave_approval_notification_template")
if not template:
- frappe.msgprint(_("Please set default template for Leave Approval Notification in HR Settings."))
+ frappe.msgprint(
+ _("Please set default template for Leave Approval Notification in HR Settings.")
+ )
return
email_template = frappe.get_doc("Email Template", template)
message = frappe.render_template(email_template.response, args)
- self.notify({
- # for post in messages
- "message": message,
- "message_to": self.leave_approver,
- # for email
- "subject": email_template.subject
- })
+ self.notify(
+ {
+ # for post in messages
+ "message": message,
+ "message_to": self.leave_approver,
+ # for email
+ "subject": email_template.subject,
+ }
+ )
def notify(self, args):
args = frappe._dict(args)
@@ -438,29 +550,30 @@
contact = args.message_to
if not isinstance(contact, list):
if not args.notify == "employee":
- contact = frappe.get_doc('User', contact).email or contact
+ contact = frappe.get_doc("User", contact).email or contact
- sender = dict()
- sender['email'] = frappe.get_doc('User', frappe.session.user).email
- sender['full_name'] = get_fullname(sender['email'])
+ sender = dict()
+ sender["email"] = frappe.get_doc("User", frappe.session.user).email
+ sender["full_name"] = get_fullname(sender["email"])
try:
frappe.sendmail(
- recipients = contact,
- sender = sender['email'],
- subject = args.subject,
- message = args.message,
+ recipients=contact,
+ sender=sender["email"],
+ subject=args.subject,
+ message=args.message,
)
frappe.msgprint(_("Email sent to {0}").format(contact))
except frappe.OutgoingEmailError:
pass
def create_leave_ledger_entry(self, submit=True):
- if self.status != 'Approved' and submit:
+ if self.status != "Approved" and submit:
return
- expiry_date = get_allocation_expiry_for_cf_leaves(self.employee, self.leave_type,
- self.to_date, self.from_date)
+ expiry_date = get_allocation_expiry_for_cf_leaves(
+ self.employee, self.leave_type, self.to_date, self.from_date
+ )
lwp = frappe.db.get_value("Leave Type", self.leave_type, "is_lwp")
if expiry_date:
@@ -478,24 +591,42 @@
from_date=self.from_date,
to_date=self.to_date,
is_lwp=lwp,
- holiday_list=get_holiday_list_for_employee(self.employee, raise_exception=raise_exception) or ''
+ holiday_list=get_holiday_list_for_employee(self.employee, raise_exception=raise_exception)
+ or "",
)
create_leave_ledger_entry(self, args, submit)
- def is_separate_ledger_entry_required(self, alloc_on_from_date: Optional[Dict] = None, alloc_on_to_date: Optional[Dict] = None) -> bool:
+ def is_separate_ledger_entry_required(
+ self, alloc_on_from_date: Optional[Dict] = None, alloc_on_to_date: Optional[Dict] = None
+ ) -> bool:
"""Checks if application dates fall in separate allocations"""
- if ((alloc_on_from_date and not alloc_on_to_date)
+ if (
+ (alloc_on_from_date and not alloc_on_to_date)
or (not alloc_on_from_date and alloc_on_to_date)
- or (alloc_on_from_date and alloc_on_to_date and alloc_on_from_date.name != alloc_on_to_date.name)):
+ or (
+ alloc_on_from_date and alloc_on_to_date and alloc_on_from_date.name != alloc_on_to_date.name
+ )
+ ):
return True
return False
def create_separate_ledger_entries(self, alloc_on_from_date, alloc_on_to_date, submit, lwp):
"""Creates separate ledger entries for application period falling into separate allocations"""
# for creating separate ledger entries existing allocation periods should be consecutive
- if submit and alloc_on_from_date and alloc_on_to_date and add_days(alloc_on_from_date.to_date, 1) != alloc_on_to_date.from_date:
- frappe.throw(_("Leave Application period cannot be across two non-consecutive leave allocations {0} and {1}.").format(
- get_link_to_form("Leave Allocation", alloc_on_from_date.name), get_link_to_form("Leave Allocation", alloc_on_to_date)))
+ if (
+ submit
+ and alloc_on_from_date
+ and alloc_on_to_date
+ and add_days(alloc_on_from_date.to_date, 1) != alloc_on_to_date.from_date
+ ):
+ frappe.throw(
+ _(
+ "Leave Application period cannot be across two non-consecutive leave allocations {0} and {1}."
+ ).format(
+ get_link_to_form("Leave Allocation", alloc_on_from_date.name),
+ get_link_to_form("Leave Allocation", alloc_on_to_date),
+ )
+ )
raise_exception = False if frappe.flags.in_patch else True
@@ -506,38 +637,48 @@
first_alloc_end = add_days(alloc_on_to_date.from_date, -1)
second_alloc_start = alloc_on_to_date.from_date
- leaves_in_first_alloc = get_number_of_leave_days(self.employee, self.leave_type,
- self.from_date, first_alloc_end, self.half_day, self.half_day_date)
- leaves_in_second_alloc = get_number_of_leave_days(self.employee, self.leave_type,
- second_alloc_start, self.to_date, self.half_day, self.half_day_date)
+ leaves_in_first_alloc = get_number_of_leave_days(
+ self.employee,
+ self.leave_type,
+ self.from_date,
+ first_alloc_end,
+ self.half_day,
+ self.half_day_date,
+ )
+ leaves_in_second_alloc = get_number_of_leave_days(
+ self.employee,
+ self.leave_type,
+ second_alloc_start,
+ self.to_date,
+ self.half_day,
+ self.half_day_date,
+ )
args = dict(
is_lwp=lwp,
- holiday_list=get_holiday_list_for_employee(self.employee, raise_exception=raise_exception) or ''
+ holiday_list=get_holiday_list_for_employee(self.employee, raise_exception=raise_exception)
+ or "",
)
if leaves_in_first_alloc:
- args.update(dict(
- from_date=self.from_date,
- to_date=first_alloc_end,
- leaves=leaves_in_first_alloc * -1
- ))
+ args.update(
+ dict(from_date=self.from_date, to_date=first_alloc_end, leaves=leaves_in_first_alloc * -1)
+ )
create_leave_ledger_entry(self, args, submit)
if leaves_in_second_alloc:
- args.update(dict(
- from_date=second_alloc_start,
- to_date=self.to_date,
- leaves=leaves_in_second_alloc * -1
- ))
+ args.update(
+ dict(from_date=second_alloc_start, to_date=self.to_date, leaves=leaves_in_second_alloc * -1)
+ )
create_leave_ledger_entry(self, args, submit)
def create_ledger_entry_for_intermediate_allocation_expiry(self, expiry_date, submit, lwp):
"""Splits leave application into two ledger entries to consider expiry of allocation"""
raise_exception = False if frappe.flags.in_patch else True
- leaves = get_number_of_leave_days(self.employee, self.leave_type,
- self.from_date, expiry_date, self.half_day, self.half_day_date)
+ leaves = get_number_of_leave_days(
+ self.employee, self.leave_type, self.from_date, expiry_date, self.half_day, self.half_day_date
+ )
if leaves:
args = dict(
@@ -545,41 +686,51 @@
to_date=expiry_date,
leaves=leaves * -1,
is_lwp=lwp,
- holiday_list=get_holiday_list_for_employee(self.employee, raise_exception=raise_exception) or ''
+ holiday_list=get_holiday_list_for_employee(self.employee, raise_exception=raise_exception)
+ or "",
)
create_leave_ledger_entry(self, args, submit)
if getdate(expiry_date) != getdate(self.to_date):
start_date = add_days(expiry_date, 1)
- leaves = get_number_of_leave_days(self.employee, self.leave_type,
- start_date, self.to_date, self.half_day, self.half_day_date)
+ leaves = get_number_of_leave_days(
+ self.employee, self.leave_type, start_date, self.to_date, self.half_day, self.half_day_date
+ )
if leaves:
- args.update(dict(
- from_date=start_date,
- to_date=self.to_date,
- leaves=leaves * -1
- ))
+ args.update(dict(from_date=start_date, to_date=self.to_date, leaves=leaves * -1))
create_leave_ledger_entry(self, args, submit)
-def get_allocation_expiry_for_cf_leaves(employee: str, leave_type: str, to_date: str, from_date: str) -> str:
- ''' Returns expiry of carry forward allocation in leave ledger entry '''
- expiry = frappe.get_all("Leave Ledger Entry",
+def get_allocation_expiry_for_cf_leaves(
+ employee: str, leave_type: str, to_date: str, from_date: str
+) -> str:
+ """Returns expiry of carry forward allocation in leave ledger entry"""
+ expiry = frappe.get_all(
+ "Leave Ledger Entry",
filters={
- 'employee': employee,
- 'leave_type': leave_type,
- 'is_carry_forward': 1,
- 'transaction_type': 'Leave Allocation',
- 'to_date': ['between', (from_date, to_date)],
- 'docstatus': 1
- },fields=['to_date'])
- return expiry[0]['to_date'] if expiry else ''
+ "employee": employee,
+ "leave_type": leave_type,
+ "is_carry_forward": 1,
+ "transaction_type": "Leave Allocation",
+ "to_date": ["between", (from_date, to_date)],
+ "docstatus": 1,
+ },
+ fields=["to_date"],
+ )
+ return expiry[0]["to_date"] if expiry else ""
@frappe.whitelist()
-def get_number_of_leave_days(employee: str, leave_type: str, from_date: str, to_date: str, half_day: Optional[int] = None,
- half_day_date: Optional[str] = None, holiday_list: Optional[str] = None) -> float:
+def get_number_of_leave_days(
+ employee: str,
+ leave_type: str,
+ from_date: str,
+ to_date: str,
+ half_day: Optional[int] = None,
+ half_day_date: Optional[str] = None,
+ holiday_list: Optional[str] = None,
+) -> float:
"""Returns number of leave days between 2 dates after considering half day and holidays
(Based on the include_holiday setting in Leave Type)"""
number_of_days = 0
@@ -587,14 +738,16 @@
if from_date == to_date:
number_of_days = 0.5
elif half_day_date and half_day_date <= to_date:
- number_of_days = date_diff(to_date, from_date) + .5
+ number_of_days = date_diff(to_date, from_date) + 0.5
else:
number_of_days = date_diff(to_date, from_date) + 1
else:
number_of_days = date_diff(to_date, from_date) + 1
if not frappe.db.get_value("Leave Type", leave_type, "include_holiday"):
- number_of_days = flt(number_of_days) - flt(get_holidays(employee, from_date, to_date, holiday_list=holiday_list))
+ number_of_days = flt(number_of_days) - flt(
+ get_holidays(employee, from_date, to_date, holiday_list=holiday_list)
+ )
return number_of_days
@@ -605,54 +758,71 @@
for d in allocation_records:
allocation = allocation_records.get(d, frappe._dict())
- total_allocated_leaves = frappe.db.get_value('Leave Allocation', {
- 'from_date': ('<=', date),
- 'to_date': ('>=', date),
- 'employee': employee,
- 'leave_type': allocation.leave_type,
- 'docstatus': 1
- }, 'SUM(total_leaves_allocated)') or 0
+ total_allocated_leaves = (
+ frappe.db.get_value(
+ "Leave Allocation",
+ {
+ "from_date": ("<=", date),
+ "to_date": (">=", date),
+ "employee": employee,
+ "leave_type": allocation.leave_type,
+ "docstatus": 1,
+ },
+ "SUM(total_leaves_allocated)",
+ )
+ or 0
+ )
- remaining_leaves = get_leave_balance_on(employee, d, date, to_date = allocation.to_date,
- consider_all_leaves_in_the_allocation_period=True)
+ remaining_leaves = get_leave_balance_on(
+ employee, d, date, to_date=allocation.to_date, consider_all_leaves_in_the_allocation_period=True
+ )
end_date = allocation.to_date
leaves_taken = get_leaves_for_period(employee, d, allocation.from_date, end_date) * -1
- leaves_pending = get_leaves_pending_approval_for_period(employee, d, allocation.from_date, end_date)
+ leaves_pending = get_leaves_pending_approval_for_period(
+ employee, d, allocation.from_date, end_date
+ )
leave_allocation[d] = {
"total_leaves": total_allocated_leaves,
"expired_leaves": total_allocated_leaves - (remaining_leaves + leaves_taken),
"leaves_taken": leaves_taken,
"leaves_pending_approval": leaves_pending,
- "remaining_leaves": remaining_leaves}
+ "remaining_leaves": remaining_leaves,
+ }
- #is used in set query
+ # is used in set query
lwp = frappe.get_list("Leave Type", filters={"is_lwp": 1}, pluck="name")
return {
"leave_allocation": leave_allocation,
"leave_approver": get_leave_approver(employee),
- "lwps": lwp
+ "lwps": lwp,
}
@frappe.whitelist()
-def get_leave_balance_on(employee: str, leave_type: str, date: str, to_date: str = None,
- consider_all_leaves_in_the_allocation_period: bool = False, for_consumption: bool = False):
- '''
- Returns leave balance till date
- :param employee: employee name
- :param leave_type: leave type
- :param date: date to check balance on
- :param to_date: future date to check for allocation expiry
- :param consider_all_leaves_in_the_allocation_period: consider all leaves taken till the allocation end date
- :param for_consumption: flag to check if leave balance is required for consumption or display
- eg: employee has leave balance = 10 but allocation is expiring in 1 day so employee can only consume 1 leave
- in this case leave_balance = 10 but leave_balance_for_consumption = 1
- if True, returns a dict eg: {'leave_balance': 10, 'leave_balance_for_consumption': 1}
- else, returns leave_balance (in this case 10)
- '''
+def get_leave_balance_on(
+ employee: str,
+ leave_type: str,
+ date: str,
+ to_date: str = None,
+ consider_all_leaves_in_the_allocation_period: bool = False,
+ for_consumption: bool = False,
+):
+ """
+ Returns leave balance till date
+ :param employee: employee name
+ :param leave_type: leave type
+ :param date: date to check balance on
+ :param to_date: future date to check for allocation expiry
+ :param consider_all_leaves_in_the_allocation_period: consider all leaves taken till the allocation end date
+ :param for_consumption: flag to check if leave balance is required for consumption or display
+ eg: employee has leave balance = 10 but allocation is expiring in 1 day so employee can only consume 1 leave
+ in this case leave_balance = 10 but leave_balance_for_consumption = 1
+ if True, returns a dict eg: {'leave_balance': 10, 'leave_balance_for_consumption': 1}
+ else, returns leave_balance (in this case 10)
+ """
if not to_date:
to_date = nowdate()
@@ -670,17 +840,21 @@
if for_consumption:
return remaining_leaves
else:
- return remaining_leaves.get('leave_balance')
+ return remaining_leaves.get("leave_balance")
def get_leave_allocation_records(employee, date, leave_type=None):
"""Returns the total allocated leaves and carry forwarded leaves based on ledger entries"""
Ledger = frappe.qb.DocType("Leave Ledger Entry")
- cf_leave_case = frappe.qb.terms.Case().when(Ledger.is_carry_forward == "1", Ledger.leaves).else_(0)
+ cf_leave_case = (
+ frappe.qb.terms.Case().when(Ledger.is_carry_forward == "1", Ledger.leaves).else_(0)
+ )
sum_cf_leaves = Sum(cf_leave_case).as_("cf_leaves")
- new_leaves_case = frappe.qb.terms.Case().when(Ledger.is_carry_forward == "0", Ledger.leaves).else_(0)
+ new_leaves_case = (
+ frappe.qb.terms.Case().when(Ledger.is_carry_forward == "0", Ledger.leaves).else_(0)
+ )
sum_new_leaves = Sum(new_leaves_case).as_("new_leaves")
query = (
@@ -690,8 +864,9 @@
sum_new_leaves,
Min(Ledger.from_date).as_("from_date"),
Max(Ledger.to_date).as_("to_date"),
- Ledger.leave_type
- ).where(
+ Ledger.leave_type,
+ )
+ .where(
(Ledger.from_date <= date)
& (Ledger.to_date >= date)
& (Ledger.docstatus == 1)
@@ -710,46 +885,57 @@
allocated_leaves = frappe._dict()
for d in allocation_details:
- allocated_leaves.setdefault(d.leave_type, frappe._dict({
- "from_date": d.from_date,
- "to_date": d.to_date,
- "total_leaves_allocated": flt(d.cf_leaves) + flt(d.new_leaves),
- "unused_leaves": d.cf_leaves,
- "new_leaves_allocated": d.new_leaves,
- "leave_type": d.leave_type
- }))
+ allocated_leaves.setdefault(
+ d.leave_type,
+ frappe._dict(
+ {
+ "from_date": d.from_date,
+ "to_date": d.to_date,
+ "total_leaves_allocated": flt(d.cf_leaves) + flt(d.new_leaves),
+ "unused_leaves": d.cf_leaves,
+ "new_leaves_allocated": d.new_leaves,
+ "leave_type": d.leave_type,
+ }
+ ),
+ )
return allocated_leaves
-def get_leaves_pending_approval_for_period(employee: str, leave_type: str, from_date: str, to_date: str) -> float:
- ''' Returns leaves that are pending for approval '''
- leaves = frappe.get_all("Leave Application",
- filters={
- "employee": employee,
- "leave_type": leave_type,
- "status": "Open"
- },
+def get_leaves_pending_approval_for_period(
+ employee: str, leave_type: str, from_date: str, to_date: str
+) -> float:
+ """Returns leaves that are pending for approval"""
+ leaves = frappe.get_all(
+ "Leave Application",
+ filters={"employee": employee, "leave_type": leave_type, "status": "Open"},
or_filters={
"from_date": ["between", (from_date, to_date)],
- "to_date": ["between", (from_date, to_date)]
- }, fields=['SUM(total_leave_days) as leaves'])[0]
- return leaves['leaves'] if leaves['leaves'] else 0.0
+ "to_date": ["between", (from_date, to_date)],
+ },
+ fields=["SUM(total_leave_days) as leaves"],
+ )[0]
+ return leaves["leaves"] if leaves["leaves"] else 0.0
-def get_remaining_leaves(allocation: Dict, leaves_taken: float, date: str, cf_expiry: str) -> Dict[str, float]:
- '''Returns a dict of leave_balance and leave_balance_for_consumption
+def get_remaining_leaves(
+ allocation: Dict, leaves_taken: float, date: str, cf_expiry: str
+) -> Dict[str, float]:
+ """Returns a dict of leave_balance and leave_balance_for_consumption
leave_balance returns the available leave balance
leave_balance_for_consumption returns the minimum leaves remaining after comparing with remaining days for allocation expiry
- '''
+ """
+
def _get_remaining_leaves(remaining_leaves, end_date):
- ''' Returns minimum leaves remaining after comparing with remaining days for allocation expiry '''
+ """Returns minimum leaves remaining after comparing with remaining days for allocation expiry"""
if remaining_leaves > 0:
remaining_days = date_diff(end_date, date) + 1
remaining_leaves = min(remaining_days, remaining_leaves)
return remaining_leaves
- leave_balance = leave_balance_for_consumption = flt(allocation.total_leaves_allocated) + flt(leaves_taken)
+ leave_balance = leave_balance_for_consumption = flt(allocation.total_leaves_allocated) + flt(
+ leaves_taken
+ )
# balance for carry forwarded leaves
if cf_expiry and allocation.unused_leaves:
@@ -763,21 +949,29 @@
return frappe._dict(leave_balance=leave_balance, leave_balance_for_consumption=remaining_leaves)
-def get_leaves_for_period(employee: str, leave_type: str, from_date: str, to_date: str, skip_expired_leaves: bool = True) -> float:
+def get_leaves_for_period(
+ employee: str, leave_type: str, from_date: str, to_date: str, skip_expired_leaves: bool = True
+) -> float:
leave_entries = get_leave_entries(employee, leave_type, from_date, to_date)
leave_days = 0
for leave_entry in leave_entries:
- inclusive_period = leave_entry.from_date >= getdate(from_date) and leave_entry.to_date <= getdate(to_date)
+ inclusive_period = leave_entry.from_date >= getdate(
+ from_date
+ ) and leave_entry.to_date <= getdate(to_date)
- if inclusive_period and leave_entry.transaction_type == 'Leave Encashment':
+ if inclusive_period and leave_entry.transaction_type == "Leave Encashment":
leave_days += leave_entry.leaves
- elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' and leave_entry.is_expired \
- and not skip_expired_leaves:
+ elif (
+ inclusive_period
+ and leave_entry.transaction_type == "Leave Allocation"
+ and leave_entry.is_expired
+ and not skip_expired_leaves
+ ):
leave_days += leave_entry.leaves
- elif leave_entry.transaction_type == 'Leave Application':
+ elif leave_entry.transaction_type == "Leave Application":
if leave_entry.from_date < getdate(from_date):
leave_entry.from_date = from_date
if leave_entry.to_date > getdate(to_date):
@@ -788,18 +982,30 @@
# fetch half day date for leaves with half days
if leave_entry.leaves % 1:
half_day = 1
- half_day_date = frappe.db.get_value('Leave Application',
- {'name': leave_entry.transaction_name}, ['half_day_date'])
+ half_day_date = frappe.db.get_value(
+ "Leave Application", {"name": leave_entry.transaction_name}, ["half_day_date"]
+ )
- leave_days += get_number_of_leave_days(employee, leave_type,
- leave_entry.from_date, leave_entry.to_date, half_day, half_day_date, holiday_list=leave_entry.holiday_list) * -1
+ leave_days += (
+ get_number_of_leave_days(
+ employee,
+ leave_type,
+ leave_entry.from_date,
+ leave_entry.to_date,
+ half_day,
+ half_day_date,
+ holiday_list=leave_entry.holiday_list,
+ )
+ * -1
+ )
return leave_days
def get_leave_entries(employee, leave_type, from_date, to_date):
- ''' Returns leave entries between from_date and to_date. '''
- return frappe.db.sql("""
+ """Returns leave entries between from_date and to_date."""
+ return frappe.db.sql(
+ """
SELECT
employee, leave_type, from_date, to_date, leaves, transaction_name, transaction_type, holiday_list,
is_carry_forward, is_expired
@@ -811,26 +1017,28 @@
AND (from_date between %(from_date)s AND %(to_date)s
OR to_date between %(from_date)s AND %(to_date)s
OR (from_date < %(from_date)s AND to_date > %(to_date)s))
- """, {
- "from_date": from_date,
- "to_date": to_date,
- "employee": employee,
- "leave_type": leave_type
- }, as_dict=1)
+ """,
+ {"from_date": from_date, "to_date": to_date, "employee": employee, "leave_type": leave_type},
+ as_dict=1,
+ )
@frappe.whitelist()
-def get_holidays(employee, from_date, to_date, holiday_list = None):
- '''get holidays between two dates for the given employee'''
+def get_holidays(employee, from_date, to_date, holiday_list=None):
+ """get holidays between two dates for the given employee"""
if not holiday_list:
holiday_list = get_holiday_list_for_employee(employee)
- holidays = frappe.db.sql("""select count(distinct holiday_date) from `tabHoliday` h1, `tabHoliday List` h2
+ holidays = frappe.db.sql(
+ """select count(distinct holiday_date) from `tabHoliday` h1, `tabHoliday List` h2
where h1.parent = h2.name and h1.holiday_date between %s and %s
- and h2.name = %s""", (from_date, to_date, holiday_list))[0][0]
+ and h2.name = %s""",
+ (from_date, to_date, holiday_list),
+ )[0][0]
return holidays
+
def is_lwp(leave_type):
lwp = frappe.db.sql("select is_lwp from `tabLeave Type` where name = %s", leave_type)
return lwp and cint(lwp[0][0]) or 0
@@ -839,18 +1047,17 @@
@frappe.whitelist()
def get_events(start, end, filters=None):
from frappe.desk.reportview import get_filters_cond
+
events = []
- employee = frappe.db.get_value("Employee",
- filters={"user_id": frappe.session.user},
- fieldname=["name", "company"],
- as_dict=True
+ employee = frappe.db.get_value(
+ "Employee", filters={"user_id": frappe.session.user}, fieldname=["name", "company"], as_dict=True
)
if employee:
employee, company = employee.name, employee.company
else:
- employee = ''
+ employee = ""
company = frappe.db.get_value("Global Defaults", None, "default_company")
conditions = get_filters_cond("Leave Application", filters, [])
@@ -872,18 +1079,24 @@
return
# department leaves
- department_employees = frappe.db.sql_list("""select name from tabEmployee where department=%s
- and company=%s""", (department, company))
+ department_employees = frappe.db.sql_list(
+ """select name from tabEmployee where department=%s
+ and company=%s""",
+ (department, company),
+ )
- filter_conditions = " and employee in (\"%s\")" % '", "'.join(department_employees)
+ filter_conditions = ' and employee in ("%s")' % '", "'.join(department_employees)
add_leaves(events, start, end, filter_conditions=filter_conditions)
def add_leaves(events, start, end, filter_conditions=None):
from frappe.desk.reportview import build_match_conditions
+
conditions = []
- if not cint(frappe.db.get_value("HR Settings", None, "show_leaves_of_all_department_members_in_calendar")):
+ if not cint(
+ frappe.db.get_value("HR Settings", None, "show_leaves_of_all_department_members_in_calendar")
+ ):
match_conditions = build_match_conditions("Leave Application")
if match_conditions:
@@ -908,12 +1121,12 @@
"""
if conditions:
- query += ' AND ' + ' AND '.join(conditions)
+ query += " AND " + " AND ".join(conditions)
if filter_conditions:
query += filter_conditions
- for d in frappe.db.sql(query, {"start":start, "end": end}, as_dict=True):
+ for d in frappe.db.sql(query, {"start": start, "end": end}, as_dict=True):
e = {
"name": d.name,
"doctype": "Leave Application",
@@ -922,7 +1135,9 @@
"docstatus": d.docstatus,
"color": d.color,
"all_day": int(not d.half_day),
- "title": cstr(d.employee_name) + f' ({cstr(d.leave_type)})' + (' ' + _('(Half Day)') if d.half_day else ''),
+ "title": cstr(d.employee_name)
+ + f" ({cstr(d.leave_type)})"
+ + (" " + _("(Half Day)") if d.half_day else ""),
}
if e not in events:
events.append(e)
@@ -936,14 +1151,16 @@
block_dates = get_applicable_block_dates(start, end, employee, company, all_lists=True)
for block_date in block_dates:
- events.append({
- "doctype": "Leave Block List Date",
- "from_date": block_date.block_date,
- "to_date": block_date.block_date,
- "title": _("Leave Blocked") + ": " + block_date.reason,
- "name": "_" + str(cnt),
- })
- cnt+=1
+ events.append(
+ {
+ "doctype": "Leave Block List Date",
+ "from_date": block_date.block_date,
+ "to_date": block_date.block_date,
+ "title": _("Leave Blocked") + ": " + block_date.reason,
+ "name": "_" + str(cnt),
+ }
+ )
+ cnt += 1
def add_holidays(events, start, end, employee, company):
@@ -951,27 +1168,34 @@
if not applicable_holiday_list:
return
- for holiday in frappe.db.sql("""select name, holiday_date, description
+ for holiday in frappe.db.sql(
+ """select name, holiday_date, description
from `tabHoliday` where parent=%s and holiday_date between %s and %s""",
- (applicable_holiday_list, start, end), as_dict=True):
- events.append({
+ (applicable_holiday_list, start, end),
+ as_dict=True,
+ ):
+ events.append(
+ {
"doctype": "Holiday",
"from_date": holiday.holiday_date,
- "to_date": holiday.holiday_date,
+ "to_date": holiday.holiday_date,
"title": _("Holiday") + ": " + cstr(holiday.description),
- "name": holiday.name
- })
+ "name": holiday.name,
+ }
+ )
@frappe.whitelist()
def get_mandatory_approval(doctype):
mandatory = ""
if doctype == "Leave Application":
- mandatory = frappe.db.get_single_value('HR Settings',
- 'leave_approver_mandatory_in_leave_application')
+ mandatory = frappe.db.get_single_value(
+ "HR Settings", "leave_approver_mandatory_in_leave_application"
+ )
else:
- mandatory = frappe.db.get_single_value('HR Settings',
- 'expense_approver_mandatory_in_expense_claim')
+ mandatory = frappe.db.get_single_value(
+ "HR Settings", "expense_approver_mandatory_in_expense_claim"
+ )
return mandatory
@@ -989,12 +1213,11 @@
if leave_type:
query += "and leave_type=%(leave_type)s"
- leave_applications = frappe.db.sql(query,{
- "from_date": from_date,
- "to_date": to_date,
- "employee": employee,
- "leave_type": leave_type
- }, as_dict=1)
+ leave_applications = frappe.db.sql(
+ query,
+ {"from_date": from_date, "to_date": to_date, "employee": employee, "leave_type": leave_type},
+ as_dict=1,
+ )
leave_days = 0
for leave_app in leave_applications:
@@ -1006,19 +1229,24 @@
if leave_app.to_date > getdate(to_date):
leave_app.to_date = to_date
- leave_days += get_number_of_leave_days(employee, leave_type,
- leave_app.from_date, leave_app.to_date)
+ leave_days += get_number_of_leave_days(
+ employee, leave_type, leave_app.from_date, leave_app.to_date
+ )
return leave_days
@frappe.whitelist()
def get_leave_approver(employee):
- leave_approver, department = frappe.db.get_value("Employee",
- employee, ["leave_approver", "department"])
+ leave_approver, department = frappe.db.get_value(
+ "Employee", employee, ["leave_approver", "department"]
+ )
if not leave_approver and department:
- leave_approver = frappe.db.get_value('Department Approver', {'parent': department,
- 'parentfield': 'leave_approvers', 'idx': 1}, 'approver')
+ leave_approver = frappe.db.get_value(
+ "Department Approver",
+ {"parent": department, "parentfield": "leave_approvers", "idx": 1},
+ "approver",
+ )
return leave_approver
diff --git a/erpnext/hr/doctype/leave_application/leave_application_dashboard.py b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py
index 8b0b98d..ee5cbe9 100644
--- a/erpnext/hr/doctype/leave_application/leave_application_dashboard.py
+++ b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py
@@ -3,16 +3,7 @@
def get_data():
return {
- 'fieldname': 'leave_application',
- 'transactions': [
- {
- 'items': ['Attendance']
- }
- ],
- 'reports': [
- {
- 'label': _('Reports'),
- 'items': ['Employee Leave Balance']
- }
- ]
- }
+ "fieldname": "leave_application",
+ "transactions": [{"items": ["Attendance"]}],
+ "reports": [{"label": _("Reports"), "items": ["Employee Leave Balance"]}],
+ }
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index 3a30990..f33d0af 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -49,7 +49,7 @@
"description": "_Test Reason",
"leave_type": "_Test Leave Type",
"posting_date": "2013-01-02",
- "to_date": "2013-05-05"
+ "to_date": "2013-05-05",
},
{
"company": "_Test Company",
@@ -59,7 +59,7 @@
"description": "_Test Reason",
"leave_type": "_Test Leave Type",
"posting_date": "2013-01-02",
- "to_date": "2013-05-05"
+ "to_date": "2013-05-05",
},
{
"company": "_Test Company",
@@ -69,8 +69,8 @@
"description": "_Test Reason",
"leave_type": "_Test Leave Type LWP",
"posting_date": "2013-01-02",
- "to_date": "2013-01-15"
- }
+ "to_date": "2013-01-15",
+ },
]
@@ -90,19 +90,19 @@
self.holiday_list = make_holiday_list(from_date=from_date, to_date=to_date)
if not frappe.db.exists("Leave Type", "_Test Leave Type"):
- frappe.get_doc(dict(
- leave_type_name="_Test Leave Type",
- doctype="Leave Type",
- include_holiday=True
- )).insert()
+ frappe.get_doc(
+ dict(leave_type_name="_Test Leave Type", doctype="Leave Type", include_holiday=True)
+ ).insert()
def tearDown(self):
frappe.db.rollback()
frappe.set_user("Administrator")
def _clear_roles(self):
- frappe.db.sql("""delete from `tabHas Role` where parent in
- ("test@example.com", "test1@example.com", "test2@example.com")""")
+ frappe.db.sql(
+ """delete from `tabHas Role` where parent in
+ ("test@example.com", "test1@example.com", "test2@example.com")"""
+ )
def _clear_applications(self):
frappe.db.sql("""delete from `tabLeave Application`""")
@@ -113,91 +113,100 @@
application.to_date = "2013-01-05"
return application
- @set_holiday_list('Salary Slip Test Holiday List', '_Test Company')
+ @set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_validate_application_across_allocations(self):
# Test validation for application dates when negative balance is disabled
frappe.delete_doc_if_exists("Leave Type", "Test Leave Validation", force=1)
- leave_type = frappe.get_doc(dict(
- leave_type_name="Test Leave Validation",
- doctype="Leave Type",
- allow_negative=False
- )).insert()
+ leave_type = frappe.get_doc(
+ dict(leave_type_name="Test Leave Validation", doctype="Leave Type", allow_negative=False)
+ ).insert()
employee = get_employee()
date = getdate()
first_sunday = get_first_sunday(self.holiday_list, for_date=get_year_start(date))
- leave_application = frappe.get_doc(dict(
- doctype='Leave Application',
- employee=employee.name,
- leave_type=leave_type.name,
- from_date=add_days(first_sunday, 1),
- to_date=add_days(first_sunday, 4),
- company="_Test Company",
- status="Approved",
- leave_approver = 'test@example.com'
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type.name,
+ from_date=add_days(first_sunday, 1),
+ to_date=add_days(first_sunday, 4),
+ company="_Test Company",
+ status="Approved",
+ leave_approver="test@example.com",
+ )
+ )
# Application period cannot be outside leave allocation period
self.assertRaises(frappe.ValidationError, leave_application.insert)
- make_allocation_record(leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date))
+ make_allocation_record(
+ leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date)
+ )
- leave_application = frappe.get_doc(dict(
- doctype='Leave Application',
- employee=employee.name,
- leave_type=leave_type.name,
- from_date=add_days(first_sunday, -10),
- to_date=add_days(first_sunday, 1),
- company="_Test Company",
- status="Approved",
- leave_approver = 'test@example.com'
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type.name,
+ from_date=add_days(first_sunday, -10),
+ to_date=add_days(first_sunday, 1),
+ company="_Test Company",
+ status="Approved",
+ leave_approver="test@example.com",
+ )
+ )
# Application period cannot be across two allocation records
self.assertRaises(LeaveAcrossAllocationsError, leave_application.insert)
- @set_holiday_list('Salary Slip Test Holiday List', '_Test Company')
+ @set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_insufficient_leave_balance_validation(self):
# CASE 1: Validation when allow negative is disabled
frappe.delete_doc_if_exists("Leave Type", "Test Leave Validation", force=1)
- leave_type = frappe.get_doc(dict(
- leave_type_name="Test Leave Validation",
- doctype="Leave Type",
- allow_negative=False
- )).insert()
+ leave_type = frappe.get_doc(
+ dict(leave_type_name="Test Leave Validation", doctype="Leave Type", allow_negative=False)
+ ).insert()
employee = get_employee()
date = getdate()
first_sunday = get_first_sunday(self.holiday_list, for_date=get_year_start(date))
# allocate 2 leaves, apply for more
- make_allocation_record(leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date), leaves=2)
- leave_application = frappe.get_doc(dict(
- doctype='Leave Application',
- employee=employee.name,
+ make_allocation_record(
leave_type=leave_type.name,
- from_date=add_days(first_sunday, 1),
- to_date=add_days(first_sunday, 3),
- company="_Test Company",
- status="Approved",
- leave_approver = 'test@example.com'
- ))
+ from_date=get_year_start(date),
+ to_date=get_year_ending(date),
+ leaves=2,
+ )
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type.name,
+ from_date=add_days(first_sunday, 1),
+ to_date=add_days(first_sunday, 3),
+ company="_Test Company",
+ status="Approved",
+ leave_approver="test@example.com",
+ )
+ )
self.assertRaises(InsufficientLeaveBalanceError, leave_application.insert)
# CASE 2: Allows creating application with a warning message when allow negative is enabled
frappe.db.set_value("Leave Type", "Test Leave Validation", "allow_negative", True)
- make_leave_application(employee.name, add_days(first_sunday, 1), add_days(first_sunday, 3), leave_type.name)
+ make_leave_application(
+ employee.name, add_days(first_sunday, 1), add_days(first_sunday, 3), leave_type.name
+ )
- @set_holiday_list('Salary Slip Test Holiday List', '_Test Company')
+ @set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_separate_leave_ledger_entry_for_boundary_applications(self):
# When application falls in 2 different allocations and Allow Negative is enabled
# creates separate leave ledger entries
frappe.delete_doc_if_exists("Leave Type", "Test Leave Validation", force=1)
- leave_type = frappe.get_doc(dict(
- leave_type_name="Test Leave Validation",
- doctype="Leave Type",
- allow_negative=True
- )).insert()
+ leave_type = frappe.get_doc(
+ dict(leave_type_name="Test Leave Validation", doctype="Leave Type", allow_negative=True)
+ ).insert()
employee = get_employee()
date = getdate()
@@ -208,13 +217,17 @@
# application across allocations
# CASE 1: from date has no allocation, to date has an allocation / both dates have allocation
- application = make_leave_application(employee.name, add_days(year_start, -10), add_days(year_start, 3), leave_type.name)
+ application = make_leave_application(
+ employee.name, add_days(year_start, -10), add_days(year_start, 3), leave_type.name
+ )
# 2 separate leave ledger entries
- ledgers = frappe.db.get_all("Leave Ledger Entry", {
- "transaction_type": "Leave Application",
- "transaction_name": application.name
- }, ["leaves", "from_date", "to_date"], order_by="from_date")
+ ledgers = frappe.db.get_all(
+ "Leave Ledger Entry",
+ {"transaction_type": "Leave Application", "transaction_name": application.name},
+ ["leaves", "from_date", "to_date"],
+ order_by="from_date",
+ )
self.assertEqual(len(ledgers), 2)
self.assertEqual(ledgers[0].from_date, application.from_date)
@@ -224,13 +237,17 @@
self.assertEqual(ledgers[1].to_date, application.to_date)
# CASE 2: from date has an allocation, to date has no allocation
- application = make_leave_application(employee.name, add_days(year_end, -3), add_days(year_end, 5), leave_type.name)
+ application = make_leave_application(
+ employee.name, add_days(year_end, -3), add_days(year_end, 5), leave_type.name
+ )
# 2 separate leave ledger entries
- ledgers = frappe.db.get_all("Leave Ledger Entry", {
- "transaction_type": "Leave Application",
- "transaction_name": application.name
- }, ["leaves", "from_date", "to_date"], order_by="from_date")
+ ledgers = frappe.db.get_all(
+ "Leave Ledger Entry",
+ {"transaction_type": "Leave Application", "transaction_name": application.name},
+ ["leaves", "from_date", "to_date"],
+ order_by="from_date",
+ )
self.assertEqual(len(ledgers), 2)
self.assertEqual(ledgers[0].from_date, application.from_date)
@@ -240,74 +257,77 @@
self.assertEqual(ledgers[1].to_date, application.to_date)
def test_overwrite_attendance(self):
- '''check attendance is automatically created on leave approval'''
+ """check attendance is automatically created on leave approval"""
make_allocation_record()
application = self.get_application(_test_records[0])
- application.status = 'Approved'
- application.from_date = '2018-01-01'
- application.to_date = '2018-01-03'
+ application.status = "Approved"
+ application.from_date = "2018-01-01"
+ application.to_date = "2018-01-03"
application.insert()
application.submit()
- attendance = frappe.get_all('Attendance', ['name', 'status', 'attendance_date'],
- dict(attendance_date=('between', ['2018-01-01', '2018-01-03']), docstatus=("!=", 2)))
+ attendance = frappe.get_all(
+ "Attendance",
+ ["name", "status", "attendance_date"],
+ dict(attendance_date=("between", ["2018-01-01", "2018-01-03"]), docstatus=("!=", 2)),
+ )
# attendance created for all 3 days
self.assertEqual(len(attendance), 3)
# all on leave
- self.assertTrue(all([d.status == 'On Leave' for d in attendance]))
+ self.assertTrue(all([d.status == "On Leave" for d in attendance]))
# dates
dates = [d.attendance_date for d in attendance]
- for d in ('2018-01-01', '2018-01-02', '2018-01-03'):
+ for d in ("2018-01-01", "2018-01-02", "2018-01-03"):
self.assertTrue(getdate(d) in dates)
- @set_holiday_list('Salary Slip Test Holiday List', '_Test Company')
+ @set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_attendance_for_include_holidays(self):
# Case 1: leave type with 'Include holidays within leaves as leaves' enabled
frappe.delete_doc_if_exists("Leave Type", "Test Include Holidays", force=1)
- leave_type = frappe.get_doc(dict(
- leave_type_name="Test Include Holidays",
- doctype="Leave Type",
- include_holiday=True
- )).insert()
+ leave_type = frappe.get_doc(
+ dict(leave_type_name="Test Include Holidays", doctype="Leave Type", include_holiday=True)
+ ).insert()
date = getdate()
- make_allocation_record(leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date))
+ make_allocation_record(
+ leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date)
+ )
employee = get_employee()
first_sunday = get_first_sunday(self.holiday_list)
- leave_application = make_leave_application(employee.name, first_sunday, add_days(first_sunday, 3), leave_type.name)
+ leave_application = make_leave_application(
+ employee.name, first_sunday, add_days(first_sunday, 3), leave_type.name
+ )
leave_application.reload()
self.assertEqual(leave_application.total_leave_days, 4)
- self.assertEqual(frappe.db.count('Attendance', {'leave_application': leave_application.name}), 4)
+ self.assertEqual(frappe.db.count("Attendance", {"leave_application": leave_application.name}), 4)
leave_application.cancel()
- @set_holiday_list('Salary Slip Test Holiday List', '_Test Company')
+ @set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_attendance_update_for_exclude_holidays(self):
# Case 2: leave type with 'Include holidays within leaves as leaves' disabled
frappe.delete_doc_if_exists("Leave Type", "Test Do Not Include Holidays", force=1)
- leave_type = frappe.get_doc(dict(
- leave_type_name="Test Do Not Include Holidays",
- doctype="Leave Type",
- include_holiday=False
- )).insert()
+ leave_type = frappe.get_doc(
+ dict(
+ leave_type_name="Test Do Not Include Holidays", doctype="Leave Type", include_holiday=False
+ )
+ ).insert()
date = getdate()
- make_allocation_record(leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date))
+ make_allocation_record(
+ leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date)
+ )
employee = get_employee()
first_sunday = get_first_sunday(self.holiday_list)
# already marked attendance on a holiday should be deleted in this case
- config = {
- "doctype": "Attendance",
- "employee": employee.name,
- "status": "Present"
- }
+ config = {"doctype": "Attendance", "employee": employee.name, "status": "Present"}
attendance_on_holiday = frappe.get_doc(config)
attendance_on_holiday.attendance_date = first_sunday
attendance_on_holiday.flags.ignore_validate = True
@@ -319,7 +339,9 @@
attendance.flags.ignore_validate = True
attendance.save()
- leave_application = make_leave_application(employee.name, first_sunday, add_days(first_sunday, 3), leave_type.name, employee.company)
+ leave_application = make_leave_application(
+ employee.name, first_sunday, add_days(first_sunday, 3), leave_type.name, employee.company
+ )
leave_application.reload()
# holiday should be excluded while marking attendance
@@ -336,11 +358,13 @@
self._clear_roles()
from frappe.utils.user import add_role
+
add_role("test@example.com", "HR User")
clear_user_permissions_for_doctype("Employee")
- frappe.db.set_value("Department", "_Test Department - _TC",
- "leave_block_list", "_Test Leave Block List")
+ frappe.db.set_value(
+ "Department", "_Test Department - _TC", "leave_block_list", "_Test Leave Block List"
+ )
make_allocation_record()
@@ -363,6 +387,7 @@
self._clear_applications()
from frappe.utils.user import add_role
+
add_role("test@example.com", "Employee")
frappe.set_user("test@example.com")
@@ -379,6 +404,7 @@
self._clear_applications()
from frappe.utils.user import add_role
+
add_role("test@example.com", "Employee")
frappe.set_user("test@example.com")
@@ -412,6 +438,7 @@
self._clear_applications()
from frappe.utils.user import add_role
+
add_role("test@example.com", "Employee")
frappe.set_user("test@example.com")
@@ -434,6 +461,7 @@
self._clear_applications()
from frappe.utils.user import add_role
+
add_role("test@example.com", "Employee")
frappe.set_user("test@example.com")
@@ -463,49 +491,49 @@
application.half_day_date = "2013-01-05"
application.insert()
- @set_holiday_list('Salary Slip Test Holiday List', '_Test Company')
+ @set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_optional_leave(self):
leave_period = get_leave_period()
today = nowdate()
- holiday_list = 'Test Holiday List for Optional Holiday'
+ holiday_list = "Test Holiday List for Optional Holiday"
employee = get_employee()
first_sunday = get_first_sunday(self.holiday_list)
optional_leave_date = add_days(first_sunday, 1)
- if not frappe.db.exists('Holiday List', holiday_list):
- frappe.get_doc(dict(
- doctype = 'Holiday List',
- holiday_list_name = holiday_list,
- from_date = add_months(today, -6),
- to_date = add_months(today, 6),
- holidays = [
- dict(holiday_date = optional_leave_date, description = 'Test')
- ]
- )).insert()
+ if not frappe.db.exists("Holiday List", holiday_list):
+ frappe.get_doc(
+ dict(
+ doctype="Holiday List",
+ holiday_list_name=holiday_list,
+ from_date=add_months(today, -6),
+ to_date=add_months(today, 6),
+ holidays=[dict(holiday_date=optional_leave_date, description="Test")],
+ )
+ ).insert()
- frappe.db.set_value('Leave Period', leave_period.name, 'optional_holiday_list', holiday_list)
- leave_type = 'Test Optional Type'
- if not frappe.db.exists('Leave Type', leave_type):
- frappe.get_doc(dict(
- leave_type_name = leave_type,
- doctype = 'Leave Type',
- is_optional_leave = 1
- )).insert()
+ frappe.db.set_value("Leave Period", leave_period.name, "optional_holiday_list", holiday_list)
+ leave_type = "Test Optional Type"
+ if not frappe.db.exists("Leave Type", leave_type):
+ frappe.get_doc(
+ dict(leave_type_name=leave_type, doctype="Leave Type", is_optional_leave=1)
+ ).insert()
allocate_leaves(employee, leave_period, leave_type, 10)
date = add_days(first_sunday, 2)
- leave_application = frappe.get_doc(dict(
- doctype = 'Leave Application',
- employee = employee.name,
- company = '_Test Company',
- description = "_Test Reason",
- leave_type = leave_type,
- from_date = date,
- to_date = date,
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ company="_Test Company",
+ description="_Test Reason",
+ leave_type=leave_type,
+ from_date=date,
+ to_date=date,
+ )
+ )
# can only apply on optional holidays
self.assertRaises(NotAnOptionalHoliday, leave_application.insert)
@@ -523,118 +551,125 @@
employee = get_employee()
leave_period = get_leave_period()
frappe.delete_doc_if_exists("Leave Type", "Test Leave Type", force=1)
- leave_type = frappe.get_doc(dict(
- leave_type_name = 'Test Leave Type',
- doctype = 'Leave Type',
- max_leaves_allowed = 5
- )).insert()
+ leave_type = frappe.get_doc(
+ dict(leave_type_name="Test Leave Type", doctype="Leave Type", max_leaves_allowed=5)
+ ).insert()
date = add_days(nowdate(), -7)
allocate_leaves(employee, leave_period, leave_type.name, 5)
- leave_application = frappe.get_doc(dict(
- doctype = 'Leave Application',
- employee = employee.name,
- leave_type = leave_type.name,
- description = "_Test Reason",
- from_date = date,
- to_date = add_days(date, 2),
- company = "_Test Company",
- docstatus = 1,
- status = "Approved"
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type.name,
+ description="_Test Reason",
+ from_date=date,
+ to_date=add_days(date, 2),
+ company="_Test Company",
+ docstatus=1,
+ status="Approved",
+ )
+ )
leave_application.submit()
- leave_application = frappe.get_doc(dict(
- doctype = 'Leave Application',
- employee = employee.name,
- leave_type = leave_type.name,
- description = "_Test Reason",
- from_date = add_days(date, 4),
- to_date = add_days(date, 8),
- company = "_Test Company",
- docstatus = 1,
- status = "Approved"
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type.name,
+ description="_Test Reason",
+ from_date=add_days(date, 4),
+ to_date=add_days(date, 8),
+ company="_Test Company",
+ docstatus=1,
+ status="Approved",
+ )
+ )
self.assertRaises(frappe.ValidationError, leave_application.insert)
def test_applicable_after(self):
employee = get_employee()
leave_period = get_leave_period()
frappe.delete_doc_if_exists("Leave Type", "Test Leave Type", force=1)
- leave_type = frappe.get_doc(dict(
- leave_type_name = 'Test Leave Type',
- doctype = 'Leave Type',
- applicable_after = 15
- )).insert()
+ leave_type = frappe.get_doc(
+ dict(leave_type_name="Test Leave Type", doctype="Leave Type", applicable_after=15)
+ ).insert()
date = add_days(nowdate(), -7)
- frappe.db.set_value('Employee', employee.name, "date_of_joining", date)
+ frappe.db.set_value("Employee", employee.name, "date_of_joining", date)
allocate_leaves(employee, leave_period, leave_type.name, 10)
- leave_application = frappe.get_doc(dict(
- doctype = 'Leave Application',
- employee = employee.name,
- leave_type = leave_type.name,
- description = "_Test Reason",
- from_date = date,
- to_date = add_days(date, 4),
- company = "_Test Company",
- docstatus = 1,
- status = "Approved"
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type.name,
+ description="_Test Reason",
+ from_date=date,
+ to_date=add_days(date, 4),
+ company="_Test Company",
+ docstatus=1,
+ status="Approved",
+ )
+ )
self.assertRaises(frappe.ValidationError, leave_application.insert)
frappe.delete_doc_if_exists("Leave Type", "Test Leave Type 1", force=1)
- leave_type_1 = frappe.get_doc(dict(
- leave_type_name = 'Test Leave Type 1',
- doctype = 'Leave Type'
- )).insert()
+ leave_type_1 = frappe.get_doc(
+ dict(leave_type_name="Test Leave Type 1", doctype="Leave Type")
+ ).insert()
allocate_leaves(employee, leave_period, leave_type_1.name, 10)
- leave_application = frappe.get_doc(dict(
- doctype = 'Leave Application',
- employee = employee.name,
- leave_type = leave_type_1.name,
- description = "_Test Reason",
- from_date = date,
- to_date = add_days(date, 4),
- company = "_Test Company",
- docstatus = 1,
- status = "Approved"
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type_1.name,
+ description="_Test Reason",
+ from_date=date,
+ to_date=add_days(date, 4),
+ company="_Test Company",
+ docstatus=1,
+ status="Approved",
+ )
+ )
self.assertTrue(leave_application.insert())
- frappe.db.set_value('Employee', employee.name, "date_of_joining", "2010-01-01")
+ frappe.db.set_value("Employee", employee.name, "date_of_joining", "2010-01-01")
def test_max_continuous_leaves(self):
employee = get_employee()
leave_period = get_leave_period()
frappe.delete_doc_if_exists("Leave Type", "Test Leave Type", force=1)
- leave_type = frappe.get_doc(dict(
- leave_type_name = 'Test Leave Type',
- doctype = 'Leave Type',
- max_leaves_allowed = 15,
- max_continuous_days_allowed = 3
- )).insert()
+ leave_type = frappe.get_doc(
+ dict(
+ leave_type_name="Test Leave Type",
+ doctype="Leave Type",
+ max_leaves_allowed=15,
+ max_continuous_days_allowed=3,
+ )
+ ).insert()
date = add_days(nowdate(), -7)
allocate_leaves(employee, leave_period, leave_type.name, 10)
- leave_application = frappe.get_doc(dict(
- doctype = 'Leave Application',
- employee = employee.name,
- leave_type = leave_type.name,
- description = "_Test Reason",
- from_date = date,
- to_date = add_days(date, 4),
- company = "_Test Company",
- docstatus = 1,
- status = "Approved"
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type.name,
+ description="_Test Reason",
+ from_date=date,
+ to_date=add_days(date, 4),
+ company="_Test Company",
+ docstatus=1,
+ status="Approved",
+ )
+ )
self.assertRaises(frappe.ValidationError, leave_application.insert)
@@ -643,60 +678,70 @@
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
- expire_carry_forwarded_leaves_after_days=90)
+ expire_carry_forwarded_leaves_after_days=90,
+ )
leave_type.insert()
create_carry_forwarded_allocation(employee, leave_type)
- details = get_leave_balance_on(employee.name, leave_type.name, nowdate(), add_days(nowdate(), 8), for_consumption=True)
+ details = get_leave_balance_on(
+ employee.name, leave_type.name, nowdate(), add_days(nowdate(), 8), for_consumption=True
+ )
self.assertEqual(details.leave_balance_for_consumption, 21)
self.assertEqual(details.leave_balance, 30)
def test_earned_leaves_creation(self):
- frappe.db.sql('''delete from `tabLeave Period`''')
- frappe.db.sql('''delete from `tabLeave Policy Assignment`''')
- frappe.db.sql('''delete from `tabLeave Allocation`''')
- frappe.db.sql('''delete from `tabLeave Ledger Entry`''')
+ frappe.db.sql("""delete from `tabLeave Period`""")
+ frappe.db.sql("""delete from `tabLeave Policy Assignment`""")
+ frappe.db.sql("""delete from `tabLeave Allocation`""")
+ frappe.db.sql("""delete from `tabLeave Ledger Entry`""")
leave_period = get_leave_period()
employee = get_employee()
- leave_type = 'Test Earned Leave Type'
- frappe.delete_doc_if_exists("Leave Type", 'Test Earned Leave Type', force=1)
- frappe.get_doc(dict(
- leave_type_name = leave_type,
- doctype = 'Leave Type',
- is_earned_leave = 1,
- earned_leave_frequency = 'Monthly',
- rounding = 0.5,
- max_leaves_allowed = 6
- )).insert()
+ leave_type = "Test Earned Leave Type"
+ frappe.delete_doc_if_exists("Leave Type", "Test Earned Leave Type", force=1)
+ frappe.get_doc(
+ dict(
+ leave_type_name=leave_type,
+ doctype="Leave Type",
+ is_earned_leave=1,
+ earned_leave_frequency="Monthly",
+ rounding=0.5,
+ max_leaves_allowed=6,
+ )
+ ).insert()
- leave_policy = frappe.get_doc({
- "doctype": "Leave Policy",
- "title": "Test Leave Policy",
- "leave_policy_details": [{"leave_type": leave_type, "annual_allocation": 6}]
- }).insert()
+ leave_policy = frappe.get_doc(
+ {
+ "doctype": "Leave Policy",
+ "title": "Test Leave Policy",
+ "leave_policy_details": [{"leave_type": leave_type, "annual_allocation": 6}],
+ }
+ ).insert()
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
- "leave_period": leave_period.name
+ "leave_period": leave_period.name,
}
- leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [employee.name], frappe._dict(data)
+ )
from erpnext.hr.utils import allocate_earned_leaves
+
i = 0
- while(i<14):
+ while i < 14:
allocate_earned_leaves(ignore_duplicates=True)
i += 1
self.assertEqual(get_leave_balance_on(employee.name, leave_type, nowdate()), 6)
# validate earned leaves creation without maximum leaves
- frappe.db.set_value('Leave Type', leave_type, 'max_leaves_allowed', 0)
+ frappe.db.set_value("Leave Type", leave_type, "max_leaves_allowed", 0)
i = 0
- while(i<6):
+ while i < 6:
allocate_earned_leaves(ignore_duplicates=True)
i += 1
self.assertEqual(get_leave_balance_on(employee.name, leave_type, nowdate()), 9)
@@ -705,34 +750,35 @@
def test_current_leave_on_submit(self):
employee = get_employee()
- leave_type = 'Sick Leave'
- if not frappe.db.exists('Leave Type', leave_type):
- frappe.get_doc(dict(
- leave_type_name=leave_type,
- doctype='Leave Type'
- )).insert()
+ leave_type = "Sick Leave"
+ if not frappe.db.exists("Leave Type", leave_type):
+ frappe.get_doc(dict(leave_type_name=leave_type, doctype="Leave Type")).insert()
- allocation = frappe.get_doc(dict(
- doctype = 'Leave Allocation',
- employee = employee.name,
- leave_type = leave_type,
- from_date = '2018-10-01',
- to_date = '2018-10-10',
- new_leaves_allocated = 1
- ))
+ allocation = frappe.get_doc(
+ dict(
+ doctype="Leave Allocation",
+ employee=employee.name,
+ leave_type=leave_type,
+ from_date="2018-10-01",
+ to_date="2018-10-10",
+ new_leaves_allocated=1,
+ )
+ )
allocation.insert(ignore_permissions=True)
allocation.submit()
- leave_application = frappe.get_doc(dict(
- doctype = 'Leave Application',
- employee = employee.name,
- leave_type = leave_type,
- description = "_Test Reason",
- from_date = '2018-10-02',
- to_date = '2018-10-02',
- company = '_Test Company',
- status = 'Approved',
- leave_approver = 'test@example.com'
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type,
+ description="_Test Reason",
+ from_date="2018-10-02",
+ to_date="2018-10-02",
+ company="_Test Company",
+ status="Approved",
+ leave_approver="test@example.com",
+ )
+ )
self.assertTrue(leave_application.insert())
leave_application.submit()
self.assertEqual(leave_application.docstatus, 1)
@@ -740,26 +786,31 @@
def test_creation_of_leave_ledger_entry_on_submit(self):
employee = get_employee()
- leave_type = create_leave_type(leave_type_name = 'Test Leave Type 1')
+ leave_type = create_leave_type(leave_type_name="Test Leave Type 1")
leave_type.save()
- leave_allocation = create_leave_allocation(employee=employee.name, employee_name=employee.employee_name,
- leave_type=leave_type.name)
+ leave_allocation = create_leave_allocation(
+ employee=employee.name, employee_name=employee.employee_name, leave_type=leave_type.name
+ )
leave_allocation.submit()
- leave_application = frappe.get_doc(dict(
- doctype = 'Leave Application',
- employee = employee.name,
- leave_type = leave_type.name,
- from_date = add_days(nowdate(), 1),
- to_date = add_days(nowdate(), 4),
- description = "_Test Reason",
- company = "_Test Company",
- docstatus = 1,
- status = "Approved"
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type.name,
+ from_date=add_days(nowdate(), 1),
+ to_date=add_days(nowdate(), 4),
+ description="_Test Reason",
+ company="_Test Company",
+ docstatus=1,
+ status="Approved",
+ )
+ )
leave_application.submit()
- leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_application.name))
+ leave_ledger_entry = frappe.get_all(
+ "Leave Ledger Entry", fields="*", filters=dict(transaction_name=leave_application.name)
+ )
self.assertEqual(leave_ledger_entry[0].employee, leave_application.employee)
self.assertEqual(leave_ledger_entry[0].leave_type, leave_application.leave_type)
@@ -767,32 +818,39 @@
# check if leave ledger entry is deleted on cancellation
leave_application.cancel()
- self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_application.name}))
+ self.assertFalse(
+ frappe.db.exists("Leave Ledger Entry", {"transaction_name": leave_application.name})
+ )
def test_ledger_entry_creation_on_intermediate_allocation_expiry(self):
employee = get_employee()
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
- expire_carry_forwarded_leaves_after_days=90)
+ expire_carry_forwarded_leaves_after_days=90,
+ )
leave_type.submit()
create_carry_forwarded_allocation(employee, leave_type)
- leave_application = frappe.get_doc(dict(
- doctype = 'Leave Application',
- employee = employee.name,
- leave_type = leave_type.name,
- from_date = add_days(nowdate(), -3),
- to_date = add_days(nowdate(), 7),
- description = "_Test Reason",
- company = "_Test Company",
- docstatus = 1,
- status = "Approved"
- ))
+ leave_application = frappe.get_doc(
+ dict(
+ doctype="Leave Application",
+ employee=employee.name,
+ leave_type=leave_type.name,
+ from_date=add_days(nowdate(), -3),
+ to_date=add_days(nowdate(), 7),
+ description="_Test Reason",
+ company="_Test Company",
+ docstatus=1,
+ status="Approved",
+ )
+ )
leave_application.submit()
- leave_ledger_entry = frappe.get_all('Leave Ledger Entry', '*', filters=dict(transaction_name=leave_application.name))
+ leave_ledger_entry = frappe.get_all(
+ "Leave Ledger Entry", "*", filters=dict(transaction_name=leave_application.name)
+ )
self.assertEqual(len(leave_ledger_entry), 2)
self.assertEqual(leave_ledger_entry[0].employee, leave_application.employee)
@@ -806,12 +864,18 @@
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
- expire_carry_forwarded_leaves_after_days=90)
+ expire_carry_forwarded_leaves_after_days=90,
+ )
leave_type.submit()
create_carry_forwarded_allocation(employee, leave_type)
- self.assertEqual(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0)
+ self.assertEqual(
+ get_leave_balance_on(
+ employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)
+ ),
+ 0,
+ )
def test_leave_approver_perms(self):
employee = get_employee()
@@ -827,8 +891,8 @@
make_allocation_record(employee.name)
application = self.get_application(_test_records[0])
- application.from_date = '2018-01-01'
- application.to_date = '2018-01-03'
+ application.from_date = "2018-01-01"
+ application.to_date = "2018-01-03"
application.leave_approver = user
application.insert()
self.assertTrue(application.name in frappe.share.get_shared("Leave Application", user))
@@ -854,7 +918,7 @@
employee.leave_approver = ""
employee.save()
- @set_holiday_list('Salary Slip Test Holiday List', '_Test Company')
+ @set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_get_leave_details_for_dashboard(self):
employee = get_employee()
date = getdate()
@@ -862,34 +926,44 @@
year_end = getdate(get_year_ending(date))
# ALLOCATION = 30
- allocation = make_allocation_record(employee=employee.name, from_date=year_start, to_date=year_end)
+ allocation = make_allocation_record(
+ employee=employee.name, from_date=year_start, to_date=year_end
+ )
# USED LEAVES = 4
first_sunday = get_first_sunday(self.holiday_list)
- leave_application = make_leave_application(employee.name, add_days(first_sunday, 1), add_days(first_sunday, 4), '_Test Leave Type')
+ leave_application = make_leave_application(
+ employee.name, add_days(first_sunday, 1), add_days(first_sunday, 4), "_Test Leave Type"
+ )
leave_application.reload()
# LEAVES PENDING APPROVAL = 1
- leave_application = make_leave_application(employee.name, add_days(first_sunday, 5), add_days(first_sunday, 5),
- '_Test Leave Type', submit=False)
- leave_application.status = 'Open'
+ leave_application = make_leave_application(
+ employee.name,
+ add_days(first_sunday, 5),
+ add_days(first_sunday, 5),
+ "_Test Leave Type",
+ submit=False,
+ )
+ leave_application.status = "Open"
leave_application.save()
details = get_leave_details(employee.name, allocation.from_date)
- leave_allocation = details['leave_allocation']['_Test Leave Type']
- self.assertEqual(leave_allocation['total_leaves'], 30)
- self.assertEqual(leave_allocation['leaves_taken'], 4)
- self.assertEqual(leave_allocation['expired_leaves'], 0)
- self.assertEqual(leave_allocation['leaves_pending_approval'], 1)
- self.assertEqual(leave_allocation['remaining_leaves'], 26)
+ leave_allocation = details["leave_allocation"]["_Test Leave Type"]
+ self.assertEqual(leave_allocation["total_leaves"], 30)
+ self.assertEqual(leave_allocation["leaves_taken"], 4)
+ self.assertEqual(leave_allocation["expired_leaves"], 0)
+ self.assertEqual(leave_allocation["leaves_pending_approval"], 1)
+ self.assertEqual(leave_allocation["remaining_leaves"], 26)
- @set_holiday_list('Salary Slip Test Holiday List', '_Test Company')
+ @set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_get_leave_allocation_records(self):
employee = get_employee()
leave_type = create_leave_type(
leave_type_name="_Test_CF_leave_expiry",
is_carry_forward=1,
- expire_carry_forwarded_leaves_after_days=90)
+ expire_carry_forwarded_leaves_after_days=90,
+ )
leave_type.insert()
leave_alloc = create_carry_forwarded_allocation(employee, leave_type)
@@ -900,86 +974,99 @@
"total_leaves_allocated": 30.0,
"unused_leaves": 15.0,
"new_leaves_allocated": 15.0,
- "leave_type": leave_type.name
+ "leave_type": leave_type.name,
}
self.assertEqual(details.get(leave_type.name), expected_data)
def create_carry_forwarded_allocation(employee, leave_type):
- # initial leave allocation
- leave_allocation = create_leave_allocation(
- leave_type="_Test_CF_leave_expiry",
- employee=employee.name,
- employee_name=employee.employee_name,
- from_date=add_months(nowdate(), -24),
- to_date=add_months(nowdate(), -12),
- carry_forward=0)
- leave_allocation.submit()
+ # initial leave allocation
+ leave_allocation = create_leave_allocation(
+ leave_type="_Test_CF_leave_expiry",
+ employee=employee.name,
+ employee_name=employee.employee_name,
+ from_date=add_months(nowdate(), -24),
+ to_date=add_months(nowdate(), -12),
+ carry_forward=0,
+ )
+ leave_allocation.submit()
- leave_allocation = create_leave_allocation(
- leave_type="_Test_CF_leave_expiry",
- employee=employee.name,
- employee_name=employee.employee_name,
- from_date=add_days(nowdate(), -84),
- to_date=add_days(nowdate(), 100),
- carry_forward=1)
- leave_allocation.submit()
+ leave_allocation = create_leave_allocation(
+ leave_type="_Test_CF_leave_expiry",
+ employee=employee.name,
+ employee_name=employee.employee_name,
+ from_date=add_days(nowdate(), -84),
+ to_date=add_days(nowdate(), 100),
+ carry_forward=1,
+ )
+ leave_allocation.submit()
- return leave_allocation
+ return leave_allocation
-def make_allocation_record(employee=None, leave_type=None, from_date=None, to_date=None, carry_forward=False, leaves=None):
- allocation = frappe.get_doc({
- "doctype": "Leave Allocation",
- "employee": employee or "_T-Employee-00001",
- "leave_type": leave_type or "_Test Leave Type",
- "from_date": from_date or "2013-01-01",
- "to_date": to_date or "2019-12-31",
- "new_leaves_allocated": leaves or 30,
- "carry_forward": carry_forward
- })
+
+def make_allocation_record(
+ employee=None, leave_type=None, from_date=None, to_date=None, carry_forward=False, leaves=None
+):
+ allocation = frappe.get_doc(
+ {
+ "doctype": "Leave Allocation",
+ "employee": employee or "_T-Employee-00001",
+ "leave_type": leave_type or "_Test Leave Type",
+ "from_date": from_date or "2013-01-01",
+ "to_date": to_date or "2019-12-31",
+ "new_leaves_allocated": leaves or 30,
+ "carry_forward": carry_forward,
+ }
+ )
allocation.insert(ignore_permissions=True)
allocation.submit()
return allocation
+
def get_employee():
return frappe.get_doc("Employee", "_T-Employee-00001")
+
def set_leave_approver():
employee = get_employee()
dept_doc = frappe.get_doc("Department", employee.department)
- dept_doc.append('leave_approvers', {
- 'approver': 'test@example.com'
- })
+ dept_doc.append("leave_approvers", {"approver": "test@example.com"})
dept_doc.save(ignore_permissions=True)
+
def get_leave_period():
leave_period_name = frappe.db.get_value("Leave Period", {"company": "_Test Company"})
if leave_period_name:
return frappe.get_doc("Leave Period", leave_period_name)
else:
- return frappe.get_doc(dict(
- name = 'Test Leave Period',
- doctype = 'Leave Period',
- from_date = add_months(nowdate(), -6),
- to_date = add_months(nowdate(), 6),
- company = "_Test Company",
- is_active = 1
- )).insert()
+ return frappe.get_doc(
+ dict(
+ name="Test Leave Period",
+ doctype="Leave Period",
+ from_date=add_months(nowdate(), -6),
+ to_date=add_months(nowdate(), 6),
+ company="_Test Company",
+ is_active=1,
+ )
+ ).insert()
+
def allocate_leaves(employee, leave_period, leave_type, new_leaves_allocated, eligible_leaves=0):
- allocate_leave = frappe.get_doc({
- "doctype": "Leave Allocation",
- "__islocal": 1,
- "employee": employee.name,
- "employee_name": employee.employee_name,
- "leave_type": leave_type,
- "from_date": leave_period.from_date,
- "to_date": leave_period.to_date,
- "new_leaves_allocated": new_leaves_allocated,
- "docstatus": 1
- }).insert()
+ allocate_leave = frappe.get_doc(
+ {
+ "doctype": "Leave Allocation",
+ "__islocal": 1,
+ "employee": employee.name,
+ "employee_name": employee.employee_name,
+ "leave_type": leave_type,
+ "from_date": leave_period.from_date,
+ "to_date": leave_period.to_date,
+ "new_leaves_allocated": new_leaves_allocated,
+ "docstatus": 1,
+ }
+ ).insert()
allocate_leave.submit()
@@ -988,11 +1075,14 @@
date = for_date or getdate()
month_start_date = get_first_day(date)
month_end_date = get_last_day(date)
- first_sunday = frappe.db.sql("""
+ first_sunday = frappe.db.sql(
+ """
select holiday_date from `tabHoliday`
where parent = %s
and holiday_date between %s and %s
order by holiday_date
- """, (holiday_list, month_start_date, month_end_date))[0][0]
+ """,
+ (holiday_list, month_start_date, month_end_date),
+ )[0][0]
return first_sunday
diff --git a/erpnext/hr/doctype/leave_block_list/leave_block_list.py b/erpnext/hr/doctype/leave_block_list/leave_block_list.py
index d6b77f9..a57ba84 100644
--- a/erpnext/hr/doctype/leave_block_list/leave_block_list.py
+++ b/erpnext/hr/doctype/leave_block_list/leave_block_list.py
@@ -10,7 +10,6 @@
class LeaveBlockList(Document):
-
def validate(self):
dates = []
for d in self.get("leave_block_list_dates"):
@@ -20,23 +19,29 @@
frappe.msgprint(_("Date is repeated") + ":" + d.block_date, raise_exception=1)
dates.append(d.block_date)
+
@frappe.whitelist()
-def get_applicable_block_dates(from_date, to_date, employee=None,
- company=None, all_lists=False):
+def get_applicable_block_dates(from_date, to_date, employee=None, company=None, all_lists=False):
block_dates = []
for block_list in get_applicable_block_lists(employee, company, all_lists):
- block_dates.extend(frappe.db.sql("""select block_date, reason
+ block_dates.extend(
+ frappe.db.sql(
+ """select block_date, reason
from `tabLeave Block List Date` where parent=%s
- and block_date between %s and %s""", (block_list, from_date, to_date),
- as_dict=1))
+ and block_date between %s and %s""",
+ (block_list, from_date, to_date),
+ as_dict=1,
+ )
+ )
return block_dates
+
def get_applicable_block_lists(employee=None, company=None, all_lists=False):
block_lists = []
if not employee:
- employee = frappe.db.get_value("Employee", {"user_id":frappe.session.user})
+ employee = frappe.db.get_value("Employee", {"user_id": frappe.session.user})
if not employee:
return []
@@ -49,18 +54,25 @@
block_lists.append(block_list)
# per department
- department = frappe.db.get_value("Employee",employee, "department")
+ department = frappe.db.get_value("Employee", employee, "department")
if department:
block_list = frappe.db.get_value("Department", department, "leave_block_list")
add_block_list(block_list)
# global
- for block_list in frappe.db.sql_list("""select name from `tabLeave Block List`
- where applies_to_all_departments=1 and company=%s""", company):
+ for block_list in frappe.db.sql_list(
+ """select name from `tabLeave Block List`
+ where applies_to_all_departments=1 and company=%s""",
+ company,
+ ):
add_block_list(block_list)
return list(set(block_lists))
+
def is_user_in_allow_list(block_list):
- return frappe.session.user in frappe.db.sql_list("""select allow_user
- from `tabLeave Block List Allow` where parent=%s""", block_list)
+ return frappe.session.user in frappe.db.sql_list(
+ """select allow_user
+ from `tabLeave Block List Allow` where parent=%s""",
+ block_list,
+ )
diff --git a/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py b/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py
index 7cca62e..afeb5de 100644
--- a/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py
+++ b/erpnext/hr/doctype/leave_block_list/leave_block_list_dashboard.py
@@ -1,9 +1,2 @@
def get_data():
- return {
- 'fieldname': 'leave_block_list',
- 'transactions': [
- {
- 'items': ['Department']
- }
- ]
- }
+ return {"fieldname": "leave_block_list", "transactions": [{"items": ["Department"]}]}
diff --git a/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py b/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py
index afbabb6..be85a35 100644
--- a/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py
+++ b/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py
@@ -15,24 +15,36 @@
def test_get_applicable_block_dates(self):
frappe.set_user("test@example.com")
- frappe.db.set_value("Department", "_Test Department - _TC", "leave_block_list",
- "_Test Leave Block List")
- self.assertTrue(getdate("2013-01-02") in
- [d.block_date for d in get_applicable_block_dates("2013-01-01", "2013-01-03")])
+ frappe.db.set_value(
+ "Department", "_Test Department - _TC", "leave_block_list", "_Test Leave Block List"
+ )
+ self.assertTrue(
+ getdate("2013-01-02")
+ in [d.block_date for d in get_applicable_block_dates("2013-01-01", "2013-01-03")]
+ )
def test_get_applicable_block_dates_for_allowed_user(self):
frappe.set_user("test1@example.com")
- frappe.db.set_value("Department", "_Test Department 1 - _TC", "leave_block_list",
- "_Test Leave Block List")
- self.assertEqual([], [d.block_date for d in get_applicable_block_dates("2013-01-01", "2013-01-03")])
+ frappe.db.set_value(
+ "Department", "_Test Department 1 - _TC", "leave_block_list", "_Test Leave Block List"
+ )
+ self.assertEqual(
+ [], [d.block_date for d in get_applicable_block_dates("2013-01-01", "2013-01-03")]
+ )
def test_get_applicable_block_dates_all_lists(self):
frappe.set_user("test1@example.com")
- frappe.db.set_value("Department", "_Test Department 1 - _TC", "leave_block_list",
- "_Test Leave Block List")
- self.assertTrue(getdate("2013-01-02") in
- [d.block_date for d in get_applicable_block_dates("2013-01-01", "2013-01-03", all_lists=True)])
+ frappe.db.set_value(
+ "Department", "_Test Department 1 - _TC", "leave_block_list", "_Test Leave Block List"
+ )
+ self.assertTrue(
+ getdate("2013-01-02")
+ in [
+ d.block_date for d in get_applicable_block_dates("2013-01-01", "2013-01-03", all_lists=True)
+ ]
+ )
+
test_dependencies = ["Employee"]
-test_records = frappe.get_test_records('Leave Block List')
+test_records = frappe.get_test_records("Leave Block List")
diff --git a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py
index 19f97b8..c57f8ae 100644
--- a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py
+++ b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.py
@@ -18,8 +18,12 @@
condition_str = " and " + " and ".join(conditions) if len(conditions) else ""
- e = frappe.db.sql("select name from tabEmployee where status='Active' {condition}"
- .format(condition=condition_str), tuple(values))
+ e = frappe.db.sql(
+ "select name from tabEmployee where status='Active' {condition}".format(
+ condition=condition_str
+ ),
+ tuple(values),
+ )
return e
@@ -27,7 +31,7 @@
for f in ["from_date", "to_date", "leave_type", "no_of_days"]:
if not self.get(f):
frappe.throw(_("{0} is required").format(self.meta.get_label(f)))
- self.validate_from_to_dates('from_date', 'to_date')
+ self.validate_from_to_dates("from_date", "to_date")
@frappe.whitelist()
def allocate_leave(self):
@@ -39,10 +43,10 @@
for d in self.get_employees():
try:
- la = frappe.new_doc('Leave Allocation')
+ la = frappe.new_doc("Leave Allocation")
la.set("__islocal", 1)
la.employee = cstr(d[0])
- la.employee_name = frappe.db.get_value('Employee',cstr(d[0]),'employee_name')
+ la.employee_name = frappe.db.get_value("Employee", cstr(d[0]), "employee_name")
la.leave_type = self.leave_type
la.from_date = self.from_date
la.to_date = self.to_date
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
index 8ef0e36..0f655e3 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
@@ -26,9 +26,12 @@
self.encashment_date = getdate(nowdate())
def validate_salary_structure(self):
- if not frappe.db.exists('Salary Structure Assignment', {'employee': self.employee}):
- frappe.throw(_("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(self.employee))
-
+ if not frappe.db.exists("Salary Structure Assignment", {"employee": self.employee}):
+ frappe.throw(
+ _("There is no Salary Structure assigned to {0}. First assign a Salary Stucture.").format(
+ self.employee
+ )
+ )
def before_submit(self):
if self.encashment_amount <= 0:
@@ -36,7 +39,7 @@
def on_submit(self):
if not self.leave_allocation:
- self.leave_allocation = self.get_leave_allocation().get('name')
+ self.leave_allocation = self.get_leave_allocation().get("name")
additional_salary = frappe.new_doc("Additional Salary")
additional_salary.company = frappe.get_value("Employee", self.employee, "company")
additional_salary.employee = self.employee
@@ -52,8 +55,13 @@
additional_salary.submit()
# Set encashed leaves in Allocation
- frappe.db.set_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed",
- frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') + self.encashable_days)
+ frappe.db.set_value(
+ "Leave Allocation",
+ self.leave_allocation,
+ "total_leaves_encashed",
+ frappe.db.get_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed")
+ + self.encashable_days,
+ )
self.create_leave_ledger_entry()
@@ -63,40 +71,69 @@
self.db_set("additional_salary", "")
if self.leave_allocation:
- frappe.db.set_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed",
- frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') - self.encashable_days)
+ frappe.db.set_value(
+ "Leave Allocation",
+ self.leave_allocation,
+ "total_leaves_encashed",
+ frappe.db.get_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed")
+ - self.encashable_days,
+ )
self.create_leave_ledger_entry(submit=False)
@frappe.whitelist()
def get_leave_details_for_encashment(self):
- salary_structure = get_assigned_salary_structure(self.employee, self.encashment_date or getdate(nowdate()))
+ salary_structure = get_assigned_salary_structure(
+ self.employee, self.encashment_date or getdate(nowdate())
+ )
if not salary_structure:
- frappe.throw(_("No Salary Structure assigned for Employee {0} on given date {1}").format(self.employee, self.encashment_date))
+ frappe.throw(
+ _("No Salary Structure assigned for Employee {0} on given date {1}").format(
+ self.employee, self.encashment_date
+ )
+ )
- if not frappe.db.get_value("Leave Type", self.leave_type, 'allow_encashment'):
+ if not frappe.db.get_value("Leave Type", self.leave_type, "allow_encashment"):
frappe.throw(_("Leave Type {0} is not encashable").format(self.leave_type))
allocation = self.get_leave_allocation()
if not allocation:
- frappe.throw(_("No Leaves Allocated to Employee: {0} for Leave Type: {1}").format(self.employee, self.leave_type))
+ frappe.throw(
+ _("No Leaves Allocated to Employee: {0} for Leave Type: {1}").format(
+ self.employee, self.leave_type
+ )
+ )
- self.leave_balance = allocation.total_leaves_allocated - allocation.carry_forwarded_leaves_count\
+ self.leave_balance = (
+ allocation.total_leaves_allocated
+ - allocation.carry_forwarded_leaves_count
- get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date)
+ )
- encashable_days = self.leave_balance - frappe.db.get_value('Leave Type', self.leave_type, 'encashment_threshold_days')
+ encashable_days = self.leave_balance - frappe.db.get_value(
+ "Leave Type", self.leave_type, "encashment_threshold_days"
+ )
self.encashable_days = encashable_days if encashable_days > 0 else 0
- per_day_encashment = frappe.db.get_value('Salary Structure', salary_structure , 'leave_encashment_amount_per_day')
- self.encashment_amount = self.encashable_days * per_day_encashment if per_day_encashment > 0 else 0
+ per_day_encashment = frappe.db.get_value(
+ "Salary Structure", salary_structure, "leave_encashment_amount_per_day"
+ )
+ self.encashment_amount = (
+ self.encashable_days * per_day_encashment if per_day_encashment > 0 else 0
+ )
self.leave_allocation = allocation.name
return True
def get_leave_allocation(self):
- leave_allocation = frappe.db.sql("""select name, to_date, total_leaves_allocated, carry_forwarded_leaves_count from `tabLeave Allocation` where '{0}'
+ leave_allocation = frappe.db.sql(
+ """select name, to_date, total_leaves_allocated, carry_forwarded_leaves_count from `tabLeave Allocation` where '{0}'
between from_date and to_date and docstatus=1 and leave_type='{1}'
- and employee= '{2}'""".format(self.encashment_date or getdate(nowdate()), self.leave_type, self.employee), as_dict=1) #nosec
+ and employee= '{2}'""".format(
+ self.encashment_date or getdate(nowdate()), self.leave_type, self.employee
+ ),
+ as_dict=1,
+ ) # nosec
return leave_allocation[0] if leave_allocation else None
@@ -105,7 +142,7 @@
leaves=self.encashable_days * -1,
from_date=self.encashment_date,
to_date=self.encashment_date,
- is_carry_forward=0
+ is_carry_forward=0,
)
create_leave_ledger_entry(self, args, submit)
@@ -114,27 +151,26 @@
if not leave_allocation:
return
- to_date = leave_allocation.get('to_date')
+ to_date = leave_allocation.get("to_date")
if to_date < getdate(nowdate()):
args = frappe._dict(
- leaves=self.encashable_days,
- from_date=to_date,
- to_date=to_date,
- is_carry_forward=0
+ leaves=self.encashable_days, from_date=to_date, to_date=to_date, is_carry_forward=0
)
create_leave_ledger_entry(self, args, submit)
def create_leave_encashment(leave_allocation):
- ''' Creates leave encashment for the given allocations '''
+ """Creates leave encashment for the given allocations"""
for allocation in leave_allocation:
if not get_assigned_salary_structure(allocation.employee, allocation.to_date):
continue
- leave_encashment = frappe.get_doc(dict(
- doctype="Leave Encashment",
- leave_period=allocation.leave_period,
- employee=allocation.employee,
- leave_type=allocation.leave_type,
- encashment_date=allocation.to_date
- ))
+ leave_encashment = frappe.get_doc(
+ dict(
+ doctype="Leave Encashment",
+ leave_period=allocation.leave_period,
+ employee=allocation.employee,
+ leave_type=allocation.leave_type,
+ encashment_date=allocation.to_date,
+ )
+ )
leave_encashment.insert(ignore_permissions=True)
diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
index 99a479d..83eb969 100644
--- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
@@ -16,18 +16,19 @@
test_dependencies = ["Leave Type"]
+
class TestLeaveEncashment(unittest.TestCase):
def setUp(self):
- frappe.db.sql('''delete from `tabLeave Period`''')
- frappe.db.sql('''delete from `tabLeave Policy Assignment`''')
- frappe.db.sql('''delete from `tabLeave Allocation`''')
- frappe.db.sql('''delete from `tabLeave Ledger Entry`''')
- frappe.db.sql('''delete from `tabAdditional Salary`''')
+ frappe.db.sql("""delete from `tabLeave Period`""")
+ frappe.db.sql("""delete from `tabLeave Policy Assignment`""")
+ frappe.db.sql("""delete from `tabLeave Allocation`""")
+ frappe.db.sql("""delete from `tabLeave Ledger Entry`""")
+ frappe.db.sql("""delete from `tabAdditional Salary`""")
# create the leave policy
leave_policy = create_leave_policy(
- leave_type="_Test Leave Type Encashment",
- annual_allocation=10)
+ leave_type="_Test Leave Type Encashment", annual_allocation=10
+ )
leave_policy.submit()
# create employee, salary structure and assignment
@@ -38,28 +39,44 @@
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
- "leave_period": self.leave_period.name
+ "leave_period": self.leave_period.name,
}
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee], frappe._dict(data))
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee], frappe._dict(data)
+ )
- salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", self.employee,
- other_details={"leave_encashment_amount_per_day": 50})
+ salary_structure = make_salary_structure(
+ "Salary Structure for Encashment",
+ "Monthly",
+ self.employee,
+ other_details={"leave_encashment_amount_per_day": 50},
+ )
def tearDown(self):
- for dt in ["Leave Period", "Leave Allocation", "Leave Ledger Entry", "Additional Salary", "Leave Encashment", "Salary Structure", "Leave Policy"]:
+ for dt in [
+ "Leave Period",
+ "Leave Allocation",
+ "Leave Ledger Entry",
+ "Additional Salary",
+ "Leave Encashment",
+ "Salary Structure",
+ "Leave Policy",
+ ]:
frappe.db.sql("delete from `tab%s`" % dt)
def test_leave_balance_value_and_amount(self):
- frappe.db.sql('''delete from `tabLeave Encashment`''')
- leave_encashment = frappe.get_doc(dict(
- doctype='Leave Encashment',
- employee=self.employee,
- leave_type="_Test Leave Type Encashment",
- leave_period=self.leave_period.name,
- payroll_date=today(),
- currency="INR"
- )).insert()
+ frappe.db.sql("""delete from `tabLeave Encashment`""")
+ leave_encashment = frappe.get_doc(
+ dict(
+ doctype="Leave Encashment",
+ employee=self.employee,
+ leave_type="_Test Leave Type Encashment",
+ leave_period=self.leave_period.name,
+ payroll_date=today(),
+ currency="INR",
+ )
+ ).insert()
self.assertEqual(leave_encashment.leave_balance, 10)
self.assertEqual(leave_encashment.encashable_days, 5)
@@ -68,23 +85,27 @@
leave_encashment.submit()
# assert links
- add_sal = frappe.get_all("Additional Salary", filters = {"ref_docname": leave_encashment.name})[0]
+ add_sal = frappe.get_all("Additional Salary", filters={"ref_docname": leave_encashment.name})[0]
self.assertTrue(add_sal)
def test_creation_of_leave_ledger_entry_on_submit(self):
- frappe.db.sql('''delete from `tabLeave Encashment`''')
- leave_encashment = frappe.get_doc(dict(
- doctype='Leave Encashment',
- employee=self.employee,
- leave_type="_Test Leave Type Encashment",
- leave_period=self.leave_period.name,
- payroll_date=today(),
- currency="INR"
- )).insert()
+ frappe.db.sql("""delete from `tabLeave Encashment`""")
+ leave_encashment = frappe.get_doc(
+ dict(
+ doctype="Leave Encashment",
+ employee=self.employee,
+ leave_type="_Test Leave Type Encashment",
+ leave_period=self.leave_period.name,
+ payroll_date=today(),
+ currency="INR",
+ )
+ ).insert()
leave_encashment.submit()
- leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_encashment.name))
+ leave_ledger_entry = frappe.get_all(
+ "Leave Ledger Entry", fields="*", filters=dict(transaction_name=leave_encashment.name)
+ )
self.assertEqual(len(leave_ledger_entry), 1)
self.assertEqual(leave_ledger_entry[0].employee, leave_encashment.employee)
@@ -93,7 +114,11 @@
# check if leave ledger entry is deleted on cancellation
- frappe.db.sql("Delete from `tabAdditional Salary` WHERE ref_docname = %s", (leave_encashment.name) )
+ frappe.db.sql(
+ "Delete from `tabAdditional Salary` WHERE ref_docname = %s", (leave_encashment.name)
+ )
leave_encashment.cancel()
- self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_encashment.name}))
+ self.assertFalse(
+ frappe.db.exists("Leave Ledger Entry", {"transaction_name": leave_encashment.name})
+ )
diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
index a5923e0..fed9f77 100644
--- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
+++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
@@ -20,9 +20,11 @@
else:
frappe.throw(_("Only expired allocation can be cancelled"))
+
def validate_leave_allocation_against_leave_application(ledger):
- ''' Checks that leave allocation has no leave application against it '''
- leave_application_records = frappe.db.sql_list("""
+ """Checks that leave allocation has no leave application against it"""
+ leave_application_records = frappe.db.sql_list(
+ """
SELECT transaction_name
FROM `tabLeave Ledger Entry`
WHERE
@@ -31,15 +33,21 @@
AND transaction_type='Leave Application'
AND from_date>=%s
AND to_date<=%s
- """, (ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date))
+ """,
+ (ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date),
+ )
if leave_application_records:
- frappe.throw(_("Leave allocation {0} is linked with the Leave Application {1}").format(
- ledger.transaction_name, ', '.join(leave_application_records)))
+ frappe.throw(
+ _("Leave allocation {0} is linked with the Leave Application {1}").format(
+ ledger.transaction_name, ", ".join(leave_application_records)
+ )
+ )
+
def create_leave_ledger_entry(ref_doc, args, submit=True):
ledger = frappe._dict(
- doctype='Leave Ledger Entry',
+ doctype="Leave Ledger Entry",
employee=ref_doc.employee,
employee_name=ref_doc.employee_name,
leave_type=ref_doc.leave_type,
@@ -47,7 +55,7 @@
transaction_name=ref_doc.name,
is_carry_forward=0,
is_expired=0,
- is_lwp=0
+ is_lwp=0,
)
ledger.update(args)
@@ -58,54 +66,69 @@
else:
delete_ledger_entry(ledger)
+
def delete_ledger_entry(ledger):
- ''' Delete ledger entry on cancel of leave application/allocation/encashment '''
+ """Delete ledger entry on cancel of leave application/allocation/encashment"""
if ledger.transaction_type == "Leave Allocation":
validate_leave_allocation_against_leave_application(ledger)
expired_entry = get_previous_expiry_ledger_entry(ledger)
- frappe.db.sql("""DELETE
+ frappe.db.sql(
+ """DELETE
FROM `tabLeave Ledger Entry`
WHERE
`transaction_name`=%s
- OR `name`=%s""", (ledger.transaction_name, expired_entry))
+ OR `name`=%s""",
+ (ledger.transaction_name, expired_entry),
+ )
+
def get_previous_expiry_ledger_entry(ledger):
- ''' Returns the expiry ledger entry having same creation date as the ledger entry to be cancelled '''
- creation_date = frappe.db.get_value("Leave Ledger Entry", filters={
- 'transaction_name': ledger.transaction_name,
- 'is_expired': 0,
- 'transaction_type': 'Leave Allocation'
- }, fieldname=['creation'])
+ """Returns the expiry ledger entry having same creation date as the ledger entry to be cancelled"""
+ creation_date = frappe.db.get_value(
+ "Leave Ledger Entry",
+ filters={
+ "transaction_name": ledger.transaction_name,
+ "is_expired": 0,
+ "transaction_type": "Leave Allocation",
+ },
+ fieldname=["creation"],
+ )
- creation_date = creation_date.strftime(DATE_FORMAT) if creation_date else ''
+ creation_date = creation_date.strftime(DATE_FORMAT) if creation_date else ""
- return frappe.db.get_value("Leave Ledger Entry", filters={
- 'creation': ('like', creation_date+"%"),
- 'employee': ledger.employee,
- 'leave_type': ledger.leave_type,
- 'is_expired': 1,
- 'docstatus': 1,
- 'is_carry_forward': 0
- }, fieldname=['name'])
+ return frappe.db.get_value(
+ "Leave Ledger Entry",
+ filters={
+ "creation": ("like", creation_date + "%"),
+ "employee": ledger.employee,
+ "leave_type": ledger.leave_type,
+ "is_expired": 1,
+ "docstatus": 1,
+ "is_carry_forward": 0,
+ },
+ fieldname=["name"],
+ )
+
def process_expired_allocation():
- ''' Check if a carry forwarded allocation has expired and create a expiry ledger entry
- Case 1: carry forwarded expiry period is set for the leave type,
- create a separate leave expiry entry against each entry of carry forwarded and non carry forwarded leaves
- Case 2: leave type has no specific expiry period for carry forwarded leaves
- and there is no carry forwarded leave allocation, create a single expiry against the remaining leaves.
- '''
+ """Check if a carry forwarded allocation has expired and create a expiry ledger entry
+ Case 1: carry forwarded expiry period is set for the leave type,
+ create a separate leave expiry entry against each entry of carry forwarded and non carry forwarded leaves
+ Case 2: leave type has no specific expiry period for carry forwarded leaves
+ and there is no carry forwarded leave allocation, create a single expiry against the remaining leaves.
+ """
# fetch leave type records that has carry forwarded leaves expiry
- leave_type_records = frappe.db.get_values("Leave Type", filters={
- 'expire_carry_forwarded_leaves_after_days': (">", 0)
- }, fieldname=['name'])
+ leave_type_records = frappe.db.get_values(
+ "Leave Type", filters={"expire_carry_forwarded_leaves_after_days": (">", 0)}, fieldname=["name"]
+ )
- leave_type = [record[0] for record in leave_type_records] or ['']
+ leave_type = [record[0] for record in leave_type_records] or [""]
# fetch non expired leave ledger entry of transaction_type allocation
- expire_allocation = frappe.db.sql("""
+ expire_allocation = frappe.db.sql(
+ """
SELECT
leaves, to_date, employee, leave_type,
is_carry_forward, transaction_name as name, transaction_type
@@ -123,32 +146,41 @@
OR (is_carry_forward = 0 AND leave_type not in %s)
)))
AND transaction_type = 'Leave Allocation'
- AND to_date < %s""", (leave_type, today()), as_dict=1)
+ AND to_date < %s""",
+ (leave_type, today()),
+ as_dict=1,
+ )
if expire_allocation:
create_expiry_ledger_entry(expire_allocation)
+
def create_expiry_ledger_entry(allocations):
- ''' Create ledger entry for expired allocation '''
+ """Create ledger entry for expired allocation"""
for allocation in allocations:
if allocation.is_carry_forward:
expire_carried_forward_allocation(allocation)
else:
expire_allocation(allocation)
+
def get_remaining_leaves(allocation):
- ''' Returns remaining leaves from the given allocation '''
- return frappe.db.get_value("Leave Ledger Entry",
+ """Returns remaining leaves from the given allocation"""
+ return frappe.db.get_value(
+ "Leave Ledger Entry",
filters={
- 'employee': allocation.employee,
- 'leave_type': allocation.leave_type,
- 'to_date': ('<=', allocation.to_date),
- 'docstatus': 1
- }, fieldname=['SUM(leaves)'])
+ "employee": allocation.employee,
+ "leave_type": allocation.leave_type,
+ "to_date": ("<=", allocation.to_date),
+ "docstatus": 1,
+ },
+ fieldname=["SUM(leaves)"],
+ )
+
@frappe.whitelist()
def expire_allocation(allocation, expiry_date=None):
- ''' expires non-carry forwarded allocation '''
+ """expires non-carry forwarded allocation"""
leaves = get_remaining_leaves(allocation)
expiry_date = expiry_date if expiry_date else allocation.to_date
@@ -157,21 +189,28 @@
args = dict(
leaves=flt(leaves) * -1,
transaction_name=allocation.name,
- transaction_type='Leave Allocation',
+ transaction_type="Leave Allocation",
from_date=expiry_date,
to_date=expiry_date,
is_carry_forward=0,
- is_expired=1
+ is_expired=1,
)
create_leave_ledger_entry(allocation, args)
frappe.db.set_value("Leave Allocation", allocation.name, "expired", 1)
+
def expire_carried_forward_allocation(allocation):
- ''' Expires remaining leaves in the on carried forward allocation '''
+ """Expires remaining leaves in the on carried forward allocation"""
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period
- leaves_taken = get_leaves_for_period(allocation.employee, allocation.leave_type,
- allocation.from_date, allocation.to_date, skip_expired_leaves=False)
+
+ leaves_taken = get_leaves_for_period(
+ allocation.employee,
+ allocation.leave_type,
+ allocation.from_date,
+ allocation.to_date,
+ skip_expired_leaves=False,
+ )
leaves = flt(allocation.leaves) + flt(leaves_taken)
# allow expired leaves entry to be created
@@ -183,6 +222,6 @@
is_carry_forward=allocation.is_carry_forward,
is_expired=1,
from_date=allocation.to_date,
- to_date=allocation.to_date
+ to_date=allocation.to_date,
)
create_leave_ledger_entry(allocation, args)
diff --git a/erpnext/hr/doctype/leave_period/leave_period.py b/erpnext/hr/doctype/leave_period/leave_period.py
index b1cb688..6e62bb5 100644
--- a/erpnext/hr/doctype/leave_period/leave_period.py
+++ b/erpnext/hr/doctype/leave_period/leave_period.py
@@ -11,7 +11,6 @@
class LeavePeriod(Document):
-
def validate(self):
self.validate_dates()
validate_overlap(self, self.from_date, self.to_date, self.company)
diff --git a/erpnext/hr/doctype/leave_period/leave_period_dashboard.py b/erpnext/hr/doctype/leave_period/leave_period_dashboard.py
index 1adae0f..854f988 100644
--- a/erpnext/hr/doctype/leave_period/leave_period_dashboard.py
+++ b/erpnext/hr/doctype/leave_period/leave_period_dashboard.py
@@ -3,11 +3,6 @@
def get_data():
return {
- 'fieldname': 'leave_period',
- 'transactions': [
- {
- 'label': _('Transactions'),
- 'items': ['Leave Allocation']
- }
- ]
+ "fieldname": "leave_period",
+ "transactions": [{"label": _("Transactions"), "items": ["Leave Allocation"]}],
}
diff --git a/erpnext/hr/doctype/leave_period/test_leave_period.py b/erpnext/hr/doctype/leave_period/test_leave_period.py
index 10936dd..0923574 100644
--- a/erpnext/hr/doctype/leave_period/test_leave_period.py
+++ b/erpnext/hr/doctype/leave_period/test_leave_period.py
@@ -9,23 +9,32 @@
test_dependencies = ["Employee", "Leave Type", "Leave Policy"]
+
class TestLeavePeriod(unittest.TestCase):
pass
+
def create_leave_period(from_date, to_date, company=None):
- leave_period = frappe.db.get_value('Leave Period',
- dict(company=company or erpnext.get_default_company(),
+ leave_period = frappe.db.get_value(
+ "Leave Period",
+ dict(
+ company=company or erpnext.get_default_company(),
from_date=from_date,
to_date=to_date,
- is_active=1), 'name')
+ is_active=1,
+ ),
+ "name",
+ )
if leave_period:
return frappe.get_doc("Leave Period", leave_period)
- leave_period = frappe.get_doc({
- "doctype": "Leave Period",
- "company": company or erpnext.get_default_company(),
- "from_date": from_date,
- "to_date": to_date,
- "is_active": 1
- }).insert()
+ leave_period = frappe.get_doc(
+ {
+ "doctype": "Leave Period",
+ "company": company or erpnext.get_default_company(),
+ "from_date": from_date,
+ "to_date": to_date,
+ "is_active": 1,
+ }
+ ).insert()
return leave_period
diff --git a/erpnext/hr/doctype/leave_policy/leave_policy.py b/erpnext/hr/doctype/leave_policy/leave_policy.py
index 80450d5..33c9493 100644
--- a/erpnext/hr/doctype/leave_policy/leave_policy.py
+++ b/erpnext/hr/doctype/leave_policy/leave_policy.py
@@ -11,6 +11,12 @@
def validate(self):
if self.leave_policy_details:
for lp_detail in self.leave_policy_details:
- max_leaves_allowed = frappe.db.get_value("Leave Type", lp_detail.leave_type, "max_leaves_allowed")
+ max_leaves_allowed = frappe.db.get_value(
+ "Leave Type", lp_detail.leave_type, "max_leaves_allowed"
+ )
if max_leaves_allowed > 0 and lp_detail.annual_allocation > max_leaves_allowed:
- frappe.throw(_("Maximum leave allowed in the leave type {0} is {1}").format(lp_detail.leave_type, max_leaves_allowed))
+ frappe.throw(
+ _("Maximum leave allowed in the leave type {0} is {1}").format(
+ lp_detail.leave_type, max_leaves_allowed
+ )
+ )
diff --git a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py
index 73782d6..57ea93e 100644
--- a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py
+++ b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py
@@ -3,11 +3,8 @@
def get_data():
return {
- 'fieldname': 'leave_policy',
- 'transactions': [
- {
- 'label': _('Leaves'),
- 'items': ['Leave Policy Assignment', 'Leave Allocation']
- },
- ]
+ "fieldname": "leave_policy",
+ "transactions": [
+ {"label": _("Leaves"), "items": ["Leave Policy Assignment", "Leave Allocation"]},
+ ],
}
diff --git a/erpnext/hr/doctype/leave_policy/test_leave_policy.py b/erpnext/hr/doctype/leave_policy/test_leave_policy.py
index a4b8af7..33d5508 100644
--- a/erpnext/hr/doctype/leave_policy/test_leave_policy.py
+++ b/erpnext/hr/doctype/leave_policy/test_leave_policy.py
@@ -15,18 +15,25 @@
leave_type.max_leaves_allowed = 2
leave_type.save()
- leave_policy = create_leave_policy(leave_type=leave_type.name, annual_allocation=leave_type.max_leaves_allowed + 1)
+ leave_policy = create_leave_policy(
+ leave_type=leave_type.name, annual_allocation=leave_type.max_leaves_allowed + 1
+ )
self.assertRaises(frappe.ValidationError, leave_policy.insert)
+
def create_leave_policy(**args):
- ''' Returns an object of leave policy '''
+ """Returns an object of leave policy"""
args = frappe._dict(args)
- return frappe.get_doc({
- "doctype": "Leave Policy",
- "title": "Test Leave Policy",
- "leave_policy_details": [{
- "leave_type": args.leave_type or "_Test Leave Type",
- "annual_allocation": args.annual_allocation or 10
- }]
- })
+ return frappe.get_doc(
+ {
+ "doctype": "Leave Policy",
+ "title": "Test Leave Policy",
+ "leave_policy_details": [
+ {
+ "leave_type": args.leave_type or "_Test Leave Type",
+ "annual_allocation": args.annual_allocation or 10,
+ }
+ ],
+ }
+ )
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
index ae5ac7b..2ed86f3 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
@@ -22,22 +22,33 @@
def set_dates(self):
if self.assignment_based_on == "Leave Period":
- self.effective_from, self.effective_to = frappe.db.get_value("Leave Period", self.leave_period, ["from_date", "to_date"])
+ self.effective_from, self.effective_to = frappe.db.get_value(
+ "Leave Period", self.leave_period, ["from_date", "to_date"]
+ )
elif self.assignment_based_on == "Joining Date":
self.effective_from = frappe.db.get_value("Employee", self.employee, "date_of_joining")
def validate_policy_assignment_overlap(self):
- leave_policy_assignments = frappe.get_all("Leave Policy Assignment", filters = {
- "employee": self.employee,
- "name": ("!=", self.name),
- "docstatus": 1,
- "effective_to": (">=", self.effective_from),
- "effective_from": ("<=", self.effective_to)
- })
+ leave_policy_assignments = frappe.get_all(
+ "Leave Policy Assignment",
+ filters={
+ "employee": self.employee,
+ "name": ("!=", self.name),
+ "docstatus": 1,
+ "effective_to": (">=", self.effective_from),
+ "effective_from": ("<=", self.effective_to),
+ },
+ )
if len(leave_policy_assignments):
- frappe.throw(_("Leave Policy: {0} already assigned for Employee {1} for period {2} to {3}")
- .format(bold(self.leave_policy), bold(self.employee), bold(formatdate(self.effective_from)), bold(formatdate(self.effective_to))))
+ frappe.throw(
+ _("Leave Policy: {0} already assigned for Employee {1} for period {2} to {3}").format(
+ bold(self.leave_policy),
+ bold(self.employee),
+ bold(formatdate(self.effective_from)),
+ bold(formatdate(self.effective_to)),
+ )
+ )
def warn_about_carry_forwarding(self):
if not self.carry_forward:
@@ -49,8 +60,9 @@
for policy in leave_policy.leave_policy_details:
leave_type = leave_types.get(policy.leave_type)
if not leave_type.is_carry_forward:
- msg = _("Leaves for the Leave Type {0} won't be carry-forwarded since carry-forwarding is disabled.").format(
- frappe.bold(get_link_to_form("Leave Type", leave_type.name)))
+ msg = _(
+ "Leaves for the Leave Type {0} won't be carry-forwarded since carry-forwarding is disabled."
+ ).format(frappe.bold(get_link_to_form("Leave Type", leave_type.name)))
frappe.msgprint(msg, indicator="orange", alert=True)
@frappe.whitelist()
@@ -67,41 +79,54 @@
for leave_policy_detail in leave_policy.leave_policy_details:
if not leave_type_details.get(leave_policy_detail.leave_type).is_lwp:
leave_allocation, new_leaves_allocated = self.create_leave_allocation(
- leave_policy_detail.leave_type, leave_policy_detail.annual_allocation,
- leave_type_details, date_of_joining
+ leave_policy_detail.leave_type,
+ leave_policy_detail.annual_allocation,
+ leave_type_details,
+ date_of_joining,
)
- leave_allocations[leave_policy_detail.leave_type] = {"name": leave_allocation, "leaves": new_leaves_allocated}
+ leave_allocations[leave_policy_detail.leave_type] = {
+ "name": leave_allocation,
+ "leaves": new_leaves_allocated,
+ }
self.db_set("leaves_allocated", 1)
return leave_allocations
- def create_leave_allocation(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining):
+ def create_leave_allocation(
+ self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining
+ ):
# Creates leave allocation for the given employee in the provided leave period
carry_forward = self.carry_forward
if self.carry_forward and not leave_type_details.get(leave_type).is_carry_forward:
carry_forward = 0
- new_leaves_allocated = self.get_new_leaves(leave_type, new_leaves_allocated,
- leave_type_details, date_of_joining)
+ new_leaves_allocated = self.get_new_leaves(
+ leave_type, new_leaves_allocated, leave_type_details, date_of_joining
+ )
- allocation = frappe.get_doc(dict(
- doctype="Leave Allocation",
- employee=self.employee,
- leave_type=leave_type,
- from_date=self.effective_from,
- to_date=self.effective_to,
- new_leaves_allocated=new_leaves_allocated,
- leave_period=self.leave_period if self.assignment_based_on == "Leave Policy" else '',
- leave_policy_assignment = self.name,
- leave_policy = self.leave_policy,
- carry_forward=carry_forward
- ))
- allocation.save(ignore_permissions = True)
+ allocation = frappe.get_doc(
+ dict(
+ doctype="Leave Allocation",
+ employee=self.employee,
+ leave_type=leave_type,
+ from_date=self.effective_from,
+ to_date=self.effective_to,
+ new_leaves_allocated=new_leaves_allocated,
+ leave_period=self.leave_period if self.assignment_based_on == "Leave Policy" else "",
+ leave_policy_assignment=self.name,
+ leave_policy=self.leave_policy,
+ carry_forward=carry_forward,
+ )
+ )
+ allocation.save(ignore_permissions=True)
allocation.submit()
return allocation.name, new_leaves_allocated
def get_new_leaves(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining):
from frappe.model.meta import get_field_precision
- precision = get_field_precision(frappe.get_meta("Leave Allocation").get_field("new_leaves_allocated"))
+
+ precision = get_field_precision(
+ frappe.get_meta("Leave Allocation").get_field("new_leaves_allocated")
+ )
# Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0
if leave_type_details.get(leave_type).is_compensatory == 1:
@@ -112,16 +137,22 @@
new_leaves_allocated = 0
else:
# get leaves for past months if assignment is based on Leave Period / Joining Date
- new_leaves_allocated = self.get_leaves_for_passed_months(leave_type, new_leaves_allocated, leave_type_details, date_of_joining)
+ new_leaves_allocated = self.get_leaves_for_passed_months(
+ leave_type, new_leaves_allocated, leave_type_details, date_of_joining
+ )
# Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period
elif getdate(date_of_joining) > getdate(self.effective_from):
- remaining_period = ((date_diff(self.effective_to, date_of_joining) + 1) / (date_diff(self.effective_to, self.effective_from) + 1))
+ remaining_period = (date_diff(self.effective_to, date_of_joining) + 1) / (
+ date_diff(self.effective_to, self.effective_from) + 1
+ )
new_leaves_allocated = ceil(new_leaves_allocated * remaining_period)
return flt(new_leaves_allocated, precision)
- def get_leaves_for_passed_months(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining):
+ def get_leaves_for_passed_months(
+ self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining
+ ):
from erpnext.hr.utils import get_monthly_earned_leave
current_date = frappe.flags.current_date or getdate()
@@ -144,8 +175,11 @@
months_passed = add_current_month_if_applicable(months_passed, date_of_joining, based_on_doj)
if months_passed > 0:
- monthly_earned_leave = get_monthly_earned_leave(new_leaves_allocated,
- leave_type_details.get(leave_type).earned_leave_frequency, leave_type_details.get(leave_type).rounding)
+ monthly_earned_leave = get_monthly_earned_leave(
+ new_leaves_allocated,
+ leave_type_details.get(leave_type).earned_leave_frequency,
+ leave_type_details.get(leave_type).rounding,
+ )
new_leaves_allocated = monthly_earned_leave * months_passed
else:
new_leaves_allocated = 0
@@ -174,7 +208,7 @@
def create_assignment_for_multiple_employees(employees, data):
if isinstance(employees, str):
- employees= json.loads(employees)
+ employees = json.loads(employees)
if isinstance(data, str):
data = frappe._dict(json.loads(data))
@@ -201,11 +235,23 @@
return docs_name
+
def get_leave_type_details():
leave_type_details = frappe._dict()
- leave_types = frappe.get_all("Leave Type",
- fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "based_on_date_of_joining",
- "is_carry_forward", "expire_carry_forwarded_leaves_after_days", "earned_leave_frequency", "rounding"])
+ leave_types = frappe.get_all(
+ "Leave Type",
+ fields=[
+ "name",
+ "is_lwp",
+ "is_earned_leave",
+ "is_compensatory",
+ "based_on_date_of_joining",
+ "is_carry_forward",
+ "expire_carry_forwarded_leaves_after_days",
+ "earned_leave_frequency",
+ "rounding",
+ ],
+ )
for d in leave_types:
leave_type_details.setdefault(d.name, d)
return leave_type_details
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py
index 4363439..13b39c7 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment_dashboard.py
@@ -3,11 +3,8 @@
def get_data():
return {
- 'fieldname': 'leave_policy_assignment',
- 'transactions': [
- {
- 'label': _('Leaves'),
- 'items': ['Leave Allocation']
- },
- ]
+ "fieldname": "leave_policy_assignment",
+ "transactions": [
+ {"label": _("Leaves"), "items": ["Leave Allocation"]},
+ ],
}
diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
index 27e4f14..9780828 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
@@ -17,9 +17,16 @@
test_dependencies = ["Employee"]
+
class TestLeavePolicyAssignment(unittest.TestCase):
def setUp(self):
- for doctype in ["Leave Period", "Leave Application", "Leave Allocation", "Leave Policy Assignment", "Leave Ledger Entry"]:
+ for doctype in [
+ "Leave Period",
+ "Leave Application",
+ "Leave Allocation",
+ "Leave Policy Assignment",
+ "Leave Ledger Entry",
+ ]:
frappe.db.delete(doctype)
employee = get_employee()
@@ -35,16 +42,25 @@
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
- "leave_period": leave_period.name
+ "leave_period": leave_period.name,
}
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
- self.assertEqual(frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "leaves_allocated"), 1)
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee.name], frappe._dict(data)
+ )
+ self.assertEqual(
+ frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "leaves_allocated"),
+ 1,
+ )
- leave_allocation = frappe.get_list("Leave Allocation", filters={
- "employee": self.employee.name,
- "leave_policy":leave_policy.name,
- "leave_policy_assignment": leave_policy_assignments[0],
- "docstatus": 1})[0]
+ leave_allocation = frappe.get_list(
+ "Leave Allocation",
+ filters={
+ "employee": self.employee.name,
+ "leave_policy": leave_policy.name,
+ "leave_policy_assignment": leave_policy_assignments[0],
+ "docstatus": 1,
+ },
+ )[0]
leave_alloc_doc = frappe.get_doc("Leave Allocation", leave_allocation)
self.assertEqual(leave_alloc_doc.new_leaves_allocated, 10)
@@ -63,68 +79,94 @@
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
- "leave_period": leave_period.name
+ "leave_period": leave_period.name,
}
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee.name], frappe._dict(data)
+ )
# every leave is allocated no more leave can be granted now
- self.assertEqual(frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "leaves_allocated"), 1)
- leave_allocation = frappe.get_list("Leave Allocation", filters={
- "employee": self.employee.name,
- "leave_policy":leave_policy.name,
- "leave_policy_assignment": leave_policy_assignments[0],
- "docstatus": 1})[0]
+ self.assertEqual(
+ frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "leaves_allocated"),
+ 1,
+ )
+ leave_allocation = frappe.get_list(
+ "Leave Allocation",
+ filters={
+ "employee": self.employee.name,
+ "leave_policy": leave_policy.name,
+ "leave_policy_assignment": leave_policy_assignments[0],
+ "docstatus": 1,
+ },
+ )[0]
leave_alloc_doc = frappe.get_doc("Leave Allocation", leave_allocation)
leave_alloc_doc.cancel()
leave_alloc_doc.delete()
- self.assertEqual(frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "leaves_allocated"), 0)
+ self.assertEqual(
+ frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "leaves_allocated"),
+ 0,
+ )
def test_earned_leave_allocation(self):
leave_period = create_leave_period("Test Earned Leave Period")
leave_type = create_earned_leave_type("Test Earned Leave")
- leave_policy = frappe.get_doc({
- "doctype": "Leave Policy",
- "title": "Test Leave Policy",
- "leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 6}]
- }).submit()
+ leave_policy = frappe.get_doc(
+ {
+ "doctype": "Leave Policy",
+ "title": "Test Leave Policy",
+ "leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 6}],
+ }
+ ).submit()
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
- "leave_period": leave_period.name
+ "leave_period": leave_period.name,
}
# second last day of the month
# leaves allocated should be 0 since it is an earned leave and allocation happens via scheduler based on set frequency
frappe.flags.current_date = add_days(get_last_day(getdate()), -1)
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee.name], frappe._dict(data)
+ )
- leaves_allocated = frappe.db.get_value("Leave Allocation", {
- "leave_policy_assignment": leave_policy_assignments[0]
- }, "total_leaves_allocated")
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
self.assertEqual(leaves_allocated, 0)
def test_earned_leave_alloc_for_passed_months_based_on_leave_period(self):
- leave_period, leave_policy = setup_leave_period_and_policy(get_first_day(add_months(getdate(), -1)))
+ leave_period, leave_policy = setup_leave_period_and_policy(
+ get_first_day(add_months(getdate(), -1))
+ )
# Case 1: assignment created one month after the leave period, should allocate 1 leave
frappe.flags.current_date = get_first_day(getdate())
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
- "leave_period": leave_period.name
+ "leave_period": leave_period.name,
}
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee.name], frappe._dict(data)
+ )
- leaves_allocated = frappe.db.get_value("Leave Allocation", {
- "leave_policy_assignment": leave_policy_assignments[0]
- }, "total_leaves_allocated")
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
self.assertEqual(leaves_allocated, 1)
def test_earned_leave_alloc_for_passed_months_on_month_end_based_on_leave_period(self):
- leave_period, leave_policy = setup_leave_period_and_policy(get_first_day(add_months(getdate(), -2)))
+ leave_period, leave_policy = setup_leave_period_and_policy(
+ get_first_day(add_months(getdate(), -2))
+ )
# Case 2: assignment created on the last day of the leave period's latter month
# should allocate 1 leave for current month even though the month has not ended
# since the daily job might have already executed
@@ -133,32 +175,48 @@
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
- "leave_period": leave_period.name
+ "leave_period": leave_period.name,
}
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee.name], frappe._dict(data)
+ )
- leaves_allocated = frappe.db.get_value("Leave Allocation", {
- "leave_policy_assignment": leave_policy_assignments[0]
- }, "total_leaves_allocated")
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
self.assertEqual(leaves_allocated, 3)
# if the daily job is not completed yet, there is another check present
# to ensure leave is not already allocated to avoid duplication
from erpnext.hr.utils import allocate_earned_leaves
+
allocate_earned_leaves()
- leaves_allocated = frappe.db.get_value("Leave Allocation", {
- "leave_policy_assignment": leave_policy_assignments[0]
- }, "total_leaves_allocated")
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
self.assertEqual(leaves_allocated, 3)
def test_earned_leave_alloc_for_passed_months_with_cf_leaves_based_on_leave_period(self):
from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
- leave_period, leave_policy = setup_leave_period_and_policy(get_first_day(add_months(getdate(), -2)))
+ leave_period, leave_policy = setup_leave_period_and_policy(
+ get_first_day(add_months(getdate(), -2))
+ )
# initial leave allocation = 5
- leave_allocation = create_leave_allocation(employee=self.employee.name, employee_name=self.employee.employee_name, leave_type="Test Earned Leave",
- from_date=add_months(getdate(), -12), to_date=add_months(getdate(), -3), new_leaves_allocated=5, carry_forward=0)
+ leave_allocation = create_leave_allocation(
+ employee=self.employee.name,
+ employee_name=self.employee.employee_name,
+ leave_type="Test Earned Leave",
+ from_date=add_months(getdate(), -12),
+ to_date=add_months(getdate(), -3),
+ new_leaves_allocated=5,
+ carry_forward=0,
+ )
leave_allocation.submit()
# Case 3: assignment created on the last day of the leave period's latter month with carry forwarding
@@ -167,14 +225,19 @@
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
"leave_period": leave_period.name,
- "carry_forward": 1
+ "carry_forward": 1,
}
# carry forwarded leaves = 5, 3 leaves allocated for passed months
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee.name], frappe._dict(data)
+ )
- details = frappe.db.get_value("Leave Allocation", {
- "leave_policy_assignment": leave_policy_assignments[0]
- }, ["total_leaves_allocated", "new_leaves_allocated", "unused_leaves", "name"], as_dict=True)
+ details = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ ["total_leaves_allocated", "new_leaves_allocated", "unused_leaves", "name"],
+ as_dict=True,
+ )
self.assertEqual(details.new_leaves_allocated, 2)
self.assertEqual(details.unused_leaves, 5)
self.assertEqual(details.total_leaves_allocated, 7)
@@ -182,20 +245,27 @@
# if the daily job is not completed yet, there is another check present
# to ensure leave is not already allocated to avoid duplication
from erpnext.hr.utils import is_earned_leave_already_allocated
+
frappe.flags.current_date = get_last_day(getdate())
allocation = frappe.get_doc("Leave Allocation", details.name)
# 1 leave is still pending to be allocated, irrespective of carry forwarded leaves
- self.assertFalse(is_earned_leave_already_allocated(allocation, leave_policy.leave_policy_details[0].annual_allocation))
+ self.assertFalse(
+ is_earned_leave_already_allocated(
+ allocation, leave_policy.leave_policy_details[0].annual_allocation
+ )
+ )
def test_earned_leave_alloc_for_passed_months_based_on_joining_date(self):
# tests leave alloc for earned leaves for assignment based on joining date in policy assignment
leave_type = create_earned_leave_type("Test Earned Leave")
- leave_policy = frappe.get_doc({
- "doctype": "Leave Policy",
- "title": "Test Leave Policy",
- "leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}]
- }).submit()
+ leave_policy = frappe.get_doc(
+ {
+ "doctype": "Leave Policy",
+ "title": "Test Leave Policy",
+ "leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}],
+ }
+ ).submit()
# joining date set to 2 months back
self.employee.date_of_joining = get_first_day(add_months(getdate(), -2))
@@ -203,29 +273,39 @@
# assignment created on the last day of the current month
frappe.flags.current_date = get_last_day(getdate())
- data = {
- "assignment_based_on": "Joining Date",
- "leave_policy": leave_policy.name
- }
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
- leaves_allocated = frappe.db.get_value("Leave Allocation", {"leave_policy_assignment": leave_policy_assignments[0]},
- "total_leaves_allocated")
- effective_from = frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "effective_from")
+ data = {"assignment_based_on": "Joining Date", "leave_policy": leave_policy.name}
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee.name], frappe._dict(data)
+ )
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
+ effective_from = frappe.db.get_value(
+ "Leave Policy Assignment", leave_policy_assignments[0], "effective_from"
+ )
self.assertEqual(effective_from, self.employee.date_of_joining)
self.assertEqual(leaves_allocated, 3)
# to ensure leave is not already allocated to avoid duplication
from erpnext.hr.utils import allocate_earned_leaves
+
frappe.flags.current_date = get_last_day(getdate())
allocate_earned_leaves()
- leaves_allocated = frappe.db.get_value("Leave Allocation", {"leave_policy_assignment": leave_policy_assignments[0]},
- "total_leaves_allocated")
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
self.assertEqual(leaves_allocated, 3)
def test_grant_leaves_on_doj_for_earned_leaves_based_on_leave_period(self):
# tests leave alloc based on leave period for earned leaves with "based on doj" configuration in leave type
- leave_period, leave_policy = setup_leave_period_and_policy(get_first_day(add_months(getdate(), -2)), based_on_doj=True)
+ leave_period, leave_policy = setup_leave_period_and_policy(
+ get_first_day(add_months(getdate(), -2)), based_on_doj=True
+ )
# joining date set to 2 months back
self.employee.date_of_joining = get_first_day(add_months(getdate(), -2))
@@ -237,34 +317,43 @@
data = {
"assignment_based_on": "Leave Period",
"leave_policy": leave_policy.name,
- "leave_period": leave_period.name
+ "leave_period": leave_period.name,
}
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee.name], frappe._dict(data)
+ )
- leaves_allocated = frappe.db.get_value("Leave Allocation", {
- "leave_policy_assignment": leave_policy_assignments[0]
- }, "total_leaves_allocated")
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
self.assertEqual(leaves_allocated, 3)
# if the daily job is not completed yet, there is another check present
# to ensure leave is not already allocated to avoid duplication
from erpnext.hr.utils import allocate_earned_leaves
+
frappe.flags.current_date = get_first_day(getdate())
allocate_earned_leaves()
- leaves_allocated = frappe.db.get_value("Leave Allocation", {
- "leave_policy_assignment": leave_policy_assignments[0]
- }, "total_leaves_allocated")
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
self.assertEqual(leaves_allocated, 3)
def test_grant_leaves_on_doj_for_earned_leaves_based_on_joining_date(self):
# tests leave alloc based on joining date for earned leaves with "based on doj" configuration in leave type
leave_type = create_earned_leave_type("Test Earned Leave", based_on_doj=True)
- leave_policy = frappe.get_doc({
- "doctype": "Leave Policy",
- "title": "Test Leave Policy",
- "leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}]
- }).submit()
+ leave_policy = frappe.get_doc(
+ {
+ "doctype": "Leave Policy",
+ "title": "Test Leave Policy",
+ "leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}],
+ }
+ ).submit()
# joining date set to 2 months back
# leave should be allocated for current month too since this day is same as the joining day
@@ -273,24 +362,32 @@
# assignment created on the first day of the current month
frappe.flags.current_date = get_first_day(getdate())
- data = {
- "assignment_based_on": "Joining Date",
- "leave_policy": leave_policy.name
- }
- leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
- leaves_allocated = frappe.db.get_value("Leave Allocation", {"leave_policy_assignment": leave_policy_assignments[0]},
- "total_leaves_allocated")
- effective_from = frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "effective_from")
+ data = {"assignment_based_on": "Joining Date", "leave_policy": leave_policy.name}
+ leave_policy_assignments = create_assignment_for_multiple_employees(
+ [self.employee.name], frappe._dict(data)
+ )
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
+ effective_from = frappe.db.get_value(
+ "Leave Policy Assignment", leave_policy_assignments[0], "effective_from"
+ )
self.assertEqual(effective_from, self.employee.date_of_joining)
self.assertEqual(leaves_allocated, 3)
# to ensure leave is not already allocated to avoid duplication
from erpnext.hr.utils import allocate_earned_leaves
+
frappe.flags.current_date = get_first_day(getdate())
allocate_earned_leaves()
- leaves_allocated = frappe.db.get_value("Leave Allocation", {"leave_policy_assignment": leave_policy_assignments[0]},
- "total_leaves_allocated")
+ leaves_allocated = frappe.db.get_value(
+ "Leave Allocation",
+ {"leave_policy_assignment": leave_policy_assignments[0]},
+ "total_leaves_allocated",
+ )
self.assertEqual(leaves_allocated, 3)
def tearDown(self):
@@ -302,15 +399,17 @@
def create_earned_leave_type(leave_type, based_on_doj=False):
frappe.delete_doc_if_exists("Leave Type", leave_type, force=1)
- return frappe.get_doc(dict(
- leave_type_name=leave_type,
- doctype="Leave Type",
- is_earned_leave=1,
- earned_leave_frequency="Monthly",
- rounding=0.5,
- is_carry_forward=1,
- based_on_date_of_joining=based_on_doj
- )).insert()
+ return frappe.get_doc(
+ dict(
+ leave_type_name=leave_type,
+ doctype="Leave Type",
+ is_earned_leave=1,
+ earned_leave_frequency="Monthly",
+ rounding=0.5,
+ is_carry_forward=1,
+ based_on_date_of_joining=based_on_doj,
+ )
+ ).insert()
def create_leave_period(name, start_date=None):
@@ -318,24 +417,27 @@
if not start_date:
start_date = get_first_day(getdate())
- return frappe.get_doc(dict(
- name=name,
- doctype="Leave Period",
- from_date=start_date,
- to_date=add_months(start_date, 12),
- company="_Test Company",
- is_active=1
- )).insert()
+ return frappe.get_doc(
+ dict(
+ name=name,
+ doctype="Leave Period",
+ from_date=start_date,
+ to_date=add_months(start_date, 12),
+ company="_Test Company",
+ is_active=1,
+ )
+ ).insert()
def setup_leave_period_and_policy(start_date, based_on_doj=False):
leave_type = create_earned_leave_type("Test Earned Leave", based_on_doj)
- leave_period = create_leave_period("Test Earned Leave Period",
- start_date=start_date)
- leave_policy = frappe.get_doc({
- "doctype": "Leave Policy",
- "title": "Test Leave Policy",
- "leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}]
- }).insert()
+ leave_period = create_leave_period("Test Earned Leave Period", start_date=start_date)
+ leave_policy = frappe.get_doc(
+ {
+ "doctype": "Leave Policy",
+ "title": "Test Leave Policy",
+ "leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}],
+ }
+ ).insert()
- return leave_period, leave_policy
\ No newline at end of file
+ return leave_period, leave_policy
diff --git a/erpnext/hr/doctype/leave_type/leave_type.py b/erpnext/hr/doctype/leave_type/leave_type.py
index 4b59c2c..82b9bd6 100644
--- a/erpnext/hr/doctype/leave_type/leave_type.py
+++ b/erpnext/hr/doctype/leave_type/leave_type.py
@@ -11,17 +11,23 @@
class LeaveType(Document):
def validate(self):
if self.is_lwp:
- leave_allocation = frappe.get_all("Leave Allocation", filters={
- 'leave_type': self.name,
- 'from_date': ("<=", today()),
- 'to_date': (">=", today())
- }, fields=['name'])
- leave_allocation = [l['name'] for l in leave_allocation]
+ leave_allocation = frappe.get_all(
+ "Leave Allocation",
+ filters={"leave_type": self.name, "from_date": ("<=", today()), "to_date": (">=", today())},
+ fields=["name"],
+ )
+ leave_allocation = [l["name"] for l in leave_allocation]
if leave_allocation:
- frappe.throw(_('Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay').format(", ".join(leave_allocation))) #nosec
+ frappe.throw(
+ _(
+ "Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay"
+ ).format(", ".join(leave_allocation))
+ ) # nosec
if self.is_lwp and self.is_ppl:
frappe.throw(_("Leave Type can be either without pay or partial pay"))
- if self.is_ppl and (self.fraction_of_daily_salary_per_leave < 0 or self.fraction_of_daily_salary_per_leave > 1):
+ if self.is_ppl and (
+ self.fraction_of_daily_salary_per_leave < 0 or self.fraction_of_daily_salary_per_leave > 1
+ ):
frappe.throw(_("The fraction of Daily Salary per Leave should be between 0 and 1"))
diff --git a/erpnext/hr/doctype/leave_type/leave_type_dashboard.py b/erpnext/hr/doctype/leave_type/leave_type_dashboard.py
index 074d3e4..269a1ec 100644
--- a/erpnext/hr/doctype/leave_type/leave_type_dashboard.py
+++ b/erpnext/hr/doctype/leave_type/leave_type_dashboard.py
@@ -1,12 +1,10 @@
def get_data():
return {
- 'fieldname': 'leave_type',
- 'transactions': [
+ "fieldname": "leave_type",
+ "transactions": [
{
- 'items': ['Leave Allocation', 'Leave Application'],
+ "items": ["Leave Allocation", "Leave Application"],
},
- {
- 'items': ['Attendance', 'Leave Encashment']
- }
- ]
+ {"items": ["Attendance", "Leave Encashment"]},
+ ],
}
diff --git a/erpnext/hr/doctype/leave_type/test_leave_type.py b/erpnext/hr/doctype/leave_type/test_leave_type.py
index c1b64e9..69f9e12 100644
--- a/erpnext/hr/doctype/leave_type/test_leave_type.py
+++ b/erpnext/hr/doctype/leave_type/test_leave_type.py
@@ -3,27 +3,30 @@
import frappe
-test_records = frappe.get_test_records('Leave Type')
+test_records = frappe.get_test_records("Leave Type")
+
def create_leave_type(**args):
- args = frappe._dict(args)
- if frappe.db.exists("Leave Type", args.leave_type_name):
- return frappe.get_doc("Leave Type", args.leave_type_name)
- leave_type = frappe.get_doc({
- "doctype": "Leave Type",
- "leave_type_name": args.leave_type_name or "_Test Leave Type",
- "include_holiday": args.include_holidays or 1,
- "allow_encashment": args.allow_encashment or 0,
- "is_earned_leave": args.is_earned_leave or 0,
- "is_lwp": args.is_lwp or 0,
- "is_ppl":args.is_ppl or 0,
- "is_carry_forward": args.is_carry_forward or 0,
- "expire_carry_forwarded_leaves_after_days": args.expire_carry_forwarded_leaves_after_days or 0,
- "encashment_threshold_days": args.encashment_threshold_days or 5,
- "earning_component": "Leave Encashment"
- })
+ args = frappe._dict(args)
+ if frappe.db.exists("Leave Type", args.leave_type_name):
+ return frappe.get_doc("Leave Type", args.leave_type_name)
+ leave_type = frappe.get_doc(
+ {
+ "doctype": "Leave Type",
+ "leave_type_name": args.leave_type_name or "_Test Leave Type",
+ "include_holiday": args.include_holidays or 1,
+ "allow_encashment": args.allow_encashment or 0,
+ "is_earned_leave": args.is_earned_leave or 0,
+ "is_lwp": args.is_lwp or 0,
+ "is_ppl": args.is_ppl or 0,
+ "is_carry_forward": args.is_carry_forward or 0,
+ "expire_carry_forwarded_leaves_after_days": args.expire_carry_forwarded_leaves_after_days or 0,
+ "encashment_threshold_days": args.encashment_threshold_days or 5,
+ "earning_component": "Leave Encashment",
+ }
+ )
- if leave_type.is_ppl:
- leave_type.fraction_of_daily_salary_per_leave = args.fraction_of_daily_salary_per_leave or 0.5
+ if leave_type.is_ppl:
+ leave_type.fraction_of_daily_salary_per_leave = args.fraction_of_daily_salary_per_leave or 0.5
- return leave_type
+ return leave_type
diff --git a/erpnext/hr/doctype/offer_term/test_offer_term.py b/erpnext/hr/doctype/offer_term/test_offer_term.py
index 2e5ed75..2bea7b2 100644
--- a/erpnext/hr/doctype/offer_term/test_offer_term.py
+++ b/erpnext/hr/doctype/offer_term/test_offer_term.py
@@ -5,5 +5,6 @@
# test_records = frappe.get_test_records('Offer Term')
+
class TestOfferTerm(unittest.TestCase):
pass
diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
index 5177302..5a12486 100644
--- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py
+++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
@@ -20,7 +20,7 @@
self.validate_overlapping_dates()
if self.end_date:
- self.validate_from_to_dates('start_date', 'end_date')
+ self.validate_from_to_dates("start_date", "end_date")
def validate_overlapping_dates(self):
if not self.name:
@@ -33,7 +33,7 @@
"""
if self.end_date:
- condition += """ or
+ condition += """ or
%(end_date)s between start_date and end_date
or
start_date between %(start_date)s and %(end_date)s
@@ -41,7 +41,8 @@
else:
condition += """ ) """
- assigned_shifts = frappe.db.sql("""
+ assigned_shifts = frappe.db.sql(
+ """
select name, shift_type, start_date ,end_date, docstatus, status
from `tabShift Assignment`
where
@@ -49,13 +50,18 @@
and name != %(name)s
and status = "Active"
{0}
- """.format(condition), {
- "employee": self.employee,
- "shift_type": self.shift_type,
- "start_date": self.start_date,
- "end_date": self.end_date,
- "name": self.name
- }, as_dict = 1)
+ """.format(
+ condition
+ ),
+ {
+ "employee": self.employee,
+ "shift_type": self.shift_type,
+ "start_date": self.start_date,
+ "end_date": self.end_date,
+ "name": self.name,
+ },
+ as_dict=1,
+ )
if len(assigned_shifts):
self.throw_overlap_error(assigned_shifts[0])
@@ -63,7 +69,9 @@
def throw_overlap_error(self, shift_details):
shift_details = frappe._dict(shift_details)
if shift_details.docstatus == 1 and shift_details.status == "Active":
- msg = _("Employee {0} already has Active Shift {1}: {2}").format(frappe.bold(self.employee), frappe.bold(self.shift_type), frappe.bold(shift_details.name))
+ msg = _("Employee {0} already has Active Shift {1}: {2}").format(
+ frappe.bold(self.employee), frappe.bold(self.shift_type), frappe.bold(shift_details.name)
+ )
if shift_details.start_date:
msg += _(" from {0}").format(getdate(self.start_date).strftime("%d-%m-%Y"))
title = "Ongoing Shift"
@@ -73,23 +81,27 @@
if msg:
frappe.throw(msg, title=title)
+
@frappe.whitelist()
def get_events(start, end, filters=None):
events = []
- employee = frappe.db.get_value("Employee", {"user_id": frappe.session.user}, ["name", "company"],
- as_dict=True)
+ employee = frappe.db.get_value(
+ "Employee", {"user_id": frappe.session.user}, ["name", "company"], as_dict=True
+ )
if employee:
employee, company = employee.name, employee.company
else:
- employee=''
- company=frappe.db.get_value("Global Defaults", None, "default_company")
+ employee = ""
+ company = frappe.db.get_value("Global Defaults", None, "default_company")
from frappe.desk.reportview import get_filters_cond
+
conditions = get_filters_cond("Shift Assignment", filters, [])
add_assignments(events, start, end, conditions=conditions)
return events
+
def add_assignments(events, start, end, conditions=None):
query = """select name, start_date, end_date, employee_name,
employee, docstatus, shift_type
@@ -101,7 +113,7 @@
if conditions:
query += conditions
- records = frappe.db.sql(query, {"start_date":start, "end_date":end}, as_dict=True)
+ records = frappe.db.sql(query, {"start_date": start, "end_date": end}, as_dict=True)
shift_timing_map = get_shift_type_timing([d.shift_type for d in records])
for d in records:
@@ -109,27 +121,33 @@
daily_event_end = d.end_date if d.end_date else getdate()
delta = timedelta(days=1)
while daily_event_start <= daily_event_end:
- start_timing = frappe.utils.get_datetime(daily_event_start)+ shift_timing_map[d.shift_type]['start_time']
- end_timing = frappe.utils.get_datetime(daily_event_start)+ shift_timing_map[d.shift_type]['end_time']
+ start_timing = (
+ frappe.utils.get_datetime(daily_event_start) + shift_timing_map[d.shift_type]["start_time"]
+ )
+ end_timing = (
+ frappe.utils.get_datetime(daily_event_start) + shift_timing_map[d.shift_type]["end_time"]
+ )
daily_event_start += delta
e = {
"name": d.name,
"doctype": "Shift Assignment",
"start_date": start_timing,
"end_date": end_timing,
- "title": cstr(d.employee_name) + ": "+ \
- cstr(d.shift_type),
+ "title": cstr(d.employee_name) + ": " + cstr(d.shift_type),
"docstatus": d.docstatus,
- "allDay": 0
+ "allDay": 0,
}
if e not in events:
events.append(e)
return events
+
def get_shift_type_timing(shift_types):
shift_timing_map = {}
- data = frappe.get_all("Shift Type", filters = {"name": ("IN", shift_types)}, fields = ['name', 'start_time', 'end_time'])
+ data = frappe.get_all(
+ "Shift Type", filters={"name": ("IN", shift_types)}, fields=["name", "start_time", "end_time"]
+ )
for d in data:
shift_timing_map[d.name] = d
@@ -137,7 +155,9 @@
return shift_timing_map
-def get_employee_shift(employee, for_date=None, consider_default_shift=False, next_shift_direction=None):
+def get_employee_shift(
+ employee, for_date=None, consider_default_shift=False, next_shift_direction=None
+):
"""Returns a Shift Type for the given employee on the given date. (excluding the holidays)
:param employee: Employee for which shift is required.
@@ -147,21 +167,25 @@
"""
if for_date is None:
for_date = nowdate()
- default_shift = frappe.db.get_value('Employee', employee, 'default_shift')
+ default_shift = frappe.db.get_value("Employee", employee, "default_shift")
shift_type_name = None
- shift_assignment_details = frappe.db.get_value('Shift Assignment', {'employee':employee, 'start_date':('<=', for_date), 'docstatus': '1', 'status': "Active"}, ['shift_type', 'end_date'])
+ shift_assignment_details = frappe.db.get_value(
+ "Shift Assignment",
+ {"employee": employee, "start_date": ("<=", for_date), "docstatus": "1", "status": "Active"},
+ ["shift_type", "end_date"],
+ )
if shift_assignment_details:
shift_type_name = shift_assignment_details[0]
# if end_date present means that shift is over after end_date else it is a ongoing shift.
- if shift_assignment_details[1] and for_date >= shift_assignment_details[1] :
+ if shift_assignment_details[1] and for_date >= shift_assignment_details[1]:
shift_type_name = None
if not shift_type_name and consider_default_shift:
shift_type_name = default_shift
if shift_type_name:
- holiday_list_name = frappe.db.get_value('Shift Type', shift_type_name, 'holiday_list')
+ holiday_list_name = frappe.db.get_value("Shift Type", shift_type_name, "holiday_list")
if not holiday_list_name:
holiday_list_name = get_holiday_list_for_employee(employee, False)
if holiday_list_name and is_holiday(holiday_list_name, for_date):
@@ -170,22 +194,30 @@
if not shift_type_name and next_shift_direction:
MAX_DAYS = 366
if consider_default_shift and default_shift:
- direction = -1 if next_shift_direction == 'reverse' else +1
+ direction = -1 if next_shift_direction == "reverse" else +1
for i in range(MAX_DAYS):
- date = for_date+timedelta(days=direction*(i+1))
+ date = for_date + timedelta(days=direction * (i + 1))
shift_details = get_employee_shift(employee, date, consider_default_shift, None)
if shift_details:
shift_type_name = shift_details.shift_type.name
for_date = date
break
else:
- direction = '<' if next_shift_direction == 'reverse' else '>'
- sort_order = 'desc' if next_shift_direction == 'reverse' else 'asc'
- dates = frappe.db.get_all('Shift Assignment',
- ['start_date', 'end_date'],
- {'employee':employee, 'start_date':(direction, for_date), 'docstatus': '1', "status": "Active"},
+ direction = "<" if next_shift_direction == "reverse" else ">"
+ sort_order = "desc" if next_shift_direction == "reverse" else "asc"
+ dates = frappe.db.get_all(
+ "Shift Assignment",
+ ["start_date", "end_date"],
+ {
+ "employee": employee,
+ "start_date": (direction, for_date),
+ "docstatus": "1",
+ "status": "Active",
+ },
as_list=True,
- limit=MAX_DAYS, order_by="start_date "+sort_order)
+ limit=MAX_DAYS,
+ order_by="start_date " + sort_order,
+ )
if dates:
for date in dates:
@@ -201,35 +233,57 @@
def get_employee_shift_timings(employee, for_timestamp=None, consider_default_shift=False):
- """Returns previous shift, current/upcoming shift, next_shift for the given timestamp and employee
- """
+ """Returns previous shift, current/upcoming shift, next_shift for the given timestamp and employee"""
if for_timestamp is None:
for_timestamp = now_datetime()
# write and verify a test case for midnight shift.
prev_shift = curr_shift = next_shift = None
- curr_shift = get_employee_shift(employee, for_timestamp.date(), consider_default_shift, 'forward')
+ curr_shift = get_employee_shift(employee, for_timestamp.date(), consider_default_shift, "forward")
if curr_shift:
- next_shift = get_employee_shift(employee, curr_shift.start_datetime.date()+timedelta(days=1), consider_default_shift, 'forward')
- prev_shift = get_employee_shift(employee, for_timestamp.date()+timedelta(days=-1), consider_default_shift, 'reverse')
+ next_shift = get_employee_shift(
+ employee,
+ curr_shift.start_datetime.date() + timedelta(days=1),
+ consider_default_shift,
+ "forward",
+ )
+ prev_shift = get_employee_shift(
+ employee, for_timestamp.date() + timedelta(days=-1), consider_default_shift, "reverse"
+ )
if curr_shift:
if prev_shift:
- curr_shift.actual_start = prev_shift.end_datetime if curr_shift.actual_start < prev_shift.end_datetime else curr_shift.actual_start
- prev_shift.actual_end = curr_shift.actual_start if prev_shift.actual_end > curr_shift.actual_start else prev_shift.actual_end
+ curr_shift.actual_start = (
+ prev_shift.end_datetime
+ if curr_shift.actual_start < prev_shift.end_datetime
+ else curr_shift.actual_start
+ )
+ prev_shift.actual_end = (
+ curr_shift.actual_start
+ if prev_shift.actual_end > curr_shift.actual_start
+ else prev_shift.actual_end
+ )
if next_shift:
- next_shift.actual_start = curr_shift.end_datetime if next_shift.actual_start < curr_shift.end_datetime else next_shift.actual_start
- curr_shift.actual_end = next_shift.actual_start if curr_shift.actual_end > next_shift.actual_start else curr_shift.actual_end
+ next_shift.actual_start = (
+ curr_shift.end_datetime
+ if next_shift.actual_start < curr_shift.end_datetime
+ else next_shift.actual_start
+ )
+ curr_shift.actual_end = (
+ next_shift.actual_start
+ if curr_shift.actual_end > next_shift.actual_start
+ else curr_shift.actual_end
+ )
return prev_shift, curr_shift, next_shift
def get_shift_details(shift_type_name, for_date=None):
"""Returns Shift Details which contain some additional information as described below.
'shift_details' contains the following keys:
- 'shift_type' - Object of DocType Shift Type,
- 'start_datetime' - Date and Time of shift start on given date,
- 'end_datetime' - Date and Time of shift end on given date,
- 'actual_start' - datetime of shift start after adding 'begin_check_in_before_shift_start_time',
- 'actual_end' - datetime of shift end after adding 'allow_check_out_after_shift_end_time'(None is returned if this is zero)
+ 'shift_type' - Object of DocType Shift Type,
+ 'start_datetime' - Date and Time of shift start on given date,
+ 'end_datetime' - Date and Time of shift end on given date,
+ 'actual_start' - datetime of shift start after adding 'begin_check_in_before_shift_start_time',
+ 'actual_end' - datetime of shift end after adding 'allow_check_out_after_shift_end_time'(None is returned if this is zero)
:param shift_type_name: shift type name for which shift_details is required.
:param for_date: Date on which shift_details are required
@@ -238,30 +292,38 @@
return None
if not for_date:
for_date = nowdate()
- shift_type = frappe.get_doc('Shift Type', shift_type_name)
+ shift_type = frappe.get_doc("Shift Type", shift_type_name)
start_datetime = datetime.combine(for_date, datetime.min.time()) + shift_type.start_time
- for_date = for_date + timedelta(days=1) if shift_type.start_time > shift_type.end_time else for_date
+ for_date = (
+ for_date + timedelta(days=1) if shift_type.start_time > shift_type.end_time else for_date
+ )
end_datetime = datetime.combine(for_date, datetime.min.time()) + shift_type.end_time
- actual_start = start_datetime - timedelta(minutes=shift_type.begin_check_in_before_shift_start_time)
+ actual_start = start_datetime - timedelta(
+ minutes=shift_type.begin_check_in_before_shift_start_time
+ )
actual_end = end_datetime + timedelta(minutes=shift_type.allow_check_out_after_shift_end_time)
- return frappe._dict({
- 'shift_type': shift_type,
- 'start_datetime': start_datetime,
- 'end_datetime': end_datetime,
- 'actual_start': actual_start,
- 'actual_end': actual_end
- })
+ return frappe._dict(
+ {
+ "shift_type": shift_type,
+ "start_datetime": start_datetime,
+ "end_datetime": end_datetime,
+ "actual_start": actual_start,
+ "actual_end": actual_end,
+ }
+ )
def get_actual_start_end_datetime_of_shift(employee, for_datetime, consider_default_shift=False):
"""Takes a datetime and returns the 'actual' start datetime and end datetime of the shift in which the timestamp belongs.
- Here 'actual' means - taking in to account the "begin_check_in_before_shift_start_time" and "allow_check_out_after_shift_end_time".
- None is returned if the timestamp is outside any actual shift timings.
- Shift Details is also returned(current/upcoming i.e. if timestamp not in any actual shift then details of next shift returned)
+ Here 'actual' means - taking in to account the "begin_check_in_before_shift_start_time" and "allow_check_out_after_shift_end_time".
+ None is returned if the timestamp is outside any actual shift timings.
+ Shift Details is also returned(current/upcoming i.e. if timestamp not in any actual shift then details of next shift returned)
"""
actual_shift_start = actual_shift_end = shift_details = None
- shift_timings_as_per_timestamp = get_employee_shift_timings(employee, for_datetime, consider_default_shift)
+ shift_timings_as_per_timestamp = get_employee_shift_timings(
+ employee, for_datetime, consider_default_shift
+ )
timestamp_list = []
for shift in shift_timings_as_per_timestamp:
if shift:
@@ -273,11 +335,11 @@
if timestamp and for_datetime <= timestamp:
timestamp_index = index
break
- if timestamp_index and timestamp_index%2 == 1:
- shift_details = shift_timings_as_per_timestamp[int((timestamp_index-1)/2)]
+ if timestamp_index and timestamp_index % 2 == 1:
+ shift_details = shift_timings_as_per_timestamp[int((timestamp_index - 1) / 2)]
actual_shift_start = shift_details.actual_start
actual_shift_end = shift_details.actual_end
elif timestamp_index:
- shift_details = shift_timings_as_per_timestamp[int(timestamp_index/2)]
+ shift_details = shift_timings_as_per_timestamp[int(timestamp_index / 2)]
return actual_shift_start, actual_shift_end, shift_details
diff --git a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py
index d490081..4a1ec29 100644
--- a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py
+++ b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py
@@ -8,19 +8,21 @@
test_dependencies = ["Shift Type"]
-class TestShiftAssignment(unittest.TestCase):
+class TestShiftAssignment(unittest.TestCase):
def setUp(self):
frappe.db.sql("delete from `tabShift Assignment`")
def test_make_shift_assignment(self):
- shift_assignment = frappe.get_doc({
- "doctype": "Shift Assignment",
- "shift_type": "Day Shift",
- "company": "_Test Company",
- "employee": "_T-Employee-00001",
- "start_date": nowdate()
- }).insert()
+ shift_assignment = frappe.get_doc(
+ {
+ "doctype": "Shift Assignment",
+ "shift_type": "Day Shift",
+ "company": "_Test Company",
+ "employee": "_T-Employee-00001",
+ "start_date": nowdate(),
+ }
+ ).insert()
shift_assignment.submit()
self.assertEqual(shift_assignment.docstatus, 1)
@@ -28,52 +30,59 @@
def test_overlapping_for_ongoing_shift(self):
# shift should be Ongoing if Only start_date is present and status = Active
- shift_assignment_1 = frappe.get_doc({
- "doctype": "Shift Assignment",
- "shift_type": "Day Shift",
- "company": "_Test Company",
- "employee": "_T-Employee-00001",
- "start_date": nowdate(),
- "status": 'Active'
- }).insert()
+ shift_assignment_1 = frappe.get_doc(
+ {
+ "doctype": "Shift Assignment",
+ "shift_type": "Day Shift",
+ "company": "_Test Company",
+ "employee": "_T-Employee-00001",
+ "start_date": nowdate(),
+ "status": "Active",
+ }
+ ).insert()
shift_assignment_1.submit()
self.assertEqual(shift_assignment_1.docstatus, 1)
- shift_assignment = frappe.get_doc({
- "doctype": "Shift Assignment",
- "shift_type": "Day Shift",
- "company": "_Test Company",
- "employee": "_T-Employee-00001",
- "start_date": add_days(nowdate(), 2)
- })
+ shift_assignment = frappe.get_doc(
+ {
+ "doctype": "Shift Assignment",
+ "shift_type": "Day Shift",
+ "company": "_Test Company",
+ "employee": "_T-Employee-00001",
+ "start_date": add_days(nowdate(), 2),
+ }
+ )
self.assertRaises(frappe.ValidationError, shift_assignment.save)
def test_overlapping_for_fixed_period_shift(self):
# shift should is for Fixed period if Only start_date and end_date both are present and status = Active
- shift_assignment_1 = frappe.get_doc({
+ shift_assignment_1 = frappe.get_doc(
+ {
"doctype": "Shift Assignment",
"shift_type": "Day Shift",
"company": "_Test Company",
"employee": "_T-Employee-00001",
"start_date": nowdate(),
"end_date": add_days(nowdate(), 30),
- "status": 'Active'
- }).insert()
- shift_assignment_1.submit()
+ "status": "Active",
+ }
+ ).insert()
+ shift_assignment_1.submit()
-
- # it should not allowed within period of any shift.
- shift_assignment_3 = frappe.get_doc({
+ # it should not allowed within period of any shift.
+ shift_assignment_3 = frappe.get_doc(
+ {
"doctype": "Shift Assignment",
"shift_type": "Day Shift",
"company": "_Test Company",
"employee": "_T-Employee-00001",
- "start_date":add_days(nowdate(), 10),
+ "start_date": add_days(nowdate(), 10),
"end_date": add_days(nowdate(), 35),
- "status": 'Active'
- })
+ "status": "Active",
+ }
+ )
- self.assertRaises(frappe.ValidationError, shift_assignment_3.save)
+ self.assertRaises(frappe.ValidationError, shift_assignment_3.save)
diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py
index d4fcf99..1e3e8ff 100644
--- a/erpnext/hr/doctype/shift_request/shift_request.py
+++ b/erpnext/hr/doctype/shift_request/shift_request.py
@@ -10,7 +10,9 @@
from erpnext.hr.utils import share_doc_with_approver, validate_active_employee
-class OverlapError(frappe.ValidationError): pass
+class OverlapError(frappe.ValidationError):
+ pass
+
class ShiftRequest(Document):
def validate(self):
@@ -39,24 +41,35 @@
assignment_doc.insert()
assignment_doc.submit()
- frappe.msgprint(_("Shift Assignment: {0} created for Employee: {1}").format(frappe.bold(assignment_doc.name), frappe.bold(self.employee)))
+ frappe.msgprint(
+ _("Shift Assignment: {0} created for Employee: {1}").format(
+ frappe.bold(assignment_doc.name), frappe.bold(self.employee)
+ )
+ )
def on_cancel(self):
- shift_assignment_list = frappe.get_list("Shift Assignment", {'employee': self.employee, 'shift_request': self.name})
+ shift_assignment_list = frappe.get_list(
+ "Shift Assignment", {"employee": self.employee, "shift_request": self.name}
+ )
if shift_assignment_list:
for shift in shift_assignment_list:
- shift_assignment_doc = frappe.get_doc("Shift Assignment", shift['name'])
+ shift_assignment_doc = frappe.get_doc("Shift Assignment", shift["name"])
shift_assignment_doc.cancel()
def validate_default_shift(self):
default_shift = frappe.get_value("Employee", self.employee, "default_shift")
if self.shift_type == default_shift:
- frappe.throw(_("You can not request for your Default Shift: {0}").format(frappe.bold(self.shift_type)))
+ frappe.throw(
+ _("You can not request for your Default Shift: {0}").format(frappe.bold(self.shift_type))
+ )
def validate_approver(self):
department = frappe.get_value("Employee", self.employee, "department")
shift_approver = frappe.get_value("Employee", self.employee, "shift_request_approver")
- approvers = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))
+ approvers = frappe.db.sql(
+ """select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""",
+ (department),
+ )
approvers = [approver[0] for approver in approvers]
approvers.append(shift_approver)
if self.approver not in approvers:
@@ -67,10 +80,11 @@
frappe.throw(_("To date cannot be before from date"))
def validate_shift_request_overlap_dates(self):
- if not self.name:
- self.name = "New Shift Request"
+ if not self.name:
+ self.name = "New Shift Request"
- d = frappe.db.sql("""
+ d = frappe.db.sql(
+ """
select
name, shift_type, from_date, to_date
from `tabShift Request`
@@ -79,20 +93,23 @@
and %(from_date)s <= to_date) or
( %(to_date)s >= from_date
and %(to_date)s <= to_date ))
- and name != %(name)s""", {
- "employee": self.employee,
- "shift_type": self.shift_type,
- "from_date": self.from_date,
- "to_date": self.to_date,
- "name": self.name
- }, as_dict=1)
+ and name != %(name)s""",
+ {
+ "employee": self.employee,
+ "shift_type": self.shift_type,
+ "from_date": self.from_date,
+ "to_date": self.to_date,
+ "name": self.name,
+ },
+ as_dict=1,
+ )
- for date_overlap in d:
- if date_overlap ['name']:
- self.throw_overlap_error(date_overlap)
+ for date_overlap in d:
+ if date_overlap["name"]:
+ self.throw_overlap_error(date_overlap)
def throw_overlap_error(self, d):
- msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(self.employee,
- d['shift_type'], formatdate(d['from_date']), formatdate(d['to_date'])) \
- + """ <b><a href="/app/Form/Shift Request/{0}">{0}</a></b>""".format(d["name"])
+ msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(
+ self.employee, d["shift_type"], formatdate(d["from_date"]), formatdate(d["to_date"])
+ ) + """ <b><a href="/app/Form/Shift Request/{0}">{0}</a></b>""".format(d["name"])
frappe.throw(msg, OverlapError)
diff --git a/erpnext/hr/doctype/shift_request/shift_request_dashboard.py b/erpnext/hr/doctype/shift_request/shift_request_dashboard.py
index 531c98d..2859b8f 100644
--- a/erpnext/hr/doctype/shift_request/shift_request_dashboard.py
+++ b/erpnext/hr/doctype/shift_request/shift_request_dashboard.py
@@ -1,9 +1,7 @@
def get_data():
- return {
- 'fieldname': 'shift_request',
- 'transactions': [
- {
- 'items': ['Shift Assignment']
- },
- ],
- }
+ return {
+ "fieldname": "shift_request",
+ "transactions": [
+ {"items": ["Shift Assignment"]},
+ ],
+ }
diff --git a/erpnext/hr/doctype/shift_request/test_shift_request.py b/erpnext/hr/doctype/shift_request/test_shift_request.py
index 3633c9b..b4f5177 100644
--- a/erpnext/hr/doctype/shift_request/test_shift_request.py
+++ b/erpnext/hr/doctype/shift_request/test_shift_request.py
@@ -10,6 +10,7 @@
test_dependencies = ["Shift Type"]
+
class TestShiftRequest(unittest.TestCase):
def setUp(self):
for doctype in ["Shift Request", "Shift Assignment"]:
@@ -20,9 +21,12 @@
def test_make_shift_request(self):
"Test creation/updation of Shift Assignment from Shift Request."
- department = frappe.get_value("Employee", "_T-Employee-00001", 'department')
+ department = frappe.get_value("Employee", "_T-Employee-00001", "department")
set_shift_approver(department)
- approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0]
+ approver = frappe.db.sql(
+ """select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""",
+ (department),
+ )[0][0]
shift_request = make_shift_request(approver)
@@ -31,7 +35,7 @@
"Shift Assignment",
filters={"shift_request": shift_request.name},
fieldname=["employee", "docstatus"],
- as_dict=True
+ as_dict=True,
)
self.assertEqual(shift_request.employee, shift_assignment.employee)
self.assertEqual(shift_assignment.docstatus, 1)
@@ -39,9 +43,7 @@
shift_request.cancel()
shift_assignment_docstatus = frappe.db.get_value(
- "Shift Assignment",
- filters={"shift_request": shift_request.name},
- fieldname="docstatus"
+ "Shift Assignment", filters={"shift_request": shift_request.name}, fieldname="docstatus"
)
self.assertEqual(shift_assignment_docstatus, 2)
@@ -62,7 +64,10 @@
shift_request.reload()
department = frappe.get_value("Employee", "_T-Employee-00001", "department")
set_shift_approver(department)
- department_approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0]
+ department_approver = frappe.db.sql(
+ """select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""",
+ (department),
+ )[0][0]
shift_request.approver = department_approver
shift_request.save()
self.assertTrue(shift_request.name not in frappe.share.get_shared("Shift Request", user))
@@ -85,22 +90,25 @@
def set_shift_approver(department):
department_doc = frappe.get_doc("Department", department)
- department_doc.append('shift_request_approver',{'approver': "test1@example.com"})
+ department_doc.append("shift_request_approver", {"approver": "test1@example.com"})
department_doc.save()
department_doc.reload()
+
def make_shift_request(approver, do_not_submit=0):
- shift_request = frappe.get_doc({
- "doctype": "Shift Request",
- "shift_type": "Day Shift",
- "company": "_Test Company",
- "employee": "_T-Employee-00001",
- "employee_name": "_Test Employee",
- "from_date": nowdate(),
- "to_date": add_days(nowdate(), 10),
- "approver": approver,
- "status": "Approved"
- }).insert()
+ shift_request = frappe.get_doc(
+ {
+ "doctype": "Shift Request",
+ "shift_type": "Day Shift",
+ "company": "_Test Company",
+ "employee": "_T-Employee-00001",
+ "employee_name": "_Test Employee",
+ "from_date": nowdate(),
+ "to_date": add_days(nowdate(), 10),
+ "approver": approver,
+ "status": "Approved",
+ }
+ ).insert()
if do_not_submit:
return shift_request
diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py
index 562a573..3f5cb22 100644
--- a/erpnext/hr/doctype/shift_type/shift_type.py
+++ b/erpnext/hr/doctype/shift_type/shift_type.py
@@ -24,20 +24,45 @@
class ShiftType(Document):
@frappe.whitelist()
def process_auto_attendance(self):
- if not cint(self.enable_auto_attendance) or not self.process_attendance_after or not self.last_sync_of_checkin:
+ if (
+ not cint(self.enable_auto_attendance)
+ or not self.process_attendance_after
+ or not self.last_sync_of_checkin
+ ):
return
filters = {
- 'skip_auto_attendance':'0',
- 'attendance':('is', 'not set'),
- 'time':('>=', self.process_attendance_after),
- 'shift_actual_end': ('<', self.last_sync_of_checkin),
- 'shift': self.name
+ "skip_auto_attendance": "0",
+ "attendance": ("is", "not set"),
+ "time": (">=", self.process_attendance_after),
+ "shift_actual_end": ("<", self.last_sync_of_checkin),
+ "shift": self.name,
}
- logs = frappe.db.get_list('Employee Checkin', fields="*", filters=filters, order_by="employee,time")
- for key, group in itertools.groupby(logs, key=lambda x: (x['employee'], x['shift_actual_start'])):
+ logs = frappe.db.get_list(
+ "Employee Checkin", fields="*", filters=filters, order_by="employee,time"
+ )
+ for key, group in itertools.groupby(
+ logs, key=lambda x: (x["employee"], x["shift_actual_start"])
+ ):
single_shift_logs = list(group)
- attendance_status, working_hours, late_entry, early_exit, in_time, out_time = self.get_attendance(single_shift_logs)
- mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, late_entry, early_exit, in_time, out_time, self.name)
+ (
+ attendance_status,
+ working_hours,
+ late_entry,
+ early_exit,
+ in_time,
+ out_time,
+ ) = self.get_attendance(single_shift_logs)
+ mark_attendance_and_link_log(
+ single_shift_logs,
+ attendance_status,
+ key[1].date(),
+ working_hours,
+ late_entry,
+ early_exit,
+ in_time,
+ out_time,
+ self.name,
+ )
for employee in self.get_assigned_employee(self.process_attendance_after, True):
self.mark_absent_for_dates_with_no_attendance(employee)
@@ -45,36 +70,66 @@
"""Return attendance_status, working_hours, late_entry, early_exit, in_time, out_time
for a set of logs belonging to a single shift.
Assumtion:
- 1. These logs belongs to an single shift, single employee and is not in a holiday date.
- 2. Logs are in chronological order
+ 1. These logs belongs to an single shift, single employee and is not in a holiday date.
+ 2. Logs are in chronological order
"""
late_entry = early_exit = False
- total_working_hours, in_time, out_time = calculate_working_hours(logs, self.determine_check_in_and_check_out, self.working_hours_calculation_based_on)
- if cint(self.enable_entry_grace_period) and in_time and in_time > logs[0].shift_start + timedelta(minutes=cint(self.late_entry_grace_period)):
+ total_working_hours, in_time, out_time = calculate_working_hours(
+ logs, self.determine_check_in_and_check_out, self.working_hours_calculation_based_on
+ )
+ if (
+ cint(self.enable_entry_grace_period)
+ and in_time
+ and in_time > logs[0].shift_start + timedelta(minutes=cint(self.late_entry_grace_period))
+ ):
late_entry = True
- if cint(self.enable_exit_grace_period) and out_time and out_time < logs[0].shift_end - timedelta(minutes=cint(self.early_exit_grace_period)):
+ if (
+ cint(self.enable_exit_grace_period)
+ and out_time
+ and out_time < logs[0].shift_end - timedelta(minutes=cint(self.early_exit_grace_period))
+ ):
early_exit = True
- if self.working_hours_threshold_for_absent and total_working_hours < self.working_hours_threshold_for_absent:
- return 'Absent', total_working_hours, late_entry, early_exit, in_time, out_time
- if self.working_hours_threshold_for_half_day and total_working_hours < self.working_hours_threshold_for_half_day:
- return 'Half Day', total_working_hours, late_entry, early_exit, in_time, out_time
- return 'Present', total_working_hours, late_entry, early_exit, in_time, out_time
+ if (
+ self.working_hours_threshold_for_absent
+ and total_working_hours < self.working_hours_threshold_for_absent
+ ):
+ return "Absent", total_working_hours, late_entry, early_exit, in_time, out_time
+ if (
+ self.working_hours_threshold_for_half_day
+ and total_working_hours < self.working_hours_threshold_for_half_day
+ ):
+ return "Half Day", total_working_hours, late_entry, early_exit, in_time, out_time
+ return "Present", total_working_hours, late_entry, early_exit, in_time, out_time
def mark_absent_for_dates_with_no_attendance(self, employee):
"""Marks Absents for the given employee on working days in this shift which have no attendance marked.
The Absent is marked starting from 'process_attendance_after' or employee creation date.
"""
- date_of_joining, relieving_date, employee_creation = frappe.db.get_value("Employee", employee, ["date_of_joining", "relieving_date", "creation"])
+ date_of_joining, relieving_date, employee_creation = frappe.db.get_value(
+ "Employee", employee, ["date_of_joining", "relieving_date", "creation"]
+ )
if not date_of_joining:
date_of_joining = employee_creation.date()
start_date = max(getdate(self.process_attendance_after), date_of_joining)
- actual_shift_datetime = get_actual_start_end_datetime_of_shift(employee, get_datetime(self.last_sync_of_checkin), True)
- last_shift_time = actual_shift_datetime[0] if actual_shift_datetime[0] else get_datetime(self.last_sync_of_checkin)
- prev_shift = get_employee_shift(employee, last_shift_time.date()-timedelta(days=1), True, 'reverse')
+ actual_shift_datetime = get_actual_start_end_datetime_of_shift(
+ employee, get_datetime(self.last_sync_of_checkin), True
+ )
+ last_shift_time = (
+ actual_shift_datetime[0]
+ if actual_shift_datetime[0]
+ else get_datetime(self.last_sync_of_checkin)
+ )
+ prev_shift = get_employee_shift(
+ employee, last_shift_time.date() - timedelta(days=1), True, "reverse"
+ )
if prev_shift:
- end_date = min(prev_shift.start_datetime.date(), relieving_date) if relieving_date else prev_shift.start_datetime.date()
+ end_date = (
+ min(prev_shift.start_datetime.date(), relieving_date)
+ if relieving_date
+ else prev_shift.start_datetime.date()
+ )
else:
return
holiday_list_name = self.holiday_list
@@ -84,37 +139,40 @@
for date in dates:
shift_details = get_employee_shift(employee, date, True)
if shift_details and shift_details.shift_type.name == self.name:
- mark_attendance(employee, date, 'Absent', self.name)
+ mark_attendance(employee, date, "Absent", self.name)
def get_assigned_employee(self, from_date=None, consider_default_shift=False):
- filters = {'start_date':('>', from_date), 'shift_type': self.name, 'docstatus': '1'}
+ filters = {"start_date": (">", from_date), "shift_type": self.name, "docstatus": "1"}
if not from_date:
del filters["start_date"]
- assigned_employees = frappe.get_all('Shift Assignment', 'employee', filters, as_list=True)
+ assigned_employees = frappe.get_all("Shift Assignment", "employee", filters, as_list=True)
assigned_employees = [x[0] for x in assigned_employees]
if consider_default_shift:
- filters = {'default_shift': self.name, 'status': ['!=', 'Inactive']}
- default_shift_employees = frappe.get_all('Employee', 'name', filters, as_list=True)
+ filters = {"default_shift": self.name, "status": ["!=", "Inactive"]}
+ default_shift_employees = frappe.get_all("Employee", "name", filters, as_list=True)
default_shift_employees = [x[0] for x in default_shift_employees]
- return list(set(assigned_employees+default_shift_employees))
+ return list(set(assigned_employees + default_shift_employees))
return assigned_employees
+
def process_auto_attendance_for_all_shifts():
- shift_list = frappe.get_all('Shift Type', 'name', {'enable_auto_attendance':'1'}, as_list=True)
+ shift_list = frappe.get_all("Shift Type", "name", {"enable_auto_attendance": "1"}, as_list=True)
for shift in shift_list:
- doc = frappe.get_doc('Shift Type', shift[0])
+ doc = frappe.get_doc("Shift Type", shift[0])
doc.process_auto_attendance()
-def get_filtered_date_list(employee, start_date, end_date, filter_attendance=True, holiday_list=None):
- """Returns a list of dates after removing the dates with attendance and holidays
- """
+
+def get_filtered_date_list(
+ employee, start_date, end_date, filter_attendance=True, holiday_list=None
+):
+ """Returns a list of dates after removing the dates with attendance and holidays"""
base_dates_query = """select adddate(%(start_date)s, t2.i*100 + t1.i*10 + t0.i) selected_date from
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2"""
- condition_query = ''
+ condition_query = ""
if filter_attendance:
condition_query += """ and a.selected_date not in (
select attendance_date from `tabAttendance`
@@ -126,10 +184,20 @@
parentfield = 'holidays' and parent = %(holiday_list)s
and holiday_date between %(start_date)s and %(end_date)s)"""
- dates = frappe.db.sql("""select * from
+ dates = frappe.db.sql(
+ """select * from
({base_dates_query}) as a
where a.selected_date <= %(end_date)s {condition_query}
- """.format(base_dates_query=base_dates_query, condition_query=condition_query),
- {"employee":employee, "start_date":start_date, "end_date":end_date, "holiday_list":holiday_list}, as_list=True)
+ """.format(
+ base_dates_query=base_dates_query, condition_query=condition_query
+ ),
+ {
+ "employee": employee,
+ "start_date": start_date,
+ "end_date": end_date,
+ "holiday_list": holiday_list,
+ },
+ as_list=True,
+ )
return [getdate(date[0]) for date in dates]
diff --git a/erpnext/hr/doctype/shift_type/shift_type_dashboard.py b/erpnext/hr/doctype/shift_type/shift_type_dashboard.py
index 919da2d..920d8fd 100644
--- a/erpnext/hr/doctype/shift_type/shift_type_dashboard.py
+++ b/erpnext/hr/doctype/shift_type/shift_type_dashboard.py
@@ -1,13 +1,8 @@
def get_data():
return {
- 'fieldname': 'shift',
- 'non_standard_fieldnames': {
- 'Shift Request': 'shift_type',
- 'Shift Assignment': 'shift_type'
- },
- 'transactions': [
- {
- 'items': ['Attendance', 'Employee Checkin', 'Shift Request', 'Shift Assignment']
- }
- ]
+ "fieldname": "shift",
+ "non_standard_fieldnames": {"Shift Request": "shift_type", "Shift Assignment": "shift_type"},
+ "transactions": [
+ {"items": ["Attendance", "Employee Checkin", "Shift Request", "Shift Assignment"]}
+ ],
}
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
index 7b2ea21..93a493c 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
@@ -9,8 +9,13 @@
from frappe.utils.nestedset import get_descendants_of
-class SubsidiaryCompanyError(frappe.ValidationError): pass
-class ParentCompanyError(frappe.ValidationError): pass
+class SubsidiaryCompanyError(frappe.ValidationError):
+ pass
+
+
+class ParentCompanyError(frappe.ValidationError):
+ pass
+
class StaffingPlan(Document):
def validate(self):
@@ -33,11 +38,11 @@
self.total_estimated_budget = 0
for detail in self.get("staffing_details"):
- #Set readonly fields
+ # Set readonly fields
self.set_number_of_positions(detail)
designation_counts = get_designation_counts(detail.designation, self.company)
- detail.current_count = designation_counts['employee_count']
- detail.current_openings = designation_counts['job_openings']
+ detail.current_count = designation_counts["employee_count"]
+ detail.current_openings = designation_counts["job_openings"]
detail.total_estimated_cost = 0
if detail.number_of_positions > 0:
@@ -52,80 +57,122 @@
def validate_overlap(self, staffing_plan_detail):
# Validate if any submitted Staffing Plan exist for any Designations in this plan
# and spd.vacancies>0 ?
- overlap = frappe.db.sql("""select spd.parent
+ overlap = frappe.db.sql(
+ """select spd.parent
from `tabStaffing Plan Detail` spd join `tabStaffing Plan` sp on spd.parent=sp.name
where spd.designation=%s and sp.docstatus=1
and sp.to_date >= %s and sp.from_date <= %s and sp.company = %s
- """, (staffing_plan_detail.designation, self.from_date, self.to_date, self.company))
- if overlap and overlap [0][0]:
- frappe.throw(_("Staffing Plan {0} already exist for designation {1}")
- .format(overlap[0][0], staffing_plan_detail.designation))
+ """,
+ (staffing_plan_detail.designation, self.from_date, self.to_date, self.company),
+ )
+ if overlap and overlap[0][0]:
+ frappe.throw(
+ _("Staffing Plan {0} already exist for designation {1}").format(
+ overlap[0][0], staffing_plan_detail.designation
+ )
+ )
def validate_with_parent_plan(self, staffing_plan_detail):
- if not frappe.get_cached_value('Company', self.company, "parent_company"):
- return # No parent, nothing to validate
+ if not frappe.get_cached_value("Company", self.company, "parent_company"):
+ return # No parent, nothing to validate
# Get staffing plan applicable for the company (Parent Company)
- parent_plan_details = get_active_staffing_plan_details(self.company, staffing_plan_detail.designation, self.from_date, self.to_date)
+ parent_plan_details = get_active_staffing_plan_details(
+ self.company, staffing_plan_detail.designation, self.from_date, self.to_date
+ )
if not parent_plan_details:
- return #no staffing plan for any parent Company in hierarchy
+ return # no staffing plan for any parent Company in hierarchy
# Fetch parent company which owns the staffing plan. NOTE: Parent could be higher up in the hierarchy
parent_company = frappe.db.get_value("Staffing Plan", parent_plan_details[0].name, "company")
# Parent plan available, validate with parent, siblings as well as children of staffing plan Company
- if cint(staffing_plan_detail.vacancies) > cint(parent_plan_details[0].vacancies) or \
- flt(staffing_plan_detail.total_estimated_cost) > flt(parent_plan_details[0].total_estimated_cost):
- frappe.throw(_("You can only plan for upto {0} vacancies and budget {1} \
- for {2} as per staffing plan {3} for parent company {4}.").format(
+ if cint(staffing_plan_detail.vacancies) > cint(parent_plan_details[0].vacancies) or flt(
+ staffing_plan_detail.total_estimated_cost
+ ) > flt(parent_plan_details[0].total_estimated_cost):
+ frappe.throw(
+ _(
+ "You can only plan for upto {0} vacancies and budget {1} \
+ for {2} as per staffing plan {3} for parent company {4}."
+ ).format(
cint(parent_plan_details[0].vacancies),
parent_plan_details[0].total_estimated_cost,
frappe.bold(staffing_plan_detail.designation),
parent_plan_details[0].name,
- parent_company), ParentCompanyError)
+ parent_company,
+ ),
+ ParentCompanyError,
+ )
- #Get vacanices already planned for all companies down the hierarchy of Parent Company
- lft, rgt = frappe.get_cached_value('Company', parent_company, ["lft", "rgt"])
- all_sibling_details = frappe.db.sql("""select sum(spd.vacancies) as vacancies,
+ # Get vacanices already planned for all companies down the hierarchy of Parent Company
+ lft, rgt = frappe.get_cached_value("Company", parent_company, ["lft", "rgt"])
+ all_sibling_details = frappe.db.sql(
+ """select sum(spd.vacancies) as vacancies,
sum(spd.total_estimated_cost) as total_estimated_cost
from `tabStaffing Plan Detail` spd join `tabStaffing Plan` sp on spd.parent=sp.name
where spd.designation=%s and sp.docstatus=1
and sp.to_date >= %s and sp.from_date <=%s
and sp.company in (select name from tabCompany where lft > %s and rgt < %s)
- """, (staffing_plan_detail.designation, self.from_date, self.to_date, lft, rgt), as_dict = 1)[0]
+ """,
+ (staffing_plan_detail.designation, self.from_date, self.to_date, lft, rgt),
+ as_dict=1,
+ )[0]
- if (cint(parent_plan_details[0].vacancies) < \
- (cint(staffing_plan_detail.vacancies) + cint(all_sibling_details.vacancies))) or \
- (flt(parent_plan_details[0].total_estimated_cost) < \
- (flt(staffing_plan_detail.total_estimated_cost) + flt(all_sibling_details.total_estimated_cost))):
- frappe.throw(_("{0} vacancies and {1} budget for {2} already planned for subsidiary companies of {3}. \
- You can only plan for upto {4} vacancies and and budget {5} as per staffing plan {6} for parent company {3}.").format(
+ if (
+ cint(parent_plan_details[0].vacancies)
+ < (cint(staffing_plan_detail.vacancies) + cint(all_sibling_details.vacancies))
+ ) or (
+ flt(parent_plan_details[0].total_estimated_cost)
+ < (
+ flt(staffing_plan_detail.total_estimated_cost) + flt(all_sibling_details.total_estimated_cost)
+ )
+ ):
+ frappe.throw(
+ _(
+ "{0} vacancies and {1} budget for {2} already planned for subsidiary companies of {3}. \
+ You can only plan for upto {4} vacancies and and budget {5} as per staffing plan {6} for parent company {3}."
+ ).format(
cint(all_sibling_details.vacancies),
all_sibling_details.total_estimated_cost,
frappe.bold(staffing_plan_detail.designation),
parent_company,
cint(parent_plan_details[0].vacancies),
parent_plan_details[0].total_estimated_cost,
- parent_plan_details[0].name))
+ parent_plan_details[0].name,
+ )
+ )
def validate_with_subsidiary_plans(self, staffing_plan_detail):
- #Valdate this plan with all child company plan
- children_details = frappe.db.sql("""select sum(spd.vacancies) as vacancies,
+ # Valdate this plan with all child company plan
+ children_details = frappe.db.sql(
+ """select sum(spd.vacancies) as vacancies,
sum(spd.total_estimated_cost) as total_estimated_cost
from `tabStaffing Plan Detail` spd join `tabStaffing Plan` sp on spd.parent=sp.name
where spd.designation=%s and sp.docstatus=1
and sp.to_date >= %s and sp.from_date <=%s
and sp.company in (select name from tabCompany where parent_company = %s)
- """, (staffing_plan_detail.designation, self.from_date, self.to_date, self.company), as_dict = 1)[0]
+ """,
+ (staffing_plan_detail.designation, self.from_date, self.to_date, self.company),
+ as_dict=1,
+ )[0]
- if children_details and \
- cint(staffing_plan_detail.vacancies) < cint(children_details.vacancies) or \
- flt(staffing_plan_detail.total_estimated_cost) < flt(children_details.total_estimated_cost):
- frappe.throw(_("Subsidiary companies have already planned for {1} vacancies at a budget of {2}. \
- Staffing Plan for {0} should allocate more vacancies and budget for {3} than planned for its subsidiary companies").format(
+ if (
+ children_details
+ and cint(staffing_plan_detail.vacancies) < cint(children_details.vacancies)
+ or flt(staffing_plan_detail.total_estimated_cost) < flt(children_details.total_estimated_cost)
+ ):
+ frappe.throw(
+ _(
+ "Subsidiary companies have already planned for {1} vacancies at a budget of {2}. \
+ Staffing Plan for {0} should allocate more vacancies and budget for {3} than planned for its subsidiary companies"
+ ).format(
self.company,
cint(children_details.vacancies),
children_details.total_estimated_cost,
- frappe.bold(staffing_plan_detail.designation)), SubsidiaryCompanyError)
+ frappe.bold(staffing_plan_detail.designation),
+ ),
+ SubsidiaryCompanyError,
+ )
+
@frappe.whitelist()
def get_designation_counts(designation, company):
@@ -133,25 +180,24 @@
return False
employee_counts = {}
- company_set = get_descendants_of('Company', company)
+ company_set = get_descendants_of("Company", company)
company_set.append(company)
- employee_counts["employee_count"] = frappe.db.get_value("Employee",
- filters={
- 'designation': designation,
- 'status': 'Active',
- 'company': ('in', company_set)
- }, fieldname=['count(name)'])
+ employee_counts["employee_count"] = frappe.db.get_value(
+ "Employee",
+ filters={"designation": designation, "status": "Active", "company": ("in", company_set)},
+ fieldname=["count(name)"],
+ )
- employee_counts['job_openings'] = frappe.db.get_value("Job Opening",
- filters={
- 'designation': designation,
- 'status': 'Open',
- 'company': ('in', company_set)
- }, fieldname=['count(name)'])
+ employee_counts["job_openings"] = frappe.db.get_value(
+ "Job Opening",
+ filters={"designation": designation, "status": "Open", "company": ("in", company_set)},
+ fieldname=["count(name)"],
+ )
return employee_counts
+
@frappe.whitelist()
def get_active_staffing_plan_details(company, designation, from_date=None, to_date=None):
if from_date is None:
@@ -161,17 +207,22 @@
if not company or not designation:
frappe.throw(_("Please select Company and Designation"))
- staffing_plan = frappe.db.sql("""
+ staffing_plan = frappe.db.sql(
+ """
select sp.name, spd.vacancies, spd.total_estimated_cost
from `tabStaffing Plan Detail` spd join `tabStaffing Plan` sp on spd.parent=sp.name
where company=%s and spd.designation=%s and sp.docstatus=1
- and to_date >= %s and from_date <= %s """, (company, designation, from_date, to_date), as_dict = 1)
+ and to_date >= %s and from_date <= %s """,
+ (company, designation, from_date, to_date),
+ as_dict=1,
+ )
if not staffing_plan:
- parent_company = frappe.get_cached_value('Company', company, "parent_company")
+ parent_company = frappe.get_cached_value("Company", company, "parent_company")
if parent_company:
- staffing_plan = get_active_staffing_plan_details(parent_company,
- designation, from_date, to_date)
+ staffing_plan = get_active_staffing_plan_details(
+ parent_company, designation, from_date, to_date
+ )
# Only a single staffing plan can be active for a designation on given date
return staffing_plan if staffing_plan else None
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py b/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py
index abde0d5..0f555d9 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan_dashboard.py
@@ -1,9 +1,5 @@
def get_data():
- return {
- 'fieldname': 'staffing_plan',
- 'transactions': [
- {
- 'items': ['Job Opening']
- }
- ],
- }
+ return {
+ "fieldname": "staffing_plan",
+ "transactions": [{"items": ["Job Opening"]}],
+ }
diff --git a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py
index 8ff0dbb..a3adbbd 100644
--- a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py
@@ -13,6 +13,7 @@
test_dependencies = ["Designation"]
+
class TestStaffingPlan(unittest.TestCase):
def test_staffing_plan(self):
_set_up()
@@ -24,11 +25,10 @@
staffing_plan.name = "Test"
staffing_plan.from_date = nowdate()
staffing_plan.to_date = add_days(nowdate(), 10)
- staffing_plan.append("staffing_details", {
- "designation": "Designer",
- "vacancies": 6,
- "estimated_cost_per_position": 50000
- })
+ staffing_plan.append(
+ "staffing_details",
+ {"designation": "Designer", "vacancies": 6, "estimated_cost_per_position": 50000},
+ )
staffing_plan.insert()
staffing_plan.submit()
self.assertEqual(staffing_plan.total_estimated_budget, 300000.00)
@@ -42,11 +42,10 @@
staffing_plan.name = "Test 1"
staffing_plan.from_date = nowdate()
staffing_plan.to_date = add_days(nowdate(), 10)
- staffing_plan.append("staffing_details", {
- "designation": "Designer",
- "vacancies": 3,
- "estimated_cost_per_position": 45000
- })
+ staffing_plan.append(
+ "staffing_details",
+ {"designation": "Designer", "vacancies": 3, "estimated_cost_per_position": 45000},
+ )
self.assertRaises(SubsidiaryCompanyError, staffing_plan.insert)
def test_staffing_plan_parent_company(self):
@@ -58,11 +57,10 @@
staffing_plan.name = "Test"
staffing_plan.from_date = nowdate()
staffing_plan.to_date = add_days(nowdate(), 10)
- staffing_plan.append("staffing_details", {
- "designation": "Designer",
- "vacancies": 7,
- "estimated_cost_per_position": 50000
- })
+ staffing_plan.append(
+ "staffing_details",
+ {"designation": "Designer", "vacancies": 7, "estimated_cost_per_position": 50000},
+ )
staffing_plan.insert()
staffing_plan.submit()
self.assertEqual(staffing_plan.total_estimated_budget, 350000.00)
@@ -73,19 +71,20 @@
staffing_plan.name = "Test 1"
staffing_plan.from_date = nowdate()
staffing_plan.to_date = add_days(nowdate(), 10)
- staffing_plan.append("staffing_details", {
- "designation": "Designer",
- "vacancies": 7,
- "estimated_cost_per_position": 60000
- })
+ staffing_plan.append(
+ "staffing_details",
+ {"designation": "Designer", "vacancies": 7, "estimated_cost_per_position": 60000},
+ )
staffing_plan.insert()
self.assertRaises(ParentCompanyError, staffing_plan.submit)
+
def _set_up():
for doctype in ["Staffing Plan", "Staffing Plan Detail"]:
frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype))
make_company()
+
def make_company():
if frappe.db.exists("Company", "_Test Company 10"):
return
diff --git a/erpnext/hr/doctype/training_event/test_training_event.py b/erpnext/hr/doctype/training_event/test_training_event.py
index f4329c9..ec7eb74 100644
--- a/erpnext/hr/doctype/training_event/test_training_event.py
+++ b/erpnext/hr/doctype/training_event/test_training_event.py
@@ -14,10 +14,7 @@
create_training_program("Basic Training")
employee = make_employee("robert_loan@trainig.com")
employee2 = make_employee("suzie.tan@trainig.com")
- self.attendees = [
- {"employee": employee},
- {"employee": employee2}
- ]
+ self.attendees = [{"employee": employee}, {"employee": employee2}]
def test_training_event_status_update(self):
training_event = create_training_event(self.attendees)
@@ -43,20 +40,25 @@
def create_training_program(training_program):
if not frappe.db.get_value("Training Program", training_program):
- frappe.get_doc({
- "doctype": "Training Program",
- "training_program": training_program,
- "description": training_program
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Training Program",
+ "training_program": training_program,
+ "description": training_program,
+ }
+ ).insert()
+
def create_training_event(attendees):
- return frappe.get_doc({
- "doctype": "Training Event",
- "event_name": "Basic Training Event",
- "training_program": "Basic Training",
- "location": "Union Square",
- "start_time": add_days(today(), 5),
- "end_time": add_days(today(), 6),
- "introduction": "Welcome to the Basic Training Event",
- "employees": attendees
- }).insert()
+ return frappe.get_doc(
+ {
+ "doctype": "Training Event",
+ "event_name": "Basic Training Event",
+ "training_program": "Basic Training",
+ "location": "Union Square",
+ "start_time": add_days(today(), 5),
+ "end_time": add_days(today(), 6),
+ "introduction": "Welcome to the Basic Training Event",
+ "employees": attendees,
+ }
+ ).insert()
diff --git a/erpnext/hr/doctype/training_event/training_event.py b/erpnext/hr/doctype/training_event/training_event.py
index c8c8bbe..59972bb 100644
--- a/erpnext/hr/doctype/training_event/training_event.py
+++ b/erpnext/hr/doctype/training_event/training_event.py
@@ -19,21 +19,20 @@
self.set_status_for_attendees()
def set_employee_emails(self):
- self.employee_emails = ', '.join(get_employee_emails([d.employee
- for d in self.employees]))
+ self.employee_emails = ", ".join(get_employee_emails([d.employee for d in self.employees]))
def validate_period(self):
if time_diff_in_seconds(self.end_time, self.start_time) <= 0:
- frappe.throw(_('End time cannot be before start time'))
+ frappe.throw(_("End time cannot be before start time"))
def set_status_for_attendees(self):
- if self.event_status == 'Completed':
+ if self.event_status == "Completed":
for employee in self.employees:
- if employee.attendance == 'Present' and employee.status != 'Feedback Submitted':
- employee.status = 'Completed'
+ if employee.attendance == "Present" and employee.status != "Feedback Submitted":
+ employee.status = "Completed"
- elif self.event_status == 'Scheduled':
+ elif self.event_status == "Scheduled":
for employee in self.employees:
- employee.status = 'Open'
+ employee.status = "Open"
self.db_update_all()
diff --git a/erpnext/hr/doctype/training_event/training_event_dashboard.py b/erpnext/hr/doctype/training_event/training_event_dashboard.py
index 141fffc..ca13938 100644
--- a/erpnext/hr/doctype/training_event/training_event_dashboard.py
+++ b/erpnext/hr/doctype/training_event/training_event_dashboard.py
@@ -1,9 +1,7 @@
def get_data():
- return {
- 'fieldname': 'training_event',
- 'transactions': [
- {
- 'items': ['Training Result', 'Training Feedback']
- },
- ],
- }
+ return {
+ "fieldname": "training_event",
+ "transactions": [
+ {"items": ["Training Result", "Training Feedback"]},
+ ],
+ }
diff --git a/erpnext/hr/doctype/training_feedback/test_training_feedback.py b/erpnext/hr/doctype/training_feedback/test_training_feedback.py
index 58ed623..c787b70 100644
--- a/erpnext/hr/doctype/training_feedback/test_training_feedback.py
+++ b/erpnext/hr/doctype/training_feedback/test_training_feedback.py
@@ -32,10 +32,9 @@
self.assertRaises(frappe.ValidationError, feedback.save)
# cannot record feedback for absent employee
- employee = frappe.db.get_value("Training Event Employee", {
- "parent": training_event.name,
- "employee": self.employee
- }, "name")
+ employee = frappe.db.get_value(
+ "Training Event Employee", {"parent": training_event.name, "employee": self.employee}, "name"
+ )
frappe.db.set_value("Training Event Employee", employee, "attendance", "Absent")
feedback = create_training_feedback(training_event.name, self.employee)
@@ -52,10 +51,9 @@
feedback = create_training_feedback(training_event.name, self.employee)
feedback.submit()
- status = frappe.db.get_value("Training Event Employee", {
- "parent": training_event.name,
- "employee": self.employee
- }, "status")
+ status = frappe.db.get_value(
+ "Training Event Employee", {"parent": training_event.name, "employee": self.employee}, "status"
+ )
self.assertEqual(status, "Feedback Submitted")
@@ -64,9 +62,11 @@
def create_training_feedback(event, employee):
- return frappe.get_doc({
- "doctype": "Training Feedback",
- "training_event": event,
- "employee": employee,
- "feedback": "Test"
- })
+ return frappe.get_doc(
+ {
+ "doctype": "Training Feedback",
+ "training_event": event,
+ "employee": employee,
+ "feedback": "Test",
+ }
+ )
diff --git a/erpnext/hr/doctype/training_feedback/training_feedback.py b/erpnext/hr/doctype/training_feedback/training_feedback.py
index 1f9ec3b..d5de28e 100644
--- a/erpnext/hr/doctype/training_feedback/training_feedback.py
+++ b/erpnext/hr/doctype/training_feedback/training_feedback.py
@@ -13,32 +13,35 @@
if training_event.docstatus != 1:
frappe.throw(_("{0} must be submitted").format(_("Training Event")))
- emp_event_details = frappe.db.get_value("Training Event Employee", {
- "parent": self.training_event,
- "employee": self.employee
- }, ["name", "attendance"], as_dict=True)
+ emp_event_details = frappe.db.get_value(
+ "Training Event Employee",
+ {"parent": self.training_event, "employee": self.employee},
+ ["name", "attendance"],
+ as_dict=True,
+ )
if not emp_event_details:
- frappe.throw(_("Employee {0} not found in Training Event Participants.").format(
- frappe.bold(self.employee_name)))
+ frappe.throw(
+ _("Employee {0} not found in Training Event Participants.").format(
+ frappe.bold(self.employee_name)
+ )
+ )
if emp_event_details.attendance == "Absent":
frappe.throw(_("Feedback cannot be recorded for an absent Employee."))
def on_submit(self):
- employee = frappe.db.get_value("Training Event Employee", {
- "parent": self.training_event,
- "employee": self.employee
- })
+ employee = frappe.db.get_value(
+ "Training Event Employee", {"parent": self.training_event, "employee": self.employee}
+ )
if employee:
frappe.db.set_value("Training Event Employee", employee, "status", "Feedback Submitted")
def on_cancel(self):
- employee = frappe.db.get_value("Training Event Employee", {
- "parent": self.training_event,
- "employee": self.employee
- })
+ employee = frappe.db.get_value(
+ "Training Event Employee", {"parent": self.training_event, "employee": self.employee}
+ )
if employee:
frappe.db.set_value("Training Event Employee", employee, "status", "Completed")
diff --git a/erpnext/hr/doctype/training_program/training_program_dashboard.py b/erpnext/hr/doctype/training_program/training_program_dashboard.py
index 374c1e8..1735db1 100644
--- a/erpnext/hr/doctype/training_program/training_program_dashboard.py
+++ b/erpnext/hr/doctype/training_program/training_program_dashboard.py
@@ -3,11 +3,8 @@
def get_data():
return {
- 'fieldname': 'training_program',
- 'transactions': [
- {
- 'label': _('Training Events'),
- 'items': ['Training Event']
- },
- ]
+ "fieldname": "training_program",
+ "transactions": [
+ {"label": _("Training Events"), "items": ["Training Event"]},
+ ],
}
diff --git a/erpnext/hr/doctype/training_result/test_training_result.py b/erpnext/hr/doctype/training_result/test_training_result.py
index 1735ff4..136543c 100644
--- a/erpnext/hr/doctype/training_result/test_training_result.py
+++ b/erpnext/hr/doctype/training_result/test_training_result.py
@@ -5,5 +5,6 @@
# test_records = frappe.get_test_records('Training Result')
+
class TestTrainingResult(unittest.TestCase):
pass
diff --git a/erpnext/hr/doctype/training_result/training_result.py b/erpnext/hr/doctype/training_result/training_result.py
index bb5c71e..48a5b2c 100644
--- a/erpnext/hr/doctype/training_result/training_result.py
+++ b/erpnext/hr/doctype/training_result/training_result.py
@@ -13,22 +13,22 @@
def validate(self):
training_event = frappe.get_doc("Training Event", self.training_event)
if training_event.docstatus != 1:
- frappe.throw(_('{0} must be submitted').format(_('Training Event')))
+ frappe.throw(_("{0} must be submitted").format(_("Training Event")))
- self.employee_emails = ', '.join(get_employee_emails([d.employee
- for d in self.employees]))
+ self.employee_emails = ", ".join(get_employee_emails([d.employee for d in self.employees]))
def on_submit(self):
training_event = frappe.get_doc("Training Event", self.training_event)
- training_event.status = 'Completed'
+ training_event.status = "Completed"
for e in self.employees:
for e1 in training_event.employees:
if e1.employee == e.employee:
- e1.status = 'Completed'
+ e1.status = "Completed"
break
training_event.save()
+
@frappe.whitelist()
def get_employees(training_event):
return frappe.get_doc("Training Event", training_event).employees
diff --git a/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py b/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py
index 4c7bd80..537c206 100644
--- a/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py
+++ b/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py
@@ -10,12 +10,15 @@
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.upload_attendance.upload_attendance import get_data
-test_dependencies = ['Holiday List']
+test_dependencies = ["Holiday List"]
+
class TestUploadAttendance(unittest.TestCase):
@classmethod
def setUpClass(cls):
- frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", '_Test Holiday List')
+ frappe.db.set_value(
+ "Company", erpnext.get_default_company(), "default_holiday_list", "_Test Holiday List"
+ )
def test_date_range(self):
employee = make_employee("test_employee@company.com")
@@ -27,14 +30,13 @@
employee_doc.date_of_joining = date_of_joining
employee_doc.relieving_date = relieving_date
employee_doc.save()
- args = {
- "from_date": from_date,
- "to_date": to_date
- }
+ args = {"from_date": from_date, "to_date": to_date}
data = get_data(args)
filtered_data = []
for row in data:
if row[1] == employee:
filtered_data.append(row)
for row in filtered_data:
- self.assertTrue(getdate(row[3]) >= getdate(date_of_joining) and getdate(row[3]) <= getdate(relieving_date))
+ self.assertTrue(
+ getdate(row[3]) >= getdate(date_of_joining) and getdate(row[3]) <= getdate(relieving_date)
+ )
diff --git a/erpnext/hr/doctype/upload_attendance/upload_attendance.py b/erpnext/hr/doctype/upload_attendance/upload_attendance.py
index 94eb300..a66a481 100644
--- a/erpnext/hr/doctype/upload_attendance/upload_attendance.py
+++ b/erpnext/hr/doctype/upload_attendance/upload_attendance.py
@@ -17,6 +17,7 @@
class UploadAttendance(Document):
pass
+
@frappe.whitelist()
def get_template():
if not frappe.has_permission("Attendance", "create"):
@@ -38,29 +39,37 @@
return
# write out response as a type csv
- frappe.response['result'] = cstr(w.getvalue())
- frappe.response['type'] = 'csv'
- frappe.response['doctype'] = "Attendance"
+ frappe.response["result"] = cstr(w.getvalue())
+ frappe.response["type"] = "csv"
+ frappe.response["doctype"] = "Attendance"
+
def add_header(w):
- status = ", ".join((frappe.get_meta("Attendance").get_field("status").options or "").strip().split("\n"))
+ status = ", ".join(
+ (frappe.get_meta("Attendance").get_field("status").options or "").strip().split("\n")
+ )
w.writerow(["Notes:"])
w.writerow(["Please do not change the template headings"])
w.writerow(["Status should be one of these values: " + status])
w.writerow(["If you are overwriting existing attendance records, 'ID' column mandatory"])
- w.writerow(["ID", "Employee", "Employee Name", "Date", "Status", "Leave Type",
- "Company", "Naming Series"])
+ w.writerow(
+ ["ID", "Employee", "Employee Name", "Date", "Status", "Leave Type", "Company", "Naming Series"]
+ )
return w
+
def add_data(w, args):
data = get_data(args)
writedata(w, data)
return w
+
def get_data(args):
dates = get_dates(args)
employees = get_active_employees()
- holidays = get_holidays_for_employees([employee.name for employee in employees], args["from_date"], args["to_date"])
+ holidays = get_holidays_for_employees(
+ [employee.name for employee in employees], args["from_date"], args["to_date"]
+ )
existing_attendance_records = get_existing_attendance_records(args)
data = []
for date in dates:
@@ -71,27 +80,33 @@
if getdate(date) > getdate(employee.relieving_date):
continue
existing_attendance = {}
- if existing_attendance_records \
- and tuple([getdate(date), employee.name]) in existing_attendance_records \
- and getdate(employee.date_of_joining) <= getdate(date) \
- and getdate(employee.relieving_date) >= getdate(date):
- existing_attendance = existing_attendance_records[tuple([getdate(date), employee.name])]
+ if (
+ existing_attendance_records
+ and tuple([getdate(date), employee.name]) in existing_attendance_records
+ and getdate(employee.date_of_joining) <= getdate(date)
+ and getdate(employee.relieving_date) >= getdate(date)
+ ):
+ existing_attendance = existing_attendance_records[tuple([getdate(date), employee.name])]
employee_holiday_list = get_holiday_list_for_employee(employee.name)
row = [
existing_attendance and existing_attendance.name or "",
- employee.name, employee.employee_name, date,
+ employee.name,
+ employee.employee_name,
+ date,
existing_attendance and existing_attendance.status or "",
- existing_attendance and existing_attendance.leave_type or "", employee.company,
+ existing_attendance and existing_attendance.leave_type or "",
+ employee.company,
existing_attendance and existing_attendance.naming_series or get_naming_series(),
]
if date in holidays[employee_holiday_list]:
- row[4] = "Holiday"
+ row[4] = "Holiday"
data.append(row)
return data
+
def get_holidays_for_employees(employees, from_date, to_date):
holidays = {}
for employee in employees:
@@ -102,30 +117,35 @@
return holidays
+
def writedata(w, data):
for row in data:
w.writerow(row)
+
def get_dates(args):
"""get list of dates in between from date and to date"""
no_of_days = date_diff(add_days(args["to_date"], 1), args["from_date"])
dates = [add_days(args["from_date"], i) for i in range(0, no_of_days)]
return dates
+
def get_active_employees():
- employees = frappe.db.get_all('Employee',
- fields=['name', 'employee_name', 'date_of_joining', 'company', 'relieving_date'],
- filters={
- 'docstatus': ['<', 2],
- 'status': 'Active'
- }
+ employees = frappe.db.get_all(
+ "Employee",
+ fields=["name", "employee_name", "date_of_joining", "company", "relieving_date"],
+ filters={"docstatus": ["<", 2], "status": "Active"},
)
return employees
+
def get_existing_attendance_records(args):
- attendance = frappe.db.sql("""select name, attendance_date, employee, status, leave_type, naming_series
+ attendance = frappe.db.sql(
+ """select name, attendance_date, employee, status, leave_type, naming_series
from `tabAttendance` where attendance_date between %s and %s and docstatus < 2""",
- (args["from_date"], args["to_date"]), as_dict=1)
+ (args["from_date"], args["to_date"]),
+ as_dict=1,
+ )
existing_attendance = {}
for att in attendance:
@@ -133,6 +153,7 @@
return existing_attendance
+
def get_naming_series():
series = frappe.get_meta("Attendance").get_field("naming_series").options.strip().split("\n")
if not series:
@@ -146,15 +167,16 @@
raise frappe.PermissionError
from frappe.utils.csvutils import read_csv_content
+
rows = read_csv_content(frappe.local.uploaded_file)
if not rows:
frappe.throw(_("Please select a csv file"))
frappe.enqueue(import_attendances, rows=rows, now=True if len(rows) < 200 else False)
-def import_attendances(rows):
+def import_attendances(rows):
def remove_holidays(rows):
- rows = [ row for row in rows if row[4] != "Holiday"]
+ rows = [row for row in rows if row[4] != "Holiday"]
return rows
from frappe.modules import scrub
@@ -172,7 +194,8 @@
from frappe.utils.csvutils import check_record, import_doc
for i, row in enumerate(rows):
- if not row: continue
+ if not row:
+ continue
row_idx = i + 5
d = frappe._dict(zip(columns, row))
@@ -183,16 +206,12 @@
try:
check_record(d)
ret.append(import_doc(d, "Attendance", 1, row_idx, submit=True))
- frappe.publish_realtime('import_attendance', dict(
- progress=i,
- total=len(rows)
- ))
+ frappe.publish_realtime("import_attendance", dict(progress=i, total=len(rows)))
except AttributeError:
pass
except Exception as e:
error = True
- ret.append('Error for row (#%d) %s : %s' % (row_idx,
- len(row)>1 and row[1] or "", cstr(e)))
+ ret.append("Error for row (#%d) %s : %s" % (row_idx, len(row) > 1 and row[1] or "", cstr(e)))
frappe.errprint(frappe.get_traceback())
if error:
@@ -200,7 +219,4 @@
else:
frappe.db.commit()
- frappe.publish_realtime('import_attendance', dict(
- messages=ret,
- error=error
- ))
+ frappe.publish_realtime("import_attendance", dict(messages=ret, error=error))
diff --git a/erpnext/hr/doctype/vehicle/test_vehicle.py b/erpnext/hr/doctype/vehicle/test_vehicle.py
index c5ea5a3..97fe651 100644
--- a/erpnext/hr/doctype/vehicle/test_vehicle.py
+++ b/erpnext/hr/doctype/vehicle/test_vehicle.py
@@ -8,18 +8,21 @@
# test_records = frappe.get_test_records('Vehicle')
+
class TestVehicle(unittest.TestCase):
def test_make_vehicle(self):
- vehicle = frappe.get_doc({
- "doctype": "Vehicle",
- "license_plate": random_string(10).upper(),
- "make": "Maruti",
- "model": "PCM",
- "last_odometer":5000,
- "acquisition_date":frappe.utils.nowdate(),
- "location": "Mumbai",
- "chassis_no": "1234ABCD",
- "uom": "Litre",
- "vehicle_value":frappe.utils.flt(500000)
- })
+ vehicle = frappe.get_doc(
+ {
+ "doctype": "Vehicle",
+ "license_plate": random_string(10).upper(),
+ "make": "Maruti",
+ "model": "PCM",
+ "last_odometer": 5000,
+ "acquisition_date": frappe.utils.nowdate(),
+ "location": "Mumbai",
+ "chassis_no": "1234ABCD",
+ "uom": "Litre",
+ "vehicle_value": frappe.utils.flt(500000),
+ }
+ )
vehicle.insert()
diff --git a/erpnext/hr/doctype/vehicle/vehicle.py b/erpnext/hr/doctype/vehicle/vehicle.py
index 946233b..22c14c3 100644
--- a/erpnext/hr/doctype/vehicle/vehicle.py
+++ b/erpnext/hr/doctype/vehicle/vehicle.py
@@ -15,9 +15,15 @@
if getdate(self.carbon_check_date) > getdate():
frappe.throw(_("Last carbon check date cannot be a future date"))
+
def get_timeline_data(doctype, name):
- '''Return timeline for vehicle log'''
- return dict(frappe.db.sql('''select unix_timestamp(date), count(*)
+ """Return timeline for vehicle log"""
+ return dict(
+ frappe.db.sql(
+ """select unix_timestamp(date), count(*)
from `tabVehicle Log` where license_plate=%s
and date > date_sub(curdate(), interval 1 year)
- group by date''', name))
+ group by date""",
+ name,
+ )
+ )
diff --git a/erpnext/hr/doctype/vehicle/vehicle_dashboard.py b/erpnext/hr/doctype/vehicle/vehicle_dashboard.py
index f6e5f06..758dfbd 100644
--- a/erpnext/hr/doctype/vehicle/vehicle_dashboard.py
+++ b/erpnext/hr/doctype/vehicle/vehicle_dashboard.py
@@ -3,18 +3,11 @@
def get_data():
return {
- 'heatmap': True,
- 'heatmap_message': _('This is based on logs against this Vehicle. See timeline below for details'),
- 'fieldname': 'license_plate',
- 'non_standard_fieldnames':{
- 'Delivery Trip': 'vehicle'
- },
- 'transactions': [
- {
- 'items': ['Vehicle Log']
- },
- {
- 'items': ['Delivery Trip']
- }
- ]
+ "heatmap": True,
+ "heatmap_message": _(
+ "This is based on logs against this Vehicle. See timeline below for details"
+ ),
+ "fieldname": "license_plate",
+ "non_standard_fieldnames": {"Delivery Trip": "vehicle"},
+ "transactions": [{"items": ["Vehicle Log"]}, {"items": ["Delivery Trip"]}],
}
diff --git a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
index abb2887..bb29670 100644
--- a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
+++ b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
@@ -12,7 +12,9 @@
class TestVehicleLog(unittest.TestCase):
def setUp(self):
- employee_id = frappe.db.sql("""select name from `tabEmployee` where name='testdriver@example.com'""")
+ employee_id = frappe.db.sql(
+ """select name from `tabEmployee` where name='testdriver@example.com'"""
+ )
self.employee_id = employee_id[0][0] if employee_id else None
if not self.employee_id:
@@ -27,11 +29,11 @@
def test_make_vehicle_log_and_syncing_of_odometer_value(self):
vehicle_log = make_vehicle_log(self.license_plate, self.employee_id)
- #checking value of vehicle odometer value on submit.
+ # checking value of vehicle odometer value on submit.
vehicle = frappe.get_doc("Vehicle", self.license_plate)
self.assertEqual(vehicle.last_odometer, vehicle_log.odometer)
- #checking value vehicle odometer on vehicle log cancellation.
+ # checking value vehicle odometer on vehicle log cancellation.
last_odometer = vehicle_log.last_odometer
current_odometer = vehicle_log.odometer
distance_travelled = current_odometer - last_odometer
@@ -48,7 +50,7 @@
expense_claim = make_expense_claim(vehicle_log.name)
fuel_expense = expense_claim.expenses[0].amount
- self.assertEqual(fuel_expense, 50*500)
+ self.assertEqual(fuel_expense, 50 * 500)
vehicle_log.cancel()
frappe.delete_doc("Expense Claim", expense_claim.name)
@@ -67,8 +69,9 @@
def get_vehicle(employee_id):
- license_plate=random_string(10).upper()
- vehicle = frappe.get_doc({
+ license_plate = random_string(10).upper()
+ vehicle = frappe.get_doc(
+ {
"doctype": "Vehicle",
"license_plate": cstr(license_plate),
"make": "Maruti",
@@ -79,8 +82,9 @@
"location": "Mumbai",
"chassis_no": "1234ABCD",
"uom": "Litre",
- "vehicle_value": flt(500000)
- })
+ "vehicle_value": flt(500000),
+ }
+ )
try:
vehicle.insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
@@ -89,29 +93,37 @@
def make_vehicle_log(license_plate, employee_id, with_services=False):
- vehicle_log = frappe.get_doc({
- "doctype": "Vehicle Log",
- "license_plate": cstr(license_plate),
- "employee": employee_id,
- "date": nowdate(),
- "odometer": 5010,
- "fuel_qty": flt(50),
- "price": flt(500)
- })
+ vehicle_log = frappe.get_doc(
+ {
+ "doctype": "Vehicle Log",
+ "license_plate": cstr(license_plate),
+ "employee": employee_id,
+ "date": nowdate(),
+ "odometer": 5010,
+ "fuel_qty": flt(50),
+ "price": flt(500),
+ }
+ )
if with_services:
- vehicle_log.append("service_detail", {
- "service_item": "Oil Change",
- "type": "Inspection",
- "frequency": "Mileage",
- "expense_amount": flt(500)
- })
- vehicle_log.append("service_detail", {
- "service_item": "Wheels",
- "type": "Change",
- "frequency": "Half Yearly",
- "expense_amount": flt(1500)
- })
+ vehicle_log.append(
+ "service_detail",
+ {
+ "service_item": "Oil Change",
+ "type": "Inspection",
+ "frequency": "Mileage",
+ "expense_amount": flt(500),
+ },
+ )
+ vehicle_log.append(
+ "service_detail",
+ {
+ "service_item": "Wheels",
+ "type": "Change",
+ "frequency": "Half Yearly",
+ "expense_amount": flt(1500),
+ },
+ )
vehicle_log.save()
vehicle_log.submit()
diff --git a/erpnext/hr/doctype/vehicle_log/vehicle_log.py b/erpnext/hr/doctype/vehicle_log/vehicle_log.py
index e414141..2c1d9a4 100644
--- a/erpnext/hr/doctype/vehicle_log/vehicle_log.py
+++ b/erpnext/hr/doctype/vehicle_log/vehicle_log.py
@@ -11,17 +11,24 @@
class VehicleLog(Document):
def validate(self):
if flt(self.odometer) < flt(self.last_odometer):
- frappe.throw(_("Current Odometer Value should be greater than Last Odometer Value {0}").format(self.last_odometer))
+ frappe.throw(
+ _("Current Odometer Value should be greater than Last Odometer Value {0}").format(
+ self.last_odometer
+ )
+ )
def on_submit(self):
frappe.db.set_value("Vehicle", self.license_plate, "last_odometer", self.odometer)
def on_cancel(self):
distance_travelled = self.odometer - self.last_odometer
- if(distance_travelled > 0):
- updated_odometer_value = int(frappe.db.get_value("Vehicle", self.license_plate, "last_odometer")) - distance_travelled
+ if distance_travelled > 0:
+ updated_odometer_value = (
+ int(frappe.db.get_value("Vehicle", self.license_plate, "last_odometer")) - distance_travelled
+ )
frappe.db.set_value("Vehicle", self.license_plate, "last_odometer", updated_odometer_value)
+
@frappe.whitelist()
def make_expense_claim(docname):
expense_claim = frappe.db.exists("Expense Claim", {"vehicle_log": docname})
@@ -39,9 +46,8 @@
exp_claim.employee = vehicle_log.employee
exp_claim.vehicle_log = vehicle_log.name
exp_claim.remark = _("Expense Claim for Vehicle Log {0}").format(vehicle_log.name)
- exp_claim.append("expenses", {
- "expense_date": vehicle_log.date,
- "description": _("Vehicle Expenses"),
- "amount": claim_amount
- })
+ exp_claim.append(
+ "expenses",
+ {"expense_date": vehicle_log.date, "description": _("Vehicle Expenses"), "amount": claim_amount},
+ )
return exp_claim.as_dict()
diff --git a/erpnext/hr/page/organizational_chart/organizational_chart.py b/erpnext/hr/page/organizational_chart/organizational_chart.py
index 1e2d758..3674912 100644
--- a/erpnext/hr/page/organizational_chart/organizational_chart.py
+++ b/erpnext/hr/page/organizational_chart/organizational_chart.py
@@ -3,26 +3,27 @@
@frappe.whitelist()
def get_children(parent=None, company=None, exclude_node=None):
- filters = [['status', '!=', 'Left']]
- if company and company != 'All Companies':
- filters.append(['company', '=', company])
+ filters = [["status", "!=", "Left"]]
+ if company and company != "All Companies":
+ filters.append(["company", "=", company])
if parent and company and parent != company:
- filters.append(['reports_to', '=', parent])
+ filters.append(["reports_to", "=", parent])
else:
- filters.append(['reports_to', '=', ''])
+ filters.append(["reports_to", "=", ""])
if exclude_node:
- filters.append(['name', '!=', exclude_node])
+ filters.append(["name", "!=", exclude_node])
- employees = frappe.get_list('Employee',
- fields=['employee_name as name', 'name as id', 'reports_to', 'image', 'designation as title'],
+ employees = frappe.get_list(
+ "Employee",
+ fields=["employee_name as name", "name as id", "reports_to", "image", "designation as title"],
filters=filters,
- order_by='name'
+ order_by="name",
)
for employee in employees:
- is_expandable = frappe.db.count('Employee', filters={'reports_to': employee.get('id')})
+ is_expandable = frappe.db.count("Employee", filters={"reports_to": employee.get("id")})
employee.connections = get_connections(employee.id)
employee.expandable = 1 if is_expandable else 0
@@ -32,16 +33,12 @@
def get_connections(employee):
num_connections = 0
- nodes_to_expand = frappe.get_list('Employee', filters=[
- ['reports_to', '=', employee]
- ])
+ nodes_to_expand = frappe.get_list("Employee", filters=[["reports_to", "=", employee]])
num_connections += len(nodes_to_expand)
while nodes_to_expand:
parent = nodes_to_expand.pop(0)
- descendants = frappe.get_list('Employee', filters=[
- ['reports_to', '=', parent.name]
- ])
+ descendants = frappe.get_list("Employee", filters=[["reports_to", "=", parent.name]])
num_connections += len(descendants)
nodes_to_expand.extend(descendants)
diff --git a/erpnext/hr/page/team_updates/team_updates.py b/erpnext/hr/page/team_updates/team_updates.py
index 0a4624c..c1fcb73 100644
--- a/erpnext/hr/page/team_updates/team_updates.py
+++ b/erpnext/hr/page/team_updates/team_updates.py
@@ -4,15 +4,20 @@
@frappe.whitelist()
def get_data(start=0):
- #frappe.only_for('Employee', 'System Manager')
- data = frappe.get_all('Communication',
- fields=('content', 'text_content', 'sender', 'creation'),
- filters=dict(reference_doctype='Daily Work Summary'),
- order_by='creation desc', limit=40, start=start)
+ # frappe.only_for('Employee', 'System Manager')
+ data = frappe.get_all(
+ "Communication",
+ fields=("content", "text_content", "sender", "creation"),
+ filters=dict(reference_doctype="Daily Work Summary"),
+ order_by="creation desc",
+ limit=40,
+ start=start,
+ )
for d in data:
- d.sender_name = frappe.db.get_value("Employee", {"user_id": d.sender},
- "employee_name") or d.sender
+ d.sender_name = (
+ frappe.db.get_value("Employee", {"user_id": d.sender}, "employee_name") or d.sender
+ )
if d.text_content:
d.content = frappe.utils.md_to_html(EmailReplyParser.parse_reply(d.text_content))
diff --git a/erpnext/hr/report/daily_work_summary_replies/daily_work_summary_replies.py b/erpnext/hr/report/daily_work_summary_replies/daily_work_summary_replies.py
index 63764bb..d93688a 100644
--- a/erpnext/hr/report/daily_work_summary_replies/daily_work_summary_replies.py
+++ b/erpnext/hr/report/daily_work_summary_replies/daily_work_summary_replies.py
@@ -9,51 +9,53 @@
def execute(filters=None):
- if not filters.group: return [], []
+ if not filters.group:
+ return [], []
columns, data = get_columns(), get_data(filters)
return columns, data
+
def get_columns(filters=None):
columns = [
- {
- "label": _("User"),
- "fieldname": "user",
- "fieldtype": "Data",
- "width": 300
- },
+ {"label": _("User"), "fieldname": "user", "fieldtype": "Data", "width": 300},
{
"label": _("Replies"),
"fieldname": "count",
"fieldtype": "data",
"width": 100,
- "align": 'right',
+ "align": "right",
},
{
"label": _("Total"),
"fieldname": "total",
"fieldtype": "data",
"width": 100,
- "align": 'right',
- }
+ "align": "right",
+ },
]
return columns
+
def get_data(filters):
- daily_summary_emails = frappe.get_all('Daily Work Summary',
- fields=["name"],
- filters=[["creation","Between", filters.range]])
- daily_summary_emails = [d.get('name') for d in daily_summary_emails]
- replies = frappe.get_all('Communication',
- fields=['content', 'text_content', 'sender'],
- filters=[['reference_doctype','=', 'Daily Work Summary'],
- ['reference_name', 'in', daily_summary_emails],
- ['communication_type', '=', 'Communication'],
- ['sent_or_received', '=', 'Received']],
- order_by='creation asc')
+ daily_summary_emails = frappe.get_all(
+ "Daily Work Summary", fields=["name"], filters=[["creation", "Between", filters.range]]
+ )
+ daily_summary_emails = [d.get("name") for d in daily_summary_emails]
+ replies = frappe.get_all(
+ "Communication",
+ fields=["content", "text_content", "sender"],
+ filters=[
+ ["reference_doctype", "=", "Daily Work Summary"],
+ ["reference_name", "in", daily_summary_emails],
+ ["communication_type", "=", "Communication"],
+ ["sent_or_received", "=", "Received"],
+ ],
+ order_by="creation asc",
+ )
data = []
total = len(daily_summary_emails)
for user in get_user_emails_from_group(filters.group):
- user_name = frappe.get_value('User', user, 'full_name')
+ user_name = frappe.get_value("User", user, "full_name")
count = len([d for d in replies if d.sender == user])
data.append([user_name, count, total])
return data
diff --git a/erpnext/hr/report/employee_advance_summary/employee_advance_summary.py b/erpnext/hr/report/employee_advance_summary/employee_advance_summary.py
index 62b83f2..29532f7 100644
--- a/erpnext/hr/report/employee_advance_summary/employee_advance_summary.py
+++ b/erpnext/hr/report/employee_advance_summary/employee_advance_summary.py
@@ -7,7 +7,8 @@
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
advances_list = get_advances(filters)
columns = get_columns()
@@ -18,8 +19,16 @@
data = []
for advance in advances_list:
- row = [advance.name, advance.employee, advance.company, advance.posting_date,
- advance.advance_amount, advance.paid_amount, advance.claimed_amount, advance.status]
+ row = [
+ advance.name,
+ advance.employee,
+ advance.company,
+ advance.posting_date,
+ advance.advance_amount,
+ advance.paid_amount,
+ advance.claimed_amount,
+ advance.status,
+ ]
data.append(row)
return columns, data
@@ -32,54 +41,40 @@
"fieldname": "title",
"fieldtype": "Link",
"options": "Employee Advance",
- "width": 120
+ "width": 120,
},
{
"label": _("Employee"),
"fieldname": "employee",
"fieldtype": "Link",
"options": "Employee",
- "width": 120
+ "width": 120,
},
{
"label": _("Company"),
"fieldname": "company",
"fieldtype": "Link",
"options": "Company",
- "width": 120
+ "width": 120,
},
- {
- "label": _("Posting Date"),
- "fieldname": "posting_date",
- "fieldtype": "Date",
- "width": 120
- },
+ {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
{
"label": _("Advance Amount"),
"fieldname": "advance_amount",
"fieldtype": "Currency",
- "width": 120
+ "width": 120,
},
- {
- "label": _("Paid Amount"),
- "fieldname": "paid_amount",
- "fieldtype": "Currency",
- "width": 120
- },
+ {"label": _("Paid Amount"), "fieldname": "paid_amount", "fieldtype": "Currency", "width": 120},
{
"label": _("Claimed Amount"),
"fieldname": "claimed_amount",
"fieldtype": "Currency",
- "width": 120
+ "width": 120,
},
- {
- "label": _("Status"),
- "fieldname": "status",
- "fieldtype": "Data",
- "width": 120
- }
+ {"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 120},
]
+
def get_conditions(filters):
conditions = ""
@@ -96,10 +91,15 @@
return conditions
+
def get_advances(filters):
conditions = get_conditions(filters)
- return frappe.db.sql("""select name, employee, paid_amount, status, advance_amount, claimed_amount, company,
+ return frappe.db.sql(
+ """select name, employee, paid_amount, status, advance_amount, claimed_amount, company,
posting_date, purpose
from `tabEmployee Advance`
- where docstatus<2 %s order by posting_date, name desc""" %
- conditions, filters, as_dict=1)
+ where docstatus<2 %s order by posting_date, name desc"""
+ % conditions,
+ filters,
+ as_dict=1,
+ )
diff --git a/erpnext/hr/report/employee_analytics/employee_analytics.py b/erpnext/hr/report/employee_analytics/employee_analytics.py
index 3a75276..12be156 100644
--- a/erpnext/hr/report/employee_analytics/employee_analytics.py
+++ b/erpnext/hr/report/employee_analytics/employee_analytics.py
@@ -7,10 +7,11 @@
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
if not filters["company"]:
- frappe.throw(_('{0} is mandatory').format(_('Company')))
+ frappe.throw(_("{0} is mandatory").format(_("Company")))
columns = get_columns()
employees = get_employees(filters)
@@ -20,28 +21,41 @@
for department in parameters_result:
parameters.append(department)
- chart = get_chart_data(parameters,employees, filters)
+ chart = get_chart_data(parameters, employees, filters)
return columns, employees, None, chart
+
def get_columns():
return [
- _("Employee") + ":Link/Employee:120", _("Name") + ":Data:200", _("Date of Birth")+ ":Date:100",
- _("Branch") + ":Link/Branch:120", _("Department") + ":Link/Department:120",
- _("Designation") + ":Link/Designation:120", _("Gender") + "::100", _("Company") + ":Link/Company:120"
+ _("Employee") + ":Link/Employee:120",
+ _("Name") + ":Data:200",
+ _("Date of Birth") + ":Date:100",
+ _("Branch") + ":Link/Branch:120",
+ _("Department") + ":Link/Department:120",
+ _("Designation") + ":Link/Designation:120",
+ _("Gender") + "::100",
+ _("Company") + ":Link/Company:120",
]
-def get_conditions(filters):
- conditions = " and "+filters.get("parameter").lower().replace(" ","_")+" IS NOT NULL "
- if filters.get("company"): conditions += " and company = '%s'" % \
- filters["company"].replace("'", "\\'")
+def get_conditions(filters):
+ conditions = " and " + filters.get("parameter").lower().replace(" ", "_") + " IS NOT NULL "
+
+ if filters.get("company"):
+ conditions += " and company = '%s'" % filters["company"].replace("'", "\\'")
return conditions
+
def get_employees(filters):
conditions = get_conditions(filters)
- return frappe.db.sql("""select name, employee_name, date_of_birth,
+ return frappe.db.sql(
+ """select name, employee_name, date_of_birth,
branch, department, designation,
- gender, company from `tabEmployee` where status = 'Active' %s""" % conditions, as_list=1)
+ gender, company from `tabEmployee` where status = 'Active' %s"""
+ % conditions,
+ as_list=1,
+ )
+
def get_parameters(filters):
if filters.get("parameter") == "Grade":
@@ -49,36 +63,37 @@
else:
parameter = filters.get("parameter")
- return frappe.db.sql("""select name from `tab"""+ parameter +"""` """, as_list=1)
+ return frappe.db.sql("""select name from `tab""" + parameter + """` """, as_list=1)
-def get_chart_data(parameters,employees, filters):
+
+def get_chart_data(parameters, employees, filters):
if not parameters:
parameters = []
datasets = []
- parameter_field_name = filters.get("parameter").lower().replace(" ","_")
+ parameter_field_name = filters.get("parameter").lower().replace(" ", "_")
label = []
for parameter in parameters:
if parameter:
- total_employee = frappe.db.sql("""select count(*) from
- `tabEmployee` where """+
- parameter_field_name + """ = %s and company = %s""" ,( parameter[0], filters.get("company")), as_list=1)
+ total_employee = frappe.db.sql(
+ """select count(*) from
+ `tabEmployee` where """
+ + parameter_field_name
+ + """ = %s and company = %s""",
+ (parameter[0], filters.get("company")),
+ as_list=1,
+ )
if total_employee[0][0]:
label.append(parameter)
datasets.append(total_employee[0][0])
- values = [ value for value in datasets if value !=0]
+ values = [value for value in datasets if value != 0]
- total_employee = frappe.db.count('Employee', {'status':'Active'})
+ total_employee = frappe.db.count("Employee", {"status": "Active"})
others = total_employee - sum(values)
label.append(["Not Set"])
values.append(others)
- chart = {
- "data": {
- 'labels': label,
- 'datasets': [{'name': 'Employees','values': values}]
- }
- }
+ chart = {"data": {"labels": label, "datasets": [{"name": "Employees", "values": values}]}}
chart["type"] = "donut"
return chart
diff --git a/erpnext/hr/report/employee_birthday/employee_birthday.py b/erpnext/hr/report/employee_birthday/employee_birthday.py
index cec5a48..a6a13d8 100644
--- a/erpnext/hr/report/employee_birthday/employee_birthday.py
+++ b/erpnext/hr/report/employee_birthday/employee_birthday.py
@@ -7,34 +7,59 @@
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
columns = get_columns()
data = get_employees(filters)
return columns, data
+
def get_columns():
return [
- _("Employee") + ":Link/Employee:120", _("Name") + ":Data:200", _("Date of Birth")+ ":Date:100",
- _("Branch") + ":Link/Branch:120", _("Department") + ":Link/Department:120",
- _("Designation") + ":Link/Designation:120", _("Gender") + "::60", _("Company") + ":Link/Company:120"
+ _("Employee") + ":Link/Employee:120",
+ _("Name") + ":Data:200",
+ _("Date of Birth") + ":Date:100",
+ _("Branch") + ":Link/Branch:120",
+ _("Department") + ":Link/Department:120",
+ _("Designation") + ":Link/Designation:120",
+ _("Gender") + "::60",
+ _("Company") + ":Link/Company:120",
]
+
def get_employees(filters):
conditions = get_conditions(filters)
- return frappe.db.sql("""select name, employee_name, date_of_birth,
+ return frappe.db.sql(
+ """select name, employee_name, date_of_birth,
branch, department, designation,
- gender, company from tabEmployee where status = 'Active' %s""" % conditions, as_list=1)
+ gender, company from tabEmployee where status = 'Active' %s"""
+ % conditions,
+ as_list=1,
+ )
+
def get_conditions(filters):
conditions = ""
if filters.get("month"):
- month = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov",
- "Dec"].index(filters["month"]) + 1
+ month = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ].index(filters["month"]) + 1
conditions += " and month(date_of_birth) = '%s'" % month
- if filters.get("company"): conditions += " and company = '%s'" % \
- filters["company"].replace("'", "\\'")
+ if filters.get("company"):
+ conditions += " and company = '%s'" % filters["company"].replace("'", "\\'")
return conditions
diff --git a/erpnext/hr/report/employee_exits/employee_exits.py b/erpnext/hr/report/employee_exits/employee_exits.py
index bde5a89..9cd9ff0 100644
--- a/erpnext/hr/report/employee_exits/employee_exits.py
+++ b/erpnext/hr/report/employee_exits/employee_exits.py
@@ -15,104 +15,106 @@
return columns, data, None, chart, report_summary
+
def get_columns():
return [
{
- 'label': _('Employee'),
- 'fieldname': 'employee',
- 'fieldtype': 'Link',
- 'options': 'Employee',
- 'width': 150
+ "label": _("Employee"),
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "options": "Employee",
+ "width": 150,
+ },
+ {"label": _("Employee Name"), "fieldname": "employee_name", "fieldtype": "Data", "width": 150},
+ {
+ "label": _("Date of Joining"),
+ "fieldname": "date_of_joining",
+ "fieldtype": "Date",
+ "width": 120,
+ },
+ {"label": _("Relieving Date"), "fieldname": "relieving_date", "fieldtype": "Date", "width": 120},
+ {
+ "label": _("Exit Interview"),
+ "fieldname": "exit_interview",
+ "fieldtype": "Link",
+ "options": "Exit Interview",
+ "width": 150,
},
{
- 'label': _('Employee Name'),
- 'fieldname': 'employee_name',
- 'fieldtype': 'Data',
- 'width': 150
+ "label": _("Interview Status"),
+ "fieldname": "interview_status",
+ "fieldtype": "Data",
+ "width": 130,
},
{
- 'label': _('Date of Joining'),
- 'fieldname': 'date_of_joining',
- 'fieldtype': 'Date',
- 'width': 120
+ "label": _("Final Decision"),
+ "fieldname": "employee_status",
+ "fieldtype": "Data",
+ "width": 150,
},
{
- 'label': _('Relieving Date'),
- 'fieldname': 'relieving_date',
- 'fieldtype': 'Date',
- 'width': 120
+ "label": _("Full and Final Statement"),
+ "fieldname": "full_and_final_statement",
+ "fieldtype": "Link",
+ "options": "Full and Final Statement",
+ "width": 180,
},
{
- 'label': _('Exit Interview'),
- 'fieldname': 'exit_interview',
- 'fieldtype': 'Link',
- 'options': 'Exit Interview',
- 'width': 150
+ "label": _("Department"),
+ "fieldname": "department",
+ "fieldtype": "Link",
+ "options": "Department",
+ "width": 120,
},
{
- 'label': _('Interview Status'),
- 'fieldname': 'interview_status',
- 'fieldtype': 'Data',
- 'width': 130
+ "label": _("Designation"),
+ "fieldname": "designation",
+ "fieldtype": "Link",
+ "options": "Designation",
+ "width": 120,
},
{
- 'label': _('Final Decision'),
- 'fieldname': 'employee_status',
- 'fieldtype': 'Data',
- 'width': 150
+ "label": _("Reports To"),
+ "fieldname": "reports_to",
+ "fieldtype": "Link",
+ "options": "Employee",
+ "width": 120,
},
- {
- 'label': _('Full and Final Statement'),
- 'fieldname': 'full_and_final_statement',
- 'fieldtype': 'Link',
- 'options': 'Full and Final Statement',
- 'width': 180
- },
- {
- 'label': _('Department'),
- 'fieldname': 'department',
- 'fieldtype': 'Link',
- 'options': 'Department',
- 'width': 120
- },
- {
- 'label': _('Designation'),
- 'fieldname': 'designation',
- 'fieldtype': 'Link',
- 'options': 'Designation',
- 'width': 120
- },
- {
- 'label': _('Reports To'),
- 'fieldname': 'reports_to',
- 'fieldtype': 'Link',
- 'options': 'Employee',
- 'width': 120
- }
]
+
def get_data(filters):
- employee = frappe.qb.DocType('Employee')
- interview = frappe.qb.DocType('Exit Interview')
- fnf = frappe.qb.DocType('Full and Final Statement')
+ employee = frappe.qb.DocType("Employee")
+ interview = frappe.qb.DocType("Exit Interview")
+ fnf = frappe.qb.DocType("Full and Final Statement")
query = (
frappe.qb.from_(employee)
- .left_join(interview).on(interview.employee == employee.name)
- .left_join(fnf).on(fnf.employee == employee.name)
- .select(
- employee.name.as_('employee'), employee.employee_name.as_('employee_name'),
- employee.date_of_joining.as_('date_of_joining'), employee.relieving_date.as_('relieving_date'),
- employee.department.as_('department'), employee.designation.as_('designation'),
- employee.reports_to.as_('reports_to'), interview.name.as_('exit_interview'),
- interview.status.as_('interview_status'), interview.employee_status.as_('employee_status'),
- interview.reference_document_name.as_('questionnaire'), fnf.name.as_('full_and_final_statement'))
- .distinct()
- .where(
- ((employee.relieving_date.isnotnull()) | (employee.relieving_date != ''))
- & ((interview.name.isnull()) | ((interview.name.isnotnull()) & (interview.docstatus != 2)))
- & ((fnf.name.isnull()) | ((fnf.name.isnotnull()) & (fnf.docstatus != 2)))
- ).orderby(employee.relieving_date, order=Order.asc)
+ .left_join(interview)
+ .on(interview.employee == employee.name)
+ .left_join(fnf)
+ .on(fnf.employee == employee.name)
+ .select(
+ employee.name.as_("employee"),
+ employee.employee_name.as_("employee_name"),
+ employee.date_of_joining.as_("date_of_joining"),
+ employee.relieving_date.as_("relieving_date"),
+ employee.department.as_("department"),
+ employee.designation.as_("designation"),
+ employee.reports_to.as_("reports_to"),
+ interview.name.as_("exit_interview"),
+ interview.status.as_("interview_status"),
+ interview.employee_status.as_("employee_status"),
+ interview.reference_document_name.as_("questionnaire"),
+ fnf.name.as_("full_and_final_statement"),
+ )
+ .distinct()
+ .where(
+ ((employee.relieving_date.isnotnull()) | (employee.relieving_date != ""))
+ & ((interview.name.isnull()) | ((interview.name.isnotnull()) & (interview.docstatus != 2)))
+ & ((fnf.name.isnull()) | ((fnf.name.isnotnull()) & (fnf.docstatus != 2)))
+ )
+ .orderby(employee.relieving_date, order=Order.asc)
)
query = get_conditions(filters, query, employee, interview, fnf)
@@ -122,44 +124,48 @@
def get_conditions(filters, query, employee, interview, fnf):
- if filters.get('from_date') and filters.get('to_date'):
- query = query.where(employee.relieving_date[getdate(filters.get('from_date')):getdate(filters.get('to_date'))])
+ if filters.get("from_date") and filters.get("to_date"):
+ query = query.where(
+ employee.relieving_date[getdate(filters.get("from_date")) : getdate(filters.get("to_date"))]
+ )
- elif filters.get('from_date'):
- query = query.where(employee.relieving_date >= filters.get('from_date'))
+ elif filters.get("from_date"):
+ query = query.where(employee.relieving_date >= filters.get("from_date"))
- elif filters.get('to_date'):
- query = query.where(employee.relieving_date <= filters.get('to_date'))
+ elif filters.get("to_date"):
+ query = query.where(employee.relieving_date <= filters.get("to_date"))
- if filters.get('company'):
- query = query.where(employee.company == filters.get('company'))
+ if filters.get("company"):
+ query = query.where(employee.company == filters.get("company"))
- if filters.get('department'):
- query = query.where(employee.department == filters.get('department'))
+ if filters.get("department"):
+ query = query.where(employee.department == filters.get("department"))
- if filters.get('designation'):
- query = query.where(employee.designation == filters.get('designation'))
+ if filters.get("designation"):
+ query = query.where(employee.designation == filters.get("designation"))
- if filters.get('employee'):
- query = query.where(employee.name == filters.get('employee'))
+ if filters.get("employee"):
+ query = query.where(employee.name == filters.get("employee"))
- if filters.get('reports_to'):
- query = query.where(employee.reports_to == filters.get('reports_to'))
+ if filters.get("reports_to"):
+ query = query.where(employee.reports_to == filters.get("reports_to"))
- if filters.get('interview_status'):
- query = query.where(interview.status == filters.get('interview_status'))
+ if filters.get("interview_status"):
+ query = query.where(interview.status == filters.get("interview_status"))
- if filters.get('final_decision'):
- query = query.where(interview.employee_status == filters.get('final_decision'))
+ if filters.get("final_decision"):
+ query = query.where(interview.employee_status == filters.get("final_decision"))
- if filters.get('exit_interview_pending'):
- query = query.where((interview.name == '') | (interview.name.isnull()))
+ if filters.get("exit_interview_pending"):
+ query = query.where((interview.name == "") | (interview.name.isnull()))
- if filters.get('questionnaire_pending'):
- query = query.where((interview.reference_document_name == '') | (interview.reference_document_name.isnull()))
+ if filters.get("questionnaire_pending"):
+ query = query.where(
+ (interview.reference_document_name == "") | (interview.reference_document_name.isnull())
+ )
- if filters.get('fnf_pending'):
- query = query.where((fnf.name == '') | (fnf.name.isnull()))
+ if filters.get("fnf_pending"):
+ query = query.where((fnf.name == "") | (fnf.name.isnull()))
return query
@@ -173,20 +179,20 @@
pending = 0
for entry in data:
- if entry.employee_status == 'Employee Retained':
+ if entry.employee_status == "Employee Retained":
retained += 1
- elif entry.employee_status == 'Exit Confirmed':
+ elif entry.employee_status == "Exit Confirmed":
exit_confirmed += 1
else:
pending += 1
chart = {
- 'data': {
- 'labels': [_('Retained'), _('Exit Confirmed'), _('Decision Pending')],
- 'datasets': [{'name': _('Employee Status'), 'values': [retained, exit_confirmed, pending]}]
+ "data": {
+ "labels": [_("Retained"), _("Exit Confirmed"), _("Decision Pending")],
+ "datasets": [{"name": _("Employee Status"), "values": [retained, exit_confirmed, pending]}],
},
- 'type': 'donut',
- 'colors': ['green', 'red', 'blue'],
+ "type": "donut",
+ "colors": ["green", "red", "blue"],
}
return chart
@@ -203,28 +209,27 @@
return [
{
- 'value': total_resignations,
- 'label': _('Total Resignations'),
- 'indicator': 'Red' if total_resignations > 0 else 'Green',
- 'datatype': 'Int',
+ "value": total_resignations,
+ "label": _("Total Resignations"),
+ "indicator": "Red" if total_resignations > 0 else "Green",
+ "datatype": "Int",
},
{
- 'value': interviews_pending,
- 'label': _('Pending Interviews'),
- 'indicator': 'Blue' if interviews_pending > 0 else 'Green',
- 'datatype': 'Int',
+ "value": interviews_pending,
+ "label": _("Pending Interviews"),
+ "indicator": "Blue" if interviews_pending > 0 else "Green",
+ "datatype": "Int",
},
{
- 'value': fnf_pending,
- 'label': _('Pending FnF'),
- 'indicator': 'Blue' if fnf_pending > 0 else 'Green',
- 'datatype': 'Int',
+ "value": fnf_pending,
+ "label": _("Pending FnF"),
+ "indicator": "Blue" if fnf_pending > 0 else "Green",
+ "datatype": "Int",
},
{
- 'value': questionnaires_pending,
- 'label': _('Pending Questionnaires'),
- 'indicator': 'Blue' if questionnaires_pending > 0 else 'Green',
- 'datatype': 'Int'
+ "value": questionnaires_pending,
+ "label": _("Pending Questionnaires"),
+ "indicator": "Blue" if questionnaires_pending > 0 else "Green",
+ "datatype": "Int",
},
]
-
diff --git a/erpnext/hr/report/employee_exits/test_employee_exits.py b/erpnext/hr/report/employee_exits/test_employee_exits.py
index d7e95a6..a9a30de 100644
--- a/erpnext/hr/report/employee_exits/test_employee_exits.py
+++ b/erpnext/hr/report/employee_exits/test_employee_exits.py
@@ -28,33 +28,33 @@
@classmethod
def create_records(cls):
cls.emp1 = make_employee(
- 'employeeexit1@example.com',
- company='Test Company',
- date_of_joining=getdate('01-10-2021'),
+ "employeeexit1@example.com",
+ company="Test Company",
+ date_of_joining=getdate("01-10-2021"),
relieving_date=add_days(getdate(), 14),
- designation='Accountant'
+ designation="Accountant",
)
cls.emp2 = make_employee(
- 'employeeexit2@example.com',
- company='Test Company',
- date_of_joining=getdate('01-12-2021'),
+ "employeeexit2@example.com",
+ company="Test Company",
+ date_of_joining=getdate("01-12-2021"),
relieving_date=add_days(getdate(), 15),
- designation='Accountant'
+ designation="Accountant",
)
cls.emp3 = make_employee(
- 'employeeexit3@example.com',
- company='Test Company',
- date_of_joining=getdate('02-12-2021'),
+ "employeeexit3@example.com",
+ company="Test Company",
+ date_of_joining=getdate("02-12-2021"),
relieving_date=add_days(getdate(), 29),
- designation='Engineer'
+ designation="Engineer",
)
cls.emp4 = make_employee(
- 'employeeexit4@example.com',
- company='Test Company',
- date_of_joining=getdate('01-12-2021'),
+ "employeeexit4@example.com",
+ company="Test Company",
+ date_of_joining=getdate("01-12-2021"),
relieving_date=add_days(getdate(), 30),
- designation='Engineer'
+ designation="Engineer",
)
# exit interview for 3 employees only
@@ -69,174 +69,177 @@
# link questionnaire for a few records
# setting employee doctype as reference instead of creating a questionnaire
# since this is just for a test
- frappe.db.set_value('Exit Interview', cls.interview1.name, {
- 'ref_doctype': 'Employee',
- 'reference_document_name': cls.emp1
- })
+ frappe.db.set_value(
+ "Exit Interview",
+ cls.interview1.name,
+ {"ref_doctype": "Employee", "reference_document_name": cls.emp1},
+ )
- frappe.db.set_value('Exit Interview', cls.interview2.name, {
- 'ref_doctype': 'Employee',
- 'reference_document_name': cls.emp2
- })
+ frappe.db.set_value(
+ "Exit Interview",
+ cls.interview2.name,
+ {"ref_doctype": "Employee", "reference_document_name": cls.emp2},
+ )
- frappe.db.set_value('Exit Interview', cls.interview3.name, {
- 'ref_doctype': 'Employee',
- 'reference_document_name': cls.emp3
- })
-
+ frappe.db.set_value(
+ "Exit Interview",
+ cls.interview3.name,
+ {"ref_doctype": "Employee", "reference_document_name": cls.emp3},
+ )
def test_employee_exits_summary(self):
filters = {
- 'company': 'Test Company',
- 'from_date': getdate(),
- 'to_date': add_days(getdate(), 15),
- 'designation': 'Accountant'
+ "company": "Test Company",
+ "from_date": getdate(),
+ "to_date": add_days(getdate(), 15),
+ "designation": "Accountant",
}
report = execute(filters)
- employee1 = frappe.get_doc('Employee', self.emp1)
- employee2 = frappe.get_doc('Employee', self.emp2)
+ employee1 = frappe.get_doc("Employee", self.emp1)
+ employee2 = frappe.get_doc("Employee", self.emp2)
expected_data = [
{
- 'employee': employee1.name,
- 'employee_name': employee1.employee_name,
- 'date_of_joining': employee1.date_of_joining,
- 'relieving_date': employee1.relieving_date,
- 'department': employee1.department,
- 'designation': employee1.designation,
- 'reports_to': None,
- 'exit_interview': self.interview1.name,
- 'interview_status': self.interview1.status,
- 'employee_status': '',
- 'questionnaire': employee1.name,
- 'full_and_final_statement': self.fnf1.name
+ "employee": employee1.name,
+ "employee_name": employee1.employee_name,
+ "date_of_joining": employee1.date_of_joining,
+ "relieving_date": employee1.relieving_date,
+ "department": employee1.department,
+ "designation": employee1.designation,
+ "reports_to": None,
+ "exit_interview": self.interview1.name,
+ "interview_status": self.interview1.status,
+ "employee_status": "",
+ "questionnaire": employee1.name,
+ "full_and_final_statement": self.fnf1.name,
},
{
- 'employee': employee2.name,
- 'employee_name': employee2.employee_name,
- 'date_of_joining': employee2.date_of_joining,
- 'relieving_date': employee2.relieving_date,
- 'department': employee2.department,
- 'designation': employee2.designation,
- 'reports_to': None,
- 'exit_interview': self.interview2.name,
- 'interview_status': self.interview2.status,
- 'employee_status': '',
- 'questionnaire': employee2.name,
- 'full_and_final_statement': self.fnf2.name
- }
+ "employee": employee2.name,
+ "employee_name": employee2.employee_name,
+ "date_of_joining": employee2.date_of_joining,
+ "relieving_date": employee2.relieving_date,
+ "department": employee2.department,
+ "designation": employee2.designation,
+ "reports_to": None,
+ "exit_interview": self.interview2.name,
+ "interview_status": self.interview2.status,
+ "employee_status": "",
+ "questionnaire": employee2.name,
+ "full_and_final_statement": self.fnf2.name,
+ },
]
- self.assertEqual(expected_data, report[1]) # rows
-
+ self.assertEqual(expected_data, report[1]) # rows
def test_pending_exit_interviews_summary(self):
filters = {
- 'company': 'Test Company',
- 'from_date': getdate(),
- 'to_date': add_days(getdate(), 30),
- 'exit_interview_pending': 1
+ "company": "Test Company",
+ "from_date": getdate(),
+ "to_date": add_days(getdate(), 30),
+ "exit_interview_pending": 1,
}
report = execute(filters)
- employee4 = frappe.get_doc('Employee', self.emp4)
- expected_data = [{
- 'employee': employee4.name,
- 'employee_name': employee4.employee_name,
- 'date_of_joining': employee4.date_of_joining,
- 'relieving_date': employee4.relieving_date,
- 'department': employee4.department,
- 'designation': employee4.designation,
- 'reports_to': None,
- 'exit_interview': None,
- 'interview_status': None,
- 'employee_status': None,
- 'questionnaire': None,
- 'full_and_final_statement': None
- }]
-
- self.assertEqual(expected_data, report[1]) # rows
-
- def test_pending_exit_questionnaire_summary(self):
- filters = {
- 'company': 'Test Company',
- 'from_date': getdate(),
- 'to_date': add_days(getdate(), 30),
- 'questionnaire_pending': 1
- }
-
- report = execute(filters)
-
- employee4 = frappe.get_doc('Employee', self.emp4)
- expected_data = [{
- 'employee': employee4.name,
- 'employee_name': employee4.employee_name,
- 'date_of_joining': employee4.date_of_joining,
- 'relieving_date': employee4.relieving_date,
- 'department': employee4.department,
- 'designation': employee4.designation,
- 'reports_to': None,
- 'exit_interview': None,
- 'interview_status': None,
- 'employee_status': None,
- 'questionnaire': None,
- 'full_and_final_statement': None
- }]
-
- self.assertEqual(expected_data, report[1]) # rows
-
-
- def test_pending_fnf_summary(self):
- filters = {
- 'company': 'Test Company',
- 'fnf_pending': 1
- }
-
- report = execute(filters)
-
- employee3 = frappe.get_doc('Employee', self.emp3)
- employee4 = frappe.get_doc('Employee', self.emp4)
+ employee4 = frappe.get_doc("Employee", self.emp4)
expected_data = [
{
- 'employee': employee3.name,
- 'employee_name': employee3.employee_name,
- 'date_of_joining': employee3.date_of_joining,
- 'relieving_date': employee3.relieving_date,
- 'department': employee3.department,
- 'designation': employee3.designation,
- 'reports_to': None,
- 'exit_interview': self.interview3.name,
- 'interview_status': self.interview3.status,
- 'employee_status': '',
- 'questionnaire': employee3.name,
- 'full_and_final_statement': None
- },
- {
- 'employee': employee4.name,
- 'employee_name': employee4.employee_name,
- 'date_of_joining': employee4.date_of_joining,
- 'relieving_date': employee4.relieving_date,
- 'department': employee4.department,
- 'designation': employee4.designation,
- 'reports_to': None,
- 'exit_interview': None,
- 'interview_status': None,
- 'employee_status': None,
- 'questionnaire': None,
- 'full_and_final_statement': None
+ "employee": employee4.name,
+ "employee_name": employee4.employee_name,
+ "date_of_joining": employee4.date_of_joining,
+ "relieving_date": employee4.relieving_date,
+ "department": employee4.department,
+ "designation": employee4.designation,
+ "reports_to": None,
+ "exit_interview": None,
+ "interview_status": None,
+ "employee_status": None,
+ "questionnaire": None,
+ "full_and_final_statement": None,
}
]
- self.assertEqual(expected_data, report[1]) # rows
+ self.assertEqual(expected_data, report[1]) # rows
+
+ def test_pending_exit_questionnaire_summary(self):
+ filters = {
+ "company": "Test Company",
+ "from_date": getdate(),
+ "to_date": add_days(getdate(), 30),
+ "questionnaire_pending": 1,
+ }
+
+ report = execute(filters)
+
+ employee4 = frappe.get_doc("Employee", self.emp4)
+ expected_data = [
+ {
+ "employee": employee4.name,
+ "employee_name": employee4.employee_name,
+ "date_of_joining": employee4.date_of_joining,
+ "relieving_date": employee4.relieving_date,
+ "department": employee4.department,
+ "designation": employee4.designation,
+ "reports_to": None,
+ "exit_interview": None,
+ "interview_status": None,
+ "employee_status": None,
+ "questionnaire": None,
+ "full_and_final_statement": None,
+ }
+ ]
+
+ self.assertEqual(expected_data, report[1]) # rows
+
+ def test_pending_fnf_summary(self):
+ filters = {"company": "Test Company", "fnf_pending": 1}
+
+ report = execute(filters)
+
+ employee3 = frappe.get_doc("Employee", self.emp3)
+ employee4 = frappe.get_doc("Employee", self.emp4)
+ expected_data = [
+ {
+ "employee": employee3.name,
+ "employee_name": employee3.employee_name,
+ "date_of_joining": employee3.date_of_joining,
+ "relieving_date": employee3.relieving_date,
+ "department": employee3.department,
+ "designation": employee3.designation,
+ "reports_to": None,
+ "exit_interview": self.interview3.name,
+ "interview_status": self.interview3.status,
+ "employee_status": "",
+ "questionnaire": employee3.name,
+ "full_and_final_statement": None,
+ },
+ {
+ "employee": employee4.name,
+ "employee_name": employee4.employee_name,
+ "date_of_joining": employee4.date_of_joining,
+ "relieving_date": employee4.relieving_date,
+ "department": employee4.department,
+ "designation": employee4.designation,
+ "reports_to": None,
+ "exit_interview": None,
+ "interview_status": None,
+ "employee_status": None,
+ "questionnaire": None,
+ "full_and_final_statement": None,
+ },
+ ]
+
+ self.assertEqual(expected_data, report[1]) # rows
def create_company():
- if not frappe.db.exists('Company', 'Test Company'):
- frappe.get_doc({
- 'doctype': 'Company',
- 'company_name': 'Test Company',
- 'default_currency': 'INR',
- 'country': 'India'
- }).insert()
\ No newline at end of file
+ if not frappe.db.exists("Company", "Test Company"):
+ frappe.get_doc(
+ {
+ "doctype": "Company",
+ "company_name": "Test Company",
+ "default_currency": "INR",
+ "country": "India",
+ }
+ ).insert()
diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
index 66c1d25..ca352f1 100644
--- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
+++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
@@ -17,7 +17,8 @@
Filters = frappe._dict
-def execute(filters: Optional[Filters] = None) -> Tuple:
+
+def execute(filters: Optional[Filters] = None) -> Tuple:
if filters.to_date <= filters.from_date:
frappe.throw(_('"From Date" can not be greater than or equal to "To Date"'))
@@ -28,91 +29,105 @@
def get_columns() -> List[Dict]:
- return [{
- 'label': _('Leave Type'),
- 'fieldtype': 'Link',
- 'fieldname': 'leave_type',
- 'width': 200,
- 'options': 'Leave Type'
- }, {
- 'label': _('Employee'),
- 'fieldtype': 'Link',
- 'fieldname': 'employee',
- 'width': 100,
- 'options': 'Employee'
- }, {
- 'label': _('Employee Name'),
- 'fieldtype': 'Dynamic Link',
- 'fieldname': 'employee_name',
- 'width': 100,
- 'options': 'employee'
- }, {
- 'label': _('Opening Balance'),
- 'fieldtype': 'float',
- 'fieldname': 'opening_balance',
- 'width': 150,
- }, {
- 'label': _('New Leave(s) Allocated'),
- 'fieldtype': 'float',
- 'fieldname': 'leaves_allocated',
- 'width': 200,
- }, {
- 'label': _('Leave(s) Taken'),
- 'fieldtype': 'float',
- 'fieldname': 'leaves_taken',
- 'width': 150,
- }, {
- 'label': _('Leave(s) Expired'),
- 'fieldtype': 'float',
- 'fieldname': 'leaves_expired',
- 'width': 150,
- }, {
- 'label': _('Closing Balance'),
- 'fieldtype': 'float',
- 'fieldname': 'closing_balance',
- 'width': 150,
- }]
+ return [
+ {
+ "label": _("Leave Type"),
+ "fieldtype": "Link",
+ "fieldname": "leave_type",
+ "width": 200,
+ "options": "Leave Type",
+ },
+ {
+ "label": _("Employee"),
+ "fieldtype": "Link",
+ "fieldname": "employee",
+ "width": 100,
+ "options": "Employee",
+ },
+ {
+ "label": _("Employee Name"),
+ "fieldtype": "Dynamic Link",
+ "fieldname": "employee_name",
+ "width": 100,
+ "options": "employee",
+ },
+ {
+ "label": _("Opening Balance"),
+ "fieldtype": "float",
+ "fieldname": "opening_balance",
+ "width": 150,
+ },
+ {
+ "label": _("New Leave(s) Allocated"),
+ "fieldtype": "float",
+ "fieldname": "leaves_allocated",
+ "width": 200,
+ },
+ {
+ "label": _("Leave(s) Taken"),
+ "fieldtype": "float",
+ "fieldname": "leaves_taken",
+ "width": 150,
+ },
+ {
+ "label": _("Leave(s) Expired"),
+ "fieldtype": "float",
+ "fieldname": "leaves_expired",
+ "width": 150,
+ },
+ {
+ "label": _("Closing Balance"),
+ "fieldtype": "float",
+ "fieldname": "closing_balance",
+ "width": 150,
+ },
+ ]
def get_data(filters: Filters) -> List:
- leave_types = frappe.db.get_list('Leave Type', pluck='name', order_by='name')
+ leave_types = frappe.db.get_list("Leave Type", pluck="name", order_by="name")
conditions = get_conditions(filters)
user = frappe.session.user
- department_approver_map = get_department_leave_approver_map(filters.get('department'))
+ department_approver_map = get_department_leave_approver_map(filters.get("department"))
- active_employees = frappe.get_list('Employee',
+ active_employees = frappe.get_list(
+ "Employee",
filters=conditions,
- fields=['name', 'employee_name', 'department', 'user_id', 'leave_approver'])
+ fields=["name", "employee_name", "department", "user_id", "leave_approver"],
+ )
data = []
for leave_type in leave_types:
if len(active_employees) > 1:
- data.append({
- 'leave_type': leave_type
- })
+ data.append({"leave_type": leave_type})
else:
- row = frappe._dict({
- 'leave_type': leave_type
- })
+ row = frappe._dict({"leave_type": leave_type})
for employee in active_employees:
- leave_approvers = department_approver_map.get(employee.department_name, []).append(employee.leave_approver)
+ leave_approvers = department_approver_map.get(employee.department_name, []).append(
+ employee.leave_approver
+ )
- if (leave_approvers and len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) \
- or ("HR Manager" in frappe.get_roles(user)):
+ if (
+ (leave_approvers and len(leave_approvers) and user in leave_approvers)
+ or (user in ["Administrator", employee.user_id])
+ or ("HR Manager" in frappe.get_roles(user))
+ ):
if len(active_employees) > 1:
row = frappe._dict()
row.employee = employee.name
row.employee_name = employee.employee_name
- leaves_taken = get_leaves_for_period(employee.name, leave_type,
- filters.from_date, filters.to_date) * -1
+ leaves_taken = (
+ get_leaves_for_period(employee.name, leave_type, filters.from_date, filters.to_date) * -1
+ )
new_allocation, expired_leaves, carry_forwarded_leaves = get_allocated_and_expired_leaves(
- filters.from_date, filters.to_date, employee.name, leave_type)
+ filters.from_date, filters.to_date, employee.name, leave_type
+ )
opening = get_opening_balance(employee.name, leave_type, filters, carry_forwarded_leaves)
row.leaves_allocated = new_allocation
@@ -121,21 +136,27 @@
row.leaves_taken = leaves_taken
# not be shown on the basis of days left it create in user mind for carry_forward leave
- row.closing_balance = (new_allocation + opening - (row.leaves_expired + leaves_taken))
+ row.closing_balance = new_allocation + opening - (row.leaves_expired + leaves_taken)
row.indent = 1
data.append(row)
return data
-def get_opening_balance(employee: str, leave_type: str, filters: Filters, carry_forwarded_leaves: float) -> float:
+def get_opening_balance(
+ employee: str, leave_type: str, filters: Filters, carry_forwarded_leaves: float
+) -> float:
# allocation boundary condition
# opening balance is the closing leave balance 1 day before the filter start date
opening_balance_date = add_days(filters.from_date, -1)
allocation = get_previous_allocation(filters.from_date, leave_type, employee)
- if allocation and allocation.get("to_date") and opening_balance_date and \
- getdate(allocation.get("to_date")) == getdate(opening_balance_date):
+ if (
+ allocation
+ and allocation.get("to_date")
+ and opening_balance_date
+ and getdate(allocation.get("to_date")) == getdate(opening_balance_date)
+ ):
# if opening balance date is same as the previous allocation's expiry
# then opening balance should only consider carry forwarded leaves
opening_balance = carry_forwarded_leaves
@@ -147,39 +168,35 @@
def get_conditions(filters: Filters) -> Dict:
- conditions={
- 'status': 'Active',
+ conditions = {
+ "status": "Active",
}
- if filters.get('employee'):
- conditions['name'] = filters.get('employee')
+ if filters.get("employee"):
+ conditions["name"] = filters.get("employee")
- if filters.get('company'):
- conditions['company'] = filters.get('company')
+ if filters.get("company"):
+ conditions["company"] = filters.get("company")
- if filters.get('department'):
- conditions['department'] = filters.get('department')
+ if filters.get("department"):
+ conditions["department"] = filters.get("department")
return conditions
def get_department_leave_approver_map(department: Optional[str] = None):
# get current department and all its child
- department_list = frappe.get_list('Department',
- filters={'disabled': 0},
- or_filters={
- 'name': department,
- 'parent_department': department
- },
- pluck='name'
+ department_list = frappe.get_list(
+ "Department",
+ filters={"disabled": 0},
+ or_filters={"name": department, "parent_department": department},
+ pluck="name",
)
# retrieve approvers list from current department and from its subsequent child departments
- approver_list = frappe.get_all('Department Approver',
- filters={
- 'parentfield': 'leave_approvers',
- 'parent': ('in', department_list)
- },
- fields=['parent', 'approver'],
- as_list=True
+ approver_list = frappe.get_all(
+ "Department Approver",
+ filters={"parentfield": "leave_approvers", "parent": ("in", department_list)},
+ fields=["parent", "approver"],
+ as_list=True,
)
approvers = {}
@@ -190,7 +207,9 @@
return approvers
-def get_allocated_and_expired_leaves(from_date: str, to_date: str, employee: str, leave_type: str) -> Tuple[float, float, float]:
+def get_allocated_and_expired_leaves(
+ from_date: str, to_date: str, employee: str, leave_type: str
+) -> Tuple[float, float, float]:
new_allocation = 0
expired_leaves = 0
carry_forwarded_leaves = 0
@@ -207,8 +226,7 @@
# leave allocations ending before to_date, reduce leaves taken within that period
# since they are already used, they won't expire
expired_leaves += record.leaves
- expired_leaves += get_leaves_for_period(employee, leave_type,
- record.from_date, record.to_date)
+ expired_leaves += get_leaves_for_period(employee, leave_type, record.from_date, record.to_date)
if record.from_date >= getdate(from_date):
if record.is_carry_forward:
@@ -219,22 +237,31 @@
return new_allocation, expired_leaves, carry_forwarded_leaves
-def get_leave_ledger_entries(from_date: str, to_date: str, employee: str, leave_type: str) -> List[Dict]:
- ledger = frappe.qb.DocType('Leave Ledger Entry')
+def get_leave_ledger_entries(
+ from_date: str, to_date: str, employee: str, leave_type: str
+) -> List[Dict]:
+ ledger = frappe.qb.DocType("Leave Ledger Entry")
records = (
frappe.qb.from_(ledger)
.select(
- ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date,
- ledger.leaves, ledger.transaction_name, ledger.transaction_type,
- ledger.is_carry_forward, ledger.is_expired
- ).where(
+ ledger.employee,
+ ledger.leave_type,
+ ledger.from_date,
+ ledger.to_date,
+ ledger.leaves,
+ ledger.transaction_name,
+ ledger.transaction_type,
+ ledger.is_carry_forward,
+ ledger.is_expired,
+ )
+ .where(
(ledger.docstatus == 1)
- & (ledger.transaction_type == 'Leave Allocation')
+ & (ledger.transaction_type == "Leave Allocation")
& (ledger.employee == employee)
& (ledger.leave_type == leave_type)
& (
- (ledger.from_date[from_date: to_date])
- | (ledger.to_date[from_date: to_date])
+ (ledger.from_date[from_date:to_date])
+ | (ledger.to_date[from_date:to_date])
| ((ledger.from_date < from_date) & (ledger.to_date > to_date))
)
)
@@ -248,16 +275,13 @@
datasets = []
employee_data = data
- if data and data[0].get('employee_name'):
+ if data and data[0].get("employee_name"):
get_dataset_for_chart(employee_data, datasets, labels)
chart = {
- 'data': {
- 'labels': labels,
- 'datasets': datasets
- },
- 'type': 'bar',
- 'colors': ['#456789', '#EE8888', '#7E77BF']
+ "data": {"labels": labels, "datasets": datasets},
+ "type": "bar",
+ "colors": ["#456789", "#EE8888", "#7E77BF"],
}
return chart
@@ -265,18 +289,17 @@
def get_dataset_for_chart(employee_data: List, datasets: List, labels: List) -> List:
leaves = []
- employee_data = sorted(employee_data, key=lambda k: k['employee_name'])
+ employee_data = sorted(employee_data, key=lambda k: k["employee_name"])
- for key, group in groupby(employee_data, lambda x: x['employee_name']):
+ for key, group in groupby(employee_data, lambda x: x["employee_name"]):
for grp in group:
if grp.closing_balance:
- leaves.append(frappe._dict({
- 'leave_type': grp.leave_type,
- 'closing_balance': grp.closing_balance
- }))
+ leaves.append(
+ frappe._dict({"leave_type": grp.leave_type, "closing_balance": grp.closing_balance})
+ )
if leaves:
labels.append(key)
for leave in leaves:
- datasets.append({'name': leave.leave_type, 'values': [leave.closing_balance]})
+ datasets.append({"name": leave.leave_type, "values": [leave.closing_balance]})
diff --git a/erpnext/hr/report/employee_leave_balance/test_employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/test_employee_leave_balance.py
index b2ed72c..dc0f4d2 100644
--- a/erpnext/hr/report/employee_leave_balance/test_employee_leave_balance.py
+++ b/erpnext/hr/report/employee_leave_balance/test_employee_leave_balance.py
@@ -21,141 +21,189 @@
make_leave_application,
)
-test_records = frappe.get_test_records('Leave Type')
+test_records = frappe.get_test_records("Leave Type")
+
class TestEmployeeLeaveBalance(unittest.TestCase):
def setUp(self):
- for dt in ['Leave Application', 'Leave Allocation', 'Salary Slip', 'Leave Ledger Entry', 'Leave Type']:
+ for dt in [
+ "Leave Application",
+ "Leave Allocation",
+ "Salary Slip",
+ "Leave Ledger Entry",
+ "Leave Type",
+ ]:
frappe.db.delete(dt)
- frappe.set_user('Administrator')
+ frappe.set_user("Administrator")
- self.employee_id = make_employee('test_emp_leave_balance@example.com', company='_Test Company')
+ self.employee_id = make_employee("test_emp_leave_balance@example.com", company="_Test Company")
self.date = getdate()
self.year_start = getdate(get_year_start(self.date))
self.mid_year = add_months(self.year_start, 6)
self.year_end = getdate(get_year_ending(self.date))
- self.holiday_list = make_holiday_list('_Test Emp Balance Holiday List', self.year_start, self.year_end)
+ self.holiday_list = make_holiday_list(
+ "_Test Emp Balance Holiday List", self.year_start, self.year_end
+ )
def tearDown(self):
frappe.db.rollback()
- @set_holiday_list('_Test Emp Balance Holiday List', '_Test Company')
+ @set_holiday_list("_Test Emp Balance Holiday List", "_Test Company")
def test_employee_leave_balance(self):
frappe.get_doc(test_records[0]).insert()
# 5 leaves
- allocation1 = make_allocation_record(employee=self.employee_id, from_date=add_days(self.year_start, -11),
- to_date=add_days(self.year_start, -1), leaves=5)
+ allocation1 = make_allocation_record(
+ employee=self.employee_id,
+ from_date=add_days(self.year_start, -11),
+ to_date=add_days(self.year_start, -1),
+ leaves=5,
+ )
# 30 leaves
- allocation2 = make_allocation_record(employee=self.employee_id, from_date=self.year_start, to_date=self.year_end)
+ allocation2 = make_allocation_record(
+ employee=self.employee_id, from_date=self.year_start, to_date=self.year_end
+ )
# expires 5 leaves
process_expired_allocation()
# 4 days leave
first_sunday = get_first_sunday(self.holiday_list, for_date=self.year_start)
- leave_application = make_leave_application(self.employee_id, add_days(first_sunday, 1), add_days(first_sunday, 4), '_Test Leave Type')
+ leave_application = make_leave_application(
+ self.employee_id, add_days(first_sunday, 1), add_days(first_sunday, 4), "_Test Leave Type"
+ )
leave_application.reload()
- filters = frappe._dict({
- 'from_date': allocation1.from_date,
- 'to_date': allocation2.to_date,
- 'employee': self.employee_id
- })
+ filters = frappe._dict(
+ {
+ "from_date": allocation1.from_date,
+ "to_date": allocation2.to_date,
+ "employee": self.employee_id,
+ }
+ )
report = execute(filters)
- expected_data = [{
- 'leave_type': '_Test Leave Type',
- 'employee': self.employee_id,
- 'employee_name': 'test_emp_leave_balance@example.com',
- 'leaves_allocated': flt(allocation1.new_leaves_allocated + allocation2.new_leaves_allocated),
- 'leaves_expired': flt(allocation1.new_leaves_allocated),
- 'opening_balance': flt(0),
- 'leaves_taken': flt(leave_application.total_leave_days),
- 'closing_balance': flt(allocation2.new_leaves_allocated - leave_application.total_leave_days),
- 'indent': 1
- }]
+ expected_data = [
+ {
+ "leave_type": "_Test Leave Type",
+ "employee": self.employee_id,
+ "employee_name": "test_emp_leave_balance@example.com",
+ "leaves_allocated": flt(allocation1.new_leaves_allocated + allocation2.new_leaves_allocated),
+ "leaves_expired": flt(allocation1.new_leaves_allocated),
+ "opening_balance": flt(0),
+ "leaves_taken": flt(leave_application.total_leave_days),
+ "closing_balance": flt(allocation2.new_leaves_allocated - leave_application.total_leave_days),
+ "indent": 1,
+ }
+ ]
self.assertEqual(report[1], expected_data)
- @set_holiday_list('_Test Emp Balance Holiday List', '_Test Company')
+ @set_holiday_list("_Test Emp Balance Holiday List", "_Test Company")
def test_opening_balance_on_alloc_boundary_dates(self):
frappe.get_doc(test_records[0]).insert()
# 30 leaves allocated
- allocation1 = make_allocation_record(employee=self.employee_id, from_date=self.year_start, to_date=self.year_end)
+ allocation1 = make_allocation_record(
+ employee=self.employee_id, from_date=self.year_start, to_date=self.year_end
+ )
# 4 days leave application in the first allocation
first_sunday = get_first_sunday(self.holiday_list, for_date=self.year_start)
- leave_application = make_leave_application(self.employee_id, add_days(first_sunday, 1), add_days(first_sunday, 4), '_Test Leave Type')
+ leave_application = make_leave_application(
+ self.employee_id, add_days(first_sunday, 1), add_days(first_sunday, 4), "_Test Leave Type"
+ )
leave_application.reload()
# Case 1: opening balance for first alloc boundary
- filters = frappe._dict({
- 'from_date': self.year_start,
- 'to_date': self.year_end,
- 'employee': self.employee_id
- })
+ filters = frappe._dict(
+ {"from_date": self.year_start, "to_date": self.year_end, "employee": self.employee_id}
+ )
report = execute(filters)
self.assertEqual(report[1][0].opening_balance, 0)
# Case 2: opening balance after leave application date
- filters = frappe._dict({
- 'from_date': add_days(leave_application.to_date, 1),
- 'to_date': self.year_end,
- 'employee': self.employee_id
- })
+ filters = frappe._dict(
+ {
+ "from_date": add_days(leave_application.to_date, 1),
+ "to_date": self.year_end,
+ "employee": self.employee_id,
+ }
+ )
report = execute(filters)
- self.assertEqual(report[1][0].opening_balance, (allocation1.new_leaves_allocated - leave_application.total_leave_days))
+ self.assertEqual(
+ report[1][0].opening_balance,
+ (allocation1.new_leaves_allocated - leave_application.total_leave_days),
+ )
# Case 3: leave balance shows actual balance and not consumption balance as per remaining days near alloc end date
# eg: 3 days left for alloc to end, leave balance should still be 26 and not 3
- filters = frappe._dict({
- 'from_date': add_days(self.year_end, -3),
- 'to_date': self.year_end,
- 'employee': self.employee_id
- })
+ filters = frappe._dict(
+ {
+ "from_date": add_days(self.year_end, -3),
+ "to_date": self.year_end,
+ "employee": self.employee_id,
+ }
+ )
report = execute(filters)
- self.assertEqual(report[1][0].opening_balance, (allocation1.new_leaves_allocated - leave_application.total_leave_days))
+ self.assertEqual(
+ report[1][0].opening_balance,
+ (allocation1.new_leaves_allocated - leave_application.total_leave_days),
+ )
- @set_holiday_list('_Test Emp Balance Holiday List', '_Test Company')
+ @set_holiday_list("_Test Emp Balance Holiday List", "_Test Company")
def test_opening_balance_considers_carry_forwarded_leaves(self):
- leave_type = create_leave_type(
- leave_type_name="_Test_CF_leave_expiry",
- is_carry_forward=1)
+ leave_type = create_leave_type(leave_type_name="_Test_CF_leave_expiry", is_carry_forward=1)
leave_type.insert()
# 30 leaves allocated for first half of the year
- allocation1 = make_allocation_record(employee=self.employee_id, from_date=self.year_start,
- to_date=self.mid_year, leave_type=leave_type.name)
+ allocation1 = make_allocation_record(
+ employee=self.employee_id,
+ from_date=self.year_start,
+ to_date=self.mid_year,
+ leave_type=leave_type.name,
+ )
# 4 days leave application in the first allocation
first_sunday = get_first_sunday(self.holiday_list, for_date=self.year_start)
- leave_application = make_leave_application(self.employee_id, first_sunday, add_days(first_sunday, 3), leave_type.name)
+ leave_application = make_leave_application(
+ self.employee_id, first_sunday, add_days(first_sunday, 3), leave_type.name
+ )
leave_application.reload()
# 30 leaves allocated for second half of the year + carry forward leaves (26) from the previous allocation
- allocation2 = make_allocation_record(employee=self.employee_id, from_date=add_days(self.mid_year, 1), to_date=self.year_end,
- carry_forward=True, leave_type=leave_type.name)
+ allocation2 = make_allocation_record(
+ employee=self.employee_id,
+ from_date=add_days(self.mid_year, 1),
+ to_date=self.year_end,
+ carry_forward=True,
+ leave_type=leave_type.name,
+ )
# Case 1: carry forwarded leaves considered in opening balance for second alloc
- filters = frappe._dict({
- 'from_date': add_days(self.mid_year, 1),
- 'to_date': self.year_end,
- 'employee': self.employee_id
- })
+ filters = frappe._dict(
+ {
+ "from_date": add_days(self.mid_year, 1),
+ "to_date": self.year_end,
+ "employee": self.employee_id,
+ }
+ )
report = execute(filters)
# available leaves from old alloc
opening_balance = allocation1.new_leaves_allocated - leave_application.total_leave_days
self.assertEqual(report[1][0].opening_balance, opening_balance)
# Case 2: opening balance one day after alloc boundary = carry forwarded leaves + new leaves alloc
- filters = frappe._dict({
- 'from_date': add_days(self.mid_year, 2),
- 'to_date': self.year_end,
- 'employee': self.employee_id
- })
+ filters = frappe._dict(
+ {
+ "from_date": add_days(self.mid_year, 2),
+ "to_date": self.year_end,
+ "employee": self.employee_id,
+ }
+ )
report = execute(filters)
# available leaves from old alloc
- opening_balance = allocation2.new_leaves_allocated + (allocation1.new_leaves_allocated - leave_application.total_leave_days)
+ opening_balance = allocation2.new_leaves_allocated + (
+ allocation1.new_leaves_allocated - leave_application.total_leave_days
+ )
self.assertEqual(report[1][0].opening_balance, opening_balance)
diff --git a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
index 71c18bb..2a16dc4 100644
--- a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
+++ b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py
@@ -19,11 +19,12 @@
return columns, data
+
def get_columns(leave_types):
columns = [
_("Employee") + ":Link.Employee:150",
_("Employee Name") + "::200",
- _("Department") +"::150"
+ _("Department") + "::150",
]
for leave_type in leave_types:
@@ -31,6 +32,7 @@
return columns
+
def get_conditions(filters):
conditions = {
"status": "Active",
@@ -43,15 +45,18 @@
return conditions
+
def get_data(filters, leave_types):
user = frappe.session.user
conditions = get_conditions(filters)
- active_employees = frappe.get_list("Employee",
+ active_employees = frappe.get_list(
+ "Employee",
filters=conditions,
- fields=["name", "employee_name", "department", "user_id", "leave_approver"])
+ fields=["name", "employee_name", "department", "user_id", "leave_approver"],
+ )
- department_approver_map = get_department_leave_approver_map(filters.get('department'))
+ department_approver_map = get_department_leave_approver_map(filters.get("department"))
data = []
for employee in active_employees:
@@ -59,14 +64,18 @@
if employee.leave_approver:
leave_approvers.append(employee.leave_approver)
- if (len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) or ("HR Manager" in frappe.get_roles(user)):
+ if (
+ (len(leave_approvers) and user in leave_approvers)
+ or (user in ["Administrator", employee.user_id])
+ or ("HR Manager" in frappe.get_roles(user))
+ ):
row = [employee.name, employee.employee_name, employee.department]
available_leave = get_leave_details(employee.name, filters.date)
for leave_type in leave_types:
remaining = 0
if leave_type in available_leave["leave_allocation"]:
# opening balance
- remaining = available_leave["leave_allocation"][leave_type]['remaining_leaves']
+ remaining = available_leave["leave_allocation"][leave_type]["remaining_leaves"]
row += [remaining]
diff --git a/erpnext/hr/report/employee_leave_balance_summary/test_employee_leave_balance_summary.py b/erpnext/hr/report/employee_leave_balance_summary/test_employee_leave_balance_summary.py
index 6f16a8d..34b665f 100644
--- a/erpnext/hr/report/employee_leave_balance_summary/test_employee_leave_balance_summary.py
+++ b/erpnext/hr/report/employee_leave_balance_summary/test_employee_leave_balance_summary.py
@@ -20,40 +20,59 @@
make_leave_application,
)
-test_records = frappe.get_test_records('Leave Type')
+test_records = frappe.get_test_records("Leave Type")
+
class TestEmployeeLeaveBalance(unittest.TestCase):
def setUp(self):
- for dt in ['Leave Application', 'Leave Allocation', 'Salary Slip', 'Leave Ledger Entry', 'Leave Type']:
+ for dt in [
+ "Leave Application",
+ "Leave Allocation",
+ "Salary Slip",
+ "Leave Ledger Entry",
+ "Leave Type",
+ ]:
frappe.db.delete(dt)
- frappe.set_user('Administrator')
+ frappe.set_user("Administrator")
- self.employee_id = make_employee('test_emp_leave_balance@example.com', company='_Test Company')
- self.employee_id = make_employee('test_emp_leave_balance@example.com', company='_Test Company')
+ self.employee_id = make_employee("test_emp_leave_balance@example.com", company="_Test Company")
+ self.employee_id = make_employee("test_emp_leave_balance@example.com", company="_Test Company")
self.date = getdate()
self.year_start = getdate(get_year_start(self.date))
self.year_end = getdate(get_year_ending(self.date))
- self.holiday_list = make_holiday_list('_Test Emp Balance Holiday List', self.year_start, self.year_end)
+ self.holiday_list = make_holiday_list(
+ "_Test Emp Balance Holiday List", self.year_start, self.year_end
+ )
def tearDown(self):
frappe.db.rollback()
- @set_holiday_list('_Test Emp Balance Holiday List', '_Test Company')
+ @set_holiday_list("_Test Emp Balance Holiday List", "_Test Company")
def test_employee_leave_balance_summary(self):
frappe.get_doc(test_records[0]).insert()
# 5 leaves
- allocation1 = make_allocation_record(employee=self.employee_id, from_date=add_days(self.year_start, -11),
- to_date=add_days(self.year_start, -1), leaves=5)
+ allocation1 = make_allocation_record(
+ employee=self.employee_id,
+ from_date=add_days(self.year_start, -11),
+ to_date=add_days(self.year_start, -1),
+ leaves=5,
+ )
# 30 leaves
- allocation2 = make_allocation_record(employee=self.employee_id, from_date=self.year_start, to_date=self.year_end)
+ allocation2 = make_allocation_record(
+ employee=self.employee_id, from_date=self.year_start, to_date=self.year_end
+ )
# 2 days leave within the first allocation
- leave_application1 = make_leave_application(self.employee_id, add_days(self.year_start, -11), add_days(self.year_start, -10),
- '_Test Leave Type')
+ leave_application1 = make_leave_application(
+ self.employee_id,
+ add_days(self.year_start, -11),
+ add_days(self.year_start, -10),
+ "_Test Leave Type",
+ )
leave_application1.reload()
# expires 3 leaves
@@ -61,57 +80,69 @@
# 4 days leave within the second allocation
first_sunday = get_first_sunday(self.holiday_list, for_date=self.year_start)
- leave_application2 = make_leave_application(self.employee_id, add_days(first_sunday, 1), add_days(first_sunday, 4), '_Test Leave Type')
+ leave_application2 = make_leave_application(
+ self.employee_id, add_days(first_sunday, 1), add_days(first_sunday, 4), "_Test Leave Type"
+ )
leave_application2.reload()
- filters = frappe._dict({
- 'date': add_days(leave_application2.to_date, 1),
- 'company': '_Test Company',
- 'employee': self.employee_id
- })
+ filters = frappe._dict(
+ {
+ "date": add_days(leave_application2.to_date, 1),
+ "company": "_Test Company",
+ "employee": self.employee_id,
+ }
+ )
report = execute(filters)
- expected_data = [[
- self.employee_id,
- 'test_emp_leave_balance@example.com',
- frappe.db.get_value('Employee', self.employee_id, 'department'),
- flt(
- allocation1.new_leaves_allocated # allocated = 5
- + allocation2.new_leaves_allocated # allocated = 30
- - leave_application1.total_leave_days # leaves taken in the 1st alloc = 2
- - (allocation1.new_leaves_allocated - leave_application1.total_leave_days) # leaves expired from 1st alloc = 3
- - leave_application2.total_leave_days # leaves taken in the 2nd alloc = 4
- )
- ]]
+ expected_data = [
+ [
+ self.employee_id,
+ "test_emp_leave_balance@example.com",
+ frappe.db.get_value("Employee", self.employee_id, "department"),
+ flt(
+ allocation1.new_leaves_allocated # allocated = 5
+ + allocation2.new_leaves_allocated # allocated = 30
+ - leave_application1.total_leave_days # leaves taken in the 1st alloc = 2
+ - (
+ allocation1.new_leaves_allocated - leave_application1.total_leave_days
+ ) # leaves expired from 1st alloc = 3
+ - leave_application2.total_leave_days # leaves taken in the 2nd alloc = 4
+ ),
+ ]
+ ]
self.assertEqual(report[1], expected_data)
- @set_holiday_list('_Test Emp Balance Holiday List', '_Test Company')
+ @set_holiday_list("_Test Emp Balance Holiday List", "_Test Company")
def test_get_leave_balance_near_alloc_expiry(self):
frappe.get_doc(test_records[0]).insert()
# 30 leaves allocated
- allocation = make_allocation_record(employee=self.employee_id, from_date=self.year_start, to_date=self.year_end)
+ allocation = make_allocation_record(
+ employee=self.employee_id, from_date=self.year_start, to_date=self.year_end
+ )
# 4 days leave application in the first allocation
first_sunday = get_first_sunday(self.holiday_list, for_date=self.year_start)
- leave_application = make_leave_application(self.employee_id, add_days(first_sunday, 1), add_days(first_sunday, 4), '_Test Leave Type')
+ leave_application = make_leave_application(
+ self.employee_id, add_days(first_sunday, 1), add_days(first_sunday, 4), "_Test Leave Type"
+ )
leave_application.reload()
# Leave balance should show actual balance, and not "consumption balance as per remaining days", near alloc end date
# eg: 3 days left for alloc to end, leave balance should still be 26 and not 3
- filters = frappe._dict({
- 'date': add_days(self.year_end, -3),
- 'company': '_Test Company',
- 'employee': self.employee_id
- })
+ filters = frappe._dict(
+ {"date": add_days(self.year_end, -3), "company": "_Test Company", "employee": self.employee_id}
+ )
report = execute(filters)
- expected_data = [[
- self.employee_id,
- 'test_emp_leave_balance@example.com',
- frappe.db.get_value('Employee', self.employee_id, 'department'),
- flt(allocation.new_leaves_allocated - leave_application.total_leave_days)
- ]]
+ expected_data = [
+ [
+ self.employee_id,
+ "test_emp_leave_balance@example.com",
+ frappe.db.get_value("Employee", self.employee_id, "department"),
+ flt(allocation.new_leaves_allocated - leave_application.total_leave_days),
+ ]
+ ]
self.assertEqual(report[1], expected_data)
diff --git a/erpnext/hr/report/employees_working_on_a_holiday/employees_working_on_a_holiday.py b/erpnext/hr/report/employees_working_on_a_holiday/employees_working_on_a_holiday.py
index 00a4a7c..f13fabf 100644
--- a/erpnext/hr/report/employees_working_on_a_holiday/employees_working_on_a_holiday.py
+++ b/erpnext/hr/report/employees_working_on_a_holiday/employees_working_on_a_holiday.py
@@ -21,16 +21,21 @@
_("Name") + ":Data:200",
_("Date") + ":Date:100",
_("Status") + ":Data:70",
- _("Holiday") + ":Data:200"
+ _("Holiday") + ":Data:200",
]
+
def get_employees(filters):
- holiday_filter = [["holiday_date", ">=", filters.from_date], ["holiday_date", "<=", filters.to_date]]
+ holiday_filter = [
+ ["holiday_date", ">=", filters.from_date],
+ ["holiday_date", "<=", filters.to_date],
+ ]
if filters.holiday_list:
holiday_filter.append(["parent", "=", filters.holiday_list])
- holidays = frappe.get_all("Holiday", fields=["holiday_date", "description"],
- filters=holiday_filter)
+ holidays = frappe.get_all(
+ "Holiday", fields=["holiday_date", "description"], filters=holiday_filter
+ )
holiday_names = {}
holidays_list = []
@@ -39,18 +44,23 @@
holidays_list.append(holiday.holiday_date)
holiday_names[holiday.holiday_date] = holiday.description
- if(holidays_list):
+ if holidays_list:
cond = " attendance_date in %(holidays_list)s"
if filters.holiday_list:
- cond += """ and (employee in (select employee from tabEmployee where holiday_list = %(holidays)s))"""
+ cond += (
+ """ and (employee in (select employee from tabEmployee where holiday_list = %(holidays)s))"""
+ )
- employee_list = frappe.db.sql("""select
+ employee_list = frappe.db.sql(
+ """select
employee, employee_name, attendance_date, status
from tabAttendance
- where %s"""% cond.format(', '.join(["%s"] * len(holidays_list))),
- {'holidays_list':holidays_list,
- 'holidays':filters.holiday_list}, as_list=True)
+ where %s"""
+ % cond.format(", ".join(["%s"] * len(holidays_list))),
+ {"holidays_list": holidays_list, "holidays": filters.holiday_list},
+ as_list=True,
+ )
for employee_data in employee_list:
employee_data.append(holiday_names[employee_data[2]])
diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
index 9a993e5..8ea4989 100644
--- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
+++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py
@@ -15,21 +15,15 @@
"Weekly Off": "<b>WO</b>",
"On Leave": "L",
"Present": "P",
- "Work From Home": "WFH"
- }
+ "Work From Home": "WFH",
+}
-day_abbr = [
- "Mon",
- "Tue",
- "Wed",
- "Thu",
- "Fri",
- "Sat",
- "Sun"
-]
+day_abbr = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
+
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
if filters.hide_year_field == 1:
filters.year = 2020
@@ -44,14 +38,19 @@
emp_map, group_by_parameters = get_employee_details(filters.group_by, filters.company)
holiday_list = []
for parameter in group_by_parameters:
- h_list = [emp_map[parameter][d]["holiday_list"] for d in emp_map[parameter] if emp_map[parameter][d]["holiday_list"]]
+ h_list = [
+ emp_map[parameter][d]["holiday_list"]
+ for d in emp_map[parameter]
+ if emp_map[parameter][d]["holiday_list"]
+ ]
holiday_list += h_list
else:
emp_map = get_employee_details(filters.group_by, filters.company)
holiday_list = [emp_map[d]["holiday_list"] for d in emp_map if emp_map[d]["holiday_list"]]
-
- default_holiday_list = frappe.get_cached_value('Company', filters.get("company"), "default_holiday_list")
+ default_holiday_list = frappe.get_cached_value(
+ "Company", filters.get("company"), "default_holiday_list"
+ )
holiday_list.append(default_holiday_list)
holiday_list = list(set(holiday_list))
holiday_map = get_holiday(holiday_list, filters["month"])
@@ -70,20 +69,39 @@
for parameter in group_by_parameters:
emp_map_set = set([key for key in emp_map[parameter].keys()])
att_map_set = set([key for key in att_map.keys()])
- if (att_map_set & emp_map_set):
- parameter_row = ["<b>"+ parameter + "</b>"] + ['' for day in range(filters["total_days_in_month"] + 2)]
+ if att_map_set & emp_map_set:
+ parameter_row = ["<b>" + parameter + "</b>"] + [
+ "" for day in range(filters["total_days_in_month"] + 2)
+ ]
data.append(parameter_row)
- record, emp_att_data = add_data(emp_map[parameter], att_map, filters, holiday_map, conditions, default_holiday_list, leave_types=leave_types)
+ record, emp_att_data = add_data(
+ emp_map[parameter],
+ att_map,
+ filters,
+ holiday_map,
+ conditions,
+ default_holiday_list,
+ leave_types=leave_types,
+ )
emp_att_map.update(emp_att_data)
data += record
else:
- record, emp_att_map = add_data(emp_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_types=leave_types)
+ record, emp_att_map = add_data(
+ emp_map,
+ att_map,
+ filters,
+ holiday_map,
+ conditions,
+ default_holiday_list,
+ leave_types=leave_types,
+ )
data += record
chart_data = get_chart_data(emp_att_map, days)
return columns, data, None, chart_data
+
def get_chart_data(emp_att_map, days):
labels = []
datasets = [
@@ -110,24 +128,20 @@
if emp_att_map[emp][idx] == "L":
total_leave_on_day += 1
-
datasets[0]["values"].append(total_absent_on_day)
datasets[1]["values"].append(total_present_on_day)
datasets[2]["values"].append(total_leave_on_day)
-
- chart = {
- "data": {
- 'labels': labels,
- 'datasets': datasets
- }
- }
+ chart = {"data": {"labels": labels, "datasets": datasets}}
chart["type"] = "line"
return chart
-def add_data(employee_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_types=None):
+
+def add_data(
+ employee_map, att_map, filters, holiday_map, conditions, default_holiday_list, leave_types=None
+):
record = []
emp_att_map = {}
@@ -141,7 +155,7 @@
row += [" "]
row += [emp, emp_det.employee_name]
- total_p = total_a = total_l = total_h = total_um= 0.0
+ total_p = total_a = total_l = total_h = total_um = 0.0
emp_status_map = []
for day in range(filters["total_days_in_month"]):
status = None
@@ -152,7 +166,7 @@
if emp_holiday_list in holiday_map:
for idx, ele in enumerate(holiday_map[emp_holiday_list]):
- if day+1 == holiday_map[emp_holiday_list][idx][0]:
+ if day + 1 == holiday_map[emp_holiday_list][idx][0]:
if holiday_map[emp_holiday_list][idx][1]:
status = "Weekly Off"
else:
@@ -162,7 +176,7 @@
abbr = status_map.get(status, "")
emp_status_map.append(abbr)
- if filters.summarized_view:
+ if filters.summarized_view:
if status == "Present" or status == "Work From Home":
total_p += 1
elif status == "Absent":
@@ -189,12 +203,21 @@
filters.update({"employee": emp})
if filters.summarized_view:
- leave_details = frappe.db.sql("""select leave_type, status, count(*) as count from `tabAttendance`\
- where leave_type is not NULL %s group by leave_type, status""" % conditions, filters, as_dict=1)
+ leave_details = frappe.db.sql(
+ """select leave_type, status, count(*) as count from `tabAttendance`\
+ where leave_type is not NULL %s group by leave_type, status"""
+ % conditions,
+ filters,
+ as_dict=1,
+ )
- time_default_counts = frappe.db.sql("""select (select count(*) from `tabAttendance` where \
+ time_default_counts = frappe.db.sql(
+ """select (select count(*) from `tabAttendance` where \
late_entry = 1 %s) as late_entry_count, (select count(*) from tabAttendance where \
- early_exit = 1 %s) as early_exit_count""" % (conditions, conditions), filters)
+ early_exit = 1 %s) as early_exit_count"""
+ % (conditions, conditions),
+ filters,
+ )
leaves = {}
for d in leave_details:
@@ -211,38 +234,48 @@
else:
row.append("0.0")
- row.extend([time_default_counts[0][0],time_default_counts[0][1]])
+ row.extend([time_default_counts[0][0], time_default_counts[0][1]])
emp_att_map[emp] = emp_status_map
record.append(row)
return record, emp_att_map
+
def get_columns(filters):
columns = []
if filters.group_by:
- columns = [_(filters.group_by)+ ":Link/Branch:120"]
+ columns = [_(filters.group_by) + ":Link/Branch:120"]
- columns += [
- _("Employee") + ":Link/Employee:120", _("Employee Name") + ":Data/:120"
- ]
+ columns += [_("Employee") + ":Link/Employee:120", _("Employee Name") + ":Data/:120"]
days = []
for day in range(filters["total_days_in_month"]):
- date = str(filters.year) + "-" + str(filters.month)+ "-" + str(day+1)
+ date = str(filters.year) + "-" + str(filters.month) + "-" + str(day + 1)
day_name = day_abbr[getdate(date).weekday()]
- days.append(cstr(day+1)+ " " +day_name +"::65")
+ days.append(cstr(day + 1) + " " + day_name + "::65")
if not filters.summarized_view:
columns += days
if filters.summarized_view:
- columns += [_("Total Present") + ":Float:120", _("Total Leaves") + ":Float:120", _("Total Absent") + ":Float:120", _("Total Holidays") + ":Float:120", _("Unmarked Days")+ ":Float:120"]
+ columns += [
+ _("Total Present") + ":Float:120",
+ _("Total Leaves") + ":Float:120",
+ _("Total Absent") + ":Float:120",
+ _("Total Holidays") + ":Float:120",
+ _("Unmarked Days") + ":Float:120",
+ ]
return columns, days
+
def get_attendance_list(conditions, filters):
- attendance_list = frappe.db.sql("""select employee, day(attendance_date) as day_of_month,
- status from tabAttendance where docstatus = 1 %s order by employee, attendance_date""" %
- conditions, filters, as_dict=1)
+ attendance_list = frappe.db.sql(
+ """select employee, day(attendance_date) as day_of_month,
+ status from tabAttendance where docstatus = 1 %s order by employee, attendance_date"""
+ % conditions,
+ filters,
+ as_dict=1,
+ )
if not attendance_list:
msgprint(_("No attendance record found"), alert=True, indicator="orange")
@@ -254,6 +287,7 @@
return att_map
+
def get_conditions(filters):
if not (filters.get("month") and filters.get("year")):
msgprint(_("Please select month and year"), raise_exception=1)
@@ -262,29 +296,35 @@
conditions = " and month(attendance_date) = %(month)s and year(attendance_date) = %(year)s"
- if filters.get("company"): conditions += " and company = %(company)s"
- if filters.get("employee"): conditions += " and employee = %(employee)s"
+ if filters.get("company"):
+ conditions += " and company = %(company)s"
+ if filters.get("employee"):
+ conditions += " and employee = %(employee)s"
return conditions, filters
+
def get_employee_details(group_by, company):
emp_map = {}
query = """select name, employee_name, designation, department, branch, company,
- holiday_list from `tabEmployee` where company = %s """ % frappe.db.escape(company)
+ holiday_list from `tabEmployee` where company = %s """ % frappe.db.escape(
+ company
+ )
if group_by:
group_by = group_by.lower()
query += " order by " + group_by + " ASC"
- employee_details = frappe.db.sql(query , as_dict=1)
+ employee_details = frappe.db.sql(query, as_dict=1)
group_by_parameters = []
if group_by:
- group_by_parameters = list(set(detail.get(group_by, "") for detail in employee_details if detail.get(group_by, "")))
+ group_by_parameters = list(
+ set(detail.get(group_by, "") for detail in employee_details if detail.get(group_by, ""))
+ )
for parameter in group_by_parameters:
- emp_map[parameter] = {}
-
+ emp_map[parameter] = {}
for d in employee_details:
if group_by and len(group_by_parameters):
@@ -299,18 +339,28 @@
else:
return emp_map, group_by_parameters
+
def get_holiday(holiday_list, month):
holiday_map = frappe._dict()
for d in holiday_list:
if d:
- holiday_map.setdefault(d, frappe.db.sql('''select day(holiday_date), weekly_off from `tabHoliday`
- where parent=%s and month(holiday_date)=%s''', (d, month)))
+ holiday_map.setdefault(
+ d,
+ frappe.db.sql(
+ """select day(holiday_date), weekly_off from `tabHoliday`
+ where parent=%s and month(holiday_date)=%s""",
+ (d, month),
+ ),
+ )
return holiday_map
+
@frappe.whitelist()
def get_attendance_years():
- year_list = frappe.db.sql_list("""select distinct YEAR(attendance_date) from tabAttendance ORDER BY YEAR(attendance_date) DESC""")
+ year_list = frappe.db.sql_list(
+ """select distinct YEAR(attendance_date) from tabAttendance ORDER BY YEAR(attendance_date) DESC"""
+ )
if not year_list:
year_list = [getdate().year]
diff --git a/erpnext/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py
index 952af81..91da08e 100644
--- a/erpnext/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py
+++ b/erpnext/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py
@@ -11,31 +11,33 @@
class TestMonthlyAttendanceSheet(FrappeTestCase):
def setUp(self):
self.employee = make_employee("test_employee@example.com")
- frappe.db.delete('Attendance', {'employee': self.employee})
+ frappe.db.delete("Attendance", {"employee": self.employee})
def test_monthly_attendance_sheet_report(self):
now = now_datetime()
previous_month = now.month - 1
previous_month_first = now.replace(day=1).replace(month=previous_month).date()
- company = frappe.db.get_value('Employee', self.employee, 'company')
+ company = frappe.db.get_value("Employee", self.employee, "company")
# mark different attendance status on first 3 days of previous month
- mark_attendance(self.employee, previous_month_first, 'Absent')
- mark_attendance(self.employee, previous_month_first + relativedelta(days=1), 'Present')
- mark_attendance(self.employee, previous_month_first + relativedelta(days=2), 'On Leave')
+ mark_attendance(self.employee, previous_month_first, "Absent")
+ mark_attendance(self.employee, previous_month_first + relativedelta(days=1), "Present")
+ mark_attendance(self.employee, previous_month_first + relativedelta(days=2), "On Leave")
- filters = frappe._dict({
- 'month': previous_month,
- 'year': now.year,
- 'company': company,
- })
+ filters = frappe._dict(
+ {
+ "month": previous_month,
+ "year": now.year,
+ "company": company,
+ }
+ )
report = execute(filters=filters)
employees = report[1][0]
- datasets = report[3]['data']['datasets']
- absent = datasets[0]['values']
- present = datasets[1]['values']
- leaves = datasets[2]['values']
+ datasets = report[3]["data"]["datasets"]
+ absent = datasets[0]["values"]
+ present = datasets[1]["values"]
+ leaves = datasets[2]["values"]
# ensure correct attendance is reflect on the report
self.assertIn(self.employee, employees)
diff --git a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py
index 6383a9b..b6caf40 100644
--- a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py
+++ b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py
@@ -8,7 +8,8 @@
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
filters = frappe._dict(filters)
columns = get_columns()
@@ -25,67 +26,53 @@
"fieldtype": "Link",
"fieldname": "staffing_plan",
"options": "Staffing Plan",
- "width": 150
+ "width": 150,
},
{
"label": _("Job Opening"),
"fieldtype": "Link",
"fieldname": "job_opening",
"options": "Job Opening",
- "width": 105
+ "width": 105,
},
{
"label": _("Job Applicant"),
"fieldtype": "Link",
"fieldname": "job_applicant",
"options": "Job Applicant",
- "width": 150
+ "width": 150,
},
- {
- "label": _("Applicant name"),
- "fieldtype": "data",
- "fieldname": "applicant_name",
- "width": 130
- },
+ {"label": _("Applicant name"), "fieldtype": "data", "fieldname": "applicant_name", "width": 130},
{
"label": _("Application Status"),
"fieldtype": "Data",
"fieldname": "application_status",
- "width": 150
+ "width": 150,
},
{
"label": _("Job Offer"),
"fieldtype": "Link",
"fieldname": "job_offer",
"options": "job Offer",
- "width": 150
+ "width": 150,
},
- {
- "label": _("Designation"),
- "fieldtype": "Data",
- "fieldname": "designation",
- "width": 100
- },
- {
- "label": _("Offer Date"),
- "fieldtype": "date",
- "fieldname": "offer_date",
- "width": 100
- },
+ {"label": _("Designation"), "fieldtype": "Data", "fieldname": "designation", "width": 100},
+ {"label": _("Offer Date"), "fieldtype": "date", "fieldname": "offer_date", "width": 100},
{
"label": _("Job Offer status"),
"fieldtype": "Data",
"fieldname": "job_offer_status",
- "width": 150
- }
+ "width": 150,
+ },
]
+
def get_data(filters):
data = []
staffing_plan_details = get_staffing_plan(filters)
- staffing_plan_list = list(set([details["name"] for details in staffing_plan_details]))
- sp_jo_map , jo_list = get_job_opening(staffing_plan_list)
- jo_ja_map , ja_list = get_job_applicant(jo_list)
+ staffing_plan_list = list(set([details["name"] for details in staffing_plan_details]))
+ sp_jo_map, jo_list = get_job_opening(staffing_plan_list)
+ jo_ja_map, ja_list = get_job_applicant(jo_list)
ja_joff_map = get_job_offer(ja_list)
for sp in sp_jo_map.keys():
@@ -100,37 +87,40 @@
if sp in sp_jo_map.keys():
for jo in sp_jo_map[sp]:
row = {
- "staffing_plan" : sp,
- "job_opening" : jo["name"],
+ "staffing_plan": sp,
+ "job_opening": jo["name"],
}
data.append(row)
- child_row = get_child_row( jo["name"], jo_ja_map, ja_joff_map)
+ child_row = get_child_row(jo["name"], jo_ja_map, ja_joff_map)
data += child_row
return data
+
def get_child_row(jo, jo_ja_map, ja_joff_map):
data = []
if jo in jo_ja_map.keys():
for ja in jo_ja_map[jo]:
row = {
- "indent":1,
+ "indent": 1,
"job_applicant": ja.name,
"applicant_name": ja.applicant_name,
"application_status": ja.status,
}
if ja.name in ja_joff_map.keys():
- jo_detail =ja_joff_map[ja.name][0]
+ jo_detail = ja_joff_map[ja.name][0]
row["job_offer"] = jo_detail.name
row["job_offer_status"] = jo_detail.status
- row["offer_date"]= jo_detail.offer_date.strftime("%d-%m-%Y")
+ row["offer_date"] = jo_detail.offer_date.strftime("%d-%m-%Y")
row["designation"] = jo_detail.designation
data.append(row)
return data
+
def get_staffing_plan(filters):
- staffing_plan = frappe.db.sql("""
+ staffing_plan = frappe.db.sql(
+ """
select
sp.name, sp.department, spd.designation, spd.vacancies, spd.current_count, spd.parent, sp.to_date
from
@@ -139,13 +129,20 @@
spd.parent = sp.name
And
sp.to_date > '{0}'
- """.format(filters.on_date), as_dict = 1)
+ """.format(
+ filters.on_date
+ ),
+ as_dict=1,
+ )
return staffing_plan
+
def get_job_opening(sp_list):
- job_openings = frappe.get_all("Job Opening", filters = [["staffing_plan", "IN", sp_list]], fields =["name", "staffing_plan"])
+ job_openings = frappe.get_all(
+ "Job Opening", filters=[["staffing_plan", "IN", sp_list]], fields=["name", "staffing_plan"]
+ )
sp_jo_map = {}
jo_list = []
@@ -160,12 +157,17 @@
return sp_jo_map, jo_list
+
def get_job_applicant(jo_list):
jo_ja_map = {}
- ja_list =[]
+ ja_list = []
- applicants = frappe.get_all("Job Applicant", filters = [["job_title", "IN", jo_list]], fields =["name", "job_title","applicant_name", 'status'])
+ applicants = frappe.get_all(
+ "Job Applicant",
+ filters=[["job_title", "IN", jo_list]],
+ fields=["name", "job_title", "applicant_name", "status"],
+ )
for applicant in applicants:
if applicant.job_title not in jo_ja_map.keys():
@@ -175,12 +177,17 @@
ja_list.append(applicant.name)
- return jo_ja_map , ja_list
+ return jo_ja_map, ja_list
+
def get_job_offer(ja_list):
ja_joff_map = {}
- offers = frappe.get_all("Job Offer", filters = [["job_applicant", "IN", ja_list]], fields =["name", "job_applicant", "status", 'offer_date', 'designation'])
+ offers = frappe.get_all(
+ "Job Offer",
+ filters=[["job_applicant", "IN", ja_list]],
+ fields=["name", "job_applicant", "status", "offer_date", "designation"],
+ )
for offer in offers:
if offer.job_applicant not in ja_joff_map.keys():
diff --git a/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py b/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py
index 8672e68..da6dace 100644
--- a/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py
+++ b/erpnext/hr/report/vehicle_expenses/test_vehicle_expenses.py
@@ -17,12 +17,14 @@
class TestVehicleExpenses(unittest.TestCase):
@classmethod
def setUpClass(self):
- frappe.db.sql('delete from `tabVehicle Log`')
+ frappe.db.sql("delete from `tabVehicle Log`")
- employee_id = frappe.db.sql('''select name from `tabEmployee` where name="testdriver@example.com"''')
+ employee_id = frappe.db.sql(
+ '''select name from `tabEmployee` where name="testdriver@example.com"'''
+ )
self.employee_id = employee_id[0][0] if employee_id else None
if not self.employee_id:
- self.employee_id = make_employee('testdriver@example.com', company='_Test Company')
+ self.employee_id = make_employee("testdriver@example.com", company="_Test Company")
self.license_plate = get_vehicle(self.employee_id)
@@ -31,36 +33,35 @@
expense_claim = make_expense_claim(vehicle_log.name)
# Based on Fiscal Year
- filters = {
- 'filter_based_on': 'Fiscal Year',
- 'fiscal_year': get_fiscal_year(getdate())[0]
- }
+ filters = {"filter_based_on": "Fiscal Year", "fiscal_year": get_fiscal_year(getdate())[0]}
report = execute(filters)
- expected_data = [{
- 'vehicle': self.license_plate,
- 'make': 'Maruti',
- 'model': 'PCM',
- 'location': 'Mumbai',
- 'log_name': vehicle_log.name,
- 'odometer': 5010,
- 'date': getdate(),
- 'fuel_qty': 50.0,
- 'fuel_price': 500.0,
- 'fuel_expense': 25000.0,
- 'service_expense': 2000.0,
- 'employee': self.employee_id
- }]
+ expected_data = [
+ {
+ "vehicle": self.license_plate,
+ "make": "Maruti",
+ "model": "PCM",
+ "location": "Mumbai",
+ "log_name": vehicle_log.name,
+ "odometer": 5010,
+ "date": getdate(),
+ "fuel_qty": 50.0,
+ "fuel_price": 500.0,
+ "fuel_expense": 25000.0,
+ "service_expense": 2000.0,
+ "employee": self.employee_id,
+ }
+ ]
self.assertEqual(report[1], expected_data)
# Based on Date Range
fiscal_year = get_fiscal_year(getdate(), as_dict=True)
filters = {
- 'filter_based_on': 'Date Range',
- 'from_date': fiscal_year.year_start_date,
- 'to_date': fiscal_year.year_end_date
+ "filter_based_on": "Date Range",
+ "from_date": fiscal_year.year_start_date,
+ "to_date": fiscal_year.year_end_date,
}
report = execute(filters)
@@ -68,9 +69,9 @@
# clean up
vehicle_log.cancel()
- frappe.delete_doc('Expense Claim', expense_claim.name)
- frappe.delete_doc('Vehicle Log', vehicle_log.name)
+ frappe.delete_doc("Expense Claim", expense_claim.name)
+ frappe.delete_doc("Vehicle Log", vehicle_log.name)
def tearDown(self):
- frappe.delete_doc('Vehicle', self.license_plate, force=1)
- frappe.delete_doc('Employee', self.employee_id, force=1)
+ frappe.delete_doc("Vehicle", self.license_plate, force=1)
+ frappe.delete_doc("Employee", self.employee_id, force=1)
diff --git a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py
index 17d1e9d..fc5510d 100644
--- a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py
+++ b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py
@@ -18,83 +18,44 @@
return columns, data, None, chart
+
def get_columns():
return [
{
- 'fieldname': 'vehicle',
- 'fieldtype': 'Link',
- 'label': _('Vehicle'),
- 'options': 'Vehicle',
- 'width': 150
+ "fieldname": "vehicle",
+ "fieldtype": "Link",
+ "label": _("Vehicle"),
+ "options": "Vehicle",
+ "width": 150,
+ },
+ {"fieldname": "make", "fieldtype": "Data", "label": _("Make"), "width": 100},
+ {"fieldname": "model", "fieldtype": "Data", "label": _("Model"), "width": 80},
+ {"fieldname": "location", "fieldtype": "Data", "label": _("Location"), "width": 100},
+ {
+ "fieldname": "log_name",
+ "fieldtype": "Link",
+ "label": _("Vehicle Log"),
+ "options": "Vehicle Log",
+ "width": 100,
+ },
+ {"fieldname": "odometer", "fieldtype": "Int", "label": _("Odometer Value"), "width": 120},
+ {"fieldname": "date", "fieldtype": "Date", "label": _("Date"), "width": 100},
+ {"fieldname": "fuel_qty", "fieldtype": "Float", "label": _("Fuel Qty"), "width": 80},
+ {"fieldname": "fuel_price", "fieldtype": "Float", "label": _("Fuel Price"), "width": 100},
+ {"fieldname": "fuel_expense", "fieldtype": "Currency", "label": _("Fuel Expense"), "width": 150},
+ {
+ "fieldname": "service_expense",
+ "fieldtype": "Currency",
+ "label": _("Service Expense"),
+ "width": 150,
},
{
- 'fieldname': 'make',
- 'fieldtype': 'Data',
- 'label': _('Make'),
- 'width': 100
+ "fieldname": "employee",
+ "fieldtype": "Link",
+ "label": _("Employee"),
+ "options": "Employee",
+ "width": 150,
},
- {
- 'fieldname': 'model',
- 'fieldtype': 'Data',
- 'label': _('Model'),
- 'width': 80
- },
- {
- 'fieldname': 'location',
- 'fieldtype': 'Data',
- 'label': _('Location'),
- 'width': 100
- },
- {
- 'fieldname': 'log_name',
- 'fieldtype': 'Link',
- 'label': _('Vehicle Log'),
- 'options': 'Vehicle Log',
- 'width': 100
- },
- {
- 'fieldname': 'odometer',
- 'fieldtype': 'Int',
- 'label': _('Odometer Value'),
- 'width': 120
- },
- {
- 'fieldname': 'date',
- 'fieldtype': 'Date',
- 'label': _('Date'),
- 'width': 100
- },
- {
- 'fieldname': 'fuel_qty',
- 'fieldtype': 'Float',
- 'label': _('Fuel Qty'),
- 'width': 80
- },
- {
- 'fieldname': 'fuel_price',
- 'fieldtype': 'Float',
- 'label': _('Fuel Price'),
- 'width': 100
- },
- {
- 'fieldname': 'fuel_expense',
- 'fieldtype': 'Currency',
- 'label': _('Fuel Expense'),
- 'width': 150
- },
- {
- 'fieldname': 'service_expense',
- 'fieldtype': 'Currency',
- 'label': _('Service Expense'),
- 'width': 150
- },
- {
- 'fieldname': 'employee',
- 'fieldtype': 'Link',
- 'label': _('Employee'),
- 'options': 'Employee',
- 'width': 150
- }
]
@@ -102,7 +63,8 @@
start_date, end_date = get_period_dates(filters)
conditions, values = get_conditions(filters)
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
SELECT
vhcl.license_plate as vehicle, vhcl.make, vhcl.model,
vhcl.location, log.name as log_name, log.odometer,
@@ -116,58 +78,70 @@
and log.docstatus = 1
and date between %(start_date)s and %(end_date)s
{0}
- ORDER BY date""".format(conditions), values, as_dict=1)
+ ORDER BY date""".format(
+ conditions
+ ),
+ values,
+ as_dict=1,
+ )
for row in data:
- row['service_expense'] = get_service_expense(row.log_name)
+ row["service_expense"] = get_service_expense(row.log_name)
return data
def get_conditions(filters):
- conditions = ''
+ conditions = ""
start_date, end_date = get_period_dates(filters)
- values = {
- 'start_date': start_date,
- 'end_date': end_date
- }
+ values = {"start_date": start_date, "end_date": end_date}
if filters.employee:
- conditions += ' and log.employee = %(employee)s'
- values['employee'] = filters.employee
+ conditions += " and log.employee = %(employee)s"
+ values["employee"] = filters.employee
if filters.vehicle:
- conditions += ' and vhcl.license_plate = %(vehicle)s'
- values['vehicle'] = filters.vehicle
+ conditions += " and vhcl.license_plate = %(vehicle)s"
+ values["vehicle"] = filters.vehicle
return conditions, values
def get_period_dates(filters):
- if filters.filter_based_on == 'Fiscal Year' and filters.fiscal_year:
- fy = frappe.db.get_value('Fiscal Year', filters.fiscal_year,
- ['year_start_date', 'year_end_date'], as_dict=True)
+ if filters.filter_based_on == "Fiscal Year" and filters.fiscal_year:
+ fy = frappe.db.get_value(
+ "Fiscal Year", filters.fiscal_year, ["year_start_date", "year_end_date"], as_dict=True
+ )
return fy.year_start_date, fy.year_end_date
else:
return filters.from_date, filters.to_date
def get_service_expense(logname):
- expense_amount = frappe.db.sql("""
+ expense_amount = frappe.db.sql(
+ """
SELECT sum(expense_amount)
FROM
`tabVehicle Log` log, `tabVehicle Service` service
WHERE
service.parent=log.name and log.name=%s
- """, logname)
+ """,
+ logname,
+ )
return flt(expense_amount[0][0]) if expense_amount else 0.0
def get_chart_data(data, filters):
- period_list = get_period_list(filters.fiscal_year, filters.fiscal_year,
- filters.from_date, filters.to_date, filters.filter_based_on, 'Monthly')
+ period_list = get_period_list(
+ filters.fiscal_year,
+ filters.fiscal_year,
+ filters.from_date,
+ filters.to_date,
+ filters.filter_based_on,
+ "Monthly",
+ )
fuel_data, service_data = [], []
@@ -184,29 +158,20 @@
service_data.append([period.key, total_service_exp])
labels = [period.label for period in period_list]
- fuel_exp_data= [row[1] for row in fuel_data]
- service_exp_data= [row[1] for row in service_data]
+ fuel_exp_data = [row[1] for row in fuel_data]
+ service_exp_data = [row[1] for row in service_data]
datasets = []
if fuel_exp_data:
- datasets.append({
- 'name': _('Fuel Expenses'),
- 'values': fuel_exp_data
- })
+ datasets.append({"name": _("Fuel Expenses"), "values": fuel_exp_data})
if service_exp_data:
- datasets.append({
- 'name': _('Service Expenses'),
- 'values': service_exp_data
- })
+ datasets.append({"name": _("Service Expenses"), "values": service_exp_data})
chart = {
- 'data': {
- 'labels': labels,
- 'datasets': datasets
- },
- 'type': 'line',
- 'fieldtype': 'Currency'
+ "data": {"labels": labels, "datasets": datasets},
+ "type": "line",
+ "fieldtype": "Currency",
}
return chart
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index c174047..fd69a9b 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -23,20 +23,26 @@
)
-class DuplicateDeclarationError(frappe.ValidationError): pass
+class DuplicateDeclarationError(frappe.ValidationError):
+ pass
+
def set_employee_name(doc):
if doc.employee and not doc.employee_name:
doc.employee_name = frappe.db.get_value("Employee", doc.employee, "employee_name")
+
def update_employee_work_history(employee, details, date=None, cancel=False):
if not employee.internal_work_history and not cancel:
- employee.append("internal_work_history", {
- "branch": employee.branch,
- "designation": employee.designation,
- "department": employee.department,
- "from_date": employee.date_of_joining
- })
+ employee.append(
+ "internal_work_history",
+ {
+ "branch": employee.branch,
+ "designation": employee.designation,
+ "department": employee.department,
+ "from_date": employee.date_of_joining,
+ },
+ )
internal_work_history = {}
for item in details:
@@ -47,7 +53,7 @@
new_data = item.new if not cancel else item.current
if fieldtype == "Date" and new_data:
new_data = getdate(new_data)
- elif fieldtype =="Datetime" and new_data:
+ elif fieldtype == "Datetime" and new_data:
new_data = get_datetime(new_data)
setattr(employee, item.fieldname, new_data)
if item.fieldname in ["department", "designation", "branch"]:
@@ -62,6 +68,7 @@
return employee
+
def delete_employee_work_history(details, employee, date):
filters = {}
for d in details:
@@ -85,12 +92,25 @@
def get_employee_fields_label():
fields = []
for df in frappe.get_meta("Employee").get("fields"):
- if df.fieldname in ["salutation", "user_id", "employee_number", "employment_type",
- "holiday_list", "branch", "department", "designation", "grade",
- "notice_number_of_days", "reports_to", "leave_policy", "company_email"]:
- fields.append({"value": df.fieldname, "label": df.label})
+ if df.fieldname in [
+ "salutation",
+ "user_id",
+ "employee_number",
+ "employment_type",
+ "holiday_list",
+ "branch",
+ "department",
+ "designation",
+ "grade",
+ "notice_number_of_days",
+ "reports_to",
+ "leave_policy",
+ "company_email",
+ ]:
+ fields.append({"value": df.fieldname, "label": df.label})
return fields
+
@frappe.whitelist()
def get_employee_field_property(employee, fieldname):
if employee and fieldname:
@@ -101,17 +121,15 @@
value = formatdate(value)
elif field.fieldtype == "Datetime":
value = format_datetime(value)
- return {
- "value" : value,
- "datatype" : field.fieldtype,
- "label" : field.label,
- "options" : options
- }
+ return {"value": value, "datatype": field.fieldtype, "label": field.label, "options": options}
else:
return False
+
def validate_dates(doc, from_date, to_date):
- date_of_joining, relieving_date = frappe.db.get_value("Employee", doc.employee, ["date_of_joining", "relieving_date"])
+ date_of_joining, relieving_date = frappe.db.get_value(
+ "Employee", doc.employee, ["date_of_joining", "relieving_date"]
+ )
if getdate(from_date) > getdate(to_date):
frappe.throw(_("To date can not be less than from date"))
elif getdate(from_date) > getdate(nowdate()):
@@ -121,7 +139,8 @@
elif relieving_date and getdate(to_date) > getdate(relieving_date):
frappe.throw(_("To date can not greater than employee's relieving date"))
-def validate_overlap(doc, from_date, to_date, company = None):
+
+def validate_overlap(doc, from_date, to_date, company=None):
query = """
select name
from `tab{0}`
@@ -131,15 +150,19 @@
if not doc.name:
# hack! if name is null, it could cause problems with !=
- doc.name = "New "+doc.doctype
+ doc.name = "New " + doc.doctype
- overlap_doc = frappe.db.sql(query.format(doc.doctype),{
+ overlap_doc = frappe.db.sql(
+ query.format(doc.doctype),
+ {
"employee": doc.get("employee"),
"from_date": from_date,
"to_date": to_date,
"name": doc.name,
- "company": company
- }, as_dict = 1)
+ "company": company,
+ },
+ as_dict=1,
+ )
if overlap_doc:
if doc.get("employee"):
@@ -148,6 +171,7 @@
exists_for = company
throw_overlap_error(doc, exists_for, overlap_doc[0].name, from_date, to_date)
+
def get_doc_condition(doctype):
if doctype == "Compensatory Leave Request":
return "and employee = %(employee)s and docstatus < 2 \
@@ -159,23 +183,36 @@
or to_date between %(from_date)s and %(to_date)s \
or (from_date < %(from_date)s and to_date > %(to_date)s))"
+
def throw_overlap_error(doc, exists_for, overlap_doc, from_date, to_date):
- msg = _("A {0} exists between {1} and {2} (").format(doc.doctype,
- formatdate(from_date), formatdate(to_date)) \
- + """ <b><a href="/app/Form/{0}/{1}">{1}</a></b>""".format(doc.doctype, overlap_doc) \
+ msg = (
+ _("A {0} exists between {1} and {2} (").format(
+ doc.doctype, formatdate(from_date), formatdate(to_date)
+ )
+ + """ <b><a href="/app/Form/{0}/{1}">{1}</a></b>""".format(doc.doctype, overlap_doc)
+ _(") for {0}").format(exists_for)
+ )
frappe.throw(msg)
+
def validate_duplicate_exemption_for_payroll_period(doctype, docname, payroll_period, employee):
- existing_record = frappe.db.exists(doctype, {
- "payroll_period": payroll_period,
- "employee": employee,
- 'docstatus': ['<', 2],
- 'name': ['!=', docname]
- })
+ existing_record = frappe.db.exists(
+ doctype,
+ {
+ "payroll_period": payroll_period,
+ "employee": employee,
+ "docstatus": ["<", 2],
+ "name": ["!=", docname],
+ },
+ )
if existing_record:
- frappe.throw(_("{0} already exists for employee {1} and period {2}")
- .format(doctype, employee, payroll_period), DuplicateDeclarationError)
+ frappe.throw(
+ _("{0} already exists for employee {1} and period {2}").format(
+ doctype, employee, payroll_period
+ ),
+ DuplicateDeclarationError,
+ )
+
def validate_tax_declaration(declarations):
subcategories = []
@@ -184,61 +221,79 @@
frappe.throw(_("More than one selection for {0} not allowed").format(d.exemption_sub_category))
subcategories.append(d.exemption_sub_category)
+
def get_total_exemption_amount(declarations):
exemptions = frappe._dict()
for d in declarations:
exemptions.setdefault(d.exemption_category, frappe._dict())
category_max_amount = exemptions.get(d.exemption_category).max_amount
if not category_max_amount:
- category_max_amount = frappe.db.get_value("Employee Tax Exemption Category", d.exemption_category, "max_amount")
+ category_max_amount = frappe.db.get_value(
+ "Employee Tax Exemption Category", d.exemption_category, "max_amount"
+ )
exemptions.get(d.exemption_category).max_amount = category_max_amount
- sub_category_exemption_amount = d.max_amount \
- if (d.max_amount and flt(d.amount) > flt(d.max_amount)) else d.amount
+ sub_category_exemption_amount = (
+ d.max_amount if (d.max_amount and flt(d.amount) > flt(d.max_amount)) else d.amount
+ )
exemptions.get(d.exemption_category).setdefault("total_exemption_amount", 0.0)
exemptions.get(d.exemption_category).total_exemption_amount += flt(sub_category_exemption_amount)
- if category_max_amount and exemptions.get(d.exemption_category).total_exemption_amount > category_max_amount:
+ if (
+ category_max_amount
+ and exemptions.get(d.exemption_category).total_exemption_amount > category_max_amount
+ ):
exemptions.get(d.exemption_category).total_exemption_amount = category_max_amount
total_exemption_amount = sum([flt(d.total_exemption_amount) for d in exemptions.values()])
return total_exemption_amount
+
@frappe.whitelist()
def get_leave_period(from_date, to_date, company):
- leave_period = frappe.db.sql("""
+ leave_period = frappe.db.sql(
+ """
select name, from_date, to_date
from `tabLeave Period`
where company=%(company)s and is_active=1
and (from_date between %(from_date)s and %(to_date)s
or to_date between %(from_date)s and %(to_date)s
or (from_date < %(from_date)s and to_date > %(to_date)s))
- """, {
- "from_date": from_date,
- "to_date": to_date,
- "company": company
- }, as_dict=1)
+ """,
+ {"from_date": from_date, "to_date": to_date, "company": company},
+ as_dict=1,
+ )
if leave_period:
return leave_period
+
def generate_leave_encashment():
- ''' Generates a draft leave encashment on allocation expiry '''
+ """Generates a draft leave encashment on allocation expiry"""
from erpnext.hr.doctype.leave_encashment.leave_encashment import create_leave_encashment
- if frappe.db.get_single_value('HR Settings', 'auto_leave_encashment'):
- leave_type = frappe.get_all('Leave Type', filters={'allow_encashment': 1}, fields=['name'])
- leave_type=[l['name'] for l in leave_type]
+ if frappe.db.get_single_value("HR Settings", "auto_leave_encashment"):
+ leave_type = frappe.get_all("Leave Type", filters={"allow_encashment": 1}, fields=["name"])
+ leave_type = [l["name"] for l in leave_type]
- leave_allocation = frappe.get_all("Leave Allocation", filters={
- 'to_date': add_days(today(), -1),
- 'leave_type': ('in', leave_type)
- }, fields=['employee', 'leave_period', 'leave_type', 'to_date', 'total_leaves_allocated', 'new_leaves_allocated'])
+ leave_allocation = frappe.get_all(
+ "Leave Allocation",
+ filters={"to_date": add_days(today(), -1), "leave_type": ("in", leave_type)},
+ fields=[
+ "employee",
+ "leave_period",
+ "leave_type",
+ "to_date",
+ "total_leaves_allocated",
+ "new_leaves_allocated",
+ ],
+ )
create_leave_encashment(leave_allocation=leave_allocation)
+
def allocate_earned_leaves(ignore_duplicates=False):
- '''Allocate earned leaves to Employees'''
+ """Allocate earned leaves to Employees"""
e_leave_types = get_earned_leaves()
today = getdate()
@@ -251,37 +306,52 @@
if not allocation.leave_policy_assignment and not allocation.leave_policy:
continue
- leave_policy = allocation.leave_policy if allocation.leave_policy else frappe.db.get_value(
- "Leave Policy Assignment", allocation.leave_policy_assignment, ["leave_policy"])
+ leave_policy = (
+ allocation.leave_policy
+ if allocation.leave_policy
+ else frappe.db.get_value(
+ "Leave Policy Assignment", allocation.leave_policy_assignment, ["leave_policy"]
+ )
+ )
- annual_allocation = frappe.db.get_value("Leave Policy Detail", filters={
- 'parent': leave_policy,
- 'leave_type': e_leave_type.name
- }, fieldname=['annual_allocation'])
+ annual_allocation = frappe.db.get_value(
+ "Leave Policy Detail",
+ filters={"parent": leave_policy, "leave_type": e_leave_type.name},
+ fieldname=["annual_allocation"],
+ )
- from_date=allocation.from_date
+ from_date = allocation.from_date
if e_leave_type.based_on_date_of_joining:
- from_date = frappe.db.get_value("Employee", allocation.employee, "date_of_joining")
+ from_date = frappe.db.get_value("Employee", allocation.employee, "date_of_joining")
- if check_effective_date(from_date, today, e_leave_type.earned_leave_frequency, e_leave_type.based_on_date_of_joining):
- update_previous_leave_allocation(allocation, annual_allocation, e_leave_type, ignore_duplicates)
+ if check_effective_date(
+ from_date, today, e_leave_type.earned_leave_frequency, e_leave_type.based_on_date_of_joining
+ ):
+ update_previous_leave_allocation(
+ allocation, annual_allocation, e_leave_type, ignore_duplicates
+ )
-def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type, ignore_duplicates=False):
- earned_leaves = get_monthly_earned_leave(annual_allocation, e_leave_type.earned_leave_frequency, e_leave_type.rounding)
- allocation = frappe.get_doc('Leave Allocation', allocation.name)
- new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves)
+def update_previous_leave_allocation(
+ allocation, annual_allocation, e_leave_type, ignore_duplicates=False
+):
+ earned_leaves = get_monthly_earned_leave(
+ annual_allocation, e_leave_type.earned_leave_frequency, e_leave_type.rounding
+ )
- if new_allocation > e_leave_type.max_leaves_allowed and e_leave_type.max_leaves_allowed > 0:
- new_allocation = e_leave_type.max_leaves_allowed
+ allocation = frappe.get_doc("Leave Allocation", allocation.name)
+ new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves)
- if new_allocation != allocation.total_leaves_allocated:
- today_date = today()
+ if new_allocation > e_leave_type.max_leaves_allowed and e_leave_type.max_leaves_allowed > 0:
+ new_allocation = e_leave_type.max_leaves_allowed
- if ignore_duplicates or not is_earned_leave_already_allocated(allocation, annual_allocation):
- allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
- create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)
+ if new_allocation != allocation.total_leaves_allocated:
+ today_date = today()
+
+ if ignore_duplicates or not is_earned_leave_already_allocated(allocation, annual_allocation):
+ allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
+ create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)
def get_monthly_earned_leave(annual_leaves, frequency, rounding):
@@ -309,8 +379,9 @@
date_of_joining = frappe.db.get_value("Employee", allocation.employee, "date_of_joining")
assignment = frappe.get_doc("Leave Policy Assignment", allocation.leave_policy_assignment)
- leaves_for_passed_months = assignment.get_leaves_for_passed_months(allocation.leave_type,
- annual_allocation, leave_type_details, date_of_joining)
+ leaves_for_passed_months = assignment.get_leaves_for_passed_months(
+ allocation.leave_type, annual_allocation, leave_type_details, date_of_joining
+ )
# exclude carry-forwarded leaves while checking for leave allocation for passed months
num_allocations = allocation.total_leaves_allocated
@@ -323,26 +394,39 @@
def get_leave_allocations(date, leave_type):
- return frappe.db.sql("""select name, employee, from_date, to_date, leave_policy_assignment, leave_policy
+ return frappe.db.sql(
+ """select name, employee, from_date, to_date, leave_policy_assignment, leave_policy
from `tabLeave Allocation`
where
%s between from_date and to_date and docstatus=1
and leave_type=%s""",
- (date, leave_type), as_dict=1)
+ (date, leave_type),
+ as_dict=1,
+ )
def get_earned_leaves():
- return frappe.get_all("Leave Type",
- fields=["name", "max_leaves_allowed", "earned_leave_frequency", "rounding", "based_on_date_of_joining"],
- filters={'is_earned_leave' : 1})
+ return frappe.get_all(
+ "Leave Type",
+ fields=[
+ "name",
+ "max_leaves_allowed",
+ "earned_leave_frequency",
+ "rounding",
+ "based_on_date_of_joining",
+ ],
+ filters={"is_earned_leave": 1},
+ )
+
def create_additional_leave_ledger_entry(allocation, leaves, date):
- ''' Create leave ledger entry for leave types '''
+ """Create leave ledger entry for leave types"""
allocation.new_leaves_allocated = leaves
allocation.from_date = date
allocation.unused_leaves = 0
allocation.create_leave_ledger_entry()
+
def check_effective_date(from_date, to_date, frequency, based_on_date_of_joining):
import calendar
@@ -351,10 +435,12 @@
from_date = get_datetime(from_date)
to_date = get_datetime(to_date)
rd = relativedelta.relativedelta(to_date, from_date)
- #last day of month
- last_day = calendar.monthrange(to_date.year, to_date.month)[1]
+ # last day of month
+ last_day = calendar.monthrange(to_date.year, to_date.month)[1]
- if (from_date.day == to_date.day and based_on_date_of_joining) or (not based_on_date_of_joining and to_date.day == last_day):
+ if (from_date.day == to_date.day and based_on_date_of_joining) or (
+ not based_on_date_of_joining and to_date.day == last_day
+ ):
if frequency == "Monthly":
return True
elif frequency == "Quarterly" and rd.months % 3:
@@ -371,16 +457,21 @@
def get_salary_assignment(employee, date):
- assignment = frappe.db.sql("""
+ assignment = frappe.db.sql(
+ """
select * from `tabSalary Structure Assignment`
where employee=%(employee)s
and docstatus = 1
- and %(on_date)s >= from_date order by from_date desc limit 1""", {
- 'employee': employee,
- 'on_date': date,
- }, as_dict=1)
+ and %(on_date)s >= from_date order by from_date desc limit 1""",
+ {
+ "employee": employee,
+ "on_date": date,
+ },
+ as_dict=1,
+ )
return assignment[0] if assignment else None
+
def get_sal_slip_total_benefit_given(employee, payroll_period, component=False):
total_given_benefit_amount = 0
query = """
@@ -398,17 +489,22 @@
if component:
query += "and sd.salary_component = %(component)s"
- sum_of_given_benefit = frappe.db.sql(query, {
- 'employee': employee,
- 'start_date': payroll_period.start_date,
- 'end_date': payroll_period.end_date,
- 'component': component
- }, as_dict=True)
+ sum_of_given_benefit = frappe.db.sql(
+ query,
+ {
+ "employee": employee,
+ "start_date": payroll_period.start_date,
+ "end_date": payroll_period.end_date,
+ "component": component,
+ },
+ as_dict=True,
+ )
if sum_of_given_benefit and flt(sum_of_given_benefit[0].total_amount) > 0:
total_given_benefit_amount = sum_of_given_benefit[0].total_amount
return total_given_benefit_amount
+
def get_holiday_dates_for_employee(employee, start_date, end_date):
"""return a list of holiday dates for the given employee between start_date and end_date"""
# return only date
@@ -417,50 +513,48 @@
return [cstr(h.holiday_date) for h in holidays]
-def get_holidays_for_employee(employee, start_date, end_date, raise_exception=True, only_non_weekly=False):
+def get_holidays_for_employee(
+ employee, start_date, end_date, raise_exception=True, only_non_weekly=False
+):
"""Get Holidays for a given employee
- `employee` (str)
- `start_date` (str or datetime)
- `end_date` (str or datetime)
- `raise_exception` (bool)
- `only_non_weekly` (bool)
+ `employee` (str)
+ `start_date` (str or datetime)
+ `end_date` (str or datetime)
+ `raise_exception` (bool)
+ `only_non_weekly` (bool)
- return: list of dicts with `holiday_date` and `description`
+ return: list of dicts with `holiday_date` and `description`
"""
holiday_list = get_holiday_list_for_employee(employee, raise_exception=raise_exception)
if not holiday_list:
return []
- filters = {
- 'parent': holiday_list,
- 'holiday_date': ('between', [start_date, end_date])
- }
+ filters = {"parent": holiday_list, "holiday_date": ("between", [start_date, end_date])}
if only_non_weekly:
- filters['weekly_off'] = False
+ filters["weekly_off"] = False
- holidays = frappe.get_all(
- 'Holiday',
- fields=['description', 'holiday_date'],
- filters=filters
- )
+ holidays = frappe.get_all("Holiday", fields=["description", "holiday_date"], filters=filters)
return holidays
+
@erpnext.allow_regional
def calculate_annual_eligible_hra_exemption(doc):
# Don't delete this method, used for localization
# Indian HRA Exemption Calculation
return {}
+
@erpnext.allow_regional
def calculate_hra_exemption_for_period(doc):
# Don't delete this method, used for localization
# Indian HRA Exemption Calculation
return {}
+
def get_previous_claimed_amount(employee, payroll_period, non_pro_rata=False, component=False):
total_claimed_amount = 0
query = """
@@ -475,24 +569,29 @@
if component:
query += "and earning_component = %(component)s"
- sum_of_claimed_amount = frappe.db.sql(query, {
- 'employee': employee,
- 'start_date': payroll_period.start_date,
- 'end_date': payroll_period.end_date,
- 'component': component
- }, as_dict=True)
+ sum_of_claimed_amount = frappe.db.sql(
+ query,
+ {
+ "employee": employee,
+ "start_date": payroll_period.start_date,
+ "end_date": payroll_period.end_date,
+ "component": component,
+ },
+ as_dict=True,
+ )
if sum_of_claimed_amount and flt(sum_of_claimed_amount[0].total_amount) > 0:
total_claimed_amount = sum_of_claimed_amount[0].total_amount
return total_claimed_amount
+
def share_doc_with_approver(doc, user):
# if approver does not have permissions, share
if not frappe.has_permission(doc=doc, ptype="submit", user=user):
- frappe.share.add(doc.doctype, doc.name, user, submit=1,
- flags={"ignore_share_permission": True})
+ frappe.share.add(doc.doctype, doc.name, user, submit=1, flags={"ignore_share_permission": True})
- frappe.msgprint(_("Shared with the user {0} with {1} access").format(
- user, frappe.bold("submit"), alert=True))
+ frappe.msgprint(
+ _("Shared with the user {0} with {1} access").format(user, frappe.bold("submit"), alert=True)
+ )
# remove shared doc if approver changes
doc_before_save = doc.get_doc_before_save()
@@ -500,14 +599,19 @@
approvers = {
"Leave Application": "leave_approver",
"Expense Claim": "expense_approver",
- "Shift Request": "approver"
+ "Shift Request": "approver",
}
approver = approvers.get(doc.doctype)
if doc_before_save.get(approver) != doc.get(approver):
frappe.share.remove(doc.doctype, doc.name, doc_before_save.get(approver))
+
def validate_active_employee(employee):
if frappe.db.get_value("Employee", employee, "status") == "Inactive":
- frappe.throw(_("Transactions cannot be created for an Inactive Employee {0}.").format(
- get_link_to_form("Employee", employee)), InactiveEmployeeStatusError)
+ frappe.throw(
+ _("Transactions cannot be created for an Inactive Employee {0}.").format(
+ get_link_to_form("Employee", employee)
+ ),
+ InactiveEmployeeStatusError,
+ )