feat: Assign shift for period or create ongoing assignment

This commit is contained in:
Anurag Mishra 2020-06-18 17:41:53 +05:30
parent e447c79f9d
commit afa98bb39b
2 changed files with 100 additions and 42 deletions

View File

@ -10,9 +10,11 @@
"employee", "employee",
"employee_name", "employee_name",
"shift_type", "shift_type",
"status",
"column_break_3", "column_break_3",
"company", "company",
"date", "start_date",
"end_date",
"shift_request", "shift_request",
"department", "department",
"amended_from" "amended_from"
@ -59,12 +61,6 @@
"options": "Company", "options": "Company",
"reqd": 1 "reqd": 1
}, },
{
"fieldname": "date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Date"
},
{ {
"fieldname": "shift_request", "fieldname": "shift_request",
"fieldtype": "Link", "fieldtype": "Link",
@ -80,11 +76,36 @@
"options": "Shift Assignment", "options": "Shift Assignment",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
},
{
"fieldname": "start_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Start Date",
"reqd": 1
},
{
"allow_on_submit": 1,
"fieldname": "end_date",
"fieldtype": "Date",
"label": "End Date",
"show_days": 1,
"show_seconds": 1
},
{
"allow_on_submit": 1,
"default": "Active",
"fieldname": "status",
"fieldtype": "Select",
"label": "Status",
"options": "Active\nInactive",
"show_days": 1,
"show_seconds": 1
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2019-12-12 15:49:06.956901", "modified": "2020-06-15 14:27:54.310773",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Shift Assignment", "name": "Shift Assignment",

View File

@ -11,38 +11,64 @@ from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
from datetime import timedelta, datetime from datetime import timedelta, datetime
class OverlapError(frappe.ValidationError): pass
class ShiftAssignment(Document): class ShiftAssignment(Document):
def validate(self): def validate(self):
self.validate_overlapping_dates() self.validate_overlapping_dates()
if self.end_date and self.end_date <= self.start_date:
frappe.throw(_("End Date should not be less than Start Date"))
def validate_overlapping_dates(self): def validate_overlapping_dates(self):
if not self.name: if not self.name:
self.name = "New Shift Assignment" self.name = "New Shift Assignment"
d = frappe.db.sql(""" condition = """and (
select end_date is null
name, shift_type, date or
from `tabShift Assignment` %(start_date)s between start_date and end_date
where employee = %(employee)s and docstatus < 2 """
and date = %(date)s
and name != %(name)s""", {
"employee": self.employee,
"shift_type": self.shift_type,
"date": self.date,
"name": self.name
}, as_dict = 1)
for date_overlap in d: if self.end_date:
if date_overlap['name']: condition += """ or
self.throw_overlap_error(date_overlap) %(end_date)s between start_date and end_date
or
start_date between %(start_date)s and %(end_date)s
) """
else:
condition += """ ) """
def throw_overlap_error(self, d): assigned_shifts = frappe.db.sql("""
msg = _("Employee {0} has already applied for {1} on {2} : ").format(self.employee, select name, shift_type, start_date ,end_date, docstatus, status
d['shift_type'], formatdate(d['date'])) \ from `tabShift Assignment`
+ """ <b><a href="#Form/Shift Assignment/{0}">{0}</a></b>""".format(d["name"]) where
frappe.throw(msg, OverlapError) employee=%(employee)s and docstatus < 2
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)
for shift in assigned_shifts:
if shift.name:
self.throw_overlap_error(shift)
def throw_overlap_error(self, shift_details):
shift_details = frappe._dict(shift_details)
if shift_details.docstatus == 0:
msg = _("Employee {0} has already applied for {1}: {2}").format(self.employee, self.shift_type, shift_details.name)
if shift_details.docstatus == 1 and shift_details.status == "Active":
msg = _("Employee {0} already have Active Shift {1}: {2}").format(self.employee, self.shift_type, shift_details.name)
if shift_details.start_date:
msg += _(" from {0}").format(getdate(self.start_date).strftime("%d-%m-%Y"))
if shift_details.end_date:
msg += _(" to {0}").format(getdate(self.end_date).strftime("%d-%m-%Y"))
if msg:
frappe.throw(msg)
@frappe.whitelist() @frappe.whitelist()
def get_events(start, end, filters=None): def get_events(start, end, filters=None):
@ -62,19 +88,22 @@ def get_events(start, end, filters=None):
return events return events
def add_assignments(events, start, end, conditions=None): def add_assignments(events, start, end, conditions=None):
query = """select name, date, employee_name, query = """select name, start_date, end_date, employee_name,
employee, docstatus employee, docstatus
from `tabShift Assignment` where from `tabShift Assignment` where
date <= %(date)s start_date >= %(start_date)s
and docstatus < 2""" and docstatus < 2""".format()
if conditions: if conditions:
query += conditions query += conditions
for d in frappe.db.sql(query, {"date":start, "date":end}, as_dict=True): for d in frappe.db.sql(query, {"start_date":start}, as_dict=True):
from pprint import pprint
pprint(d)
e = { e = {
"name": d.name, "name": d.name,
"doctype": "Shift Assignment", "doctype": "Shift Assignment",
"date": d.date, "start_date": d.start_date,
"end_date": d.end_date if d.end_date else nowdate(),
"title": cstr(d.employee_name) + \ "title": cstr(d.employee_name) + \
cstr(d.shift_type), cstr(d.shift_type),
"docstatus": d.docstatus "docstatus": d.docstatus
@ -92,7 +121,14 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals
:param next_shift_direction: One of: None, 'forward', 'reverse'. Direction to look for next shift if shift not found on given date. :param next_shift_direction: One of: None, 'forward', 'reverse'. Direction to look for next shift if shift not found on given date.
""" """
default_shift = frappe.db.get_value('Employee', employee, 'default_shift') default_shift = frappe.db.get_value('Employee', employee, 'default_shift')
shift_type_name = frappe.db.get_value('Shift Assignment', {'employee':employee, 'date': for_date, 'docstatus': '1'}, 'shift_type') shift_assignment_details = frappe.db.get_value('Shift Assignment', {'employee':employee, 'start_date':('<=', for_date), 'docstatus': '1', 'status': "Active"}, ['shift_type', 'end_date'])
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] :
shift_type_name = None
if not shift_type_name and consider_default_shift: if not shift_type_name and consider_default_shift:
shift_type_name = default_shift shift_type_name = default_shift
if shift_type_name: if shift_type_name:
@ -117,12 +153,13 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals
direction = '<' if next_shift_direction == 'reverse' else '>' direction = '<' if next_shift_direction == 'reverse' else '>'
sort_order = 'desc' if next_shift_direction == 'reverse' else 'asc' sort_order = 'desc' if next_shift_direction == 'reverse' else 'asc'
dates = frappe.db.get_all('Shift Assignment', dates = frappe.db.get_all('Shift Assignment',
'date', 'start_date',
{'employee':employee, 'date':(direction, for_date), 'docstatus': '1'}, {'employee':employee, 'start_date':(direction, for_date), 'docstatus': '1', "status": "Active"},
as_list=True, as_list=True,
limit=MAX_DAYS, order_by="date "+sort_order) limit=MAX_DAYS, order_by="date "+sort_order)
for date in dates: for date in dates:
shift_details = get_employee_shift(employee, date[0], consider_default_shift, None) shift_details = get_employee_shift(employee, date.start_date, consider_default_shift, None)
if shift_details: if shift_details:
shift_type_name = shift_details.shift_type.name shift_type_name = shift_details.shift_type.name
for_date = date[0] for_date = date[0]
@ -134,7 +171,7 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals
def get_employee_shift_timings(employee, for_timestamp=now_datetime(), consider_default_shift=False): def get_employee_shift_timings(employee, for_timestamp=now_datetime(), 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
""" """
# write and verify a test case for midnight shift. # write and verify a test case for midnight shift.
prev_shift = curr_shift = next_shift = None 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: if curr_shift: