feat: Assign shift for period or create ongoing assignment
diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.json b/erpnext/hr/doctype/shift_assignment/shift_assignment.json
index 72cbba8..ce2a10f 100644
--- a/erpnext/hr/doctype/shift_assignment/shift_assignment.json
+++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.json
@@ -10,9 +10,11 @@
"employee",
"employee_name",
"shift_type",
+ "status",
"column_break_3",
"company",
- "date",
+ "start_date",
+ "end_date",
"shift_request",
"department",
"amended_from"
@@ -60,12 +62,6 @@
"reqd": 1
},
{
- "fieldname": "date",
- "fieldtype": "Date",
- "in_list_view": 1,
- "label": "Date"
- },
- {
"fieldname": "shift_request",
"fieldtype": "Link",
"label": "Shift Request",
@@ -80,11 +76,36 @@
"options": "Shift Assignment",
"print_hide": 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,
"links": [],
- "modified": "2019-12-12 15:49:06.956901",
+ "modified": "2020-06-15 14:27:54.310773",
"modified_by": "Administrator",
"module": "HR",
"name": "Shift Assignment",
diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
index 40c78cd..e2bb980 100644
--- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py
+++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
@@ -11,38 +11,64 @@
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
from datetime import timedelta, datetime
-class OverlapError(frappe.ValidationError): pass
-
class ShiftAssignment(Document):
def validate(self):
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):
- if not self.name:
- self.name = "New Shift Assignment"
+ if not self.name:
+ self.name = "New Shift Assignment"
- d = frappe.db.sql("""
- select
- name, shift_type, date
- from `tabShift Assignment`
- 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)
+ condition = """and (
+ end_date is null
+ or
+ %(start_date)s between start_date and end_date
+ """
- for date_overlap in d:
- if date_overlap['name']:
- self.throw_overlap_error(date_overlap)
+ if self.end_date:
+ condition += """ or
+ %(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):
- msg = _("Employee {0} has already applied for {1} on {2} : ").format(self.employee,
- d['shift_type'], formatdate(d['date'])) \
- + """ <b><a href="#Form/Shift Assignment/{0}">{0}</a></b>""".format(d["name"])
- frappe.throw(msg, OverlapError)
+ assigned_shifts = frappe.db.sql("""
+ select name, shift_type, start_date ,end_date, docstatus, status
+ from `tabShift Assignment`
+ where
+ 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()
def get_events(start, end, filters=None):
@@ -62,19 +88,22 @@
return events
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
from `tabShift Assignment` where
- date <= %(date)s
- and docstatus < 2"""
+ start_date >= %(start_date)s
+ and docstatus < 2""".format()
if 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 = {
"name": d.name,
"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) + \
cstr(d.shift_type),
"docstatus": d.docstatus
@@ -92,7 +121,14 @@
: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')
- 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:
shift_type_name = default_shift
if shift_type_name:
@@ -117,12 +153,13 @@
direction = '<' if next_shift_direction == 'reverse' else '>'
sort_order = 'desc' if next_shift_direction == 'reverse' else 'asc'
dates = frappe.db.get_all('Shift Assignment',
- 'date',
- {'employee':employee, 'date':(direction, for_date), 'docstatus': '1'},
+ 'start_date',
+ {'employee':employee, 'start_date':(direction, for_date), 'docstatus': '1', "status": "Active"},
as_list=True,
limit=MAX_DAYS, order_by="date "+sort_order)
+
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:
shift_type_name = shift_details.shift_type.name
for_date = date[0]
@@ -134,7 +171,7 @@
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
"""
- # 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
curr_shift = get_employee_shift(employee, for_timestamp.date(), consider_default_shift, 'forward')
if curr_shift: