diff --git a/.github/workflows/attendance.yml b/.github/workflows/attendance.yml new file mode 100644 index 0000000000..0def926f8e --- /dev/null +++ b/.github/workflows/attendance.yml @@ -0,0 +1,36 @@ +name: Deploy CI/CD to Attendance + +on: + push: # tells github to run this on any push to the repository + branches: + - attendance + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + timeout-minutes: 90 + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - name: Deploy to Attendance + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.STAGING_HOST }} + username: ${{ secrets.USERNAME }} + key: ${{ secrets.STAGING_KEY }} + port: 22 + script: | + cd /home/frappe/attendance-bench/apps/one_fm # we move into our app's folder + # git pull upstream attendance # we pull any changes from git + git pull + cd /home/frappe + pip3 install --upgrade attendance-bench + cd /home/frappe/attendance-bench + bench setup requirements one_fm + bench migrate # sync database + bench restart #${{secrets.PASSKEY}} + # we remove any unused dependencies + bench doctor diff --git a/one_fm/api/doc_events.py b/one_fm/api/doc_events.py index 58cffd371b..050492b34c 100644 --- a/one_fm/api/doc_events.py +++ b/one_fm/api/doc_events.py @@ -6,7 +6,6 @@ from frappe import _ from frappe.utils import cstr, cint, get_datetime, getdate, add_to_date from frappe.core.doctype.version.version import get_diff -from one_fm.utils import get_current_shift from one_fm.operations.doctype.operations_site.operations_site import create_notification_log import datetime from frappe.permissions import remove_user_permission diff --git a/one_fm/api/mobile/Leave_application.py b/one_fm/api/mobile/Leave_application.py index 26b08bb74d..cc42dcfeea 100644 --- a/one_fm/api/mobile/Leave_application.py +++ b/one_fm/api/mobile/Leave_application.py @@ -8,7 +8,6 @@ import base64, json from one_fm.api.v1.utils import response, validate_date from frappe.utils import cint, cstr, getdate -from one_fm.utils import get_current_shift from one_fm.api.tasks import get_action_user from one_fm.api.api import push_notification_rest_api_for_leave_application from one_fm.processor import sendemail diff --git a/one_fm/api/mobile/face_recognition.py b/one_fm/api/mobile/face_recognition.py index 4aeaad361c..348b85e838 100644 --- a/one_fm/api/mobile/face_recognition.py +++ b/one_fm/api/mobile/face_recognition.py @@ -10,14 +10,17 @@ # setup channel for face recognition face_recognition_service_url = frappe.local.conf.face_recognition_service_url -channels = [ - grpc.secure_channel(i, grpc.ssl_channel_credentials()) for i in face_recognition_service_url -] +# channels = [ +# grpc.secure_channel(i, grpc.ssl_channel_credentials()) for i in face_recognition_service_url +# ] # setup stub for face recognition -stubs = [ - facial_recognition_pb2_grpc.FaceRecognitionServiceStub(i) for i in channels -] +# stubs = [ +# facial_recognition_pb2_grpc.FaceRecognitionServiceStub(i) for i in channels +# ] + +stubs = list() + @frappe.whitelist() diff --git a/one_fm/api/tasks.py b/one_fm/api/tasks.py index 76ba2da39e..3ab7321a26 100644 --- a/one_fm/api/tasks.py +++ b/one_fm/api/tasks.py @@ -20,7 +20,6 @@ from one_fm.utils import get_current_shift from one_fm.processor import sendemail from one_fm.api.api import push_notification_for_checkin, push_notification_rest_api_for_checkin -from one_fm.operations.doctype.employee_checkin_issue.employee_checkin_issue import approve_open_employee_checkin_issue from hrms.hr.utils import get_holidays_for_employee class DeltaTemplate(Template): @@ -227,6 +226,7 @@ def checkin_checkout_supervisor_reminder(): """ frappe.enqueue(supervisor_reminder,shift = shift,today_datetime = today_datetime ,now_time=now_time, is_async=True, queue='long') + def supervisor_reminder(shift, today_datetime, now_time): title = "Checkin Report" category = "Attendance" @@ -234,6 +234,7 @@ def supervisor_reminder(shift, today_datetime, now_time): if shift.start_time < shift.end_time and nowtime() < cstr(shift.start_time): date = getdate() - timedelta(days=1) + if (strfdelta(shift.start_time, '%H:%M:%S') == cstr((get_datetime(now_time) - timedelta(minutes=cint(shift.supervisor_reminder_shift_start))).time())) or (shift.has_split_shift == 1 and strfdelta(shift.second_shift_start_time, '%H:%M:%S') == cstr((get_datetime(now_time) - timedelta(minutes=cint(shift.supervisor_reminder_shift_start))).time())): checkin_time = today_datetime + " " + strfdelta(shift.start_time, '%H:%M:%S') recipients = checkin_checkout_query(date=cstr(date), shift_type=shift.name, log_type="IN") @@ -324,9 +325,9 @@ def get_active_shifts(now_time): def checkin_checkout_query(date, shift_type, log_type): if log_type == "IN": - permission_type = ("Arrive Late", "Forget to Checkin", "Checkin Issue") + permission_type = ("Arrive Late", ) else: - permission_type = ("Leave Early", "Forget to Checkout", "Checkout Issue") + permission_type = ("Leave Early",) query = frappe.db.sql("""SELECT DISTINCT emp.user_id, emp.name , emp.employee_name, emp.holiday_list, tSA.shift FROM `tabShift Assignment` tSA, `tabEmployee` emp @@ -721,11 +722,13 @@ def fetch_non_shift(date, s_type): def assign_am_shift(): date = cstr(getdate()) end_previous_shifts("AM") + employment_type = ["Full-time", "Part-time", "Intern", "Subcontractor"] roster = frappe.db.sql(""" SELECT * from `tabEmployee Schedule` ES WHERE ES.date = '{date}' AND ES.employee_availability = "Working" + AND ES.is_replaced = 0 AND ES.roster_type = "Basic" AND ES.shift_type IN( SELECT name from `tabShift Type` st @@ -733,8 +736,9 @@ def assign_am_shift(): AND st.start_time < '13:00:00') AND ES.employee IN( SELECT name from `tabEmployee` - WHERE status = "Active") - """.format(date=cstr(date)), as_dict=1) + WHERE status = "Active" + AND employment_type IN {employment_type}) + """.format(date=cstr(date), employment_type=tuple(employment_type)), as_dict=1) non_shift = fetch_non_shift(date, "AM") if non_shift: @@ -746,11 +750,13 @@ def assign_am_shift(): def assign_pm_shift(): date = cstr(getdate()) end_previous_shifts("PM") + employment_type = ["Full-time", "Part-time", "Intern", "Subcontractor"] roster = frappe.db.sql(""" SELECT * from `tabEmployee Schedule` ES WHERE ES.date = '{date}' AND ES.employee_availability = "Working" + AND ES.is_replaced = 0 AND ES.roster_type = "Basic" AND ES.shift_type IN( SELECT name from `tabShift Type` st @@ -758,8 +764,9 @@ def assign_pm_shift(): ) AND ES.employee IN( SELECT name from `tabEmployee` - WHERE status = "Active") - """.format(date=cstr(date)), as_dict=1) + WHERE status = "Active" + AND employment_type IN {employment_type}) + """.format(date=cstr(date), employment_type=tuple(employment_type)), as_dict=1) non_shift = fetch_non_shift(date, "PM") if non_shift: @@ -788,6 +795,8 @@ def get_shift_type(time): def create_shift_assignment(roster, date, time): try: + + owner = frappe.session.user creation = now() shift_type = get_shift_type(time) @@ -849,19 +858,21 @@ def create_shift_assignment(roster, date, time): INSERT INTO `tabShift Assignment` (`name`, `company`, `docstatus`, `employee`, `employee_name`, `shift_type`, `site`, `project`, `status`, `shift_classification`, `site_location`, `start_date`, `start_datetime`, `end_datetime`, `department`, `shift`, `operations_role`, `post_abbrv`, `roster_type`, `owner`, `modified_by`, `creation`, `modified`, - `shift_request`, `check_in_site`, `check_out_site`) + `shift_request`, `check_in_site`, `check_out_site`,`custom_day_off_ot`) VALUES """ query_body = """""" # check if all roster has been done has_rostered = [] for r in roster: + _day_off_ot = 0 if not r.employee in existing_shift_list: if shift_request_dict.get(r.employee): _shift_request = shift_request_dict.get(r.employee) _shift_type = shift_types_dict.get(_shift_request.shift_type) or default_shift _project_r = frappe.db.get_value("Operations Shift", {'name':_shift_request.operations_shift}, ['project']) shift_r_start_time = date+ " " + str(_shift_type.start_time) + _day_off_ot = 1 if shift_request_dict.get(r.employee).get('purpose') == 'Day Off Overtime' else 0 if _shift_type.start_time > _shift_type.end_time: shift_r_end_time = str(add_to_date(date, days=1))+ " " + str(_shift_type.end_time) @@ -874,7 +885,7 @@ def create_shift_assignment(roster, date, time): "{_shift_request.site or ''}", "{_project_r or ''}", 'Active', '{_shift_request.shift_type}', "{sites_list_dict.get(_shift_request.site) or ''}", "{date}", "{shift_r_start_time or str(date)+' 08:00:00'}", "{shift_r_end_time or str(date)+' 17:00:00'}", "{r.department}", "{_shift_request.operations_shift or ''}", "{_shift_request.operations_role or ''}", "{r.post_abbrv or ''}", "{_shift_request.roster_type}", - "{owner}", "{owner}", "{creation}", "{creation}", "{_shift_request.name}", "{_shift_request.check_in_site}", "{_shift_request.check_out_site}"),""" + "{owner}", "{owner}", "{creation}", "{creation}", "{_shift_request.name}", "{_shift_request.check_in_site}", "{_shift_request.check_out_site}","{_day_off_ot}"),""" else: _shift_type = shift_types_dict.get(r.shift_type) or default_shift query_body += f""" @@ -883,7 +894,7 @@ def create_shift_assignment(roster, date, time): "{r.site or ''}", "{r.project or ''}", 'Active', '{_shift_type.shift_type}', "{sites_list_dict.get(r.site) or ''}", "{date}", "{_shift_type.start_datetime or str(date)+' 08:00:00'}", "{_shift_type.end_datetime or str(date)+' 17:00:00'}", "{r.department}", "{r.shift or ''}", "{r.operations_role or ''}", "{r.post_abbrv or ''}", "{r.roster_type}", - "{owner}", "{owner}", "{creation}", "{creation}", '', '', ''),""" + "{owner}", "{owner}", "{creation}", "{creation}", '', '', '',"{_day_off_ot}"),""" else: has_rostered.append(r.employee_name) @@ -911,7 +922,8 @@ def create_shift_assignment(roster, date, time): check_in_site = VALUES(check_in_site), check_out_site = VALUES(check_out_site), shift_classification = VALUES(shift_classification), - status = VALUES(status) + status = VALUES(status), + custom_day_off_ot = '{_day_off_ot}' """ frappe.db.sql(query, values=[], as_dict=1) frappe.db.commit() @@ -934,7 +946,6 @@ def validate_shift_assignment(is_scheduled_event=True): shift_request = frappe.db.sql(""" SELECT * from `tabShift Request` SR WHERE '{date}' BETWEEN SR.from_date AND SR.to_date - AND SR.assign_day_off = 0 AND SR.workflow_state = 'Approved' AND SR.employee NOT IN (Select employee from `tabShift Assignment` tSA @@ -947,11 +958,12 @@ def validate_shift_assignment(is_scheduled_event=True): IN (Select employee from `tabEmployee` E WHERE E.name = SR.employee AND E.status = 'Active')""".format(now_time=now_time,date=cstr(date), now=now), as_dict=1) - + roster = frappe.db.sql(""" SELECT * from `tabEmployee Schedule` ES WHERE ES.start_datetime = '{now}' AND ES.employee_availability = "Working" + AND ES.is_replaced = 0 AND ES.employee NOT IN (Select employee from `tabShift Assignment` tSA WHERE tSA.employee = ES.employee @@ -979,7 +991,7 @@ def validate_shift_assignment(is_scheduled_event=True): if non_shift: roster.extend(non_shift) - + if shift_request: roster_emp = shift_request employee_list = [i.employee for i in shift_request] @@ -1006,6 +1018,7 @@ def validate_am_shift_assignment(is_scheduled_event=True): ES.date = '{date}' AND ES.employee_availability = "Working" AND ES.roster_type = "Basic" + AND ES.is_replaced = 0 AND ES.shift_type IN( SELECT name from `tabShift Type` st WHERE st.start_time >= '01:00:00' @@ -1051,6 +1064,7 @@ def validate_pm_shift_assignment(): ES.date = '{date}' AND ES.employee_availability = "Working" AND ES.roster_type = "Basic" + AND ES.is_replaced = 0 AND ES.shift_type IN( SELECT name from `tabShift Type` st WHERE st.start_time < '01:00:00' OR st.start_time >= '13:00:00' @@ -1095,7 +1109,7 @@ def overtime_shift_assignment(): """ date = cstr(getdate()) now_time = add_to_date(now_datetime(), hours=1).strftime("%H:%M:00") - roster = frappe.get_all("Employee Schedule", {"date": date, "employee_availability": "Working" , "roster_type": "Over-Time"}, ["*"]) + roster = frappe.get_all("Employee Schedule", {"date": date, "employee_availability": "Working" , "roster_type": "Over-Time", "is_replaced": 0}, ["*"]) shift_request = frappe.db.sql(f"""SELECT sr.*, 'Shift Request' as doctype FROM `tabShift Request` sr WHERE '{date}' between sr.from_date and sr.to_date AND sr.roster_type = 'Over-Time' @@ -1108,7 +1122,7 @@ def process_overtime_shift(roster, date, time): for schedule in roster: #Check for employee's shift assignment of the day, if he has any. try: - if frappe.db.exists('Employee', {'employee':schedule.employee, 'status':'Active'}): + if frappe.db.exists('Employee', {'employee':schedule.employee, 'status':'Active', "employment_type": ["IN", ["Full-time", "Part-time", "Intern", "Subcontractor"]]}): shift_assignment = frappe.db.sql(f"""SELECT name, shift_type, end_datetime, roster_type from `tabShift Assignment` WHERE employee = '{schedule.employee}' AND date(end_datetime) = '{date}'""", as_dict=1) if shift_assignment: shift_end_time = frappe.get_value("Shift Type",shift_assignment[0].shift_type, "end_time") @@ -1123,7 +1137,7 @@ def process_overtime_shift(roster, date, time): except Exception as e: continue -def create_overtime_shift_assignment(schedule, date): +def create_overtime_shift_assignment(schedule, date): try: shift_assignment = frappe.new_doc("Shift Assignment") shift_assignment.start_date = date @@ -1620,7 +1634,9 @@ def get_current_schedules(employee, log_type=None): # Attendance Request, Leaves employee_doc = frappe.db.get_value("Employee", employee, ['name', 'holiday_list']) start_date = str(getdate()) - curr_shift = get_current_shift(employee) + shift_exists = get_current_shift(employee) + if shift_exists['type'] == "On Time": + curr_shift = shift_exists['data'] # check day off if frappe.db.exists('Employee Schedule', { 'employee':employee, @@ -1701,27 +1717,23 @@ def fetch_employees_not_in_checkin(): for log_type in log_types: # capture current minute if log_type=='IN': - reminder_minutes = [i.minute for i in frappe.db.sql(""" + initial_reminder_minutes = [i.minute for i in frappe.db.sql(""" SELECT notification_reminder_after_shift_start as minute FROM `tabShift Type` WHERE notification_reminder_after_shift_start>0 GROUP BY notification_reminder_after_shift_start; """, as_dict=1)] - - - late_entry_reminder_minutes = [i.minute for i in frappe.db.sql(""" + grace_period_minute = [i.minute for i in frappe.db.sql(""" SELECT late_entry_grace_period as minute FROM `tabShift Type` WHERE late_entry_grace_period>0 GROUP BY late_entry_grace_period; """, as_dict=1)] - - supervisor_reminder_minutes = [i.minute for i in frappe.db.sql(""" SELECT supervisor_reminder_shift_start as minute FROM `tabShift Type` WHERE supervisor_reminder_shift_start>0 GROUP BY supervisor_reminder_shift_start; """, as_dict=1)] else: - reminder_minutes = [i.minute for i in frappe.db.sql(""" + initial_reminder_minutes = [i.minute for i in frappe.db.sql(""" SELECT notification_reminder_after_shift_end as minute FROM `tabShift Type` WHERE notification_reminder_after_shift_end>0 GROUP BY notification_reminder_after_shift_end; @@ -1738,14 +1750,12 @@ def fetch_employees_not_in_checkin(): shift_assignments_employees_list = frappe.db.sql(f""" SELECT DISTINCT sa.employee, sa.shift_type, sa.start_datetime, sa.end_datetime, sa.shift as operations_shift, st.notification_reminder_after_shift_start, - st.notification_reminder_after_shift_end, st.supervisor_reminder_shift_start, - st.supervisor_reminder_start_ends,st.late_entry_grace_period, os.supervisor as shift_supervisor, + st.notification_reminder_after_shift_end, st.late_entry_grace_period, st.supervisor_reminder_shift_start, + st.supervisor_reminder_start_ends, os.supervisor as shift_supervisor, osi.account_supervisor as site_supervisor,sa.name as shift_assignment_id - FROM `tabShift Assignment` sa RIGHT JOIN `tabShift Type` st ON sa.shift_type=st.name RIGHT JOIN `tabOperations Shift` os ON sa.shift=os.name RIGHT JOIN `tabOperations Site` osi ON sa.site=osi.name - WHERE {'sa.start_datetime' if log_type=='IN' else 'sa.end_datetime'}='{cur_date} {shift_start_time}' AND sa.status='Active' AND sa.docstatus=1 GROUP BY sa.employee @@ -1767,7 +1777,6 @@ def fetch_employees_not_in_checkin(): GROUP BY employee """, as_dict=1)] employees_yet_to_checkin = [i for i in shift_assignments_employees if not i in checkins] - # check shift permissions, attendance request, leave application shift_permissions = [i.employee for i in frappe.db.sql(f""" SELECT employee FROM `tabShift Permission` @@ -1804,37 +1813,38 @@ def fetch_employees_not_in_checkin(): # holiday list holiday_list = [i for i,j in get_holiday_today(cur_date).items()] - holiday_list_employees = [i.name for i in frappe.db.get_list("Employee", filters={ - 'name': ['IN', employees_yet_to_checkin], - 'status':'Active', - 'holiday_list': ['IN', holiday_list] - })] + holiday_list_tuple = str(tuple(holiday_list)).replace(',)', ')') + holiday_list_employees = [i.name for i in frappe.db.sql(f"""SELECT name from `tabEmployee` WHERE + status = 'Active' AND + holiday_list IN {holiday_list_tuple} + """)] employees_yet_to_checkin = [i for i in employees_yet_to_checkin if not i in holiday_list_employees] - + employee_details = frappe.db.get_list("Employee", filters={ 'name': ['IN', employees_yet_to_checkin]}, fields=['name', 'employee_id', 'employee_name', 'user_id', 'prefered_contact_email', 'prefered_email', 'reports_to'] ) - if log_type=='IN': for i in employee_details: if shift_assignments_employees_dict.get(i.name): i = {**i, **shift_assignments_employees_dict.get(i.name), **{'log_type':'IN'}} del i['name'] - # check if is_after_grace_period - if minute in reminder_minutes:i['is_after_grace_checkin'] = True - #Setup this way because the users do not want both emails(late_entry and is_after_grace_checkin) sent - else:i['is_after_grace_checkin'] = False - if minute in late_entry_reminder_minutes:i['is_late_entry_checkin'] = True - else:i['is_late_entry_checkin'] = False + if minute in initial_reminder_minutes: + i['initial_checkin_reminder']=True + else: + i['initial_checkin_reminder']=False # check if supervisor reminder - if minute in supervisor_reminder_minutes:i['is_supervisor_checkin_reminder'] = True - else:i['is_supervisor_checkin_reminder'] = False + if minute in supervisor_reminder_minutes: + i['is_supervisor_checkin_reminder'] = True + else: + i['is_supervisor_checkin_reminder'] = False # check initial - if (minute==i['start_datetime'].minute and hour==i['start_datetime'].hour):i['initial_checkin_reminder']=True - else:i['initial_checkin_reminder']=False + if minute in grace_period_minute: + i['is_after_grace_checkin'] = True + else: + i['is_after_grace_checkin'] = False return_data.append(frappe._dict(i)) else: for i in employee_details: @@ -1842,16 +1852,16 @@ def fetch_employees_not_in_checkin(): i = {**i, **shift_assignments_employees_dict.get(i.name), **{'log_type':'OUT'}} del i['name'] # check if is_after_grace_period - if minute in reminder_minutes:i['is_after_grace_checkout'] = True - else:i['is_after_grace_checkout'] = False + if minute in initial_reminder_minutes: + i['initial_checkout_reminder']=True + else: + i['initial_checkout_reminder']=False # check if supervisor reminder - if minute in supervisor_reminder_minutes:i['is_supervisor_checkout_reminder'] = True - else:i['is_supervisor_checkout_reminder'] = False - # check initial - if (minute==i['end_datetime'].minute and hour==i['end_datetime'].hour):i['initial_checkout_reminder']=True - else:i['initial_checkout_reminder']=False + if minute in supervisor_reminder_minutes: + i['is_supervisor_checkout_reminder'] = True + else: + i['is_supervisor_checkout_reminder'] = False return_data.append(frappe._dict(i)) - return frappe._dict({ 'employees':return_data, # 'reminder_minutes':reminder_minutes, @@ -2115,8 +2125,8 @@ def notify_approver_about_pending_shift_request(is_scheduled_event=True): FROM `tabShift Request` sr LEFT JOIN `tabOperations Shift` os ON sr.operations_shift = os.name LEFT JOIN `tabShift Request Approvers` ap ON sr.name = ap.parent - WHERE sr.workflow_state = 'Pending Approval' - AND ap.parentfield="shift_request_approver" + WHERE sr.workflow_state = 'Pending Approver' + AND ap.parentfield="custom_shift_approvers" AND sr.from_date = %s AND os.start_time BETWEEN %s AND %s """, (date_time.date(), date_time.time(), one_hour.time()), as_dict=1) @@ -2132,9 +2142,8 @@ def notify_approver_about_pending_shift_request(is_scheduled_event=True): data_dict.get(obj["shift_approver"]).append({obj.get("employee_name"): get_url_to_form("Shift Request", obj.get("name"))}) - for key, value in data_dict.items(): title = "Pending Shift Request for upcoming shift" - msg = frappe.render_template('one_fm/templates/emails/notify_shift_request_approver.html', context={"data": value}) sendemail(recipients=key, subject=title, content=msg, is_scheduler_email=is_scheduled_event) + diff --git a/one_fm/api/v1/employee_checkin_issue.py b/one_fm/api/v1/employee_checkin_issue.py index fdcbdf2f0b..c8097d9507 100644 --- a/one_fm/api/v1/employee_checkin_issue.py +++ b/one_fm/api/v1/employee_checkin_issue.py @@ -5,17 +5,18 @@ from one_fm.api.notification import create_notification_log from one_fm.api.v1.utils import response, validate_date, validate_time from one_fm.operations.doctype.employee_checkin_issue.employee_checkin_issue import fetch_approver +from frappe.model.workflow import apply_workflow +from frappe.utils import getdate @frappe.whitelist() -def create_employee_checkin_issue(employee_id: str = None, log_type: str = None, issue_type: str = None, date: str = None, - issue_details: str = None, latitude: str = None, longitude: str = None) -> dict: +def create_employee_checkin_issue(employee_id: str = None, log_type: str = None, issue_type: str = None, + issue_details: str = None, latitude: float = None, longitude: float = None) -> dict: """This method creates a employee checkin issue for a given employee. Args: employee (str): employee id log_type (str): type of log(IN/OUT). issue_type (str): type of issue requested. - date (str): yyyy-mm-dd issue_details (str): reason to create a employee checkin issue latitude (float, optional): Latitude od user. longitude (float, optional): Longitude od user. @@ -29,6 +30,7 @@ def create_employee_checkin_issue(employee_id: str = None, log_type: str = None, } """ try: + date = getdate() if not employee_id: return response("Bad Request", 400, None, "employee_id required.") @@ -38,8 +40,6 @@ def create_employee_checkin_issue(employee_id: str = None, log_type: str = None, if not issue_type: return response("Bad Request", 400, None, "issue_type required.") - if not date: - return response("Bad Request", 400, None, "date required.") if issue_type == "Other": if not issue_details: @@ -65,11 +65,6 @@ def create_employee_checkin_issue(employee_id: str = None, log_type: str = None, except: return response("Bad Request", 400, None, "Latitude and longitude must be float.") - if not isinstance(date, str): - return response("Bad Request", 400, None, "date must be of type str.") - - if not validate_date(date): - return response("Bad Request", 400, None, "date must be of type yyyy-mm-dd.") employee = frappe.db.get_value("Employee", {"employee_id": employee_id}) @@ -77,10 +72,13 @@ def create_employee_checkin_issue(employee_id: str = None, log_type: str = None, return response("Resource Not Found", 404, None, "No employee found with {employee_id}" .format(employee_id=employee_id)) - shift_details = get_shift_details(employee) + shift_details = fetch_approver(employee) - if shift_details.found: - shift, shift_type, shift_assignment, shift_supervisor = shift_details.data + if shift_details: + shift_assignment = shift_details['assigned_shift'] + shift_supervisor = shift_details['shift_supervisor'] + shift = shift_details['shift'] + shift_type = shift_details['shift_type'] else: return response("Resource Not Found", 404, None, "shift not found in employee schedule for {employee}" .format(employee=employee)) @@ -96,6 +94,10 @@ def create_employee_checkin_issue(employee_id: str = None, log_type: str = None, if not shift_supervisor: return response("Resource Not Found", 404, None, "shift supervisor not found for {employee}" .format(employee=employee_id)) + + if frappe.db.exists("Attendance", {"shift_assignment": shift_assignment}): + return response("Attendance Exists", 400, None, f"Attendance already marked for {date}") + if not frappe.db.exists("Employee Checkin Issue", {"employee": employee, "date": date, "assigned_shift": shift_assignment, "log_type": log_type, "issue_type": issue_type}): @@ -112,7 +114,10 @@ def create_employee_checkin_issue(employee_id: str = None, log_type: str = None, employee_checkin_issue_doc.shift_supervisor = shift_supervisor employee_checkin_issue_doc.shift = shift employee_checkin_issue_doc.shift_type = shift_type + employee_checkin_issue_doc.flags.ignore_permissions = 1 employee_checkin_issue_doc.save() + apply_workflow(employee_checkin_issue_doc, "Submit for Approval") + frappe.db.commit() return response("Success", 201, employee_checkin_issue_doc.as_dict()) diff --git a/one_fm/api/v1/face_recognition.py b/one_fm/api/v1/face_recognition.py index a2f07cb07a..1b2d3babbb 100644 --- a/one_fm/api/v1/face_recognition.py +++ b/one_fm/api/v1/face_recognition.py @@ -3,14 +3,14 @@ from one_fm.one_fm.page.face_recognition.face_recognition import ( update_onboarding_employee, check_existing, ) -from one_fm.utils import get_current_shift +from one_fm.utils import get_current_shift, is_holiday from one_fm.api.v1.utils import ( response, verify_via_face_recogniton_service ) from frappe.utils import cstr, getdate,now_datetime from one_fm.proto import facial_recognition_pb2, facial_recognition_pb2_grpc, enroll_pb2, enroll_pb2_grpc from one_fm.api.doc_events import haversine - +from one_fm.overrides.employee import has_day_off, is_employee_on_leave # setup channel for face recognition @@ -34,12 +34,11 @@ def base64_to_mp4(base64_string): @frappe.whitelist() -def enroll(employee_id: str = None, video: str = None, filename: str = None) -> dict: +def enroll(employee_id: str = None, filename: str = None, video: str = None) -> dict: """This method enrolls the user face into the system for future face recognition use cases. Args: employee_id (str): employee_id of user - video (str): Base64 encoded string of the video captured of user's face. Returns: response (dict): { @@ -81,29 +80,27 @@ def enroll(employee_id: str = None, video: str = None, filename: str = None) -> return response("Bad Request", 400, None, message) # Set a context flag to indicate an API update (It will affect in 'Employee' validate method) - frappe.flags.allow_enrollment_update = True - + frappe.flags.allow_enrollment_update = True doc.enrolled = 1 doc.save(ignore_permissions=True) update_onboarding_employee(doc) frappe.db.commit() - return response("Success", 201, "User enrolled successfully.
Please wait for 10sec, you will be redirected to checkin.") - + return response("Success", 201, + "User enrolled successfully.
Please wait for 10sec, you will be redirected to checkin.") except Exception as error: frappe.log_error(message=frappe.get_traceback(), title="Enrollment") return response("Internal Server Error", 500, None, error) @frappe.whitelist() -def verify_checkin_checkout(employee_id: str = None, video : str = None, log_type: str = None, - skip_attendance: str = None, latitude: str = None, longitude: str = None, - filename=None): +def verify_checkin_checkout(employee_id: str = None, log_type: str = None, + skip_attendance: str = None, latitude: str = None, longitude: str = None, + filename: str = None): """This method verifies user checking in/checking out. Args: employee_id (srt): employee_id of user - video (str, optional): base64 encoded video of user checking in/checking out. log_type (str, optional): IN/OUT skip_attendance (int, optional): 0/1. latitude (float, optional): Latitude od user. @@ -123,15 +120,13 @@ def verify_checkin_checkout(employee_id: str = None, video : str = None, log_typ skip_attendance = int(skip_attendance) if skip_attendance else 0 latitude = float(latitude) longitude = float(longitude) - except Exception as e: - return response("Bad Request", 400, None, "skip_attendance must be an integer, latitude and longitude must be float.") + except: + return response("Bad Request", 400, None, + "skip_attendance must be an integer, latitude and longitude must be float.") if not employee_id: return response("Bad Request", 400, None, "employee_id required.") - if not video: - return response("Bad Request", 400, None, "video required.") - if not log_type: return response("Bad Request", 400, None, "log_type required.") @@ -144,19 +139,19 @@ def verify_checkin_checkout(employee_id: str = None, video : str = None, log_typ if not longitude: return response("Bad Request", 400, None, "longitude required.") - if not isinstance(video, str): - return response("Bad Request", 400, None, "video must be of type str.") + if not filename: + return response("Bad Request", 400, None, "Filename is required.") if not isinstance(log_type, str): return response("Bad Request", 400, None, "log_type must be of type str.") - if log_type not in ["IN", "OUT"]: + if log_type not in {"IN", "OUT"}: return response("Bad Request", 400, None, "Invalid log_type. log_type must be IN/OUT.") if not isinstance(skip_attendance, int): return response("Bad Request", 400, None, "skip_attendance must be of type int.") - if skip_attendance not in [0, 1]: + if skip_attendance not in {0, 1}: return response("Bad Request", 400, "Invalid skip_attendance. skip_attendance must be 0 or 1.") if not isinstance(latitude, float): @@ -165,6 +160,10 @@ def verify_checkin_checkout(employee_id: str = None, video : str = None, log_typ if not isinstance(longitude, float): return response("Bad Request", 400, None, "longitude must be of type float.") + video_file = frappe.request.files.get("video_file") + if not video_file: + return response("Bad Request", 400, None, "Video File is required.") + employee = frappe.db.get_value("Employee", {"employee_id": employee_id}) if not employee: @@ -172,7 +171,7 @@ def verify_checkin_checkout(employee_id: str = None, video : str = None, log_typ # check Face Recognition Endpoint endpoint_state = frappe.db.get_single_value("ONEFM General Setting", 'enable_face_recognition_endpoint') - video_file = video or frappe.request.files.get("video_file") or frappe.request.files.get("video") + video_file = frappe.request.files.get("video_file") or frappe.request.files.get("video") if not filename: filename = frappe.session.user+'.mp4' if endpoint_state: @@ -194,20 +193,22 @@ def verify_checkin_checkout(employee_id: str = None, video : str = None, log_typ return response("Internal Server Error", 500, None, error) -def create_checkin_log(employee: str, log_type: str, skip_attendance: int, latitude: float, longitude: float, source: str) -> dict: +def create_checkin_log(employee: str, log_type: str, skip_attendance: int, latitude: float, longitude: float, + source: str) -> dict: checkin = frappe.new_doc("Employee Checkin") checkin.employee = employee checkin.log_type = log_type - checkin.device_id = frappe.utils.cstr(latitude)+","+frappe.utils.cstr(longitude) - checkin.skip_auto_attendance = 0 #skip_attendance + checkin.device_id = frappe.utils.cstr(latitude) + "," + frappe.utils.cstr(longitude) + checkin.skip_auto_attendance = 0 #skip_attendance checkin.source = source checkin.save() frappe.db.commit() return checkin.as_dict() + def check_employee_non_shift(employee): - shift_working, employement_type = frappe.get_value("Employee", employee, ["shift_working","employment_type"]) - if shift_working==0 and employement_type!="Contract": + shift_working, employement_type = frappe.get_value("Employee", employee, ["shift_working", "employment_type"]) + if shift_working == 0 and employement_type != "Contract": return True return False @@ -215,19 +216,11 @@ def has_day_off(employee,date): """ Confirm if the employee schedule for that day and employee is set to day off """ - is_day_off = False + return frappe.db.exists("Employee Schedule", {"employee": employee, "date": date, "employee_availability": "Day Off"}) - schedule = frappe.db.exists("Employee Schedule", {'employee':employee,'date':date}) - existing_schedule = frappe.get_value("Employee Schedule", schedule, ['employee_availability']) if schedule else None - - if existing_schedule: - if existing_schedule == 'Day Off': - is_day_off = True - return is_day_off @frappe.whitelist() def get_site_location(employee_id: str = None, latitude: float = None, longitude: float = None) -> dict: - try: if not employee_id: return response("Bad Request", 400, None, "employee_id required.") @@ -240,91 +233,205 @@ def get_site_location(employee_id: str = None, latitude: float = None, longitude if not isinstance(employee_id, str): return response("Bad Request", 400, None, "employee must be of type str.") - - employee = frappe.db.get_value("Employee", {"employee_id": employee_id}) + + employee = frappe.db.get_value("Employee", {"employee_id": employee_id}, ["name", "employee_name", "shift_working"], as_dict=1) if not employee: - return response("Resource Not Found", 404, None, "No employee found with {employee_id}".format(employee_id=employee_id)) + return response("Resource Not Found", 404, None, + "No employee found with {employee_id}".format(employee_id=employee_id)) + + shift = False + shift_details = get_current_shift(employee.name) + if shift_details: + if shift_details['type'] == "Early": + # check if user can checkin with the correct time + return response("Resource Not Found", 404, None, + f"You are checking in too early, checkin is allowed in {shift_details['data']} minutes ") + elif shift_details['type'] == "Late": + return response("Resource Not Found", 404, None, + f"You are checking out too late, checkout was allowed {shift_details['data']} minutes ago ") + elif shift_details['type'] == "On Time": + shift = shift_details['data'] # Return the object of Shift Assignment - shift = get_current_shift(employee) date = cstr(getdate()) - - site, location = None, None + if shift: - if not shift.can_checkin_out: - # check if user can checkin with the correct time - return response("Resource Not Found", 404, None, "Your are outside shift hours") - - log_type = shift.check_existing_checking() - if log_type=='IN': - if shift.after_4hrs(): - # check if hrs has passed since shift start. Here we can also allow those who checked out tp checkin by checkin if OUT exist for same shift - return response("Resource Not Found", 404, None, "You are 4 or more hours late, you cannot checkin at this time.") - - if frappe.db.exists("Shift Request", { - "employee":employee, 'from_date':['<=',date], - 'to_date':['>=',date], "status": "Approved"} - ): - check_in_site, check_out_site = frappe.get_value("Shift Request", { - "employee":employee, 'from_date':['<=',date],'to_date':['>=',date], - "status": "Approved"},["check_in_site","check_out_site"] - ) - if log_type == "IN": - site = check_in_site - location = frappe.get_list("Location", {'name':check_in_site}, ["latitude","longitude", "geofence_radius"]) - else: - site = check_out_site - location = frappe.get_list("Location", {'name':check_out_site}, ["latitude","longitude", "geofence_radius"]) - - else: - if shift.site_location: - site = shift.site_location - location = frappe.get_list("Location", {'name':shift.site_location}, ["latitude","longitude", "geofence_radius"]) - elif shift.shift: - site = frappe.get_value("Operations Shift", shift.shift, "site") - location= frappe.db.sql(""" - SELECT loc.latitude, loc.longitude, loc.geofence_radius - FROM `tabLocation` as loc - WHERE - loc.name IN (SELECT site_location FROM `tabOperations Site` where name="{site}") - """.format(site=site), as_dict=1) - - - if not site: - if has_day_off(employee,date): - employee_name = frappe.get_value("Employee",employee,'employee_name') - return response("Resource Not Found", 404, None, f"Dear {employee_name}, Today is your day off. Happy Recharging!.") - return response("Resource Not Found", 404, None, "User not assigned to a shift.") + if shift.is_replaced == 1: + return response("Resource Not Found", 404, None, f"You have been replaced with another Employee") - if not location and site: - return response("Resource Not Found", 404, None, "No site location set for {site}".format(site=site)) + if is_attendance_request_exists(employee.name, date): + return response("Resource Not Found", 404, None, + f"You have an attendance request for today. Your attendance will be marked.") - result=location[0] - result['user_within_geofence_radius'] = True + log_type = shift.get_next_checkin_log_type() + if log_type == 'IN': + if shift.after_4hrs(): + # check if hrs has passed since shift start. Here we can also allow those who checked out tp checkin by checkin if OUT exist for same shift + return response("Resource Not Found", 404, None, + "You are 4 or more hours late, you cannot checkin at this time.") + + location = get_shift_site_location(shift, date, log_type) + site = frappe.get_value("Operations Shift", shift.shift, "site") + + if location: + result = location + result['user_within_geofence_radius'] = True + + distance = float(haversine(result.latitude, result.longitude, latitude, longitude)) + if distance > float(result.geofence_radius): + result['user_within_geofence_radius'] = False + + result['site_name'] = site + if shift: + result['shift'] = shift + + # log to checkin radius log + data = result.copy() + data = { + **data, + **{'employee': employee_id, 'user_latitude': latitude, 'user_longitude': longitude, + 'user_distance': distance, 'diff': distance - result.geofence_radius} + } + if not result['user_within_geofence_radius']: + frappe.enqueue( + 'one_fm.operations.doctype.checkin_radius_log.checkin_radius_log.create_checkin_radius_log', + **{'data': data}) + result['log_type'] = log_type + return response("Success", 200, result) + + elif site: + return response("Resource Not Found", 404, None, "No site location set for {site}".format(site=site)) - distance = float(haversine(result.latitude, result.longitude, latitude, longitude)) - if distance > float(result.geofence_radius): - result['user_within_geofence_radius'] = False + else: + if employee.shift_working: + if has_day_off(employee.name, date): + return response("Resource Not Found", 404, None, + f"Dear {employee.employee_name}, Today is your day off. Happy Recharging!.") - result['site_name'] = site - if shift: - result['shift'] = shift + if is_employee_on_leave(employee.name, date): + return response("Resource Not Found", 404, None, "You are currently on leave, see you soon!") - # log to checkin radius log - data = result.copy() - data = { - **data, - **{'employee':employee_id, 'user_latitude':latitude, 'user_longitude':longitude, 'user_distance':distance, 'diff':distance-result.geofence_radius} - } - if not result['user_within_geofence_radius']: - frappe.enqueue('one_fm.operations.doctype.checkin_radius_log.checkin_radius_log.create_checkin_radius_log', **{'data':data}) - result['log_type'] = log_type - return response("Success", 200, result) + status, message = is_holiday(employee=employee, date=date) + if status: + return response("Resource Not Found", 404, None, message) + return response("Resource Not Found", 404, None, "User not assigned to a shift.") except Exception as error: frappe.log_error(title="API Site location", message=frappe.get_traceback()) return response("Internal Server Error", 500, None, error) +def is_attendance_request_exists(employee, date): + return frappe.db.exists( + "Attendance Request", + { + "employee": employee, + "from_date": ["<=", date], + "to_date": [">=", date], + "docstatus": 1 + } + ) + + +def get_shift_site_location(shift, date, log_type): + """ + Method to retrieves the site location details (latitude, longitude, and optionally geofence radius) + for a given shift on a specific date, considering both shift and shift request information. + + Args: + shift (object): A object of Shift Assignment + date (str): The date (YYYY-MM-DD format) for which to retrieve the location. + log_type (str): "IN" or "OUT". + + Return: + dict (or None): If a valid location is found, a dictionary containing the following keys is returned: + latitude (float): The latitude of the site location. + longitude (float): The longitude of the site location. + geofence_radius (float, optional): The geofence radius of the site location. + None: If no valid location information is found. + """ + location = get_shift_request_site_location(shift.employee, date, log_type) + if not location: + if shift.site_location: + return frappe.get_value( + "Location", + {"name": shift.site_location}, + ["latitude", "longitude", "geofence_radius"], + as_dict=True + ) + elif shift.shift: + # Fetch the site from Operations Shift to get the location of the Site + site = frappe.get_value("Operations Shift", shift.shift, "site") + return frappe.db.sql(""" + SELECT + loc.latitude, loc.longitude, loc.geofence_radius + FROM + `tabLocation` as loc + WHERE + loc.name IN ( + SELECT site_location FROM `tabOperations Site` where name="{site}" + ) + """.format(site=site), as_dict=1) + return location + + +def get_shift_request_site_location(employee, date, log_type): + """ + This function retrieves the site location details for an employee's approved shift request on a specific date. + It checks for an "Approved" shift request that overlaps with the provided date and returns the corresponding check-in + or check-out site location (depending on the log_type) along with its latitude, longitude, and geofence radius. + + Args: + employee (str): The employee ID for whom to fetch the shift request details. + date (str): The date (YYYY-MM-DD format) for which to check the shift request. + log_type (str): "IN" or "OUT". This specifies whether to retrieve the check-in + or check-out site location from the shift request. + + Return: + dict (or None): If an approved shift request is found overlapping the provided date and log_type, + a dictionary containing the following keys is returned: + latitude (float): The latitude of the site location. + longitude (float): The longitude of the site location. + geofence_radius (float): The geofence radius of the site location (optional, depending on your data model). + None: If no approved shift request is found or there's an error retrieving the location details. + """ + + location = False + shift_request_exists = frappe.db.exists( + "Shift Request", + { + "employee": employee, + "from_date": ["<=", date], + "to_date": [">=", date], + "status": "Approved" + } + ) + if shift_request_exists: + shift_request_details = frappe.get_value( + "Shift Request", + { + "employee": employee, + "from_date": ["<=", date], + "to_date": [">=", date], + "status": "Approved" + }, + ["check_in_site", "check_out_site"], + as_dict=True + ) + if log_type == "IN": + # Fetch check in site location from shift request + location = shift_request_details.check_in_site + else: + # Fetch check out site location from shift request + location = shift_request_details.check_out_site + if location: + # Return the location details + return frappe.get_value( + "Location", + {"name": location}, + ["latitude", "longitude", "geofence_radius"], + as_dict=True + ) + return None @frappe.whitelist() def checkin_list(employee_id, from_date, to_date): """ @@ -344,4 +451,4 @@ def checkin_list(employee_id, from_date, to_date): return response("success", 200, checkins) except Exception as e: return response("error", 500, None, str(e)) - \ No newline at end of file + diff --git a/one_fm/api/v1/leave_application.py b/one_fm/api/v1/leave_application.py index 8b23fbd1fa..3f4ec2bc22 100644 --- a/one_fm/api/v1/leave_application.py +++ b/one_fm/api/v1/leave_application.py @@ -12,10 +12,8 @@ from one_fm.api.api import upload_file from one_fm.api.tasks import get_action_user,get_notification_user from one_fm.api.v1.utils import response, validate_date -from one_fm.utils import ( - get_current_shift, check_if_backdate_allowed, - get_approver, get_approver_user, -) +from frappe.utils import cint, cstr, getdate +from one_fm.utils import check_if_backdate_allowed, get_approver from one_fm.api.utils import validate_sick_leave_attachment @frappe.whitelist() diff --git a/one_fm/api/v1/legal.py b/one_fm/api/v1/legal.py index 0bdcafd01b..f030f9b619 100644 --- a/one_fm/api/v1/legal.py +++ b/one_fm/api/v1/legal.py @@ -13,15 +13,17 @@ # setup channel for face recognition face_recognition_service_url = frappe.local.conf.face_recognition_service_url -options = [('grpc.max_message_length', 100 * 1024 * 1024* 10)] -channels = [ - grpc.secure_channel(i, grpc.ssl_channel_credentials(), options=options) for i in face_recognition_service_url -] +# options = [('grpc.max_message_length', 100 * 1024 * 1024* 10)] +# channels = [ +# grpc.secure_channel(i, grpc.ssl_channel_credentials(), options=options) for i in face_recognition_service_url +# ] # setup stub for face recognition -stubs = [ - facial_recognition_pb2_grpc.FaceRecognitionServiceStub(i) for i in channels -] +# stubs = [ +# facial_recognition_pb2_grpc.FaceRecognitionServiceStub(i) for i in channels +# ] + +stubs = list() @frappe.whitelist() diff --git a/one_fm/api/v1/roster.py b/one_fm/api/v1/roster.py index 3dde6a69ae..bdabdee40b 100644 --- a/one_fm/api/v1/roster.py +++ b/one_fm/api/v1/roster.py @@ -7,7 +7,6 @@ from frappe.client import attach_file from one_fm.one_fm.page.roster.roster import get_post_view as _get_post_view # , get_roster_view as _get_roster_view from one_fm.api.v1.utils import response -from one_fm.utils import get_current_shift from one_fm.one_fm.page.roster.employee_map import CreateMap, PostMap diff --git a/one_fm/api/v1/shift_permission.py b/one_fm/api/v1/shift_permission.py index bdc050a30f..2772fd92fe 100644 --- a/one_fm/api/v1/shift_permission.py +++ b/one_fm/api/v1/shift_permission.py @@ -26,17 +26,16 @@ def get_approver_details(employee_id): message = frappe.get_traceback() frappe.log_error(title="Error fetching approver", message=message) return response("Internal Server Error", 500, None, message) + @frappe.whitelist() -def create_shift_permission(employee_id: str = None, log_type: str = None, permission_type: str = None, date: str = None, - reason: str = None, leaving_time: str = None, arrival_time: str = None, latitude: str = None, - longitude: str = None) -> dict: +def create_shift_permission(employee_id: str = None, log_type: str = None, date: str = None, + reason: str = None, leaving_time: str = None, arrival_time: str = None) -> dict: """This method creates a shift permission for a given employee. Args: employee (str): employee id log_type (str): type of log(IN/OUT). - permission_type (str): type of permission requested. date (str): yyyy-mm-dd reason (str): reason to create a shift permission leaving_time (str): time for leave in hh:mm:ss @@ -58,9 +57,6 @@ def create_shift_permission(employee_id: str = None, log_type: str = None, permi if not employee_id: return response("Bad Request", 400, None, "employee_id required.") - if not permission_type: - return response("Bad Request", 400, None, "permission_type required.") - if not date: return response("Bad Request", 400, None, "date required.") @@ -68,52 +64,19 @@ def create_shift_permission(employee_id: str = None, log_type: str = None, permi return response("Bad Request", 400, None, "reason required.") if not log_type: - if permission_type in ['Arrive Late', 'Forget to Checkin', 'Checkin Issue']: - log_type='IN' - elif permission_type in ['Leave Early', 'Forget to Checkout', 'Checkout Issue']: - log_type='OUT' - else: - return response("Bad Request", 400, None, "log_type required.") - - if log_type == "IN" and permission_type not in ['Arrive Late', 'Forget to Checkin', 'Checkin Issue']: - - return response("Bad Request", 400, None, _('Permission Type cannot be {0}. It should be one of \ - "Arrive Late", "Forget to Checkin", "Checkin Issue" for Log Type "IN"'.format(permission_type))) - - if log_type == "OUT" and permission_type not in ['Leave Early', 'Forget to Checkout', 'Checkout Issue']: - - return response("Bad Request", 400, None, _('Permission Type cannot be {0}. It should be one of \ - "Leave Early", "Forget to Checkout", "Checkout Issue" for Log Type "OUT"'.format(permission_type))) - - if permission_type == "Arrive Late" and not arrival_time: + return response("Bad Request", 400, None, "log_type required.") + if log_type == "IN" and not arrival_time: return response("Bad Request", 400, None, "Arrival time required for late arrival shift permission.") - if permission_type == "Leave Early" and not leaving_time: - + if log_type == "OUT" and not leaving_time: return response("Bad Request", 400, None, "Leaving time required for early exit shift permission") if not isinstance(employee_id, str): return response("Bad Request", 400, None, "employee must be of type str.") - if not isinstance(permission_type, str): - - return response("Bad Request", 400, None, "permission_type must be of type str.") - - if permission_type not in ["Arrive Late", "Leave Early", "Checkin Issue", "Checkout Issue","Forget to Checkout","Forget to Checkin"]: - - return response("Bad Request", 400, None, "permission type must be either 'Arrive Late' or 'Leave Early' or 'Checkin Issue' or 'Checkout Issue'.") - if permission_type in ["Checkin Issue", "Checkout Issue"] and latitude and longitude: - try: - latitude = float(latitude) - longitude = float(longitude) - except: - frappe.log_error(title="API Shift Permission", message= "Latitude and longitude must be float.") - return response("Bad Request", 400, None, "Latitude and longitude must be float.") if not isinstance(date, str): - - frappe.log_error(title="API Shift Permission", message="date must be of type str.") return response("Bad Request", 400, None, "date must be of type str.") if not validate_date(date): @@ -169,20 +132,16 @@ def create_shift_permission(employee_id: str = None, log_type: str = None, permi return response("Resource Not Found", 404, None, "shift supervisor not found for {employee}".format(employee=employee_id)) - if not frappe.db.exists("Shift Permission", {"employee": employee, "date": date, "assigned_shift": shift_assignment, "permission_type": permission_type}): + if not frappe.db.exists("Shift Permission", {"employee": employee, "date": date, "assigned_shift": shift_assignment, "log_type": log_type}): shift_permission_doc = frappe.new_doc('Shift Permission') shift_permission_doc.employee = employee shift_permission_doc.date = date shift_permission_doc.log_type = log_type - shift_permission_doc.permission_type = permission_type shift_permission_doc.reason = reason - if permission_type == "Arrive Late" and arrival_time: + if log_type == "IN" and arrival_time: shift_permission_doc.arrival_time = arrival_time - if permission_type == "Leave Early" and leaving_time: + if log_type == "OUT" and leaving_time: shift_permission_doc.leaving_time = leaving_time - if permission_type in ["Checkin Issue", "Checkout Issue"]: - shift_permission_doc.latitude = latitude if latitude else 0.0 - shift_permission_doc.longitude = longitude if longitude else 0.0 shift_permission_doc.assigned_shift = shift_assignment shift_permission_doc.shift_supervisor = shift_supervisor shift_permission_doc.shift = shift @@ -223,7 +182,7 @@ def list_shift_permission(employee_id: str = None): try: if not employee_id: return response("Bad Request", 400, None, "employee_id required.") - + if not isinstance(employee_id, str): return response("Bad Request", 400, None, "employee_id must be of type str.") @@ -232,8 +191,8 @@ def list_shift_permission(employee_id: str = None): if not employee: return response("Resource Not Found", 404, None, "No employee found with {employee_id}".format(employee_id=employee_id)) - shift_permission_list = frappe.get_list("Shift Permission", filters={'docstatus':['<',2],'employee': employee}, fields=["name", "date",'log_type','permission_type','emp_name', "workflow_state",'docstatus']) - line_manager_shift_permission = frappe.get_list("Shift Permission", filters={'docstatus':['<',2],'shift_supervisor': employee}, fields=["name", "date",'log_type','permission_type','emp_name', "workflow_state"]) + shift_permission_list = frappe.get_list("Shift Permission", filters={'docstatus':['<',2],'employee': employee}, fields=["name", "date",'log_type','emp_name', "workflow_state",'docstatus']) + line_manager_shift_permission = frappe.get_list("Shift Permission", filters={'docstatus':['<',2],'shift_supervisor': employee}, fields=["name", "date",'log_type','emp_name', "workflow_state"]) return response("Success", 200, shift_permission_list+line_manager_shift_permission) @@ -300,8 +259,8 @@ def approve_shift_permission(employee_id: str = None, shift_permission_id: str = frappe.db.commit() user_id, supervisor_name = frappe.db.get_value('Employee', {'name': shift_supervisor}, ['user_id', 'employee_name']) - subject = _("{name} has approved the permission to {type} on {date}.".format(name=supervisor_name, type=shift_permission_doc.permission_type.lower(), date=shift_permission_doc.date)) - message = _("{name} has approved the permission to {type} on {date}.".format(name=supervisor_name, type=shift_permission_doc.permission_type.lower(), date=shift_permission_doc.date)) + subject = _("{name} has approved the permission to Check-{type} on {date}.".format(name=supervisor_name, type=shift_permission_doc.log_type.lower(), date=shift_permission_doc.date)) + message = _("{name} has approved the permission to Check-{type} on {date}.".format(name=supervisor_name, type=shift_permission_doc.log_type.lower(), date=shift_permission_doc.date)) notify_for_shift_permission_status(subject, message, user_id, shift_permission_doc, 1) return response("Success", 201, shift_permission_doc.as_dict()) @@ -350,8 +309,8 @@ def reject_shift_permission(employee_id: str = None, shift_permission_id: str = frappe.db.commit() user_id, supervisor_name= frappe.db.get_value('Employee',{'name':shift_supervisor},['user_id','employee_name']) - subject = _("{name} has rejected the permission to {type} on {date}.".format(name=supervisor_name, type=shift_permission_doc.permission_type.lower(), date=shift_permission_doc.date)) - message = _("{name} has rejected the permission to {type} on {date}.".format(name=supervisor_name, type=shift_permission_doc.permission_type.lower(), date=shift_permission_doc.date)) + subject = _("{name} has rejected the permission to check-{type} on {date}.".format(name=supervisor_name, type=shift_permission_doc.log_type.lower(), date=shift_permission_doc.date)) + message = _("{name} has rejected the permission to check-{type} on {date}.".format(name=supervisor_name, type=shift_permission_doc.log_type.lower(), date=shift_permission_doc.date)) notify_for_shift_permission_status(subject, message,user_id, shift_permission_doc, 1) return response("Success", 201, shift_permission_doc.as_dict()) diff --git a/one_fm/api/v1/utils.py b/one_fm/api/v1/utils.py index e13656ab47..79ad7ddfbe 100644 --- a/one_fm/api/v1/utils.py +++ b/one_fm/api/v1/utils.py @@ -1,6 +1,5 @@ -import frappe, requests +import frappe, datetime, requests from frappe.utils import getdate, cint, cstr, random_string, now_datetime -from one_fm.utils import get_current_shift def response(message, status_code, data=None, error=None): """This method generates a response for an API call with appropriate data and status code. @@ -202,6 +201,13 @@ def get_employee_by_id(employee_id): except Exception as e: frappe._dict({'status': False, 'message': str(e), 'http_status_code':500}) + + +@frappe.whitelist() +def log_error_via_api(traceback: str, message: str, medium: str): + frappe.log_error(title=f"Error from {medium} -- {frappe.session.user}", message=f"{traceback} -- {message}") + return response(message="Error Logged Successfully", status_code=201) + @frappe.whitelist() def google_map_api(): try: @@ -221,4 +227,4 @@ def verify_via_face_recogniton_service(url: str, data: dict, files: dict) -> tup frappe.log_error(title=f"Error from face recognition system -- {frappe.session.user}", message=f"{traceback} -- {message}")# if traceback else None return False, message return True, "" - return False, "Facial Recogniton Service is currently available" \ No newline at end of file + return False, "Facial Recogniton Service is currently available" diff --git a/one_fm/api/v1/web.py b/one_fm/api/v1/web.py index e006592afa..d6fce25fb2 100644 --- a/one_fm/api/v1/web.py +++ b/one_fm/api/v1/web.py @@ -14,7 +14,7 @@ import json # from imutils import face_utils, paths from one_fm.api.doc_events import haversine -from one_fm.api.v1.roster import get_current_shift +from one_fm.utils import get_current_shift from one_fm.api.v1.utils import response from one_fm.api.v1.face_recognition import ( create_checkin_log, verify_checkin_checkout, diff --git a/one_fm/api/v2/face_recognition.py b/one_fm/api/v2/face_recognition.py index bd2497932f..ab6ce28c95 100644 --- a/one_fm/api/v2/face_recognition.py +++ b/one_fm/api/v2/face_recognition.py @@ -14,14 +14,14 @@ # setup channel for face recognition face_recognition_service_url = frappe.local.conf.face_recognition_service_url -channels = [ - grpc.secure_channel(i, grpc.ssl_channel_credentials()) for i in face_recognition_service_url -] +# channels = [ +# grpc.secure_channel(i, grpc.ssl_channel_credentials()) for i in face_recognition_service_url +# ] # setup stub for face recognition -stubs = [ - facial_recognition_pb2_grpc.FaceRecognitionServiceStub(i) for i in channels -] +# stubs = [ +# facial_recognition_pb2_grpc.FaceRecognitionServiceStub(i) for i in channels +# ] @frappe.whitelist() @@ -324,7 +324,16 @@ def get_site_location(employee_id: str = None, latitude: float = None, longitude if has_attendance(employee, date): return response("Resource Not Found", 404, None, "Your attendance has been marked For the Day") - shift = get_current_shift(employee) + shift_exists = get_current_shift(employee) + if shift_exists['type'] == "Early": + # check if user can checkin with the correct time + return response("Resource Not Found", 404, None, f"You are checking in too early, checkin is allowed in {shift_exists['data']} minutes ") + elif shift_exists['type'] == "Late": + return response("Resource Not Found", 404, None, f"You are checking out too late, checkout was allowed {shift_exists['data']} minutes ago ") + elif shift_exists['type'] == "On Time": + shift = shift_exists['data'] + else: + shift = None site, location, shift_assignment = None, None, None if shift: diff --git a/one_fm/api/v2/leave_application.py b/one_fm/api/v2/leave_application.py index b4e9c8e80d..ea918af2b6 100644 --- a/one_fm/api/v2/leave_application.py +++ b/one_fm/api/v2/leave_application.py @@ -7,7 +7,6 @@ from one_fm.api.api import upload_file from one_fm.api.tasks import get_action_user,get_notification_user from one_fm.api.v1.utils import response, validate_date -from one_fm.utils import get_current_shift from frappe.utils import cint, cstr, getdate from one_fm.utils import check_if_backdate_allowed from one_fm.api.utils import validate_sick_leave_attachment diff --git a/one_fm/api/v2/shift_permission.py b/one_fm/api/v2/shift_permission.py index 0f9f476875..ff78206ef4 100644 --- a/one_fm/api/v2/shift_permission.py +++ b/one_fm/api/v2/shift_permission.py @@ -46,13 +46,13 @@ def create_shift_permission(employee_id: str = None, log_type: str = None, permi if not reason: return response("Bad Request", 400, None, "reason required.") - if log_type == "IN" and permission_type not in ['Arrive Late', 'Forget to Checkin', 'Checkin Issue']: - return response("Bad Request", 400, None, _('Permission Type cannot be {0}. It should be one of \ - "Arrive Late", "Forget to Checkin", "Checkin Issue" for Log Type "IN"'.format(permission_type))) + if log_type == "IN" and permission_type not in ['Arrive Late', ]: + return response("Bad Request", 400, None, _('Permission Type cannot be {0}. It should be \ + "Arrive Late" for Log Type "IN"'.format(permission_type))) - if log_type == "OUT" and permission_type not in ['Leave Early', 'Forget to Checkout', 'Checkout Issue']: + if log_type == "OUT" and permission_type not in ['Leave Early', ]: return response("Bad Request", 400, None, _('Permission Type cannot be {0}. It should be one of \ - "Leave Early", "Forget to Checkout", "Checkout Issue" for Log Type "OUT"'.format(permission_type))) + "Leave Early" for Log Type "OUT"'.format(permission_type))) if permission_type == "Arrive Late" and not arrival_time: return response("Bad Request", 400, None, "Arrival time required for late arrival shift permission.") @@ -66,15 +66,8 @@ def create_shift_permission(employee_id: str = None, log_type: str = None, permi if not isinstance(permission_type, str): return response("Bad Request", 400, None, "permission_type must be of type str.") - if permission_type not in ["Arrive Late", "Leave Early", "Checkin Issue", "Checkout Issue"]: - return response("Bad Request", 400, None, "permission type must be either 'Arrive Late' or 'Leave Early' or 'Checkin Issue' or 'Checkout Issue'.") - - if permission_type in ["Checkin Issue", "Checkout Issue"] and latitude and longitude: - try: - latitude = float(latitude) - longitude = float(longitude) - except: - return response("Bad Request", 400, None, "Latitude and longitude must be float.") + if permission_type not in ["Arrive Late", "Leave Early", ]: + return response("Bad Request", 400, None, "permission type must be either 'Arrive Late' or 'Leave Early'.") if not isinstance(date, str): return response("Bad Request", 400, None, "date must be of type str.") diff --git a/one_fm/api/v2/web.py b/one_fm/api/v2/web.py index 65b84b4f6c..dbcc7f9edc 100644 --- a/one_fm/api/v2/web.py +++ b/one_fm/api/v2/web.py @@ -20,14 +20,16 @@ # setup channel for face recognition face_recognition_service_url = frappe.local.conf.face_recognition_service_url -channels = [ - grpc.secure_channel(i, grpc.ssl_channel_credentials()) for i in face_recognition_service_url -] +# channels = [ +# grpc.secure_channel(i, grpc.ssl_channel_credentials()) for i in face_recognition_service_url +# ] # setup stub for face recognition -stubs = [ - facial_recognition_pb2_grpc.FaceRecognitionServiceStub(i) for i in channels -] +# stubs = [ +# facial_recognition_pb2_grpc.FaceRecognitionServiceStub(i) for i in channels +# ] + +stubs = list() class NumpyArrayEncoder(JSONEncoder): def default(self, obj): diff --git a/one_fm/api/v3/web.py b/one_fm/api/v3/web.py index ff56565913..5f3a4f0be6 100644 --- a/one_fm/api/v3/web.py +++ b/one_fm/api/v3/web.py @@ -19,14 +19,16 @@ # setup channel for face recognition face_recognition_service_url = frappe.local.conf.face_recognition_service_url -channels = [ - grpc.secure_channel(i, grpc.ssl_channel_credentials()) for i in face_recognition_service_url -] +# channels = [ +# grpc.secure_channel(i, grpc.ssl_channel_credentials()) for i in face_recognition_service_url +# ] # setup stub for face recognition -stubs = [ - facial_recognition_pb2_grpc.FaceRecognitionServiceStub(i) for i in channels -] +# stubs = [ +# facial_recognition_pb2_grpc.FaceRecognitionServiceStub(i) for i in channels +# ] + +stubs = list() channel = frappe.local.conf.face_recognition_channel.get('url') bucketpath = frappe.local.conf.face_recognition_channel.get('bucket') diff --git a/one_fm/events/issue.py b/one_fm/events/issue.py index 86211ebb77..53253be17a 100644 --- a/one_fm/events/issue.py +++ b/one_fm/events/issue.py @@ -19,6 +19,12 @@ def send_google_chat_notification(doc, method): # Fetch the Key and Token for the API default_api_integration = frappe.get_doc("Default API Integration") + if not default_api_integration: + return + + if not default_api_integration.integration_setting: + return + google_chat = frappe.get_doc("API Integration", [i for i in default_api_integration.integration_setting if i.app_name=='Google Chat'][0].app_name) diff --git a/one_fm/fixtures/assignment_rule.json b/one_fm/fixtures/assignment_rule.json index a08547c37d..2ecaa2e995 100644 --- a/one_fm/fixtures/assignment_rule.json +++ b/one_fm/fixtures/assignment_rule.json @@ -1,4 +1,132 @@ [ + { + "assign_condition": "workflow_state == 'Pending Approver'", + "assignment_days": [ + { + "day": "Monday", + "parent": "Shift Request Pending Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Tuesday", + "parent": "Shift Request Pending Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Wednesday", + "parent": "Shift Request Pending Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Thursday", + "parent": "Shift Request Pending Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Friday", + "parent": "Shift Request Pending Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Saturday", + "parent": "Shift Request Pending Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Sunday", + "parent": "Shift Request Pending Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + } + ], + "close_condition": "workflow_state in (\"Approved\", \"Rejected\", \"Draft\")", + "custom_routine_task": null, + "description": "Automatic Assignment", + "disabled": 0, + "docstatus": 0, + "doctype": "Assignment Rule", + "document_type": "Shift Request", + "due_date_based_on": null, + "field": "shift_approver", + "is_assignment_rule_with_workflow": 0, + "last_user": "abdullah@one-fm.com", + "modified": "2024-07-22 13:32:41.603305", + "name": "Shift Request Pending Approval", + "priority": 0, + "rule": "Based on Field", + "unassign_condition": "workflow_state in (\"Approved\", \"Rejected\", \"Draft\")", + "users": [] + }, + { + "assign_condition": "workflow_state == 'Pending Approval'", + "assignment_days": [ + { + "day": "Monday", + "parent": "Attendance Request Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Tuesday", + "parent": "Attendance Request Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Wednesday", + "parent": "Attendance Request Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Thursday", + "parent": "Attendance Request Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Friday", + "parent": "Attendance Request Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Saturday", + "parent": "Attendance Request Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Sunday", + "parent": "Attendance Request Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + } + ], + "close_condition": "workflow_state in ('Approved', 'Draft', 'Rejected')", + "custom_routine_task": null, + "description": "

Here is to inform you that the following {{ doctype }}({{ name }}) requires your attention/action.\n
\n The details of the request are as follows:\n
\n

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
LabelValue
Employee{{employee}}
Company{{company}}
From Date{{from_date}}
To Date{{to_date}}
Reason{{reason}}
Explanation{{explanation}}

", + "disabled": 0, + "docstatus": 0, + "doctype": "Assignment Rule", + "document_type": "Attendance Request", + "due_date_based_on": null, + "field": "approver_user", + "is_assignment_rule_with_workflow": 1, + "last_user": "", + "modified": "2024-07-17 17:59:49.745883", + "name": "Attendance Request Approval", + "priority": 0, + "rule": "Based on Field", + "unassign_condition": "workflow_state in ('Approved', 'Draft', 'Rejected')", + "users": [] + }, { "assign_condition": "workflow_state in ('Approved', \"Rejected\")", "assignment_days": [ @@ -120,7 +248,7 @@ "field": "reports_to_user", "is_assignment_rule_with_workflow": 0, "last_user": "", - "modified": "2024-01-24 18:00:54.349067", + "modified": "2024-03-22 09:51:17.332071", "name": "Attendance Check Reports To", "priority": 2, "rule": "Based on Field", @@ -320,7 +448,7 @@ "users": [] }, { - "assign_condition": "workflow_state == 'Pending'", + "assign_condition": "workflow_state == 'Pending Approver'", "assignment_days": [ { "day": "Monday", @@ -367,20 +495,20 @@ ], "close_condition": "workflow_state in ('Approved', 'Rejected')", "custom_routine_task": null, - "description": "

Here is to inform you that the following {{ doctype }}({{ name }}) requires your attention/action.\n\t\t\t
\n\t\t\tThe details of the request are as follows:
\n\t\t\t

\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
LabelValue
Employee Id{{employee}}
Date{{date}}
Log Type{{log_type}}
Permission Type{{permission_type}}
Reason{{reason}}

", - "disabled": 0, + "description": "

Here is to inform you that the following {{ doctype }}({{ name }}) requires your attention/action.\n\t\t\t
\n\t\t\tThe details of the request are as follows:
\n\t\t\t

\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
LabelValue
Employee Id{{employee}}
Date{{date}}
Log Type{{log_type}}
Reason{{reason}}

", + "disabled" :0, "docstatus": 0, "doctype": "Assignment Rule", "document_type": "Shift Permission", "due_date_based_on": null, "field": "approver_user_id", "is_assignment_rule_with_workflow": 0, - "last_user": "j.poil@one-fm.com", - "modified": "2023-06-04 11:45:04.269847", + "last_user": "", + "modified": "2024-03-10 15:19:46.392833", "name": "Shift Permission Approver", "priority": 0, "rule": "Based on Field", - "unassign_condition": "workflow_state == 'Approved'", + "unassign_condition": "workflow_state in ('Approved', 'Rejected')", "users": [] }, { @@ -431,7 +559,7 @@ ], "close_condition": null, "custom_routine_task": null, - "description": "

Dear {{ custom_purchase_order_approver_name }}

\n

Here is to inform you that the following {{ doctype }}({{ name }}) requires your attention/action.\n
\n The details of the request are as follows:\n
\n

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
LabelValue
Name{{ name }}
Supplier{{supplier}}
Date{{transaction_date}}
Company{{company}}
Currency{{currency}}
Exchange Rate{{conversion_rate}}
Status{{status}}

", + "description": "

Dear {{ custom_purchase_order_approver_name }}

\n

Here is to inform you that the following {{ doctype }}({{ name }}) requires your attention/action.\n
\n The details of the request are as follows:\n
\n

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
LabelValue
Name{{ name }}
Supplier{{supplier}}
Date{{transaction_date}}
Company{{company}}
Currency{{currency}}
Exchange Rate{{conversion_rate}}
Status{{status}}

", "disabled": 0, "docstatus": 0, "doctype": "Assignment Rule", @@ -495,7 +623,7 @@ ], "close_condition": null, "custom_routine_task": null, - "description": "

Here is to inform you that the following {{ doctype }}({{ name }}) requires your attention/action.\n
\n The details of the request are as follows:\n
\n

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
LabelValue
Name{{ name }}
Supplier{{supplier}}
Date{{transaction_date}}
Company{{company}}
Currency{{currency}}
Exchange Rate{{conversion_rate}}
Status{{status}}

", + "description": "

Here is to inform you that the following {{ doctype }}({{ name }}) requires your attention/action.\n
\n The details of the request are as follows:\n
\n

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
LabelValue
Name{{ name }}
Supplier{{supplier}}
Date{{transaction_date}}
Company{{company}}
Currency{{currency}}
Exchange Rate{{conversion_rate}}
Status{{status}}

", "disabled": 0, "docstatus": 0, "doctype": "Assignment Rule", @@ -566,7 +694,7 @@ ], "close_condition": null, "custom_routine_task": null, - "description": "

Here is to inform you that the following {{ doctype }}({{ name }}) requires your attention/action.\n
\n The details of the request are as follows:\n
\n

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
LabelValue
Name{{ name }}
Supplier{{supplier}}
Date{{transaction_date}}
Company{{company}}
Currency{{currency}}
Exchange Rate{{conversion_rate}}
Status{{status}}

", + "description": "

Here is to inform you that the following {{ doctype }}({{ name }}) requires your attention/action.\n
\n The details of the request are as follows:\n
\n

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
LabelValue
Name{{ name }}
Supplier{{supplier}}
Date{{transaction_date}}
Company{{company}}
Currency{{currency}}
Exchange Rate{{conversion_rate}}
Status{{status}}

", "disabled": 0, "docstatus": 0, "doctype": "Assignment Rule", @@ -588,5 +716,133 @@ "user": "saoud@one-fm.com" } ] + }, + { + "assign_condition": "workflow_state == 'Pending Approval'", + "assignment_days": [ + { + "day": "Monday", + "parent": "Employee Checkin Issue Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Tuesday", + "parent": "Employee Checkin Issue Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Wednesday", + "parent": "Employee Checkin Issue Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Thursday", + "parent": "Employee Checkin Issue Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Friday", + "parent": "Employee Checkin Issue Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Saturday", + "parent": "Employee Checkin Issue Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Sunday", + "parent": "Employee Checkin Issue Approval", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + } + ], + "close_condition": "workflow_state in ('Approved', 'Draft')", + "custom_routine_task": null, + "description": "

Here is to inform you that the following {{ doctype }}({{ name }}) requires your attention/action.\n
\n The details of the request are as follows:\n
\n

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
LabelValue
Employee{{employee}}
Date{{date}}
Log Type{{log_type}}

", + "disabled": 0, + "docstatus": 0, + "doctype": "Assignment Rule", + "document_type": "Employee Checkin Issue", + "due_date_based_on": null, + "field": "approver_user_id", + "is_assignment_rule_with_workflow": 1, + "last_user": "", + "modified": "2024-03-25 11:55:45.809300", + "name": "Employee Checkin Issue Approval", + "priority": 0, + "rule": "Based on Field", + "unassign_condition": "workflow_state in ('Approved', 'Draft')", + "users": [] + }, + { + "assign_condition": "workflow_state ==\"Draft\" and creation < modified", + "assignment_days": [ + { + "day": "Monday", + "parent": "Shift Request Draft", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Tuesday", + "parent": "Shift Request Draft", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Wednesday", + "parent": "Shift Request Draft", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Thursday", + "parent": "Shift Request Draft", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Friday", + "parent": "Shift Request Draft", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Saturday", + "parent": "Shift Request Draft", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + }, + { + "day": "Sunday", + "parent": "Shift Request Draft", + "parentfield": "assignment_days", + "parenttype": "Assignment Rule" + } + ], + "close_condition": "workflow_state in ('Pending Approver', )", + "custom_routine_task": null, + "description": "Automatic Assignment", + "disabled": 0, + "docstatus": 0, + "doctype": "Assignment Rule", + "document_type": "Shift Request", + "due_date_based_on": null, + "field": "owner", + "is_assignment_rule_with_workflow": 0, + "last_user": "Administrator", + "modified": "2024-07-22 13:46:56.508148", + "name": "Shift Request Draft", + "priority": 0, + "rule": "Based on Field", + "unassign_condition": "workflow_state in ('Pending Approver', )", + "users": [] } ] \ No newline at end of file diff --git a/one_fm/fixtures/property_setter.json b/one_fm/fixtures/property_setter.json index 26a24295ff..d28ca68476 100755 --- a/one_fm/fixtures/property_setter.json +++ b/one_fm/fixtures/property_setter.json @@ -3287,6 +3287,7 @@ "doctype_or_field": "DocField", "field_name": "shift", "is_system_generated": 0, + "modified": "2024-07-21 17:30:02.315075", "modified": "2024-08-05 15:06:28.483195", "module": null, "name": "Attendance-shift-fetch_from", @@ -6157,15 +6158,15 @@ "property": "field_order", "property_type": "Data", "row_name": null, - "value": "[\"interview_rounds_sb\", \"interview_rounds\", \"details_section\", \"applicant_name\", \"email_id\", \"one_fm_application_id\", \"phone_number\", \"country\", \"column_break_3\", \"job_title\", \"one_fm_designation\", \"applicant_lead\", \"designation\", \"status\", \"one_fm_reason_for_rejection\", \"one_fm_applicant_status\", \"one_fm_document_verification\", \"source_and_rating_section\", \"source\", \"source_name\", \"one_fm_erf_application_details_section\", \"one_fm_erf\", \"project\", \"department\", \"one_fm_sourcing_team\", \"one_fm_agency\", \"one_fm_is_agency_applying\", \"one_fm_job_applicant_cb_1\", \"one_fm_source_of_hire\", \"one_fm_hiring_method\", \"interview_round\", \"bulk_interview\", \"employment_type\", \"attendance_by_timesheet\", \"one_fm_applicant_personal_details_sb\", \"one_fm_first_name\", \"one_fm_second_name\", \"one_fm_third_name\", \"one_fm_forth_name\", \"one_fm_last_name\", \"one_fm_first_name_in_arabic\", \"one_fm_second_name_in_arabic\", \"one_fm_third_name_in_arabic\", \"one_fm_forth_name_in_arabic\", \"one_fm_last_name_in_arabic\", \"one_fm_height\", \"one_fm_i_am_currently_working\", \"one_fm_applicant_demographics_cb\", \"one_fm_gender\", \"one_fm_religion\", \"one_fm_date_of_birth\", \"one_fm_place_of_birth\", \"one_fm_marital_status\", \"one_fm_nationality\", \"one_fm_date_of_entry\", \"employee_referral\", \"column_break_13\", \"applicant_rating\", \"section_break_6\", \"notes\", \"cover_letter\", \"resume_attachment\", \"children_details_section\", \"one_fm_number_of_kids\", \"one_fm_kids_details\", \"day_off_details\", \"day_off_category\", \"column_break_66\", \"number_of_days_off\", \"one_fm_work_details_section\", \"one_fm_rotation_shift\", \"one_fm_night_shift\", \"one_fm_work_details_cb\", \"one_fm_type_of_travel\", \"one_fm_type_of_driving_license\", \"one_fm_uniform_measurements\", \"one_fm_is_uniform_needed_for_this_job\", \"one_fm_shoulder_width\", \"one_fm_waist_size\", \"one_fm_shoe_size\", \"one_fm_basic_skill_section\", \"one_fm_designation_skill\", \"one_fm_documents_required_section\", \"one_fm_documents_required\", \"one_fm_is_easy_apply\", \"previous_work_details\", \"one_fm_work_permit_number\", \"one_fm_duration_of_work_permit\", \"one_fm_previous_designation\", \"column_break_51\", \"one_fm_work_permit_salary\", \"one_fm_last_working_date\", \"resume_link\", \"section_break_16\", \"currency\", \"column_break_18\", \"lower_range\", \"upper_range\", \"one_fm_contact_details_section\", \"one_fm_email_id\", \"one_fm_country_code\", \"one_fm_contact_number\", \"one_fm_country_code_second\", \"one_fm_secondary_contact_number\", \"one_fm_contact_cb\", \"one_fm_language_section\", \"one_fm_languages\", \"country_and_nationality_section\", \"nationality_no\", \"nationality_subject\", \"nationality_cb\", \"date_of_naturalization\", \"one_fm_passport_section\", \"one_fm_passport_number\", \"one_fm_passport_holder_of\", \"one_fm_passport_issued\", \"one_fm_passport_expire\", \"one_fm_passport_cb\", \"one_fm_passport_type\", \"one_fm_centralized_number\", \"one_fm_visa_and_residency_section\", \"one_fm_have_a_valid_visa_in_kuwait\", \"one_fm_visa_type\", \"one_fm_cid_number\", \"one_fm_cid_expire\", \"one_fm_in_kuwait_at_present\", \"one_fm_visa_cb\", \"one_fm_current_employment_section_\", \"one_fm_current_employer\", \"one_fm_current_employer_website_link\", \"one_fm_employment_start_date\", \"one_fm_employment_end_date\", \"one_fm_current_employment_cb\", \"one_fm_current_job_title\", \"one_fm_current_salary\", \"one_fm_notice_period_in_days\", \"one_fm_educational_qualification_section\", \"one_fm_educational_qualification\", \"other_education\", \"one_fm_education_specialization\", \"one_fm_educational_qualification_cb\", \"one_fm_university\", \"one_fm_country_of_employment\", \"section_break_88\", \"one_fm_are_you_currently_studying\", \"one_fm_current_educational_institution\", \"column_break_91\", \"one_fm_place_of_study\", \"one_fm_entry_date_of_current_educational_institution\", \"section_break_66\", \"one_fm_applicant_is_overseas_or_local\", \"one_fm_country_of_overseas\", \"one_fm_is_transferable\", \"custom_transfer_reminder_date\", \"column_break_72\", \"one_fm_applicant_password\", \"authorized_signatory\", \"one_fm_old_number\", \"one_fm_old_designation\", \"one_fm_erf_pam_file_number\", \"one_fm_erf_pam_designation\", \"send_changes_to_supervisor\", \"accept_changes\", \"reject_changes\", \"suggestions\", \"save_me\", \"no_internal_issues\", \"one_fm_has_issue\", \"one_fm_type_of_issues\", \"column_break_149\", \"one_fm_change_pam_file_number\", \"pam_number_button\", \"pam_designation_button\", \"one_fm_change_pam_designation\", \"column_break_152\", \"one_fm_pam_file_number\", \"one_fm_pam_designation\", \"column_break_154\", \"one_fm_file_number\", \"one_fm_notify_recruiter\", \"previous_company_details\", \"one_fm_previous_company_trade_name_in_arabic\", \"one_fm__previous_company_authorized_signatory_name_arabic\", \"one_fm_previous_company_issuer_number\", \"column_break_142\", \"one_fm_previous_company_pam_file_number\", \"one_fm_government_project\", \"authorized_signatory_section\", \"one_fm_pam_authorized_signatory\", \"one_fm_signatory_name\", \"one_fm_grd_operator\", \"scans\", \"passport_data_page\", \"civil_id_front\", \"civil_id_back\", \"magic_link_details\", \"career_history_ml\", \"career_history_ml_url\", \"career_history_ml_expired\", \"column_break_avxor\", \"applicant_doc_ml\", \"applicant_doc_ml_url\", \"applicant_doc_ml_expired\"]" + "value": "[\"interview_rounds_sb\", \"interview_rounds\", \"details_section\", \"applicant_name\", \"email_id\", \"one_fm_application_id\", \"phone_number\", \"country\", \"column_break_3\", \"job_title\", \"one_fm_designation\", \"applicant_lead\", \"designation\", \"status\", \"one_fm_reason_for_rejection\", \"one_fm_applicant_status\", \"one_fm_document_verification\", \"source_and_rating_section\", \"source\", \"source_name\", \"one_fm_erf_application_details_section\", \"one_fm_erf\", \"project\", \"department\", \"one_fm_sourcing_team\", \"one_fm_agency\", \"one_fm_is_agency_applying\", \"one_fm_job_applicant_cb_1\", \"one_fm_source_of_hire\", \"one_fm_hiring_method\", \"interview_round\", \"bulk_interview\", \"employment_type\", \"attendance_by_timesheet\", \"one_fm_applicant_personal_details_sb\", \"one_fm_first_name\", \"one_fm_second_name\", \"one_fm_third_name\", \"one_fm_forth_name\", \"one_fm_last_name\", \"one_fm_first_name_in_arabic\", \"one_fm_second_name_in_arabic\", \"one_fm_third_name_in_arabic\", \"one_fm_forth_name_in_arabic\", \"one_fm_last_name_in_arabic\", \"one_fm_height\", \"one_fm_i_am_currently_working\", \"one_fm_applicant_demographics_cb\", \"one_fm_gender\", \"one_fm_religion\", \"one_fm_date_of_birth\", \"one_fm_place_of_birth\", \"one_fm_marital_status\", \"one_fm_nationality\", \"employee_referral\", \"column_break_13\", \"applicant_rating\", \"section_break_6\", \"notes\", \"cover_letter\", \"resume_attachment\", \"children_details_section\", \"one_fm_number_of_kids\", \"one_fm_kids_details\", \"day_off_details\", \"day_off_category\", \"column_break_66\", \"number_of_days_off\", \"one_fm_work_details_section\", \"one_fm_rotation_shift\", \"one_fm_night_shift\", \"one_fm_work_details_cb\", \"one_fm_type_of_travel\", \"one_fm_type_of_driving_license\", \"one_fm_uniform_measurements\", \"one_fm_is_uniform_needed_for_this_job\", \"one_fm_shoulder_width\", \"one_fm_waist_size\", \"one_fm_shoe_size\", \"one_fm_basic_skill_section\", \"one_fm_designation_skill\", \"one_fm_documents_required_section\", \"one_fm_documents_required\", \"one_fm_is_easy_apply\", \"previous_work_details\", \"one_fm_work_permit_number\", \"one_fm_duration_of_work_permit\", \"one_fm_previous_designation\", \"column_break_51\", \"one_fm_work_permit_salary\", \"one_fm_last_working_date\", \"resume_link\", \"section_break_16\", \"currency\", \"column_break_18\", \"lower_range\", \"upper_range\", \"one_fm_contact_details_section\", \"one_fm_email_id\", \"one_fm_country_code\", \"one_fm_contact_number\", \"one_fm_country_code_second\", \"one_fm_secondary_contact_number\", \"one_fm_contact_cb\", \"one_fm_language_section\", \"one_fm_languages\", \"country_and_nationality_section\", \"nationality_no\", \"nationality_subject\", \"nationality_cb\", \"date_of_naturalization\", \"one_fm_passport_section\", \"one_fm_passport_number\", \"one_fm_passport_holder_of\", \"one_fm_passport_issued\", \"one_fm_passport_expire\", \"one_fm_passport_cb\", \"one_fm_passport_type\", \"one_fm_visa_and_residency_section\", \"one_fm_have_a_valid_visa_in_kuwait\", \"one_fm_visa_type\", \"one_fm_cid_number\", \"one_fm_cid_expire\", \"one_fm_in_kuwait_at_present\", \"one_fm_visa_cb\", \"one_fm_current_employment_section_\", \"one_fm_current_employer\", \"one_fm_current_employer_website_link\", \"one_fm_employment_start_date\", \"one_fm_employment_end_date\", \"one_fm_current_employment_cb\", \"one_fm_current_job_title\", \"one_fm_current_salary\", \"one_fm_notice_period_in_days\", \"one_fm_educational_qualification_section\", \"one_fm_educational_qualification\", \"other_education\", \"one_fm_education_specialization\", \"one_fm_educational_qualification_cb\", \"one_fm_university\", \"one_fm_country_of_employment\", \"section_break_88\", \"one_fm_are_you_currently_studying\", \"one_fm_current_educational_institution\", \"column_break_91\", \"one_fm_place_of_study\", \"one_fm_entry_date_of_current_educational_institution\", \"section_break_66\", \"one_fm_applicant_is_overseas_or_local\", \"one_fm_country_of_overseas\", \"one_fm_is_transferable\", \"custom_transfer_reminder_date\", \"column_break_72\", \"one_fm_applicant_password\", \"authorized_signatory\", \"one_fm_old_number\", \"one_fm_old_designation\", \"one_fm_erf_pam_file_number\", \"one_fm_erf_pam_designation\", \"send_changes_to_supervisor\", \"accept_changes\", \"reject_changes\", \"suggestions\", \"save_me\", \"no_internal_issues\", \"one_fm_has_issue\", \"one_fm_type_of_issues\", \"column_break_149\", \"one_fm_change_pam_file_number\", \"pam_number_button\", \"pam_designation_button\", \"one_fm_change_pam_designation\", \"column_break_152\", \"one_fm_pam_file_number\", \"one_fm_pam_designation\", \"column_break_154\", \"one_fm_file_number\", \"one_fm_notify_recruiter\", \"previous_company_details\", \"one_fm_previous_company_trade_name_in_arabic\", \"one_fm__previous_company_authorized_signatory_name_arabic\", \"one_fm_previous_company_issuer_number\", \"column_break_142\", \"one_fm_previous_company_pam_file_number\", \"one_fm_government_project\", \"authorized_signatory_section\", \"one_fm_pam_authorized_signatory\", \"one_fm_signatory_name\", \"one_fm_grd_operator\", \"scans\", \"passport_data_page\", \"civil_id_front\", \"civil_id_back\", \"magic_link_details\", \"career_history_ml\", \"career_history_ml_url\", \"career_history_ml_expired\", \"column_break_avxor\", \"applicant_doc_ml\", \"applicant_doc_ml_url\", \"applicant_doc_ml_expired\"]" }, { "default_value": null, "doc_type": "Attendance Request", "docstatus": 0, "doctype": "Property Setter", - "doctype_or_field": "DocType", - "field_name": null, + "doctype_or_field": "DocField", + "field_name": "shift_type", "is_system_generated": 0, "modified": "2024-08-05 15:07:06.238931", "module": null, diff --git a/one_fm/fixtures/workflow.json b/one_fm/fixtures/workflow.json index dc5c3a9902..c8ef185a86 100755 --- a/one_fm/fixtures/workflow.json +++ b/one_fm/fixtures/workflow.json @@ -5662,7 +5662,7 @@ "parent": "Shift Permission", "parentfield": "states", "parenttype": "Workflow", - "state": "Pending", + "state": "Draft", "update_field": null, "update_value": null, "workflow_builder_id": null @@ -5670,14 +5670,14 @@ { "allow_edit": "Shift Supervisor", "avoid_status_override": 0, - "doc_status": "1", + "doc_status": "0", "is_optional_state": 0, "message": null, "next_action_email_template": null, "parent": "Shift Permission", "parentfield": "states", "parenttype": "Workflow", - "state": "Approved", + "state": "Pending Approver", "update_field": null, "update_value": null, "workflow_builder_id": null @@ -5692,7 +5692,7 @@ "parent": "Shift Permission", "parentfield": "states", "parenttype": "Workflow", - "state": "Rejected", + "state": "Approved", "update_field": null, "update_value": null, "workflow_builder_id": null @@ -5700,14 +5700,14 @@ { "allow_edit": "Shift Supervisor", "avoid_status_override": 0, - "doc_status": "2", + "doc_status": "1", "is_optional_state": 0, "message": null, "next_action_email_template": null, "parent": "Shift Permission", "parentfield": "states", "parenttype": "Workflow", - "state": "Cancelled", + "state": "Rejected", "update_field": null, "update_value": null, "workflow_builder_id": null @@ -5715,7 +5715,25 @@ ], "transitions": [ { - "action": "Approve", + "action": "Submit for Review", + "allow_self_approval": 1, + "allowed": "Employee", + "allowed_user_field": "employee", + "allowed_user_id": null, + "condition": "", + "custom_confirm_message": null, + "custom_confirm_transition": 0, + "next_state": "Pending Approver", + "parent": "Shift Permission", + "parentfield": "transitions", + "parenttype": "Workflow", + "skip_creation_of_workflow_action": 0, + "skip_multiple_action": 0, + "state": "Draft", + "workflow_builder_id": null + }, + { + "action": "Return To Draft", "allow_self_approval": 1, "allowed": "Shift Supervisor", "allowed_user_field": "shift_supervisor", @@ -5723,17 +5741,17 @@ "condition": "frappe.session.user in [doc.approver_user_id, 'abdullah@one-fm.com']", "custom_confirm_message": null, "custom_confirm_transition": 0, - "next_state": "Approved", + "next_state": "Draft", "parent": "Shift Permission", "parentfield": "transitions", "parenttype": "Workflow", "skip_creation_of_workflow_action": 0, "skip_multiple_action": 0, - "state": "Pending", + "state": "Pending Approver", "workflow_builder_id": null }, { - "action": "Reject", + "action": "Approve", "allow_self_approval": 1, "allowed": "Shift Supervisor", "allowed_user_field": "shift_supervisor", @@ -5741,17 +5759,17 @@ "condition": "frappe.session.user in [doc.approver_user_id, 'abdullah@one-fm.com']", "custom_confirm_message": null, "custom_confirm_transition": 0, - "next_state": "Rejected", + "next_state": "Approved", "parent": "Shift Permission", "parentfield": "transitions", "parenttype": "Workflow", "skip_creation_of_workflow_action": 0, "skip_multiple_action": 0, - "state": "Pending", + "state": "Pending Approver", "workflow_builder_id": null }, { - "action": "Cancel", + "action": "Reject", "allow_self_approval": 1, "allowed": "Shift Supervisor", "allowed_user_field": "shift_supervisor", @@ -5759,13 +5777,13 @@ "condition": "frappe.session.user in [doc.approver_user_id, 'abdullah@one-fm.com']", "custom_confirm_message": null, "custom_confirm_transition": 0, - "next_state": "Cancelled", + "next_state": "Rejected", "parent": "Shift Permission", "parentfield": "transitions", "parenttype": "Workflow", "skip_creation_of_workflow_action": 0, "skip_multiple_action": 0, - "state": "Approved", + "state": "Pending Approver", "workflow_builder_id": null } ], @@ -8037,7 +8055,7 @@ "parent": "Shift Request", "parentfield": "states", "parenttype": "Workflow", - "state": "Pending Approval", + "state": "Pending Approver", "update_field": null, "update_value": null, "workflow_builder_id": null @@ -8071,41 +8089,11 @@ "update_field": "", "update_value": "", "workflow_builder_id": null - }, - { - "allow_edit": "Shift Supervisor", - "avoid_status_override": 0, - "doc_status": "2", - "is_optional_state": 0, - "message": null, - "next_action_email_template": null, - "parent": "Shift Request", - "parentfield": "states", - "parenttype": "Workflow", - "state": "Cancelled", - "update_field": "", - "update_value": "", - "workflow_builder_id": null - }, - { - "allow_edit": "Shift Supervisor", - "avoid_status_override": 0, - "doc_status": "1", - "is_optional_state": 0, - "message": null, - "next_action_email_template": null, - "parent": "Shift Request", - "parentfield": "states", - "parenttype": "Workflow", - "state": "Update Request", - "update_field": "", - "update_value": "", - "workflow_builder_id": null } ], "transitions": [ { - "action": "Send to Supervisor", + "action": "Submit for Review", "allow_self_approval": 1, "allowed": "Employee", "allowed_user_field": null, @@ -8113,7 +8101,7 @@ "condition": "frappe.session.user not in [approver.user for approver in doc.custom_shift_approvers]", "custom_confirm_message": null, "custom_confirm_transition": 0, - "next_state": "Pending Approval", + "next_state": "Pending Approver", "parent": "Shift Request", "parentfield": "transitions", "parenttype": "Workflow", @@ -8137,7 +8125,7 @@ "parenttype": "Workflow", "skip_creation_of_workflow_action": 0, "skip_multiple_action": 0, - "state": "Pending Approval", + "state": "Pending Approver", "workflow_builder_id": null }, { @@ -8155,47 +8143,11 @@ "parenttype": "Workflow", "skip_creation_of_workflow_action": 0, "skip_multiple_action": 0, - "state": "Pending Approval", - "workflow_builder_id": null - }, - { - "action": "Cancel", - "allow_self_approval": 1, - "allowed": "Shift Supervisor", - "allowed_user_field": null, - "allowed_user_id": null, - "condition": "", - "custom_confirm_message": null, - "custom_confirm_transition": 0, - "next_state": "Cancelled", - "parent": "Shift Request", - "parentfield": "transitions", - "parenttype": "Workflow", - "skip_creation_of_workflow_action": 0, - "skip_multiple_action": 0, - "state": "Approved", - "workflow_builder_id": null - }, - { - "action": "Update Request", - "allow_self_approval": 1, - "allowed": "Shift Supervisor", - "allowed_user_field": null, - "allowed_user_id": null, - "condition": "doc.update_request", - "custom_confirm_message": null, - "custom_confirm_transition": 0, - "next_state": "Update Request", - "parent": "Shift Request", - "parentfield": "transitions", - "parenttype": "Workflow", - "skip_creation_of_workflow_action": 0, - "skip_multiple_action": 0, - "state": "Approved", + "state": "Pending Approver", "workflow_builder_id": null }, { - "action": "Approve", + "action": "Return To Draft", "allow_self_approval": 1, "allowed": "Shift Supervisor", "allowed_user_field": null, @@ -8203,13 +8155,13 @@ "condition": null, "custom_confirm_message": null, "custom_confirm_transition": 0, - "next_state": "Approved", + "next_state": "Draft", "parent": "Shift Request", "parentfield": "transitions", "parenttype": "Workflow", "skip_creation_of_workflow_action": 0, "skip_multiple_action": 0, - "state": "Update Request", + "state": "Pending Approver", "workflow_builder_id": null }, { @@ -8258,7 +8210,7 @@ "doctype": "Workflow", "document_type": "Attendance Request", "is_active": 1, - "modified": "2023-06-18 09:27:53.645303", + "modified": "2024-03-22 10:02:21.030159", "name": "Attendance Request", "override_status": 0, "send_email_alert": 0, @@ -8337,26 +8289,11 @@ "update_field": null, "update_value": null, "workflow_builder_id": null - }, - { - "allow_edit": "Shift Supervisor", - "avoid_status_override": 0, - "doc_status": "1", - "is_optional_state": 0, - "message": null, - "next_action_email_template": null, - "parent": "Attendance Request", - "parentfield": "states", - "parenttype": "Workflow", - "state": "Update Request", - "update_field": null, - "update_value": null, - "workflow_builder_id": null } ], "transitions": [ { - "action": "Send to Supervisor", + "action": "Submit for Review", "allow_self_approval": 1, "allowed": "Employee", "allowed_user_field": "owner", @@ -8410,43 +8347,7 @@ "workflow_builder_id": null }, { - "action": "Cancel", - "allow_self_approval": 1, - "allowed": "Shift Supervisor", - "allowed_user_field": null, - "allowed_user_id": null, - "condition": null, - "custom_confirm_message": null, - "custom_confirm_transition": 0, - "next_state": "Cancelled", - "parent": "Attendance Request", - "parentfield": "transitions", - "parenttype": "Workflow", - "skip_creation_of_workflow_action": 0, - "skip_multiple_action": 0, - "state": "Approved", - "workflow_builder_id": null - }, - { - "action": "Update Request", - "allow_self_approval": 1, - "allowed": "Shift Supervisor", - "allowed_user_field": null, - "allowed_user_id": null, - "condition": "doc.update_request", - "custom_confirm_message": null, - "custom_confirm_transition": 0, - "next_state": "Update Request", - "parent": "Attendance Request", - "parentfield": "transitions", - "parenttype": "Workflow", - "skip_creation_of_workflow_action": 0, - "skip_multiple_action": 0, - "state": "Approved", - "workflow_builder_id": null - }, - { - "action": "Approve", + "action": "Return To Draft", "allow_self_approval": 1, "allowed": "Shift Supervisor", "allowed_user_field": null, @@ -8454,19 +8355,19 @@ "condition": null, "custom_confirm_message": null, "custom_confirm_transition": 0, - "next_state": "Approved", + "next_state": "Draft", "parent": "Attendance Request", "parentfield": "transitions", "parenttype": "Workflow", "skip_creation_of_workflow_action": 0, "skip_multiple_action": 0, - "state": "Update Request", + "state": "Pending Approval", "workflow_builder_id": null }, { - "action": "Reject", + "action": "Cancel", "allow_self_approval": 1, - "allowed": "Shift Supervisor", + "allowed": "System Manager", "allowed_user_field": null, "allowed_user_id": null, "condition": null, @@ -8478,7 +8379,7 @@ "parenttype": "Workflow", "skip_creation_of_workflow_action": 0, "skip_multiple_action": 0, - "state": "Update Request", + "state": "Approved", "workflow_builder_id": null } ], @@ -8640,7 +8541,7 @@ "doctype": "Workflow", "document_type": "Timesheet", "is_active": 1, - "modified": "2023-12-28 23:31:07.237828", + "modified": "2024-03-26 09:02:55.302435", "name": "Timesheet", "override_status": 0, "send_email_alert": 0, @@ -8670,7 +8571,7 @@ "parent": "Timesheet", "parentfield": "states", "parenttype": "Workflow", - "state": "Open", + "state": "Pending Approval", "update_field": null, "update_value": null, "workflow_builder_id": null @@ -8721,7 +8622,7 @@ "workflow_builder_id": null }, { - "allow_edit": "Employee", + "allow_edit": "System Manager", "avoid_status_override": 0, "doc_status": "2", "is_optional_state": 0, @@ -8738,7 +8639,7 @@ ], "transitions": [ { - "action": "Open Request", + "action": "Submit for Review", "allow_self_approval": 1, "allowed": "Employee", "allowed_user_field": "owner", @@ -8746,7 +8647,7 @@ "condition": "doc.attendance_by_timesheet == 1", "custom_confirm_message": null, "custom_confirm_transition": 0, - "next_state": "Open", + "next_state": "Pending Approval", "parent": "Timesheet", "parentfield": "transitions", "parenttype": "Workflow", @@ -8788,11 +8689,11 @@ "parenttype": "Workflow", "skip_creation_of_workflow_action": 0, "skip_multiple_action": 0, - "state": "Open", + "state": "Pending Approval", "workflow_builder_id": null }, { - "action": "Reject", + "action": "Return To Draft", "allow_self_approval": 1, "allowed": "Employee", "allowed_user_field": "approver", @@ -8800,22 +8701,22 @@ "condition": "doc.attendance_by_timesheet == 1", "custom_confirm_message": null, "custom_confirm_transition": 0, - "next_state": "Rejected", + "next_state": "Draft", "parent": "Timesheet", "parentfield": "transitions", "parenttype": "Workflow", "skip_creation_of_workflow_action": 0, "skip_multiple_action": 0, - "state": "Open", + "state": "Pending Approval", "workflow_builder_id": null }, { "action": "Cancel", "allow_self_approval": 1, - "allowed": "Employee", - "allowed_user_field": "approver", + "allowed": "System Manager", + "allowed_user_field": "", "allowed_user_id": null, - "condition": "frappe.session.user == doc.approver and doc.attendance_by_timesheet == 1", + "condition": "doc.attendance_by_timesheet == 1", "custom_confirm_message": null, "custom_confirm_transition": 0, "next_state": "Cancelled", @@ -8830,26 +8731,8 @@ { "action": "Cancel", "allow_self_approval": 1, - "allowed": "Employee", - "allowed_user_field": "approver", - "allowed_user_id": null, - "condition": "frappe.session.user == doc.approver and doc.attendance_by_timesheet == 1", - "custom_confirm_message": null, - "custom_confirm_transition": 0, - "next_state": "Cancelled", - "parent": "Timesheet", - "parentfield": "transitions", - "parenttype": "Workflow", - "skip_creation_of_workflow_action": 0, - "skip_multiple_action": 0, - "state": "Rejected", - "workflow_builder_id": null - }, - { - "action": "Cancel", - "allow_self_approval": 1, - "allowed": "Employee", - "allowed_user_field": "approver", + "allowed": "System Manager", + "allowed_user_field": "", "allowed_user_id": null, "condition": "doc.attendance_by_timesheet == 0", "custom_confirm_message": null, @@ -8873,7 +8756,7 @@ "doctype": "Workflow", "document_type": "Employee Checkin Issue", "is_active": 1, - "modified": "2023-01-24 11:00:16.795074", + "modified": "2024-03-25 11:52:50.847003", "name": "Employee Checkin Issue", "override_status": 0, "send_email_alert": 0, @@ -8888,7 +8771,22 @@ "parent": "Employee Checkin Issue", "parentfield": "states", "parenttype": "Workflow", - "state": "Pending", + "state": "Draft", + "update_field": null, + "update_value": null, + "workflow_builder_id": null + }, + { + "allow_edit": "Employee", + "avoid_status_override": 0, + "doc_status": "0", + "is_optional_state": 0, + "message": null, + "next_action_email_template": null, + "parent": "Employee Checkin Issue", + "parentfield": "states", + "parenttype": "Workflow", + "state": "Pending Approval", "update_field": null, "update_value": null, "workflow_builder_id": null @@ -8940,13 +8838,31 @@ } ], "transitions": [ + { + "action": "Submit for Approval", + "allow_self_approval": 1, + "allowed": "Employee", + "allowed_user_field": null, + "allowed_user_id": null, + "condition": null, + "custom_confirm_message": null, + "custom_confirm_transition": 0, + "next_state": "Pending Approval", + "parent": "Employee Checkin Issue", + "parentfield": "transitions", + "parenttype": "Workflow", + "skip_creation_of_workflow_action": 0, + "skip_multiple_action": 0, + "state": "Draft", + "workflow_builder_id": null + }, { "action": "Approve", "allow_self_approval": 1, "allowed": "Shift Supervisor", - "allowed_user_field": null, + "allowed_user_field": "approver_user_id", "allowed_user_id": null, - "condition": "frappe.session.user in [doc.approver_user_id]", + "condition": "frappe.session.user in [doc.approver_user_id, 'Administrator']", "custom_confirm_message": null, "custom_confirm_transition": 0, "next_state": "Approved", @@ -8955,16 +8871,16 @@ "parenttype": "Workflow", "skip_creation_of_workflow_action": 0, "skip_multiple_action": 0, - "state": "Pending", + "state": "Pending Approval", "workflow_builder_id": null }, { "action": "Reject", "allow_self_approval": 1, "allowed": "Shift Supervisor", - "allowed_user_field": null, + "allowed_user_field": "approver_user_id", "allowed_user_id": null, - "condition": "frappe.session.user in [doc.approver_user_id]", + "condition": "frappe.session.user in [doc.approver_user_id, 'Administrator']", "custom_confirm_message": null, "custom_confirm_transition": 0, "next_state": "Rejected", @@ -8973,7 +8889,7 @@ "parenttype": "Workflow", "skip_creation_of_workflow_action": 0, "skip_multiple_action": 0, - "state": "Pending", + "state": "Pending Approval", "workflow_builder_id": null }, { @@ -8982,7 +8898,7 @@ "allowed": "Shift Supervisor", "allowed_user_field": null, "allowed_user_id": null, - "condition": "frappe.session.user in [doc.approver_user_id]", + "condition": "frappe.session.user in [doc.approver_user_id, 'Administrator']", "custom_confirm_message": null, "custom_confirm_transition": 0, "next_state": "Cancelled", diff --git a/one_fm/hooks.py b/one_fm/hooks.py index e50675ba30..8e4f03e1af 100644 --- a/one_fm/hooks.py +++ b/one_fm/hooks.py @@ -394,6 +394,19 @@ "before_insert": "one_fm.api.doc_methods.help_article.before_insert", # "on_update": "one_fm.api.doc_methods.help_article.on_update", }, + "Shift Request":{ + "before_save":[ + "one_fm.overrides.shift_request.fill_to_date", + "one_fm.overrides.shift_request.send_shift_request_mail", + "one_fm.overrides.shift_request.validate_from_date" + ], + "on_update": [ + "one_fm.overrides.shift_request.on_update", + ], + "validate": [ + "one_fm.overrides.shift_request.validate", + ] + }, "Customer": { "on_update":"one_fm.tasks.erpnext.customer.on_update", }, @@ -686,11 +699,8 @@ ], "15 13 * * *":[ # Attendance Check 'one_fm.one_fm.doctype.attendance_check.attendance_check.schedule_attendance_check', - 'one_fm.one_fm.doctype.attendance_check.attendance_check.assign_attendance_manager_after_48_hours' + 'one_fm.one_fm.doctype.attendance_check.attendance_check.attendance_check_pending_approval_check' ], - # "07 13 * * *":[ # Auto approve attendance check - # 'one_fm.one_fm.doctype.attendance_check.attendance_check.approve_attendance_check' - # ], "15 12 * * *": [ # create shift assignment 'one_fm.api.tasks.assign_pm_shift' ], @@ -783,9 +793,11 @@ "dt": "Assignment Rule", "filters": [["name", "in", [ - "RFM Approver", "Shift Permission Approver", "Attendance Check Reports To", + "RFM Approver", "Shift Permission Approver", "Attendance Check Reports To", "Shift Permission Approver", "Attendance Check Site Supervisor", "Attendance Check Shift Supervisor", "Subcontract Staff Request", - "Purchase Order Approver Action", "Purchase Order Finance Manager Action", "Purchase Order Purchase Manager Action" + "Purchase Order Approver Action", "Purchase Order Finance Manager Action", "Purchase Order Purchase Manager Action", + "Timesheet Return to Draft", "Timesheet Approval Assignment", "Shift Request Draft", "Shift Request Pending Approval", + "Attendance Request Return to Draft", "Attendance Request Approval", "Employee Checkin Issue Approval" ] ]] }, diff --git a/one_fm/legal/doctype/penalty/penalty.py b/one_fm/legal/doctype/penalty/penalty.py index d897d8560d..70b652f49c 100644 --- a/one_fm/legal/doctype/penalty/penalty.py +++ b/one_fm/legal/doctype/penalty/penalty.py @@ -16,13 +16,15 @@ import grpc face_recognition_service_url = frappe.local.conf.face_recognition_service_url -channels = [ - grpc.secure_channel(i, grpc.ssl_channel_credentials()) for i in face_recognition_service_url -] +# channels = [ +# grpc.secure_channel(i, grpc.ssl_channel_credentials()) for i in face_recognition_service_url +# ] # setup stub for face recognition -stubs = [ - facial_recognition_pb2_grpc.FaceRecognitionServiceStub(i) for i in channels -] +# stubs = [ +# facial_recognition_pb2_grpc.FaceRecognitionServiceStub(i) for i in channels +# ] + +stubs = list() class Penalty(Document): def after_insert(self): diff --git a/one_fm/legal/doctype/penalty_issuance_details/penalty_issuance_details.json b/one_fm/legal/doctype/penalty_issuance_details/penalty_issuance_details.json index 28cf33457d..16ec36e7c4 100644 --- a/one_fm/legal/doctype/penalty_issuance_details/penalty_issuance_details.json +++ b/one_fm/legal/doctype/penalty_issuance_details/penalty_issuance_details.json @@ -33,7 +33,7 @@ }, { "fieldname": "exact_notes", - "fieldtype": "Small Text", + "fieldtype": "Text Editor", "label": "Exact Notes on Penalty" }, { @@ -113,7 +113,7 @@ ], "istable": 1, "links": [], - "modified": "2021-09-23 10:28:22.233449", + "modified": "2024-07-21 11:01:20.104225", "modified_by": "Administrator", "module": "Legal", "name": "Penalty Issuance Details", @@ -122,5 +122,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/one_fm/one_fm/custom/attendance.json b/one_fm/one_fm/custom/attendance.json index 84a7d8c06a..ed6c81b526 100644 --- a/one_fm/one_fm/custom/attendance.json +++ b/one_fm/one_fm/custom/attendance.json @@ -11,7 +11,68 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2020-10-06 16:31:16.390600", + "creation": "2024-04-25 14:23:30.195015", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Attendance", + "fetch_from": "employee.employment_type", + "fetch_if_empty": 0, + "fieldname": "custom_employment_type", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 4, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "employee_name", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Employment Type", + "length": 0, + "mandatory_depends_on": null, + "modified": "2024-04-25 14:23:30.195015", + "modified_by": "Administrator", + "module": null, + "name": "Attendance-custom_employment_type", + "no_copy": 0, + "non_negative": 0, + "options": "Employment Type", + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2023-06-07 22:54:35.910972", "default": null, "depends_on": null, "description": null, @@ -19,36 +80,33 @@ "dt": "Attendance", "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "section_break_17", - "fieldtype": "Section Break", + "fieldname": "column_break_nahps", + "fieldtype": "Column Break", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 21, + "idx": 39, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "early_exit", - "is_system_generated": 1, + "insert_after": "reference_doctype", + "is_system_generated": 0, "is_virtual": 0, "label": null, "length": 0, "mandatory_depends_on": null, - "modified": "2020-10-06 16:31:16.390600", + "modified": "2023-06-07 22:54:35.910972", "modified_by": "Administrator", "module": null, - "name": "Attendance-section_break_17", + "name": "Attendance-column_break_nahps", "no_copy": 0, "non_negative": 0, "options": null, "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", "print_hide": 0, @@ -75,50 +133,47 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2020-10-06 16:31:17.654555", + "creation": "2023-06-07 22:49:37.281876", "default": null, - "depends_on": null, + "depends_on": "eval:doc.reference_doctype", "description": null, "docstatus": 0, "dt": "Attendance", - "fetch_from": "shift_assignment.site", + "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "site", - "fieldtype": "Link", + "fieldname": "reference_docname", + "fieldtype": "Dynamic Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 24, + "idx": 40, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "operations_shift", - "is_system_generated": 1, + "insert_after": "column_break_nahps", + "is_system_generated": 0, "is_virtual": 0, - "label": "Site", + "label": "Reference Docname", "length": 0, "mandatory_depends_on": null, - "modified": "2020-10-06 16:31:17.654555", + "modified": "2023-06-07 22:49:37.281876", "modified_by": "Administrator", "module": null, - "name": "Attendance-site", + "name": "Attendance-reference_docname", "no_copy": 0, "non_negative": 0, - "options": "Operations Site", + "options": "reference_doctype", "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 1, + "read_only": 0, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, @@ -139,50 +194,47 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2020-10-06 16:31:18.571753", + "creation": "2023-06-07 22:49:26.738481", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Attendance", - "fetch_from": "shift_assignment.project", + "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "project", + "fieldname": "reference_doctype", "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 25, + "idx": 38, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "site", - "is_system_generated": 1, + "insert_after": "references", + "is_system_generated": 0, "is_virtual": 0, - "label": "Project", + "label": "Reference Doctype", "length": 0, "mandatory_depends_on": null, - "modified": "2020-10-06 16:31:18.571753", + "modified": "2023-06-07 22:49:26.738481", "modified_by": "Administrator", "module": null, - "name": "Attendance-project", + "name": "Attendance-reference_doctype", "no_copy": 0, "non_negative": 0, - "options": "Project", + "options": "DocType", "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 1, + "read_only": 0, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, @@ -203,7 +255,7 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2020-10-06 16:31:19.502006", + "creation": "2023-06-07 22:49:26.496439", "default": null, "depends_on": null, "description": null, @@ -211,36 +263,33 @@ "dt": "Attendance", "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "column_break_21", - "fieldtype": "Column Break", + "fieldname": "references", + "fieldtype": "Section Break", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 27, + "idx": 37, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "timesheet", - "is_system_generated": 1, + "insert_after": "comment", + "is_system_generated": 0, "is_virtual": 0, - "label": null, + "label": "References", "length": 0, "mandatory_depends_on": null, - "modified": "2020-10-06 16:31:19.502006", + "modified": "2023-06-07 22:49:26.496439", "modified_by": "Administrator", "module": null, - "name": "Attendance-column_break_21", + "name": "Attendance-references", "no_copy": 0, "non_negative": 0, "options": null, "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", "print_hide": 0, @@ -267,47 +316,44 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2020-10-06 16:31:20.181570", + "creation": "2023-05-28 11:45:33.517065", "default": null, - "depends_on": null, + "depends_on": "eval:doc.operations_role", "description": null, "docstatus": 0, "dt": "Attendance", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "post_type", - "fieldtype": "Link", + "fetch_from": "operations_role.sale_item", + "fetch_if_empty": 1, + "fieldname": "sale_item", + "fieldtype": "Data", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 31, + "idx": 33, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "roster_type", - "is_system_generated": 1, + "insert_after": "post_type", + "is_system_generated": 0, "is_virtual": 0, - "label": "Post Type", + "label": "Sale Item", "length": 0, "mandatory_depends_on": null, - "modified": "2020-10-06 16:31:20.181570", + "modified": "2023-05-28 11:45:33.517065", "modified_by": "Administrator", "module": null, - "name": "Attendance-post_type", + "name": "Attendance-sale_item", "no_copy": 0, "non_negative": 0, - "options": "Operations Role", + "options": null, "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", - "print_hide": 0, + "print_hide": 1, "print_hide_if_no_value": 0, "print_width": null, "read_only": 1, @@ -331,56 +377,53 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2020-10-06 16:31:20.181570", + "creation": "2023-01-22 12:33:39.207185", "default": null, - "depends_on": null, + "depends_on": "", "description": null, "docstatus": 0, "dt": "Attendance", - "fetch_from": "shift_assignment.operations_role", + "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "operations_role", - "fieldtype": "Link", + "fieldname": "comment", + "fieldtype": "Small Text", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 28, + "idx": 36, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "column_break_21", - "is_system_generated": 1, + "insert_after": "attendance_comment", + "is_system_generated": 0, "is_virtual": 0, - "label": "Operations Role", + "label": "Comment", "length": 0, - "mandatory_depends_on": null, - "modified": "2020-10-06 16:31:20.181570", + "mandatory_depends_on": "", + "modified": "2023-01-22 12:33:39.207185", "modified_by": "Administrator", "module": null, - "name": "Attendance-operations_role", + "name": "Attendance-comment", "no_copy": 0, "non_negative": 0, - "options": "Operations Role", + "options": null, "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 1, + "read_only": 0, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, "sort_options": 0, - "translatable": 0, + "translatable": 1, "unique": 0, "width": null }, @@ -395,7 +438,7 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2020-10-06 16:31:21.131695", + "creation": "2023-01-22 12:33:38.989680", "default": null, "depends_on": null, "description": null, @@ -403,42 +446,39 @@ "dt": "Attendance", "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "post_abbrv", - "fieldtype": "Data", + "fieldname": "attendance_comment", + "fieldtype": "Section Break", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 29, + "idx": 35, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "operations_role", - "is_system_generated": 1, + "insert_after": "day_off_ot", + "is_system_generated": 0, "is_virtual": 0, - "label": "Post Abbrv", + "label": "Attendance Comment", "length": 0, "mandatory_depends_on": null, - "modified": "2020-10-06 16:31:21.131695", + "modified": "2023-01-22 12:33:38.989680", "modified_by": "Administrator", "module": null, - "name": "Attendance-post_abbrv", + "name": "Attendance-attendance_comment", "no_copy": 0, "non_negative": 0, "options": null, "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 1, + "read_only": 0, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, @@ -459,44 +499,41 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2020-10-16 13:38:23.800388", + "creation": "2023-01-03 01:00:44.771086", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Attendance", - "fetch_from": "shift_assignment.shift", - "fetch_if_empty": 1, - "fieldname": "operations_shift", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "timesheet", "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 23, + "idx": 27, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "shift_assignment", - "is_system_generated": 1, + "insert_after": "project", + "is_system_generated": 0, "is_virtual": 0, - "label": "Operations Shift", + "label": "Timesheet", "length": 0, "mandatory_depends_on": null, - "modified": "2020-10-16 13:38:23.800388", + "modified": "2023-01-03 01:00:44.771086", "modified_by": "Administrator", "module": null, - "name": "Attendance-operations_shift", + "name": "Attendance-timesheet", "no_copy": 0, "non_negative": 0, - "options": "Operations Shift", + "options": "Timesheet", "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", "print_hide": 0, @@ -523,44 +560,41 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2021-11-10 12:20:53.433210", - "default": "Basic", + "creation": "2022-11-06 10:49:34.555115", + "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Attendance", - "fetch_from": "shift_assignment.roster_type", + "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "roster_type", - "fieldtype": "Select", + "fieldname": "shift_assignment", + "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 30, + "idx": 23, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, - "in_list_view": 1, + "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "post_abbrv", - "is_system_generated": 1, + "insert_after": "section_break_17", + "is_system_generated": 0, "is_virtual": 0, - "label": "Roster Type", + "label": "Shift Assignment", "length": 0, "mandatory_depends_on": null, - "modified": "2021-11-10 12:20:53.433210", + "modified": "2022-11-06 10:49:34.555115", "modified_by": "Administrator", "module": null, - "name": "Attendance-roster_type", + "name": "Attendance-shift_assignment", "no_copy": 0, "non_negative": 0, - "options": "Basic\nOver-Time", + "options": "Shift Assignment", "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", "print_hide": 0, @@ -572,7 +606,7 @@ "reqd": 0, "search_index": 0, "sort_options": 0, - "translatable": 1, + "translatable": 0, "unique": 0, "width": null }, @@ -622,9 +656,6 @@ "non_negative": 0, "options": null, "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", "print_hide": 0, @@ -651,44 +682,41 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2022-11-06 10:49:34.555115", - "default": null, + "creation": "2021-11-10 12:20:53.433210", + "default": "Basic", "depends_on": null, "description": null, "docstatus": 0, "dt": "Attendance", - "fetch_from": null, + "fetch_from": "shift_assignment.roster_type", "fetch_if_empty": 0, - "fieldname": "shift_assignment", - "fieldtype": "Link", + "fieldname": "roster_type", + "fieldtype": "Select", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 22, + "idx": 30, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "section_break_17", - "is_system_generated": 0, + "insert_after": "post_abbrv", + "is_system_generated": 1, "is_virtual": 0, - "label": "Shift Assignment", + "label": "Roster Type", "length": 0, "mandatory_depends_on": null, - "modified": "2022-11-06 10:49:34.555115", + "modified": "2021-11-10 12:20:53.433210", "modified_by": "Administrator", "module": null, - "name": "Attendance-shift_assignment", + "name": "Attendance-roster_type", "no_copy": 0, "non_negative": 0, - "options": "Shift Assignment", + "options": "Basic\nOver-Time", "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", "print_hide": 0, @@ -700,7 +728,7 @@ "reqd": 0, "search_index": 0, "sort_options": 0, - "translatable": 0, + "translatable": 1, "unique": 0, "width": null }, @@ -715,44 +743,41 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-01-03 01:00:44.771086", + "creation": "2020-10-16 13:38:23.800388", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Attendance", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "timesheet", + "fetch_from": "shift_assignment.shift", + "fetch_if_empty": 1, + "fieldname": "operations_shift", "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 26, + "idx": 23, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "project", - "is_system_generated": 0, + "insert_after": "shift_assignment", + "is_system_generated": 1, "is_virtual": 0, - "label": "Timesheet", + "label": "Operations Shift", "length": 0, "mandatory_depends_on": null, - "modified": "2023-01-03 01:00:44.771086", + "modified": "2020-10-16 13:38:23.800388", "modified_by": "Administrator", "module": null, - "name": "Attendance-timesheet", + "name": "Attendance-operations_shift", "no_copy": 0, "non_negative": 0, - "options": "Timesheet", + "options": "Operations Shift", "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", "print_hide": 0, @@ -779,7 +804,7 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-01-22 12:33:38.989680", + "creation": "2020-10-06 16:31:21.131695", "default": null, "depends_on": null, "description": null, @@ -787,42 +812,39 @@ "dt": "Attendance", "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "attendance_comment", - "fieldtype": "Section Break", + "fieldname": "post_abbrv", + "fieldtype": "Data", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 34, + "idx": 29, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "day_off_ot", - "is_system_generated": 0, + "insert_after": "operations_role", + "is_system_generated": 1, "is_virtual": 0, - "label": "Attendance Comment", + "label": "Post Abbrv", "length": 0, "mandatory_depends_on": null, - "modified": "2023-01-22 12:33:38.989680", + "modified": "2020-10-06 16:31:21.131695", "modified_by": "Administrator", "module": null, - "name": "Attendance-attendance_comment", + "name": "Attendance-post_abbrv", "no_copy": 0, "non_negative": 0, "options": null, "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 0, + "read_only": 1, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, @@ -843,56 +865,53 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-01-22 12:33:39.207185", + "creation": "2020-10-06 16:31:20.181570", "default": null, - "depends_on": "", + "depends_on": null, "description": null, "docstatus": 0, "dt": "Attendance", "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "comment", - "fieldtype": "Small Text", + "fieldname": "post_type", + "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 35, + "idx": 31, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "attendance_comment", - "is_system_generated": 0, + "insert_after": "roster_type", + "is_system_generated": 1, "is_virtual": 0, - "label": "Comment", + "label": "Post Type", "length": 0, - "mandatory_depends_on": "", - "modified": "2023-01-22 12:33:39.207185", + "mandatory_depends_on": null, + "modified": "2020-10-06 16:31:20.181570", "modified_by": "Administrator", "module": null, - "name": "Attendance-comment", + "name": "Attendance-post_type", "no_copy": 0, "non_negative": 0, - "options": null, + "options": "Operations Role", "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 0, + "read_only": 1, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, "sort_options": 0, - "translatable": 1, + "translatable": 0, "unique": 0, "width": null }, @@ -907,47 +926,44 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-05-28 11:45:33.517065", + "creation": "2020-10-06 16:31:20.181570", "default": null, - "depends_on": "eval:doc.operations_role", + "depends_on": null, "description": null, "docstatus": 0, "dt": "Attendance", - "fetch_from": "operations_role.sale_item", - "fetch_if_empty": 1, - "fieldname": "sale_item", - "fieldtype": "Data", + "fetch_from": "shift_assignment.operations_role", + "fetch_if_empty": 0, + "fieldname": "operations_role", + "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 32, + "idx": 28, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "post_type", - "is_system_generated": 0, + "insert_after": "column_break_21", + "is_system_generated": 1, "is_virtual": 0, - "label": "Sale Item", + "label": "Operations Role", "length": 0, "mandatory_depends_on": null, - "modified": "2023-05-28 11:45:33.517065", + "modified": "2020-10-06 16:31:20.181570", "modified_by": "Administrator", "module": null, - "name": "Attendance-sale_item", + "name": "Attendance-operations_role", "no_copy": 0, "non_negative": 0, - "options": null, + "options": "Operations Role", "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", - "print_hide": 1, + "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, "read_only": 1, @@ -971,7 +987,7 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-06-07 22:49:26.496439", + "creation": "2020-10-06 16:31:19.502006", "default": null, "depends_on": null, "description": null, @@ -979,36 +995,33 @@ "dt": "Attendance", "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "references", - "fieldtype": "Section Break", + "fieldname": "column_break_21", + "fieldtype": "Column Break", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 36, + "idx": 27, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "comment", - "is_system_generated": 0, + "insert_after": "timesheet", + "is_system_generated": 1, "is_virtual": 0, - "label": "References", + "label": null, "length": 0, "mandatory_depends_on": null, - "modified": "2023-06-07 22:49:26.496439", + "modified": "2020-10-06 16:31:19.502006", "modified_by": "Administrator", "module": null, - "name": "Attendance-references", + "name": "Attendance-column_break_21", "no_copy": 0, "non_negative": 0, "options": null, "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", "print_hide": 0, @@ -1035,50 +1048,47 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-06-07 22:49:26.738481", + "creation": "2020-10-06 16:31:18.571753", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Attendance", - "fetch_from": null, + "fetch_from": "shift_assignment.project", "fetch_if_empty": 0, - "fieldname": "reference_doctype", + "fieldname": "project", "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 37, + "idx": 25, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "references", - "is_system_generated": 0, + "insert_after": "site", + "is_system_generated": 1, "is_virtual": 0, - "label": "Reference Doctype", + "label": "Project", "length": 0, "mandatory_depends_on": null, - "modified": "2023-06-07 22:49:26.738481", + "modified": "2020-10-06 16:31:18.571753", "modified_by": "Administrator", "module": null, - "name": "Attendance-reference_doctype", + "name": "Attendance-project", "no_copy": 0, "non_negative": 0, - "options": "DocType", + "options": "Project", "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 0, + "read_only": 1, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, @@ -1099,50 +1109,47 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-06-07 22:49:37.281876", + "creation": "2020-10-06 16:31:17.654555", "default": null, - "depends_on": "eval:doc.reference_doctype", + "depends_on": null, "description": null, "docstatus": 0, "dt": "Attendance", - "fetch_from": null, + "fetch_from": "shift_assignment.site", "fetch_if_empty": 0, - "fieldname": "reference_docname", - "fieldtype": "Dynamic Link", + "fieldname": "site", + "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 39, + "idx": 24, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "column_break_nahps", - "is_system_generated": 0, + "insert_after": "operations_shift", + "is_system_generated": 1, "is_virtual": 0, - "label": "Reference Docname", + "label": "Site", "length": 0, "mandatory_depends_on": null, - "modified": "2023-06-07 22:49:37.281876", + "modified": "2020-10-06 16:31:17.654555", "modified_by": "Administrator", "module": null, - "name": "Attendance-reference_docname", + "name": "Attendance-site", "no_copy": 0, "non_negative": 0, - "options": "reference_doctype", + "options": "Operations Site", "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 0, + "read_only": 1, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, @@ -1163,7 +1170,7 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-06-07 22:54:35.910972", + "creation": "2020-10-06 16:31:16.390600", "default": null, "depends_on": null, "description": null, @@ -1171,36 +1178,33 @@ "dt": "Attendance", "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "column_break_nahps", - "fieldtype": "Column Break", + "fieldname": "section_break_17", + "fieldtype": "Section Break", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 38, + "idx": 21, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "reference_doctype", - "is_system_generated": 0, + "insert_after": "early_exit", + "is_system_generated": 1, "is_virtual": 0, "label": null, "length": 0, "mandatory_depends_on": null, - "modified": "2023-06-07 22:54:35.910972", + "modified": "2020-10-06 16:31:16.390600", "modified_by": "Administrator", "module": null, - "name": "Attendance-column_break_nahps", + "name": "Attendance-section_break_17", "no_copy": 0, "non_negative": 0, "options": null, "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "permlevel": 0, "precision": "", "print_hide": 0, @@ -1226,52 +1230,46 @@ "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2023-12-24 15:24:16.904880", + "creation": "2024-04-25 14:23:30.068325", "default_value": null, "doc_type": "Attendance", "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "roster_type", + "doctype_or_field": "DocType", + "field_name": null, "idx": 0, "is_system_generated": 0, - "modified": "2023-12-24 15:24:16.904880", + "modified": "2024-04-25 14:23:30.068325", "modified_by": "Administrator", "module": null, - "name": "Attendance-roster_type-in_standard_filter", + "name": "Attendance-main-field_order", "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "property": "in_standard_filter", - "property_type": "Check", + "property": "field_order", + "property_type": "Data", "row_name": null, - "value": "1" + "value": "[\"attendance_details\", \"naming_series\", \"employee\", \"employee_name\", \"employment_type\", \"working_hours\", \"status\", \"leave_type\", \"leave_application\", \"column_break0\", \"attendance_date\", \"company\", \"department\", \"attendance_request\", \"details_section\", \"shift\", \"in_time\", \"out_time\", \"column_break_18\", \"late_entry\", \"early_exit\", \"section_break_17\", \"shift_assignment\", \"operations_shift\", \"site\", \"project\", \"timesheet\", \"column_break_21\", \"operations_role\", \"post_abbrv\", \"roster_type\", \"post_type\", \"sale_item\", \"day_off_ot\", \"attendance_comment\", \"comment\", \"references\", \"reference_doctype\", \"column_break_nahps\", \"reference_docname\", \"amended_from\"]" }, { "_assign": null, "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2023-12-24 15:24:16.715079", + "creation": "2023-12-24 15:24:16.904880", "default_value": null, "doc_type": "Attendance", "docstatus": 0, - "doctype_or_field": "DocType", - "field_name": null, + "doctype_or_field": "DocField", + "field_name": "roster_type", "idx": 0, "is_system_generated": 0, - "modified": "2023-12-24 15:24:16.715079", + "modified": "2023-12-24 15:24:16.904880", "modified_by": "Administrator", "module": null, - "name": "Attendance-main-field_order", + "name": "Attendance-roster_type-in_standard_filter", "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "property": "field_order", - "property_type": "Data", + "property": "in_standard_filter", + "property_type": "Check", "row_name": null, - "value": "[\"attendance_details\", \"naming_series\", \"employee\", \"employee_name\", \"working_hours\", \"status\", \"leave_type\", \"leave_application\", \"column_break0\", \"attendance_date\", \"company\", \"department\", \"attendance_request\", \"details_section\", \"shift\", \"in_time\", \"out_time\", \"column_break_18\", \"late_entry\", \"early_exit\", \"section_break_17\", \"shift_assignment\", \"operations_shift\", \"site\", \"project\", \"timesheet\", \"column_break_21\", \"operations_role\", \"post_abbrv\", \"roster_type\", \"post_type\", \"sale_item\", \"day_off_ot\", \"attendance_comment\", \"comment\", \"references\", \"reference_doctype\", \"column_break_nahps\", \"reference_docname\", \"amended_from\"]" + "value": "1" }, { "_assign": null, @@ -1291,9 +1289,6 @@ "module": null, "name": "Attendance-working_hours-depends_on", "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "property": "depends_on", "property_type": "Data", "row_name": null, @@ -1317,9 +1312,6 @@ "module": null, "name": "Attendance-shift-read_only", "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "property": "read_only", "property_type": "Check", "row_name": null, @@ -1343,9 +1335,6 @@ "module": null, "name": "Attendance-shift-fetch_if_empty", "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "property": "fetch_if_empty", "property_type": "Check", "row_name": null, @@ -1369,9 +1358,6 @@ "module": null, "name": "Attendance-shift-fetch_from", "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "property": "fetch_from", "property_type": "Small Text", "row_name": null, @@ -1395,9 +1381,6 @@ "module": null, "name": "Attendance-attendance_date-in_standard_filter", "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "property": "in_standard_filter", "property_type": "Check", "row_name": null, @@ -1421,9 +1404,6 @@ "module": "One Fm", "name": "Attendance-status-options", "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, "property": "options", "property_type": "Select", "row_name": null, diff --git a/one_fm/one_fm/custom/attendance_request.json b/one_fm/one_fm/custom/attendance_request.json index c53a6acce0..6958df5d31 100644 --- a/one_fm/one_fm/custom/attendance_request.json +++ b/one_fm/one_fm/custom/attendance_request.json @@ -1,5 +1,137 @@ { "custom_fields": [ + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2023-06-18 14:47:21.591893", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Attendance Request", + "fetch_from": "approver.user_id", + "fetch_if_empty": 1, + "fieldname": "approver_user", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 8, + "ignore_user_permissions": 1, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "approver_name", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Approver User", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2023-06-18 14:47:21.591893", + "modified_by": "Administrator", + "module": null, + "name": "Attendance Request-approver_user", + "no_copy": 0, + "non_negative": 0, + "options": "User", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2023-06-18 14:43:58.477374", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Attendance Request", + "fetch_from": "approver.employee_name", + "fetch_if_empty": 1, + "fieldname": "approver_name", + "fieldtype": "Data", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 7, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "approver", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Approver Name", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2023-06-18 14:43:58.477374", + "modified_by": "Administrator", + "module": null, + "name": "Attendance Request-approver_name", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, { "_assign": null, "_comments": null, @@ -21,7 +153,7 @@ "fetch_if_empty": 0, "fieldname": "approver", "fieldtype": "Link", - "hidden": 0, + "hidden": 1, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, @@ -208,101 +340,101 @@ "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2024-07-01 15:09:07.209381", + "creation": "2024-09-30 15:05:01.798681", "default_value": null, "doc_type": "Attendance Request", "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "employee", + "doctype_or_field": "DocType", + "field_name": null, "idx": 0, "is_system_generated": 0, - "modified": "2024-07-01 15:09:07.209381", + "modified": "2024-09-30 15:05:01.798681", "modified_by": "Administrator", "module": null, - "name": "Attendance Request-employee-ignore_user_permissions", + "name": "Attendance Request-main-field_order", "owner": "Administrator", "parent": null, "parentfield": null, "parenttype": null, - "property": "ignore_user_permissions", - "property_type": "Check", + "property": "field_order", + "property_type": "Data", "row_name": null, - "value": "1" + "value": "[\"workflow_state\", \"employee\", \"employee_name\", \"department\", \"update_request\", \"approver\", \"approver_name\", \"approver_user\", \"column_break_5\", \"company\", \"from_date\", \"to_date\", \"half_day\", \"half_day_date\", \"include_holidays\", \"shift\", \"reason_section\", \"reason\", \"column_break_4\", \"explanation\", \"amended_from\"]" }, { "_assign": null, "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2024-07-01 15:09:06.992261", + "creation": "2023-11-13 10:52:21.502016", "default_value": null, "doc_type": "Attendance Request", "docstatus": 0, - "doctype_or_field": "DocType", - "field_name": null, + "doctype_or_field": "DocField", + "field_name": "half_day", "idx": 0, "is_system_generated": 0, - "modified": "2024-07-01 15:09:06.992261", + "modified": "2024-09-30 13:58:54.883633", "modified_by": "Administrator", "module": null, - "name": "Attendance Request-main-field_order", + "name": "Attendance Request-half_day-read_only", "owner": "Administrator", "parent": null, "parentfield": null, "parenttype": null, - "property": "field_order", - "property_type": "Data", + "property": "read_only", + "property_type": "Check", "row_name": null, - "value": "[\"workflow_state\", \"employee\", \"employee_name\", \"department\", \"update_request\", \"approver\", \"column_break_5\", \"company\", \"from_date\", \"to_date\", \"half_day\", \"half_day_date\", \"include_holidays\", \"shift\", \"reason_section\", \"reason\", \"column_break_4\", \"explanation\", \"amended_from\"]" + "value": "1" }, { "_assign": null, "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2023-02-08 11:38:39.429724", + "creation": "2023-11-13 10:52:21.451378", "default_value": null, "doc_type": "Attendance Request", "docstatus": 0, "doctype_or_field": "DocField", - "field_name": "reason", + "field_name": "half_day", "idx": 0, "is_system_generated": 0, - "modified": "2024-06-25 13:56:31.109566", + "modified": "2024-09-30 13:58:54.872443", "modified_by": "Administrator", "module": null, - "name": "Attendance Request-reason-in_list_view", + "name": "Attendance Request-half_day-hidden", "owner": "Administrator", "parent": null, "parentfield": null, "parenttype": null, - "property": "in_list_view", + "property": "hidden", "property_type": "Check", "row_name": null, - "value": "0" + "value": "1" }, { "_assign": null, "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2023-02-08 11:38:39.442269", + "creation": "2023-02-11 23:31:05.168275", "default_value": null, "doc_type": "Attendance Request", "docstatus": 0, "doctype_or_field": "DocField", - "field_name": "employee_name", + "field_name": "explanation", "idx": 0, "is_system_generated": 0, - "modified": "2024-06-25 13:56:31.087880", + "modified": "2024-09-30 13:58:54.858923", "modified_by": "Administrator", "module": null, - "name": "Attendance Request-employee_name-in_list_view", + "name": "Attendance Request-explanation-reqd", "owner": "Administrator", "parent": null, "parentfield": null, "parenttype": null, - "property": "in_list_view", + "property": "reqd", "property_type": "Check", "row_name": null, "value": "1" @@ -312,18 +444,18 @@ "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2023-02-08 11:38:39.455077", + "creation": "2023-02-08 11:38:39.492398", "default_value": null, "doc_type": "Attendance Request", "docstatus": 0, "doctype_or_field": "DocField", - "field_name": "employee", + "field_name": "workflow_state", "idx": 0, "is_system_generated": 0, - "modified": "2024-06-25 13:56:31.067192", + "modified": "2024-09-30 13:58:54.848002", "modified_by": "Administrator", "module": null, - "name": "Attendance Request-employee-in_list_view", + "name": "Attendance Request-workflow_state-in_list_view", "owner": "Administrator", "parent": null, "parentfield": null, @@ -338,18 +470,18 @@ "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2023-02-08 11:38:39.467485", + "creation": "2023-02-08 11:38:39.480029", "default_value": null, "doc_type": "Attendance Request", "docstatus": 0, "doctype_or_field": "DocField", - "field_name": "from_date", + "field_name": "to_date", "idx": 0, "is_system_generated": 0, - "modified": "2024-06-25 13:56:31.046702", + "modified": "2024-09-30 13:58:54.835000", "modified_by": "Administrator", "module": null, - "name": "Attendance Request-from_date-in_list_view", + "name": "Attendance Request-to_date-in_list_view", "owner": "Administrator", "parent": null, "parentfield": null, @@ -364,18 +496,18 @@ "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2023-02-08 11:38:39.480029", + "creation": "2023-02-08 11:38:39.467485", "default_value": null, "doc_type": "Attendance Request", "docstatus": 0, "doctype_or_field": "DocField", - "field_name": "to_date", + "field_name": "from_date", "idx": 0, "is_system_generated": 0, - "modified": "2024-06-25 13:56:31.027948", + "modified": "2024-09-30 13:58:54.822943", "modified_by": "Administrator", "module": null, - "name": "Attendance Request-to_date-in_list_view", + "name": "Attendance Request-from_date-in_list_view", "owner": "Administrator", "parent": null, "parentfield": null, @@ -390,18 +522,18 @@ "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2023-02-08 11:38:39.492398", + "creation": "2023-02-08 11:38:39.455077", "default_value": null, "doc_type": "Attendance Request", "docstatus": 0, "doctype_or_field": "DocField", - "field_name": "workflow_state", + "field_name": "employee", "idx": 0, "is_system_generated": 0, - "modified": "2024-06-25 13:56:31.004431", + "modified": "2024-09-30 13:58:54.810560", "modified_by": "Administrator", "module": null, - "name": "Attendance Request-workflow_state-in_list_view", + "name": "Attendance Request-employee-in_list_view", "owner": "Administrator", "parent": null, "parentfield": null, @@ -416,23 +548,23 @@ "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2023-02-11 23:31:05.168275", + "creation": "2023-02-08 11:38:39.442269", "default_value": null, "doc_type": "Attendance Request", "docstatus": 0, "doctype_or_field": "DocField", - "field_name": "explanation", + "field_name": "employee_name", "idx": 0, "is_system_generated": 0, - "modified": "2024-06-25 13:56:30.979633", + "modified": "2024-09-30 13:58:54.796896", "modified_by": "Administrator", "module": null, - "name": "Attendance Request-explanation-reqd", + "name": "Attendance Request-employee_name-in_list_view", "owner": "Administrator", "parent": null, "parentfield": null, "parenttype": null, - "property": "reqd", + "property": "in_list_view", "property_type": "Check", "row_name": null, "value": "1" @@ -442,49 +574,49 @@ "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2023-11-13 10:52:21.451378", + "creation": "2023-02-08 11:38:39.429724", "default_value": null, "doc_type": "Attendance Request", "docstatus": 0, "doctype_or_field": "DocField", - "field_name": "half_day", + "field_name": "reason", "idx": 0, "is_system_generated": 0, - "modified": "2024-06-25 13:56:30.937615", + "modified": "2024-09-30 13:58:54.783464", "modified_by": "Administrator", "module": null, - "name": "Attendance Request-half_day-hidden", + "name": "Attendance Request-reason-in_list_view", "owner": "Administrator", "parent": null, "parentfield": null, "parenttype": null, - "property": "hidden", + "property": "in_list_view", "property_type": "Check", "row_name": null, - "value": "1" + "value": "0" }, { "_assign": null, "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2023-11-13 10:52:21.502016", + "creation": "2024-07-01 15:09:07.209381", "default_value": null, "doc_type": "Attendance Request", "docstatus": 0, "doctype_or_field": "DocField", - "field_name": "half_day", + "field_name": "employee", "idx": 0, "is_system_generated": 0, - "modified": "2024-06-25 13:56:30.918618", + "modified": "2024-09-30 13:58:54.749641", "modified_by": "Administrator", "module": null, - "name": "Attendance Request-half_day-read_only", + "name": "Attendance Request-employee-ignore_user_permissions", "owner": "Administrator", "parent": null, "parentfield": null, "parenttype": null, - "property": "read_only", + "property": "ignore_user_permissions", "property_type": "Check", "row_name": null, "value": "1" diff --git a/one_fm/one_fm/custom/employee_checkin.json b/one_fm/one_fm/custom/employee_checkin.json index 70804859ed..d5917217eb 100644 --- a/one_fm/one_fm/custom/employee_checkin.json +++ b/one_fm/one_fm/custom/employee_checkin.json @@ -11,40 +11,40 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2022-01-26 16:05:02.103679", + "creation": "2024-03-25 01:31:19.979822", "default": null, - "depends_on": "eval:doc.shift_assignment", + "depends_on": "eval: doc.is_replaced;", "description": null, "docstatus": 0, "dt": "Employee Checkin", - "fetch_from": "shift_assignment.shift", - "fetch_if_empty": 1, - "fieldname": "operations_shift", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "replaced_employee_checkin", "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 19, - "ignore_user_permissions": 1, + "idx": 24, + "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "shift_assignment", + "insert_after": "is_replaced", "is_system_generated": 0, "is_virtual": 0, - "label": "Operations Shift", + "label": "Replaced Employee Checkin", "length": 0, "mandatory_depends_on": null, - "modified": "2020-05-26 19:06:49.143490", + "modified": "2024-03-25 01:31:19.979822", "modified_by": "Administrator", "module": null, - "name": "Employee Checkin-operations_shift", + "name": "Employee Checkin-replaced_employee_checkin", "no_copy": 0, "non_negative": 0, - "options": "Operations Shift", + "options": "Shift Assignment", "owner": "Administrator", "parent": null, "parentfield": null, @@ -54,11 +54,12 @@ "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 1, + "read_only": 0, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, + "sort_options": 0, "translatable": 0, "unique": 0, "width": null @@ -74,7 +75,7 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2022-01-26 16:05:01.508577", + "creation": "2024-03-11 22:56:55.324796", "default": null, "depends_on": null, "description": null, @@ -82,29 +83,29 @@ "dt": "Employee Checkin", "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "shift_details", - "fieldtype": "Section Break", + "fieldname": "is_replaced", + "fieldtype": "Check", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 17, + "idx": 23, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "shift_actual_end", + "insert_after": "company", "is_system_generated": 0, "is_virtual": 0, - "label": "Shift Details", + "label": "Is Replaced", "length": 0, "mandatory_depends_on": null, - "modified": "2020-05-26 20:08:30.510474", + "modified": "2024-03-11 22:56:55.324796", "modified_by": "Administrator", "module": null, - "name": "Employee Checkin-shift_details", + "name": "Employee Checkin-is_replaced", "no_copy": 0, "non_negative": 0, "options": null, @@ -117,11 +118,12 @@ "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 0, + "read_only": 1, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, + "sort_options": 0, "translatable": 0, "unique": 0, "width": null @@ -137,40 +139,40 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2022-01-26 16:05:02.532340", - "default": null, - "depends_on": "eval:doc.shift_assignment", + "creation": "2023-08-02 09:00:58.483110", + "default": "Check-in Form", + "depends_on": null, "description": null, "docstatus": 0, "dt": "Employee Checkin", - "fetch_from": "shift_assignment.shift_type", - "fetch_if_empty": 1, - "fieldname": "shift_type", - "fieldtype": "Data", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "source", + "fieldtype": "Select", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 27, - "ignore_user_permissions": 1, + "idx": 33, + "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "roster_type", + "insert_after": "employee_checkin_issue", "is_system_generated": 0, "is_virtual": 0, - "label": "Shift Type", + "label": "Source", "length": 0, "mandatory_depends_on": null, - "modified": "2020-05-26 20:09:19.795190", + "modified": "2023-08-02 09:00:58.483110", "modified_by": "Administrator", "module": null, - "name": "Employee Checkin-shift_type", + "name": "Employee Checkin-source", "no_copy": 0, "non_negative": 0, - "options": null, + "options": "\nMobile App\nMobile Web\nCheck-in Form\nFrappe Page", "owner": "Administrator", "parent": null, "parentfield": null, @@ -185,6 +187,7 @@ "report_hide": 0, "reqd": 0, "search_index": 0, + "sort_options": 0, "translatable": 1, "unique": 0, "width": null @@ -200,37 +203,37 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2022-01-26 16:05:02.397579", + "creation": "2023-06-09 05:21:24.630687", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Employee Checkin", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "column_break_15", - "fieldtype": "Column Break", + "fetch_from": "operations_role.post_abbrv", + "fetch_if_empty": 1, + "fieldname": "post_abbrv", + "fieldtype": "Data", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 23, + "idx": 27, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "company", + "insert_after": "operations_role", "is_system_generated": 0, "is_virtual": 0, - "label": null, + "label": "Post Abbrv", "length": 0, "mandatory_depends_on": null, - "modified": "2020-05-26 20:09:43.875444", + "modified": "2023-06-09 05:21:24.630687", "modified_by": "Administrator", "module": null, - "name": "Employee Checkin-column_break_15", + "name": "Employee Checkin-post_abbrv", "no_copy": 0, "non_negative": 0, "options": null, @@ -243,11 +246,12 @@ "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 0, + "read_only": 1, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, + "sort_options": 0, "translatable": 0, "unique": 0, "width": null @@ -263,40 +267,40 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2022-01-26 16:05:02.669233", + "creation": "2023-06-09 05:20:54.998213", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Employee Checkin", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "shift_permission", + "fetch_from": "shift_assignment.operations_role", + "fetch_if_empty": 1, + "fieldname": "operations_role", "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 28, + "idx": 26, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "shift_type", + "insert_after": "column_break_15", "is_system_generated": 0, "is_virtual": 0, - "label": "Shift Permission", + "label": "Operations Role", "length": 0, "mandatory_depends_on": null, - "modified": "2020-06-02 00:02:30.944886", + "modified": "2023-06-09 05:20:54.998213", "modified_by": "Administrator", "module": null, - "name": "Employee Checkin-shift_permission", + "name": "Employee Checkin-operations_role", "no_copy": 0, "non_negative": 0, - "options": "Shift Permission", + "options": "Operations Role", "owner": "Administrator", "parent": null, "parentfield": null, @@ -311,6 +315,7 @@ "report_hide": 0, "reqd": 0, "search_index": 0, + "sort_options": 0, "translatable": 0, "unique": 0, "width": null @@ -326,40 +331,40 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2022-01-26 16:05:02.797262", - "default": "now", + "creation": "2023-06-09 03:37:57.748123", + "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Employee Checkin", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "actual_time", - "fieldtype": "Datetime", + "fetch_from": "shift_assignment.company", + "fetch_if_empty": 1, + "fieldname": "company", + "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 29, + "idx": 22, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "shift_permission", + "insert_after": "project", "is_system_generated": 0, "is_virtual": 0, - "label": "Actual Time", + "label": "Company", "length": 0, "mandatory_depends_on": null, - "modified": "2020-06-02 21:47:57.757045", + "modified": "2023-06-09 03:37:57.748123", "modified_by": "Administrator", "module": null, - "name": "Employee Checkin-actual_time", + "name": "Employee Checkin-company", "no_copy": 0, "non_negative": 0, - "options": null, + "options": "Company", "owner": "Administrator", "parent": null, "parentfield": null, @@ -374,6 +379,7 @@ "report_hide": 0, "reqd": 0, "search_index": 0, + "sort_options": 0, "translatable": 0, "unique": 0, "width": null @@ -389,40 +395,40 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2022-05-10 11:21:36.399486", + "creation": "2023-06-09 03:34:51.630935", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Employee Checkin", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "shift_assignment", + "fetch_from": "shift_assignment.project", + "fetch_if_empty": 1, + "fieldname": "project", "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 18, + "idx": 21, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "shift_details", - "is_system_generated": 1, + "insert_after": "operations_site", + "is_system_generated": 0, "is_virtual": 0, - "label": "Shift Assignment", + "label": "Project", "length": 0, "mandatory_depends_on": null, - "modified": "2022-05-10 11:21:36.399486", + "modified": "2023-06-09 03:34:51.630935", "modified_by": "Administrator", "module": null, - "name": "Employee Checkin-shift_assignment", + "name": "Employee Checkin-project", "no_copy": 0, "non_negative": 0, - "options": "Shift Assignment", + "options": "Project", "owner": "Administrator", "parent": null, "parentfield": null, @@ -432,11 +438,12 @@ "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 0, + "read_only": 1, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, + "sort_options": 0, "translatable": 0, "unique": 0, "width": null @@ -452,41 +459,41 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-01-10 18:33:54.209569", - "default": "0", + "creation": "2023-06-09 03:27:59.831109", + "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Employee Checkin", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "late_entry", - "fieldtype": "Check", + "fetch_from": "shift_assignment.site", + "fetch_if_empty": 1, + "fieldname": "operations_site", + "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 5, + "idx": 20, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "shift", + "insert_after": "operations_shift", "is_system_generated": 0, "is_virtual": 0, - "label": "Late Entry", + "label": "Operations Site", "length": 0, "mandatory_depends_on": null, - "modified": "2023-01-10 18:33:54.209569", - "modified_by": "e.anthony@one-fm.com", + "modified": "2023-06-09 03:27:59.831109", + "modified_by": "Administrator", "module": null, - "name": "Employee Checkin-late_entry", + "name": "Employee Checkin-operations_site", "no_copy": 0, "non_negative": 0, - "options": null, - "owner": "e.anthony@one-fm.com", + "options": "Operations Site", + "owner": "Administrator", "parent": null, "parentfield": null, "parenttype": null, @@ -500,6 +507,7 @@ "report_hide": 0, "reqd": 0, "search_index": 0, + "sort_options": 0, "translatable": 0, "unique": 0, "width": null @@ -515,41 +523,41 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-01-10 19:21:18.836779", + "creation": "2023-03-04 16:23:07.886381", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Employee Checkin", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "early_exit", - "fieldtype": "Check", + "fetch_from": "shift_assignment.roster_type", + "fetch_if_empty": 1, + "fieldname": "roster_type", + "fieldtype": "Data", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 6, + "idx": 28, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "late_entry", + "insert_after": "post_abbrv", "is_system_generated": 0, "is_virtual": 0, - "label": "Early Exit", + "label": "Roster Type", "length": 0, "mandatory_depends_on": null, - "modified": "2023-01-10 19:21:18.836779", - "modified_by": "e.anthony@one-fm.com", + "modified": "2023-03-04 16:23:07.886381", + "modified_by": "Administrator", "module": null, - "name": "Employee Checkin-early_exit", + "name": "Employee Checkin-roster_type", "no_copy": 0, "non_negative": 0, "options": null, - "owner": "e.anthony@one-fm.com", + "owner": "Administrator", "parent": null, "parentfield": null, "parenttype": null, @@ -558,12 +566,13 @@ "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 1, + "read_only": 0, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, - "translatable": 0, + "sort_options": 0, + "translatable": 1, "unique": 0, "width": null }, @@ -578,7 +587,7 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-01-24 11:06:17.669178", + "creation": "2023-02-02 11:01:34.236890", "default": null, "depends_on": null, "description": null, @@ -586,32 +595,32 @@ "dt": "Employee Checkin", "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "employee_checkin_issue", - "fieldtype": "Link", + "fieldname": "date", + "fieldtype": "Date", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 30, + "idx": 9, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "actual_time", + "in_standard_filter": 1, + "insert_after": "time", "is_system_generated": 0, "is_virtual": 0, - "label": "Employee Checkin Issue", + "label": "Date", "length": 0, "mandatory_depends_on": null, - "modified": "2023-01-24 11:06:17.669178", + "modified": "2023-02-02 11:01:34.236890", "modified_by": "Administrator", "module": null, - "name": "Employee Checkin-employee_checkin_issue", + "name": "Employee Checkin-date", "no_copy": 0, "non_negative": 0, - "options": "Employee Checkin Issue", + "options": null, "owner": "Administrator", "parent": null, "parentfield": null, @@ -626,6 +635,7 @@ "report_hide": 0, "reqd": 0, "search_index": 0, + "sort_options": 0, "translatable": 0, "unique": 0, "width": null @@ -641,7 +651,7 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-02-02 11:01:34.236890", + "creation": "2023-01-24 11:06:17.669178", "default": null, "depends_on": null, "description": null, @@ -649,32 +659,32 @@ "dt": "Employee Checkin", "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "date", - "fieldtype": "Date", + "fieldname": "employee_checkin_issue", + "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 9, + "idx": 32, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, - "in_standard_filter": 1, - "insert_after": "time", + "in_standard_filter": 0, + "insert_after": "actual_time", "is_system_generated": 0, "is_virtual": 0, - "label": "Date", + "label": "Employee Checkin Issue", "length": 0, "mandatory_depends_on": null, - "modified": "2023-02-02 11:01:34.236890", + "modified": "2023-01-24 11:06:17.669178", "modified_by": "Administrator", "module": null, - "name": "Employee Checkin-date", + "name": "Employee Checkin-employee_checkin_issue", "no_copy": 0, "non_negative": 0, - "options": null, + "options": "Employee Checkin Issue", "owner": "Administrator", "parent": null, "parentfield": null, @@ -689,6 +699,7 @@ "report_hide": 0, "reqd": 0, "search_index": 0, + "sort_options": 0, "translatable": 0, "unique": 0, "width": null @@ -704,41 +715,41 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-03-04 16:23:07.886381", + "creation": "2023-01-10 19:21:18.836779", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Employee Checkin", - "fetch_from": "shift_assignment.roster_type", - "fetch_if_empty": 1, - "fieldname": "roster_type", - "fieldtype": "Data", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "early_exit", + "fieldtype": "Check", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 26, + "idx": 6, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "post_abbrv", + "insert_after": "late_entry", "is_system_generated": 0, "is_virtual": 0, - "label": "Roster Type", + "label": "Early Exit", "length": 0, "mandatory_depends_on": null, - "modified": "2023-03-04 16:23:07.886381", - "modified_by": "Administrator", + "modified": "2023-01-10 19:21:18.836779", + "modified_by": "e.anthony@one-fm.com", "module": null, - "name": "Employee Checkin-roster_type", + "name": "Employee Checkin-early_exit", "no_copy": 0, "non_negative": 0, "options": null, - "owner": "Administrator", + "owner": "e.anthony@one-fm.com", "parent": null, "parentfield": null, "parenttype": null, @@ -747,12 +758,13 @@ "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 0, + "read_only": 1, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, - "translatable": 1, + "sort_options": 0, + "translatable": 0, "unique": 0, "width": null }, @@ -767,41 +779,41 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-06-09 03:27:59.831109", - "default": null, + "creation": "2023-01-10 18:33:54.209569", + "default": "0", "depends_on": null, "description": null, "docstatus": 0, "dt": "Employee Checkin", - "fetch_from": "shift_assignment.site", - "fetch_if_empty": 1, - "fieldname": "operations_site", - "fieldtype": "Link", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "late_entry", + "fieldtype": "Check", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 20, + "idx": 5, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "operations_shift", + "insert_after": "shift", "is_system_generated": 0, "is_virtual": 0, - "label": "Operations Site", + "label": "Late Entry", "length": 0, "mandatory_depends_on": null, - "modified": "2023-06-09 03:27:59.831109", - "modified_by": "Administrator", + "modified": "2023-01-10 18:33:54.209569", + "modified_by": "e.anthony@one-fm.com", "module": null, - "name": "Employee Checkin-operations_site", + "name": "Employee Checkin-late_entry", "no_copy": 0, "non_negative": 0, - "options": "Operations Site", - "owner": "Administrator", + "options": null, + "owner": "e.anthony@one-fm.com", "parent": null, "parentfield": null, "parenttype": null, @@ -815,6 +827,7 @@ "report_hide": 0, "reqd": 0, "search_index": 0, + "sort_options": 0, "translatable": 0, "unique": 0, "width": null @@ -830,40 +843,104 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-06-09 03:34:51.630935", + "creation": "2022-05-10 11:21:36.399486", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Employee Checkin", - "fetch_from": "shift_assignment.project", - "fetch_if_empty": 1, - "fieldname": "project", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "shift_assignment", "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 21, + "idx": 18, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "operations_site", + "insert_after": "shift_details", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Shift Assignment", + "length": 0, + "mandatory_depends_on": null, + "modified": "2022-05-10 11:21:36.399486", + "modified_by": "Administrator", + "module": null, + "name": "Employee Checkin-shift_assignment", + "no_copy": 0, + "non_negative": 0, + "options": "Shift Assignment", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-01-26 16:05:02.797262", + "default": "now", + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Employee Checkin", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "actual_time", + "fieldtype": "Datetime", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 31, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "shift_permission", "is_system_generated": 0, "is_virtual": 0, - "label": "Project", + "label": "Actual Time", "length": 0, "mandatory_depends_on": null, - "modified": "2023-06-09 03:34:51.630935", + "modified": "2020-06-02 21:47:57.757045", "modified_by": "Administrator", "module": null, - "name": "Employee Checkin-project", + "name": "Employee Checkin-actual_time", "no_copy": 0, "non_negative": 0, - "options": "Project", + "options": null, "owner": "Administrator", "parent": null, "parentfield": null, @@ -878,6 +955,7 @@ "report_hide": 0, "reqd": 0, "search_index": 0, + "sort_options": 0, "translatable": 0, "unique": 0, "width": null @@ -893,40 +971,40 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-06-09 03:37:57.748123", + "creation": "2022-01-26 16:05:02.669233", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Employee Checkin", - "fetch_from": "shift_assignment.company", - "fetch_if_empty": 1, - "fieldname": "company", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "shift_permission", "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 22, + "idx": 30, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "project", + "insert_after": "shift_type", "is_system_generated": 0, "is_virtual": 0, - "label": "Company", + "label": "Shift Permission", "length": 0, "mandatory_depends_on": null, - "modified": "2023-06-09 03:37:57.748123", + "modified": "2020-06-02 00:02:30.944886", "modified_by": "Administrator", "module": null, - "name": "Employee Checkin-company", + "name": "Employee Checkin-shift_permission", "no_copy": 0, "non_negative": 0, - "options": "Company", + "options": "Shift Permission", "owner": "Administrator", "parent": null, "parentfield": null, @@ -941,6 +1019,7 @@ "report_hide": 0, "reqd": 0, "search_index": 0, + "sort_options": 0, "translatable": 0, "unique": 0, "width": null @@ -956,40 +1035,40 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-06-09 05:20:54.998213", + "creation": "2022-01-26 16:05:02.397579", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Employee Checkin", - "fetch_from": "shift_assignment.operations_role", - "fetch_if_empty": 1, - "fieldname": "operations_role", - "fieldtype": "Link", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "column_break_15", + "fieldtype": "Column Break", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 24, + "idx": 25, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "column_break_15", + "insert_after": "replaced_employee_checkin", "is_system_generated": 0, "is_virtual": 0, - "label": "Operations Role", + "label": null, "length": 0, "mandatory_depends_on": null, - "modified": "2023-06-09 05:20:54.998213", + "modified": "2020-05-26 20:09:43.875444", "modified_by": "Administrator", "module": null, - "name": "Employee Checkin-operations_role", + "name": "Employee Checkin-column_break_15", "no_copy": 0, "non_negative": 0, - "options": "Operations Role", + "options": null, "owner": "Administrator", "parent": null, "parentfield": null, @@ -999,11 +1078,12 @@ "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 1, + "read_only": 0, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, + "sort_options": 0, "translatable": 0, "unique": 0, "width": null @@ -1019,37 +1099,37 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-06-09 05:21:24.630687", + "creation": "2022-01-26 16:05:02.532340", "default": null, - "depends_on": null, + "depends_on": "eval:doc.shift_assignment", "description": null, "docstatus": 0, "dt": "Employee Checkin", - "fetch_from": "operations_role.post_abbrv", + "fetch_from": "shift_assignment.shift_type", "fetch_if_empty": 1, - "fieldname": "post_abbrv", + "fieldname": "shift_type", "fieldtype": "Data", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 25, - "ignore_user_permissions": 0, + "idx": 29, + "ignore_user_permissions": 1, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "operations_role", + "insert_after": "roster_type", "is_system_generated": 0, "is_virtual": 0, - "label": "Post Abbrv", + "label": "Shift Type", "length": 0, "mandatory_depends_on": null, - "modified": "2023-06-09 05:21:24.630687", + "modified": "2020-05-26 20:09:19.795190", "modified_by": "Administrator", "module": null, - "name": "Employee Checkin-post_abbrv", + "name": "Employee Checkin-shift_type", "no_copy": 0, "non_negative": 0, "options": null, @@ -1067,7 +1147,8 @@ "report_hide": 0, "reqd": 0, "search_index": 0, - "translatable": 0, + "sort_options": 0, + "translatable": 1, "unique": 0, "width": null }, @@ -1082,40 +1163,104 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2023-08-02 09:00:58.483110", - "default": "Check-in Form", + "creation": "2022-01-26 16:05:01.508577", + "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Employee Checkin", "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "source", - "fieldtype": "Select", + "fieldname": "shift_details", + "fieldtype": "Section Break", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 31, + "idx": 17, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "employee_checkin_issue", + "insert_after": "shift_actual_end", "is_system_generated": 0, "is_virtual": 0, - "label": "Source", + "label": "Shift Details", "length": 0, "mandatory_depends_on": null, - "modified": "2023-08-02 09:00:58.483110", + "modified": "2020-05-26 20:08:30.510474", "modified_by": "Administrator", "module": null, - "name": "Employee Checkin-source", + "name": "Employee Checkin-shift_details", "no_copy": 0, "non_negative": 0, - "options": "\nMobile App\nMobile Web\nCheck-in Form\nFrappe Page", + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-01-26 16:05:02.103679", + "default": null, + "depends_on": "eval:doc.shift_assignment", + "description": null, + "docstatus": 0, + "dt": "Employee Checkin", + "fetch_from": "shift_assignment.shift", + "fetch_if_empty": 1, + "fieldname": "operations_shift", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 19, + "ignore_user_permissions": 1, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "shift_assignment", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Operations Shift", + "length": 0, + "mandatory_depends_on": null, + "modified": "2020-05-26 19:06:49.143490", + "modified_by": "Administrator", + "module": null, + "name": "Employee Checkin-operations_shift", + "no_copy": 0, + "non_negative": 0, + "options": "Operations Shift", "owner": "Administrator", "parent": null, "parentfield": null, @@ -1130,7 +1275,8 @@ "report_hide": 0, "reqd": 0, "search_index": 0, - "translatable": 1, + "sort_options": 0, + "translatable": 0, "unique": 0, "width": null } @@ -1139,6 +1285,32 @@ "doctype": "Employee Checkin", "links": [], "property_setters": [ + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2024-03-25 01:53:05.703005", + "default_value": null, + "doc_type": "Employee Checkin", + "docstatus": 0, + "doctype_or_field": "DocType", + "field_name": null, + "idx": 0, + "is_system_generated": 0, + "modified": "2024-03-25 01:53:05.703005", + "modified_by": "Administrator", + "module": null, + "name": "Employee Checkin-main-field_order", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "field_order", + "property_type": "Data", + "row_name": null, + "value": "[\"employee\", \"employee_name\", \"log_type\", \"shift\", \"late_entry\", \"early_exit\", \"column_break_4\", \"time\", \"date\", \"device_id\", \"skip_auto_attendance\", \"attendance\", \"shift_start\", \"shift_end\", \"shift_actual_start\", \"shift_actual_end\", \"shift_details\", \"shift_assignment\", \"operations_shift\", \"operations_site\", \"project\", \"company\", \"is_replaced\", \"replaced_employee_checkin\", \"column_break_15\", \"operations_role\", \"post_abbrv\", \"roster_type\", \"shift_type\", \"shift_permission\", \"actual_time\", \"employee_checkin_issue\", \"source\"]" + }, { "_assign": null, "_comments": null, @@ -1635,4 +1807,4 @@ } ], "sync_on_migrate": 1 -} \ No newline at end of file +} diff --git a/one_fm/one_fm/custom/shift_assignment.json b/one_fm/one_fm/custom/shift_assignment.json index 72dc5c66f8..43ba3c066d 100644 --- a/one_fm/one_fm/custom/shift_assignment.json +++ b/one_fm/one_fm/custom/shift_assignment.json @@ -11,7 +11,7 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2020-10-08 21:21:52.246455", + "creation": "2024-07-15 23:46:05.847965", "default": null, "depends_on": null, "description": null, @@ -19,32 +19,33 @@ "dt": "Shift Assignment", "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "shift", - "fieldtype": "Link", + "fieldname": "custom_day_off_ot", + "fieldtype": "Check", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 18, + "idx": 12, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "department", + "insert_after": "replaced_shift_assignment", "is_system_generated": 0, "is_virtual": 0, - "label": "Shift", + "label": "Day Off OT", "length": 0, + "link_filters": null, "mandatory_depends_on": null, - "modified": "2020-07-19 10:06:11.379683", + "modified": "2024-07-15 23:46:05.847965", "modified_by": "Administrator", "module": null, - "name": "Shift Assignment-shift", + "name": "Shift Assignment-custom_day_off_ot", "no_copy": 0, "non_negative": 0, - "options": "Operations Shift", + "options": null, "owner": "Administrator", "parent": null, "parentfield": null, @@ -59,6 +60,7 @@ "report_hide": 0, "reqd": 0, "search_index": 0, + "show_dashboard": 0, "sort_options": 0, "translatable": 0, "unique": 0, @@ -70,45 +72,46 @@ "_liked_by": null, "_user_tags": null, "allow_in_quick_entry": 0, - "allow_on_submit": 1, + "allow_on_submit": 0, "bold": 0, "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2020-10-06 13:39:36.617647", + "creation": "2024-04-25 14:19:37.445452", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Shift Assignment", - "fetch_from": "shift.site", + "fetch_from": "employee.employment_type", "fetch_if_empty": 0, - "fieldname": "site", + "fieldname": "custom_employment_type", "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 4, + "idx": 3, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "shift_type", - "is_system_generated": 1, + "insert_after": "employee_name", + "is_system_generated": 0, "is_virtual": 0, - "label": "Site", + "label": "Employment Type", "length": 0, + "link_filters": null, "mandatory_depends_on": null, - "modified": "2020-10-06 13:39:36.617647", + "modified": "2024-04-25 14:19:37.445452", "modified_by": "Administrator", "module": null, - "name": "Shift Assignment-site", + "name": "Shift Assignment-custom_employment_type", "no_copy": 0, "non_negative": 0, - "options": "Operations Site", + "options": "Employment Type", "owner": "Administrator", "parent": null, "parentfield": null, @@ -118,11 +121,12 @@ "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 0, + "read_only": 1, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, + "show_dashboard": 0, "sort_options": 0, "translatable": 0, "unique": 0, @@ -139,40 +143,41 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2020-10-06 13:39:37.549249", + "creation": "2024-03-25 01:19:32.655200", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Shift Assignment", - "fetch_from": "shift.project", + "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "project", + "fieldname": "replaced_shift_assignment", "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 5, + "idx": 12, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "site", - "is_system_generated": 1, + "insert_after": "is_replaced", + "is_system_generated": 0, "is_virtual": 0, - "label": "Project", + "label": "Replaced Shift Assignment", "length": 0, + "link_filters": null, "mandatory_depends_on": null, - "modified": "2020-10-06 13:39:37.549249", - "modified_by": "Administrator", + "modified": "2024-03-25 01:19:32.655200", + "modified_by": "s.shaikh@one-fm.com", "module": null, - "name": "Shift Assignment-project", + "name": "Shift Assignment-replaced_shift_assignment", "no_copy": 0, "non_negative": 0, - "options": "Project", + "options": "Shift Assignment", "owner": "Administrator", "parent": null, "parentfield": null, @@ -187,6 +192,7 @@ "report_hide": 0, "reqd": 0, "search_index": 0, + "show_dashboard": 0, "sort_options": 0, "translatable": 0, "unique": 0, @@ -203,40 +209,41 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2020-10-06 13:39:38.553796", + "creation": "2024-03-11 22:32:18.152808", "default": null, - "depends_on": null, + "depends_on": "eval: doc.is_replaced;", "description": null, "docstatus": 0, "dt": "Shift Assignment", "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "operations_role", - "fieldtype": "Link", + "fieldname": "is_replaced", + "fieldtype": "Check", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 20, + "idx": 11, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "amended_from", - "is_system_generated": 1, + "insert_after": "employee_schedule", + "is_system_generated": 0, "is_virtual": 0, - "label": "Operations Role", + "label": "Is Replaced", "length": 0, + "link_filters": null, "mandatory_depends_on": null, - "modified": "2020-10-06 13:39:38.553796", + "modified": "2024-03-11 22:32:18.152808", "modified_by": "Administrator", "module": null, - "name": "Shift Assignment-operations_role", + "name": "Shift Assignment-is_replaced", "no_copy": 0, "non_negative": 0, - "options": "Operations Role", + "options": null, "owner": "Administrator", "parent": null, "parentfield": null, @@ -246,11 +253,12 @@ "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 0, + "read_only": 1, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, + "show_dashboard": 0, "sort_options": 0, "translatable": 0, "unique": 0, @@ -267,40 +275,41 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2020-10-06 13:39:39.574629", + "creation": "2024-01-28 12:15:59.392296", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Shift Assignment", - "fetch_from": "operations_role.post_abbrv", + "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "post_abbrv", - "fieldtype": "Data", + "fieldname": "employee_schedule", + "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 21, + "idx": 10, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "operations_role", - "is_system_generated": 1, + "insert_after": "site_location", + "is_system_generated": 0, "is_virtual": 0, - "label": "Post Abbreviation", + "label": "Employee Schedule", "length": 0, + "link_filters": null, "mandatory_depends_on": null, - "modified": "2020-10-06 13:39:39.574629", + "modified": "2024-01-28 12:15:59.392296", "modified_by": "Administrator", "module": null, - "name": "Shift Assignment-post_abbrv", + "name": "Shift Assignment-employee_schedule", "no_copy": 0, "non_negative": 0, - "options": null, + "options": "Employee Schedule", "owner": "Administrator", "parent": null, "parentfield": null, @@ -310,13 +319,14 @@ "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 0, + "read_only": 1, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, + "show_dashboard": 0, "sort_options": 0, - "translatable": 1, + "translatable": 0, "unique": 0, "width": null }, @@ -331,40 +341,41 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2021-11-10 12:18:45.778534", + "creation": "2022-09-11 10:59:43.588068", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Shift Assignment", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "roster_type", - "fieldtype": "Select", + "fetch_from": "site.site_location", + "fetch_if_empty": 1, + "fieldname": "site_location", + "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 22, + "idx": 8, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "post_abbrv", + "insert_after": "shift_classification", "is_system_generated": 1, "is_virtual": 0, - "label": "Roster Type", + "label": "Site Location", "length": 0, + "link_filters": null, "mandatory_depends_on": null, - "modified": "2021-11-10 12:18:45.778534", + "modified": "2022-09-11 10:59:43.588068", "modified_by": "Administrator", "module": null, - "name": "Shift Assignment-roster_type", + "name": "Shift Assignment-site_location", "no_copy": 0, "non_negative": 0, - "options": "Basic\nOver-Time", + "options": "Location", "owner": "Administrator", "parent": null, "parentfield": null, @@ -379,8 +390,9 @@ "report_hide": 0, "reqd": 0, "search_index": 0, + "show_dashboard": 0, "sort_options": 0, - "translatable": 1, + "translatable": 0, "unique": 0, "width": null }, @@ -393,39 +405,40 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "collapsible_depends_on": "", + "collapsible_depends_on": null, "columns": 0, - "creation": "2022-06-09 12:10:56.437130", + "creation": "2022-07-05 09:57:27.270636", "default": null, - "depends_on": "eval: doc.shift_request;", + "depends_on": "shift", "description": null, "docstatus": 0, "dt": "Shift Assignment", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "site_request", - "fieldtype": "Section Break", + "fetch_from": "shift.shift_classification", + "fetch_if_empty": 1, + "fieldname": "shift_classification", + "fieldtype": "Data", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 23, + "idx": 7, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "roster_type", + "insert_after": "status", "is_system_generated": 1, "is_virtual": 0, - "label": "Site Request", + "label": "Shift Classification", "length": 0, + "link_filters": null, "mandatory_depends_on": null, - "modified": "2022-06-09 12:10:56.437130", + "modified": "2022-07-05 09:57:27.270636", "modified_by": "Administrator", "module": null, - "name": "Shift Assignment-site_request", + "name": "Shift Assignment-shift_classification", "no_copy": 0, "non_negative": 0, "options": null, @@ -438,13 +451,14 @@ "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 0, + "read_only": 1, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, + "show_dashboard": 0, "sort_options": 0, - "translatable": 0, + "translatable": 1, "unique": 0, "width": null }, @@ -459,40 +473,41 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2022-06-09 12:10:56.624201", + "creation": "2022-07-05 09:43:25.363729", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Shift Assignment", - "fetch_from": "shift_request.check_in_site", + "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "check_in_site", - "fieldtype": "Link", + "fieldname": "end_datetime", + "fieldtype": "Datetime", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 24, + "idx": 15, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "site_request", + "insert_after": "start_datetime", "is_system_generated": 1, "is_virtual": 0, - "label": "Check In Site", + "label": "End Datetime", "length": 0, + "link_filters": null, "mandatory_depends_on": null, - "modified": "2022-06-09 12:10:56.624201", + "modified": "2022-07-05 09:43:25.363729", "modified_by": "Administrator", "module": null, - "name": "Shift Assignment-check_in_site", + "name": "Shift Assignment-end_datetime", "no_copy": 0, "non_negative": 0, - "options": "Location", + "options": null, "owner": "Administrator", "parent": null, "parentfield": null, @@ -502,11 +517,12 @@ "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 0, + "read_only": 1, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, + "show_dashboard": 0, "sort_options": 0, "translatable": 0, "unique": 0, @@ -523,7 +539,7 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2022-06-09 12:10:56.843708", + "creation": "2022-07-05 09:42:38.285022", "default": null, "depends_on": null, "description": null, @@ -531,29 +547,30 @@ "dt": "Shift Assignment", "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "column_break_19", - "fieldtype": "Column Break", + "fieldname": "start_datetime", + "fieldtype": "Datetime", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 25, + "idx": 14, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "check_in_site", + "insert_after": "end_date", "is_system_generated": 1, "is_virtual": 0, - "label": null, + "label": "Start Datetime", "length": 0, + "link_filters": null, "mandatory_depends_on": null, - "modified": "2022-06-09 12:10:56.843708", + "modified": "2022-07-05 09:42:38.285022", "modified_by": "Administrator", "module": null, - "name": "Shift Assignment-column_break_19", + "name": "Shift Assignment-start_datetime", "no_copy": 0, "non_negative": 0, "options": null, @@ -566,11 +583,12 @@ "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 0, + "read_only": 1, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, + "show_dashboard": 0, "sort_options": 0, "translatable": 0, "unique": 0, @@ -613,6 +631,7 @@ "is_virtual": 0, "label": "Check Out Site", "length": 0, + "link_filters": null, "mandatory_depends_on": null, "modified": "2022-06-09 12:10:57.024639", "modified_by": "Administrator", @@ -635,6 +654,7 @@ "report_hide": 0, "reqd": 0, "search_index": 0, + "show_dashboard": 0, "sort_options": 0, "translatable": 0, "unique": 0, @@ -651,7 +671,7 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2022-07-05 09:42:38.285022", + "creation": "2022-06-09 12:10:56.843708", "default": null, "depends_on": null, "description": null, @@ -659,29 +679,30 @@ "dt": "Shift Assignment", "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "start_datetime", - "fieldtype": "Datetime", + "fieldname": "column_break_19", + "fieldtype": "Column Break", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 14, + "idx": 25, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "end_date", + "insert_after": "check_in_site", "is_system_generated": 1, "is_virtual": 0, - "label": "Start Datetime", + "label": null, "length": 0, + "link_filters": null, "mandatory_depends_on": null, - "modified": "2022-07-05 09:42:38.285022", + "modified": "2022-06-09 12:10:56.843708", "modified_by": "Administrator", "module": null, - "name": "Shift Assignment-start_datetime", + "name": "Shift Assignment-column_break_19", "no_copy": 0, "non_negative": 0, "options": null, @@ -694,11 +715,12 @@ "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 1, + "read_only": 0, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, + "show_dashboard": 0, "sort_options": 0, "translatable": 0, "unique": 0, @@ -715,40 +737,41 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2022-07-05 09:43:25.363729", + "creation": "2022-06-09 12:10:56.624201", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Shift Assignment", - "fetch_from": null, + "fetch_from": "shift_request.check_in_site", "fetch_if_empty": 0, - "fieldname": "end_datetime", - "fieldtype": "Datetime", + "fieldname": "check_in_site", + "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 15, + "idx": 24, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "start_datetime", + "insert_after": "site_request", "is_system_generated": 1, "is_virtual": 0, - "label": "End Datetime", + "label": "Check In Site", "length": 0, + "link_filters": null, "mandatory_depends_on": null, - "modified": "2022-07-05 09:43:25.363729", + "modified": "2022-06-09 12:10:56.624201", "modified_by": "Administrator", "module": null, - "name": "Shift Assignment-end_datetime", + "name": "Shift Assignment-check_in_site", "no_copy": 0, "non_negative": 0, - "options": null, + "options": "Location", "owner": "Administrator", "parent": null, "parentfield": null, @@ -758,11 +781,12 @@ "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 1, + "read_only": 0, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, + "show_dashboard": 0, "sort_options": 0, "translatable": 0, "unique": 0, @@ -777,39 +801,40 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "collapsible_depends_on": null, + "collapsible_depends_on": "", "columns": 0, - "creation": "2022-07-05 09:57:27.270636", + "creation": "2022-06-09 12:10:56.437130", "default": null, - "depends_on": "shift", + "depends_on": "eval: doc.shift_request;", "description": null, "docstatus": 0, "dt": "Shift Assignment", - "fetch_from": "shift.shift_classification", - "fetch_if_empty": 1, - "fieldname": "shift_classification", - "fieldtype": "Data", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "site_request", + "fieldtype": "Section Break", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 7, + "idx": 23, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "status", + "insert_after": "roster_type", "is_system_generated": 1, "is_virtual": 0, - "label": "Shift Classification", + "label": "Site Request", "length": 0, + "link_filters": null, "mandatory_depends_on": null, - "modified": "2022-07-05 09:57:27.270636", + "modified": "2022-06-09 12:10:56.437130", "modified_by": "Administrator", "module": null, - "name": "Shift Assignment-shift_classification", + "name": "Shift Assignment-site_request", "no_copy": 0, "non_negative": 0, "options": null, @@ -822,13 +847,14 @@ "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 1, + "read_only": 0, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, + "show_dashboard": 0, "sort_options": 0, - "translatable": 1, + "translatable": 0, "unique": 0, "width": null }, @@ -843,40 +869,41 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2022-09-11 10:59:43.588068", + "creation": "2021-11-10 12:18:45.778534", "default": null, "depends_on": null, "description": null, "docstatus": 0, "dt": "Shift Assignment", - "fetch_from": "site.site_location", - "fetch_if_empty": 1, - "fieldname": "site_location", - "fieldtype": "Link", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "roster_type", + "fieldtype": "Select", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 8, + "idx": 22, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "shift_classification", + "insert_after": "post_abbrv", "is_system_generated": 1, "is_virtual": 0, - "label": "Site Location", + "label": "Roster Type", "length": 0, + "link_filters": null, "mandatory_depends_on": null, - "modified": "2022-09-11 10:59:43.588068", + "modified": "2021-11-10 12:18:45.778534", "modified_by": "Administrator", "module": null, - "name": "Shift Assignment-site_location", + "name": "Shift Assignment-roster_type", "no_copy": 0, "non_negative": 0, - "options": "Location", + "options": "Basic\nOver-Time", "owner": "Administrator", "parent": null, "parentfield": null, @@ -891,6 +918,139 @@ "report_hide": 0, "reqd": 0, "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 1, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2020-10-06 13:39:39.574629", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Assignment", + "fetch_from": "operations_role.post_abbrv", + "fetch_if_empty": 0, + "fieldname": "post_abbrv", + "fieldtype": "Data", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 21, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "operations_role", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Post Abbreviation", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2020-10-06 13:39:39.574629", + "modified_by": "Administrator", + "module": null, + "name": "Shift Assignment-post_abbrv", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 1, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2023-05-16 21:50:39.040758", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Assignment", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "post_type", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 26, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "check_out_site", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Post Type", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2020-10-06 13:39:38.553796", + "modified_by": "Administrator", + "module": null, + "name": "Shift Assignment-post_type", + "no_copy": 0, + "non_negative": 0, + "options": "Operations Role", + "owner": null, + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, "sort_options": 0, "translatable": 0, "unique": 0, @@ -907,7 +1067,7 @@ "collapsible": 0, "collapsible_depends_on": null, "columns": 0, - "creation": "2024-01-28 12:15:59.392296", + "creation": "2020-10-06 13:39:38.553796", "default": null, "depends_on": null, "description": null, @@ -915,32 +1075,231 @@ "dt": "Shift Assignment", "fetch_from": null, "fetch_if_empty": 0, - "fieldname": "employee_schedule", + "fieldname": "operations_role", "fieldtype": "Link", "hidden": 0, "hide_border": 0, "hide_days": 0, "hide_seconds": 0, - "idx": 9, + "idx": 20, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_preview": 0, "in_standard_filter": 0, - "insert_after": "site_location", + "insert_after": "amended_from", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Operations Role", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2020-10-06 13:39:38.553796", + "modified_by": "Administrator", + "module": null, + "name": "Shift Assignment-operations_role", + "no_copy": 0, + "non_negative": 0, + "options": "Operations Role", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2020-10-06 13:39:37.549249", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Assignment", + "fetch_from": "shift.project", + "fetch_if_empty": 0, + "fieldname": "project", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 5, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "site", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Project", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2020-10-06 13:39:37.549249", + "modified_by": "Administrator", + "module": null, + "name": "Shift Assignment-project", + "no_copy": 0, + "non_negative": 0, + "options": "Project", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2020-10-06 13:39:36.617647", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Assignment", + "fetch_from": "shift.site", + "fetch_if_empty": 0, + "fieldname": "site", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 4, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "shift_type", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Site", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2020-10-06 13:39:36.617647", + "modified_by": "Administrator", + "module": null, + "name": "Shift Assignment-site", + "no_copy": 0, + "non_negative": 0, + "options": "Operations Site", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2020-10-08 21:21:52.246455", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Assignment", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "shift", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 22, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "department", "is_system_generated": 0, "is_virtual": 0, - "label": "Employee Schedule", + "label": "Shift", "length": 0, + "link_filters": null, "mandatory_depends_on": null, - "modified": "2024-01-28 12:15:59.392296", + "modified": "2020-07-19 10:06:11.379683", "modified_by": "Administrator", "module": null, - "name": "Shift Assignment-employee_schedule", + "name": "Shift Assignment-shift", "no_copy": 0, "non_negative": 0, - "options": "Employee Schedule", + "options": "Operations Shift", "owner": "Administrator", "parent": null, "parentfield": null, @@ -950,11 +1309,12 @@ "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 1, + "read_only": 0, "read_only_depends_on": null, "report_hide": 0, "reqd": 0, "search_index": 0, + "show_dashboard": 0, "sort_options": 0, "translatable": 0, "unique": 0, @@ -970,44 +1330,70 @@ "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2023-02-01 18:47:40.479659", + "creation": "2024-07-15 23:46:05.530118", + "default_value": null, + "doc_type": "Shift Assignment", + "docstatus": 0, + "doctype_or_field": "DocType", + "field_name": null, + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-15 23:46:05.530118", + "modified_by": "Administrator", + "module": null, + "name": "Shift Assignment-main-field_order", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "field_order", + "property_type": "Data", + "row_name": null, + "value": "[\"employee\", \"employee_name\", \"custom_employment_type\", \"shift_type\", \"site\", \"project\", \"status\", \"shift_classification\", \"site_location\", \"employee_schedule\", \"is_replaced\", \"replaced_shift_assignment\", \"day_off_ot\", \"column_break_3\", \"company\", \"start_date\", \"end_date\", \"start_datetime\", \"end_datetime\", \"shift_request\", \"department\", \"shift\", \"amended_from\", \"operations_role\", \"post_abbrv\", \"roster_type\", \"site_request\", \"check_in_site\", \"column_break_19\", \"check_out_site\", \"post_type\"]" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2020-10-08 21:25:24.944941", "default_value": null, "doc_type": "Shift Assignment", "docstatus": 0, "doctype_or_field": "DocField", - "field_name": "employee", + "field_name": "shift_type", "idx": 0, "is_system_generated": 0, - "modified": "2023-02-01 18:47:40.479659", - "modified_by": "e.anthony@one-fm.com", + "modified": "2024-07-15 23:39:03.480409", + "modified_by": "Administrator", "module": null, - "name": "Shift Assignment-employee-in_standard_filter", - "owner": "e.anthony@one-fm.com", + "name": "Shift Assignment-shift_type-fetch_from", + "owner": "Administrator", "parent": null, "parentfield": null, "parenttype": null, - "property": "in_standard_filter", - "property_type": "Check", + "property": "fetch_from", + "property_type": "Small Text", "row_name": null, - "value": "1" + "value": "shift.shift_type" }, { "_assign": null, "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2023-02-01 18:46:59.050508", + "creation": "2023-02-01 18:46:58.929151", "default_value": null, "doc_type": "Shift Assignment", "docstatus": 0, "doctype_or_field": "DocField", - "field_name": "end_date", + "field_name": "start_date", "idx": 0, "is_system_generated": 0, - "modified": "2023-02-01 18:46:59.050508", - "modified_by": "e.anthony@one-fm.com", + "modified": "2024-07-15 23:39:03.454811", + "modified_by": "Administrator", "module": null, - "name": "Shift Assignment-end_date-in_standard_filter", + "name": "Shift Assignment-start_date-in_standard_filter", "owner": "e.anthony@one-fm.com", "parent": null, "parentfield": null, @@ -1030,8 +1416,8 @@ "field_name": "end_date", "idx": 0, "is_system_generated": 0, - "modified": "2023-02-01 18:46:59.001473", - "modified_by": "e.anthony@one-fm.com", + "modified": "2024-07-15 23:39:03.427065", + "modified_by": "Administrator", "module": null, "name": "Shift Assignment-end_date-in_list_view", "owner": "e.anthony@one-fm.com", @@ -1048,18 +1434,18 @@ "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2023-02-01 18:46:58.929151", + "creation": "2023-02-01 18:46:59.050508", "default_value": null, "doc_type": "Shift Assignment", "docstatus": 0, "doctype_or_field": "DocField", - "field_name": "start_date", + "field_name": "end_date", "idx": 0, "is_system_generated": 0, - "modified": "2023-02-01 18:46:58.929151", - "modified_by": "e.anthony@one-fm.com", + "modified": "2024-07-15 23:39:03.399366", + "modified_by": "Administrator", "module": null, - "name": "Shift Assignment-start_date-in_standard_filter", + "name": "Shift Assignment-end_date-in_standard_filter", "owner": "e.anthony@one-fm.com", "parent": null, "parentfield": null, @@ -1074,26 +1460,26 @@ "_comments": null, "_liked_by": null, "_user_tags": null, - "creation": "2020-10-08 21:25:24.944941", + "creation": "2023-02-01 18:47:40.479659", "default_value": null, "doc_type": "Shift Assignment", "docstatus": 0, "doctype_or_field": "DocField", - "field_name": "shift_type", + "field_name": "employee", "idx": 0, "is_system_generated": 0, - "modified": "2020-05-31 22:44:02.659032", + "modified": "2024-07-15 23:39:03.373559", "modified_by": "Administrator", "module": null, - "name": "Shift Assignment-shift_type-fetch_from", - "owner": "Administrator", + "name": "Shift Assignment-employee-in_standard_filter", + "owner": "e.anthony@one-fm.com", "parent": null, "parentfield": null, "parenttype": null, - "property": "fetch_from", - "property_type": "Small Text", + "property": "in_standard_filter", + "property_type": "Check", "row_name": null, - "value": "shift.shift_type" + "value": "1" } ], "sync_on_migrate": 1 diff --git a/one_fm/one_fm/custom/shift_request.json b/one_fm/one_fm/custom/shift_request.json index 423942d7c0..6d4964b4dc 100644 --- a/one_fm/one_fm/custom/shift_request.json +++ b/one_fm/one_fm/custom/shift_request.json @@ -1,2182 +1,2503 @@ { - "custom_fields": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-06-10 10:54:45.691280", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "custom_shift_approvers", - "fieldtype": "Table MultiSelect", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 21, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "company_name", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Shift Approvers", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2024-06-10 10:54:45.691280", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-custom_shift_approvers", - "no_copy": 0, - "non_negative": 0, - "options": "Shift Request Approvers", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-09-07 09:46:46.735944", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": "site.project", - "fetch_if_empty": 1, - "fieldname": "project", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 10, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "site", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Project", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2023-09-07 09:46:46.735944", - "modified_by": "administrator", - "module": null, - "name": "Shift Request-project", - "no_copy": 0, - "non_negative": 0, - "options": "Project", - "owner": "administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-08-27 12:29:23.451324", - "default": "0", - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "assign_day_off", - "fieldtype": "Check", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 6, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "employee_name", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Assign Day Off", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2023-08-27 12:29:23.451324", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-assign_day_off", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-08-24 14:42:29.507724", - "default": "", - "depends_on": "eval:doc.assign_day_off == 0;", - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": "operations_shift.site", - "fetch_if_empty": 0, - "fieldname": "site", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 9, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "shift", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Site", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2023-08-24 14:42:29.507724", - "modified_by": "saoud@one-fm.com", - "module": null, - "name": "Shift Request-site", - "no_copy": 0, - "non_negative": 0, - "options": "Operations Site", - "owner": "saoud@one-fm.com", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-08-17 09:01:43.226555", - "default": null, - "depends_on": "eval:doc.assign_day_off == 0;", - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "roster_type", - "fieldtype": "Select", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 19, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "operations_role", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Roster Type", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2023-08-17 09:01:43.226555", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-roster_type", - "no_copy": 0, - "non_negative": 0, - "options": "Basic\nOver-Time", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 1, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-08-13 11:13:58.647338", - "default": null, - "depends_on": "eval:doc.assign_day_off == 0;", - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "operations_role", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 18, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "to_date", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Operations Role", - "length": 0, - "link_filters": null, - "mandatory_depends_on": "eval:doc.assign_day_off == 0;", - "modified": "2023-08-13 11:13:58.647338", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-operations_role", - "no_copy": 0, - "non_negative": 0, - "options": "Operations Role", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-10-25 19:22:07.712273", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": "employee.company", - "fetch_if_empty": 0, - "fieldname": "company_name", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 20, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "roster_type", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Company Name", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-10-25 19:22:07.712273", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-company_name", - "no_copy": 0, - "non_negative": 0, - "options": "Company", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-10-25 19:22:07.480541", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": "approver", - "fetch_if_empty": 0, - "fieldname": "shift_approver", - "fieldtype": "Link", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 22, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "custom_shift_approvers", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Shift Approver", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-10-25 19:22:07.480541", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-shift_approver", - "no_copy": 0, - "non_negative": 0, - "options": "User", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-10-25 19:17:33.755555", - "default": null, - "depends_on": "eval:doc.assign_day_off == 0;", - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": "operations_shift.shift_type", - "fetch_if_empty": 0, - "fieldname": "shift", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 8, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "operations_shift", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Shift", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-10-25 19:17:33.755555", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-shift", - "no_copy": 0, - "non_negative": 0, - "options": "Shift Type", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-09-15 14:47:57.229223", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": "check_in_site.geofence_radius", - "fetch_if_empty": 0, - "fieldname": "checkout_radius", - "fieldtype": "Data", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 34, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "checkout_latitude", - "is_system_generated": 1, - "is_virtual": 0, - "label": "CheckOut Radius", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-09-15 14:47:57.229223", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-checkout_radius", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 1, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-09-15 14:47:28.051018", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": "check_in_site.geofence_radius", - "fetch_if_empty": 0, - "fieldname": "checkin_radius", - "fieldtype": "Data", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 28, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "checkin_latitude", - "is_system_generated": 1, - "is_virtual": 0, - "label": "Checkin Radius", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-09-15 14:47:28.051018", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-checkin_radius", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 1, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-09-15 14:32:22.655683", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": "check_out_site.longitude", - "fetch_if_empty": 0, - "fieldname": "checkout_longitude", - "fieldtype": "Data", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 32, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "check_out_site", - "is_system_generated": 1, - "is_virtual": 0, - "label": "CheckOut Longitude", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-09-15 14:32:22.655683", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-checkout_longitude", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 1, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-09-15 14:32:22.431809", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": "check_out_site.latitude", - "fetch_if_empty": 0, - "fieldname": "checkout_latitude", - "fieldtype": "Data", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 33, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "checkout_longitude", - "is_system_generated": 1, - "is_virtual": 0, - "label": "CheckOut Latitude", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-09-15 14:32:22.431809", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-checkout_latitude", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 1, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-09-15 14:32:22.211146", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": "check_in_site.latitude", - "fetch_if_empty": 0, - "fieldname": "checkin_latitude", - "fieldtype": "Data", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 27, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "checkin_longitude", - "is_system_generated": 1, - "is_virtual": 0, - "label": "Checkin Latitude", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-09-15 14:32:22.211146", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-checkin_latitude", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 1, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-09-15 14:32:21.865468", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": "check_in_site.longitude", - "fetch_if_empty": 0, - "fieldname": "checkin_longitude", - "fieldtype": "Data", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 26, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "check_in_site", - "is_system_generated": 1, - "is_virtual": 0, - "label": "Checkin Longitude", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-09-15 14:32:21.865468", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-checkin_longitude", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 1, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-09-14 08:29:37.811938", - "default": null, - "depends_on": "eval: doc.check_out_site;", - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "checkout_map_html", - "fieldtype": "HTML", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 35, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "checkout_radius", - "is_system_generated": 1, - "is_virtual": 0, - "label": "Checkout Map HTML", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-09-14 08:29:37.811938", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-checkout_map_html", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-09-14 08:29:37.538698", - "default": null, - "depends_on": "eval: doc.check_in_site;", - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "checkin_map_html", - "fieldtype": "HTML", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 29, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "checkin_radius", - "is_system_generated": 1, - "is_virtual": 0, - "label": "Checkin Map HTML", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-09-14 08:29:37.538698", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-checkin_map_html", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-09-14 08:27:55.575742", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "checkout_map", - "fieldtype": "Geolocation", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 36, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "checkout_map_html", - "is_system_generated": 1, - "is_virtual": 0, - "label": "Checkout Map", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-09-14 08:27:55.575742", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-checkout_map", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-09-14 08:27:55.168412", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "checkin_map", - "fieldtype": "Geolocation", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 37, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "checkout_map", - "is_system_generated": 1, - "is_virtual": 0, - "label": "Checkin Map", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-09-14 08:27:55.168412", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-checkin_map", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-09-12 12:03:55.326660", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": "employee.employee_name", - "fetch_if_empty": 1, - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 3, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "workflow_state", - "is_system_generated": 1, - "is_virtual": 0, - "label": "title", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-09-12 12:03:55.326660", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-title", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-07-16 00:47:33.304148", - "default": null, - "depends_on": "eval:doc.docstatus > 0", - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "update_request", - "fieldtype": "Check", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 22, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "shift_approver", - "is_system_generated": 1, - "is_virtual": 0, - "label": "Update Request", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-07-16 00:47:33.304148", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-update_request", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-06-09 11:57:59.882895", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "column_break_15", - "fieldtype": "Column Break", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 30, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "checkin_map_html", - "is_system_generated": 1, - "is_virtual": 0, - "label": null, - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-06-09 11:57:59.882895", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-column_break_15", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-06-09 11:51:59.922003", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": "site.site_location", - "fetch_if_empty": 1, - "fieldname": "check_out_site", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 31, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "column_break_15", - "is_system_generated": 1, - "is_virtual": 0, - "label": "Check Out Site", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-06-09 11:51:59.922003", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-check_out_site", - "no_copy": 0, - "non_negative": 0, - "options": "Location", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-06-09 11:51:59.742040", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": "site.site_location", - "fetch_if_empty": 1, - "fieldname": "check_in_site", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 25, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "site_request", - "is_system_generated": 1, - "is_virtual": 0, - "label": "Check In Site", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-06-09 11:51:59.742040", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-check_in_site", - "no_copy": 0, - "non_negative": 0, - "options": "Location", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-06-09 11:51:59.517107", - "default": null, - "depends_on": "eval:doc.assign_day_off == 0;", - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "site_request", - "fieldtype": "Section Break", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 24, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "amended_from", - "is_system_generated": 1, - "is_virtual": 0, - "label": "Site Request", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-06-09 11:51:59.517107", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-site_request", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-06-05 09:26:05.254538", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "workflow_state", - "fieldtype": "Link", - "hidden": 1, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 2, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "shift_type", - "is_system_generated": 1, - "is_virtual": 0, - "label": "Workflow State", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "modified": "2022-06-05 09:26:05.254538", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-workflow_state", - "no_copy": 1, - "non_negative": 0, - "options": "Workflow State", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2022-01-26 16:04:58.243003", - "default": null, - "depends_on": "eval:doc.assign_day_off == 0;", - "description": null, - "docstatus": 0, - "dt": "Shift Request", - "fetch_from": "employee.shift", - "fetch_if_empty": 1, - "fieldname": "operations_shift", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 7, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "assign_day_off", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Operations Shift", - "length": 0, - "link_filters": null, - "mandatory_depends_on": "eval:doc.assign_day_off == 0;", - "modified": "2020-06-09 11:39:10.240577", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-operations_shift", - "no_copy": 0, - "non_negative": 0, - "options": "Operations Shift", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - } - ], - "custom_perms": [], - "doctype": "Shift Request", - "links": [], - "property_setters": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-06-12 10:53:30.413468", - "default_value": null, - "doc_type": "Shift Request", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "approver", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-06-11 10:59:17.526227", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-approver-ignore_user_permissions", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "property": "ignore_user_permissions", - "property_type": "Check", - "row_name": null, - "value": "1" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-06-12 10:53:30.434921", - "default_value": null, - "doc_type": "Shift Request", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "status", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-06-11 10:59:17.514474", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-status-read_only", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "property": "read_only", - "property_type": "Check", - "row_name": null, - "value": "1" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-06-12 10:53:30.458234", - "default_value": null, - "doc_type": "Shift Request", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "company", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-06-11 10:59:17.501617", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-company-fetch_from", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "property": "fetch_from", - "property_type": "Small Text", - "row_name": null, - "value": "employee.company" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-06-12 10:53:30.478227", - "default_value": null, - "doc_type": "Shift Request", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "company", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-06-11 10:59:17.482857", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-company-read_only", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "property": "read_only", - "property_type": "Check", - "row_name": null, - "value": "1" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-06-12 10:53:30.499959", - "default_value": null, - "doc_type": "Shift Request", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "approver", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-06-11 10:59:17.460702", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-approver-fetch_from", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "property": "fetch_from", - "property_type": "Small Text", - "row_name": null, - "value": "" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-06-12 10:53:30.519702", - "default_value": null, - "doc_type": "Shift Request", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "approver", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-06-11 10:59:17.447888", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-approver-fetch_if_empty", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "property": "fetch_if_empty", - "property_type": "Check", - "row_name": null, - "value": "0" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-06-12 10:53:30.542631", - "default_value": null, - "doc_type": "Shift Request", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "shift_type", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-06-11 10:59:17.434958", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-shift_type-Hidden", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "property": "Hidden", - "property_type": null, - "row_name": null, - "value": "1" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-06-12 10:53:30.587785", - "default_value": null, - "doc_type": "Shift Request", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "company", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-06-11 10:59:17.422074", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-company-Hidden", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "property": "Hidden", - "property_type": "Check", - "row_name": null, - "value": "1" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-06-12 10:53:30.563579", - "default_value": null, - "doc_type": "Shift Request", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "approver", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-06-11 10:59:17.409950", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-approver-Hidden", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "property": "Hidden", - "property_type": "check", - "row_name": null, - "value": "1" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-06-12 10:53:30.608274", - "default_value": null, - "doc_type": "Shift Request", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "approver", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-06-11 10:59:17.397840", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-approver-read_only", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "property": "read_only", - "property_type": "Check", - "row_name": null, - "value": "1" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-06-12 10:53:31.406572", - "default_value": null, - "doc_type": "Shift Request", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "shift_type", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-06-11 10:59:17.385128", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-shift_type-fetch_from", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "property": "fetch_from", - "property_type": "Small Text", - "row_name": null, - "value": "operations_shift.shift_type" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-06-12 10:53:31.430007", - "default_value": null, - "doc_type": "Shift Request", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "shift_type", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-06-11 10:59:17.372817", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-shift_type-depends_on", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "property": "depends_on", - "property_type": "Data", - "row_name": null, - "value": "eval:doc.assign_day_off == 0;" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-06-12 10:53:31.449267", - "default_value": null, - "doc_type": "Shift Request", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "shift_type", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-06-11 10:59:17.361228", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-shift_type-read_only", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "property": "read_only", - "property_type": "Check", - "row_name": null, - "value": "1" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-06-12 10:53:31.610534", - "default_value": null, - "doc_type": "Shift Request", - "docstatus": 0, - "doctype_or_field": "DocType", - "field_name": null, - "idx": 0, - "is_system_generated": 0, - "modified": "2024-06-11 10:59:17.347720", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-main-field_order", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "property": "field_order", - "property_type": "Data", - "row_name": null, - "value": "[\"shift_type\", \"workflow_state\", \"title\", \"employee\", \"employee_name\", \"assign_day_off\", \"operations_shift\", \"shift\", \"site\", \"project\", \"department\", \"status\", \"column_break_4\", \"company\", \"approver\", \"from_date\", \"to_date\", \"operations_role\", \"roster_type\", \"company_name\", \"shift_approver\", \"update_request\", \"amended_from\", \"site_request\", \"check_in_site\", \"checkin_longitude\", \"checkin_latitude\", \"checkin_radius\", \"checkin_map_html\", \"column_break_15\", \"check_out_site\", \"checkout_longitude\", \"checkout_latitude\", \"checkout_radius\", \"checkout_map_html\", \"checkout_map\", \"checkin_map\"]" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2024-06-12 10:53:31.630157", - "default_value": null, - "doc_type": "Shift Request", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "employee", - "idx": 0, - "is_system_generated": 0, - "modified": "2024-06-11 10:59:17.335897", - "modified_by": "Administrator", - "module": null, - "name": "Shift Request-employee-ignore_user_permissions", - "owner": "Administrator", - "parent": null, - "parentfield": null, - "parenttype": null, - "property": "ignore_user_permissions", - "property_type": "Check", - "row_name": null, - "value": "1" - } - ], - "sync_on_migrate": 1 -} \ No newline at end of file + "custom_fields": [ + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2024-06-19 17:55:38.138857", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": "replaced_employee.employee_name", + "fetch_if_empty": 0, + "fieldname": "custom_replaced_employee_name", + "fieldtype": "Data", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 9, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "replaced_employee", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Replaced Employee Name", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2024-06-19 17:55:38.138857", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-custom_replaced_employee_name", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 1, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2024-03-25 00:34:36.720161", + "default": null, + "depends_on": "eval:doc.purpose == 'Replace Existing Assignment';", + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "replaced_employee", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 8, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "purpose", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Replaced Employee", + "length": 0, + "link_filters": null, + "mandatory_depends_on": "eval:doc.purpose == 'Replace Existing Assignment';", + "modified": "2024-03-25 00:34:36.720161", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-replaced_employee", + "no_copy": 0, + "non_negative": 0, + "options": "Employee", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2024-03-12 11:43:09.115655", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "purpose", + "fieldtype": "Select", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 6, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "employee_name", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Purpose", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2024-03-12 11:43:09.115655", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-purpose", + "no_copy": 0, + "non_negative": 0, + "options": " \nAssign Day Off\nAssign Unrostered Employee\nReplace Existing Assignment\nUpdate Existing Assignment\nDay Off Overtime", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 1, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2023-09-07 09:46:46.735944", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": "site.project", + "fetch_if_empty": 1, + "fieldname": "project", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 13, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "site", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Project", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2023-09-07 09:46:46.735944", + "modified_by": "administrator", + "module": null, + "name": "Shift Request-project", + "no_copy": 0, + "non_negative": 0, + "options": "Project", + "owner": "administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2023-08-24 14:42:29.507724", + "default": "", + "depends_on": "eval:doc.purpose != 'Assign Day Off'", + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": "operations_shift.site", + "fetch_if_empty": 0, + "fieldname": "site", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 12, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "shift", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Site", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2023-08-24 14:42:29.507724", + "modified_by": "saoud@one-fm.com", + "module": null, + "name": "Shift Request-site", + "no_copy": 0, + "non_negative": 0, + "options": "Operations Site", + "owner": "saoud@one-fm.com", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2023-08-17 09:01:43.226555", + "default": null, + "depends_on": "eval:doc.purpose != 'Assign Day Off';", + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "roster_type", + "fieldtype": "Select", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 21, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "operations_role", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Roster Type", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2023-08-17 09:01:43.226555", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-roster_type", + "no_copy": 0, + "non_negative": 0, + "options": "Basic\nOver-Time", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 1, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2023-08-13 11:13:58.647338", + "default": null, + "depends_on": "eval:doc.purpose != 'Assign Day Off';", + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": null, + "fetch_if_empty": 1, + "fieldname": "operations_role", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 20, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "to_date", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Operations Role", + "length": 0, + "link_filters": null, + "mandatory_depends_on": "eval:doc.purpose != 'Assign Day Off';", + "modified": "2023-08-13 11:13:58.647338", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-operations_role", + "no_copy": 0, + "non_negative": 0, + "options": "Operations Role", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-10-25 19:22:07.712273", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": "employee.company", + "fetch_if_empty": 0, + "fieldname": "company_name", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 22, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "roster_type", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Company Name", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-10-25 19:22:07.712273", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-company_name", + "no_copy": 0, + "non_negative": 0, + "options": "Company", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-10-25 19:22:07.480541", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": "approver", + "fetch_if_empty": 0, + "fieldname": "shift_approver", + "fieldtype": "Link", + "hidden": 1, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 23, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "company_name", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Shift Approver", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-10-25 19:22:07.480541", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-shift_approver", + "no_copy": 0, + "non_negative": 0, + "options": "User", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-10-25 19:17:33.755555", + "default": null, + "depends_on": "eval:doc.purpose != 'Assign Day Off'", + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": "operations_shift.shift_type", + "fetch_if_empty": 0, + "fieldname": "shift", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 11, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "operations_shift", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Shift", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-10-25 19:17:33.755555", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-shift", + "no_copy": 0, + "non_negative": 0, + "options": "Shift Type", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-09-15 14:47:57.229223", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": "check_in_site.geofence_radius", + "fetch_if_empty": 0, + "fieldname": "checkout_radius", + "fieldtype": "Data", + "hidden": 1, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 34, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "checkout_latitude", + "is_system_generated": 1, + "is_virtual": 0, + "label": "CheckOut Radius", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-09-15 14:47:57.229223", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-checkout_radius", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 1, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-09-15 14:47:28.051018", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": "check_in_site.geofence_radius", + "fetch_if_empty": 0, + "fieldname": "checkin_radius", + "fieldtype": "Data", + "hidden": 1, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 28, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "checkin_latitude", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Checkin Radius", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-09-15 14:47:28.051018", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-checkin_radius", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 1, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-09-15 14:32:22.655683", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": "check_out_site.longitude", + "fetch_if_empty": 0, + "fieldname": "checkout_longitude", + "fieldtype": "Data", + "hidden": 1, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 32, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "check_out_site", + "is_system_generated": 1, + "is_virtual": 0, + "label": "CheckOut Longitude", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-09-15 14:32:22.655683", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-checkout_longitude", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 1, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-09-15 14:32:22.431809", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": "check_out_site.latitude", + "fetch_if_empty": 0, + "fieldname": "checkout_latitude", + "fieldtype": "Data", + "hidden": 1, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 33, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "checkout_longitude", + "is_system_generated": 1, + "is_virtual": 0, + "label": "CheckOut Latitude", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-09-15 14:32:22.431809", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-checkout_latitude", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 1, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-09-15 14:32:22.211146", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": "check_in_site.latitude", + "fetch_if_empty": 0, + "fieldname": "checkin_latitude", + "fieldtype": "Data", + "hidden": 1, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 27, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "checkin_longitude", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Checkin Latitude", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-09-15 14:32:22.211146", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-checkin_latitude", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 1, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-09-15 14:32:21.865468", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": "check_in_site.longitude", + "fetch_if_empty": 0, + "fieldname": "checkin_longitude", + "fieldtype": "Data", + "hidden": 1, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 26, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "check_in_site", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Checkin Longitude", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-09-15 14:32:21.865468", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-checkin_longitude", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 1, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-09-14 08:29:37.811938", + "default": null, + "depends_on": "eval: doc.check_out_site;", + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "checkout_map_html", + "fieldtype": "HTML", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 35, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "checkout_radius", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Checkout Map HTML", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-09-14 08:29:37.811938", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-checkout_map_html", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-09-14 08:29:37.538698", + "default": null, + "depends_on": "eval: doc.check_in_site;", + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "checkin_map_html", + "fieldtype": "HTML", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 29, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "checkin_radius", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Checkin Map HTML", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-09-14 08:29:37.538698", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-checkin_map_html", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-09-14 08:27:55.575742", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "checkout_map", + "fieldtype": "Geolocation", + "hidden": 1, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 36, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "checkout_map_html", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Checkout Map", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-09-14 08:27:55.575742", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-checkout_map", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-09-14 08:27:55.168412", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "checkin_map", + "fieldtype": "Geolocation", + "hidden": 1, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 37, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "checkout_map", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Checkin Map", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-09-14 08:27:55.168412", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-checkin_map", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-09-12 12:03:55.326660", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": "employee.employee_name", + "fetch_if_empty": 1, + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 3, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "workflow_state", + "is_system_generated": 1, + "is_virtual": 0, + "label": "title", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-09-12 12:03:55.326660", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-title", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-07-16 00:47:33.304148", + "default": null, + "depends_on": "eval:doc.docstatus > 0", + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "update_request", + "fieldtype": "Check", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 22, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "shift_approver", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Update Request", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-07-16 00:47:33.304148", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-update_request", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-06-09 11:57:59.882895", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "column_break_15", + "fieldtype": "Column Break", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 30, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "checkin_map_html", + "is_system_generated": 1, + "is_virtual": 0, + "label": null, + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-06-09 11:57:59.882895", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-column_break_15", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-06-09 11:51:59.922003", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": "site.site_location", + "fetch_if_empty": 1, + "fieldname": "check_out_site", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 31, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "column_break_15", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Check Out Site", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-06-09 11:51:59.922003", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-check_out_site", + "no_copy": 0, + "non_negative": 0, + "options": "Location", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-06-09 11:51:59.742040", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": "site.site_location", + "fetch_if_empty": 1, + "fieldname": "check_in_site", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 25, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "site_request", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Check In Site", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-06-09 11:51:59.742040", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-check_in_site", + "no_copy": 0, + "non_negative": 0, + "options": "Location", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-06-09 11:51:59.517107", + "default": null, + "depends_on": "eval:doc.purpose != 'Assign Day Off'", + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "site_request", + "fieldtype": "Section Break", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 24, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "amended_from", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Site Request", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-06-09 11:51:59.517107", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-site_request", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-06-05 09:26:05.254538", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "workflow_state", + "fieldtype": "Link", + "hidden": 1, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 2, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "shift_type", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Workflow State", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2022-06-05 09:26:05.254538", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-workflow_state", + "no_copy": 1, + "non_negative": 0, + "options": "Workflow State", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2022-01-26 16:04:58.243003", + "default": null, + "depends_on": "eval:doc.purpose != 'Assign Day Off';", + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": "employee.shift", + "fetch_if_empty": 1, + "fieldname": "operations_shift", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 10, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "custom_replaced_employee_name", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Operations Shift", + "length": 0, + "link_filters": null, + "mandatory_depends_on": "eval:doc.purpose != 'Assign Day Off';", + "modified": "2020-06-09 11:39:10.240577", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-operations_shift", + "no_copy": 0, + "non_negative": 0, + "options": "Operations Shift", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2024-06-10 10:54:45.691280", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Shift Request", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "custom_shift_approvers", + "fieldtype": "Table MultiSelect", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 22, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "shift_approver", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Shift Approvers", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2024-06-10 10:54:45.691280", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-custom_shift_approvers", + "no_copy": 0, + "non_negative": 0, + "options": "Shift Request Approvers", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + } + ], + "custom_perms": [ + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "amend": 0, + "cancel": 0, + "create": 1, + "creation": "2024-07-15 12:14:46.536113", + "delete": 0, + "docstatus": 0, + "email": 1, + "export": 1, + "idx": 0, + "if_owner": 0, + "import": 0, + "modified": "2022-06-21 08:29:12.248404", + "modified_by": "Administrator", + "name": "88b8d0b4bb", + "owner": "Administrator", + "parent": "Shift Request", + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "select": 0, + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "amend": 1, + "cancel": 1, + "create": 1, + "creation": "2024-07-15 12:14:44.268206", + "delete": 1, + "docstatus": 0, + "email": 1, + "export": 1, + "idx": 0, + "if_owner": 0, + "import": 0, + "modified": "2022-06-21 08:29:12.325404", + "modified_by": "Administrator", + "name": "95852459e8", + "owner": "Administrator", + "parent": "Shift Request", + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "select": 0, + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "amend": 0, + "cancel": 0, + "create": 1, + "creation": "2024-07-15 12:14:40.430392", + "delete": 0, + "docstatus": 0, + "email": 1, + "export": 1, + "idx": 0, + "if_owner": 0, + "import": 0, + "modified": "2022-06-21 08:29:12.331484", + "modified_by": "Administrator", + "name": "2fbdd67c34", + "owner": "Administrator", + "parent": "Shift Request", + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "select": 0, + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "amend": 1, + "cancel": 1, + "create": 1, + "creation": "2024-07-15 12:14:35.753245", + "delete": 1, + "docstatus": 0, + "email": 1, + "export": 1, + "idx": 0, + "if_owner": 0, + "import": 0, + "modified": "2023-10-01 10:40:24.375344", + "modified_by": "Administrator", + "name": "b12f17194f", + "owner": "Administrator", + "parent": "Shift Request", + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "Employee Self Service", + "select": 0, + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "doctype": "Shift Request", + "links": [], + "property_setters": [ + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2024-07-15 23:42:37.881380", + "default_value": null, + "doc_type": "Shift Request", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "shift_type", + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-15 23:42:37.881380", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-shift_type-mandatory_depends_on", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "mandatory_depends_on", + "property_type": "Data", + "row_name": null, + "value": "eval:doc.purpose != 'Assign Day Off';" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2024-01-04 11:32:05.704330", + "default_value": null, + "doc_type": "Shift Request", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "shift_type", + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-15 23:39:02.223831", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-shift_type-mandatory_depends_on", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "mandatory_depends_on", + "property_type": "Data", + "row_name": null, + "value": "eval:doc.purpose != 'Assign Day Off';" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2024-01-04 11:32:05.704330", + "default_value": null, + "doc_type": "Shift Request", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "employee", + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-15 23:39:02.196761", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-employee-ignore_user_permissions", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "ignore_user_permissions", + "property_type": "Check", + "row_name": null, + "value": "1" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2023-12-03 13:28:16.705746", + "default_value": null, + "doc_type": "Shift Request", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "shift_type", + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-15 23:39:02.170543", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-shift_type-read_only", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "read_only", + "property_type": "Check", + "row_name": null, + "value": "1" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2023-12-03 13:28:16.557588", + "default_value": null, + "doc_type": "Shift Request", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "shift_type", + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-15 23:39:02.143369", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-shift_type-depends_on", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "depends_on", + "property_type": "Data", + "row_name": null, + "value": "eval:doc.purpose != 'Assign Day Off';" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2023-12-03 13:24:51.627067", + "default_value": null, + "doc_type": "Shift Request", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "shift_type", + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-15 23:39:02.116838", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-shift_type-fetch_from", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "fetch_from", + "property_type": "Small Text", + "row_name": null, + "value": "operations_shift.shift_type" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2023-08-15 08:25:24.328373", + "default_value": null, + "doc_type": "Shift Request", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "approver", + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-15 23:39:02.090167", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-approver-read_only", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "read_only", + "property_type": "Check", + "row_name": null, + "value": "1" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2023-08-15 08:25:24.296662", + "default_value": null, + "doc_type": "Shift Request", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "approver", + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-15 23:39:02.064144", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-approver-Hidden", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "Hidden", + "property_type": "check", + "row_name": null, + "value": "1" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2023-08-15 08:25:24.311426", + "default_value": null, + "doc_type": "Shift Request", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "company", + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-15 23:39:02.037445", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-company-Hidden", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "Hidden", + "property_type": "Check", + "row_name": null, + "value": "1" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2023-08-15 08:25:24.282157", + "default_value": null, + "doc_type": "Shift Request", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "shift_type", + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-15 23:39:02.005395", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-shift_type-Hidden", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "Hidden", + "property_type": null, + "row_name": null, + "value": "1" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2023-08-15 08:25:24.267657", + "default_value": null, + "doc_type": "Shift Request", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "approver", + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-15 23:39:01.979549", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-approver-fetch_if_empty", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "fetch_if_empty", + "property_type": "Check", + "row_name": null, + "value": "0" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2023-08-15 08:25:24.252811", + "default_value": null, + "doc_type": "Shift Request", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "approver", + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-15 23:39:01.951776", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-approver-fetch_from", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "fetch_from", + "property_type": "Small Text", + "row_name": null, + "value": "" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2023-08-15 08:25:24.222399", + "default_value": null, + "doc_type": "Shift Request", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "company", + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-15 23:39:01.924818", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-company-read_only", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "read_only", + "property_type": "Check", + "row_name": null, + "value": "1" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2023-08-15 08:25:24.207824", + "default_value": null, + "doc_type": "Shift Request", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "company", + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-15 23:39:01.897645", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-company-fetch_from", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "fetch_from", + "property_type": "Small Text", + "row_name": null, + "value": "employee.company" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2023-08-15 08:25:24.193261", + "default_value": null, + "doc_type": "Shift Request", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "status", + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-15 23:39:01.870953", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-status-read_only", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "read_only", + "property_type": "Check", + "row_name": null, + "value": "1" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2023-08-15 08:25:24.163911", + "default_value": null, + "doc_type": "Shift Request", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "approver", + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-15 23:39:01.844511", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-approver-ignore_user_permissions", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "ignore_user_permissions", + "property_type": "Check", + "row_name": null, + "value": "1" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2024-06-19 17:55:42.044477", + "default_value": null, + "doc_type": "Shift Request", + "docstatus": 0, + "doctype_or_field": "DocType", + "field_name": null, + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-15 12:15:40.541256", + "modified_by": "Administrator", + "module": null, + "name": "Shift Request-main-field_order", + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "property": "field_order", + "property_type": "Data", + "row_name": null, + "value": "[\"shift_type\", \"workflow_state\", \"title\", \"employee\", \"employee_name\", \"purpose\", \"replaced_employee\", \"custom_replaced_employee_name\", \"operations_shift\", \"shift\", \"site\", \"project\", \"department\", \"column_break_4\", \"company\", \"approver\", \"from_date\", \"to_date\", \"operations_role\", \"roster_type\", \"company_name\", \"shift_approver\", \"custom_shift_approvers\", \"update_request\", \"status\", \"amended_from\", \"site_request\", \"check_in_site\", \"checkin_longitude\", \"checkin_latitude\", \"checkin_radius\", \"checkin_map_html\", \"column_break_15\", \"check_out_site\", \"checkout_longitude\", \"checkout_latitude\", \"checkout_radius\", \"checkout_map_html\", \"checkout_map\", \"checkin_map\"]" + } + ], + "sync_on_migrate": 1 + } diff --git a/one_fm/one_fm/doctype/attendance_check/attendance_check.js b/one_fm/one_fm/doctype/attendance_check/attendance_check.js index d77dc099a0..766fc1093e 100644 --- a/one_fm/one_fm/doctype/attendance_check/attendance_check.js +++ b/one_fm/one_fm/doctype/attendance_check/attendance_check.js @@ -18,10 +18,17 @@ frappe.ui.form.on('Attendance Check', { } } }, - + attendance_status: function(frm){ + if (frm.doc.attendance_status != "Present"){ + frm.doc.justification = "" + } + }, }); + + + var allow_only_attendance_manager = (frm) => { const time_difference_check = calculate_time_difference(frm.doc.creation, 48) if (time_difference_check && frm.doc.docstatus != 1){ diff --git a/one_fm/one_fm/doctype/attendance_check/attendance_check.json b/one_fm/one_fm/doctype/attendance_check/attendance_check.json index adbb9a9542..e551561997 100644 --- a/one_fm/one_fm/doctype/attendance_check/attendance_check.json +++ b/one_fm/one_fm/doctype/attendance_check/attendance_check.json @@ -343,12 +343,12 @@ "mandatory_depends_on": "eval:doc.justification == \"Mobile isn't supporting the app\"" }, { - "depends_on": "justification", + "depends_on": "eval:[\"Invalid media content\", \"Mobile isn't supporting the app\", \"Employees insist that he/she did check in or out\", \"Other\"].includes(doc.justification)", "description": "Upload screenshot with the same issue!", "fieldname": "screenshot", "fieldtype": "Attach", "label": "Screenshot", - "mandatory_depends_on": "justification" + "mandatory_depends_on": "eval:[\"Invalid media content\", \"Mobile isn't supporting the app\", \"Employees insist that he/she did check in or out\", \"Other\"].includes(doc.justification)" }, { "depends_on": "eval:doc.justification == \"Other\"", @@ -370,6 +370,7 @@ }, { "default": "0", + "fetch_from": "employee.attendance_by_timesheet", "fieldname": "attendance_by_timesheet", "fieldtype": "Check", "label": "Attendance By Timesheet", @@ -384,7 +385,7 @@ "link_fieldname": "reference_docname" } ], - "modified": "2024-01-24 13:03:12.127598", + "modified": "2024-07-21 18:09:03.081135", "modified_by": "Administrator", "module": "One Fm", "name": "Attendance Check", diff --git a/one_fm/one_fm/doctype/attendance_check/attendance_check.py b/one_fm/one_fm/doctype/attendance_check/attendance_check.py index 533808e23d..b9c8e55d37 100644 --- a/one_fm/one_fm/doctype/attendance_check/attendance_check.py +++ b/one_fm/one_fm/doctype/attendance_check/attendance_check.py @@ -1,83 +1,129 @@ # Copyright (c) 2023, omar jaber and contributors # For license information, please see license.txt from datetime import datetime, timedelta -from itertools import chain from frappe.model.document import Document +from one_fm.processor import sendemail import frappe,json from frappe import _ from frappe.desk.form.assign_to import add as add_assignment -from frappe.utils import nowdate, add_to_date, cstr, add_days, today, format_date, now, get_url_to_form +from frappe.utils import add_days, today, now, get_url_to_form, getdate from one_fm.utils import ( production_domain, - fetch_attendance_manager_user_obj, + fetch_attendance_manager_user, get_approver ) from one_fm.operations.doctype.operations_shift.operations_shift import get_shift_supervisor class AttendanceCheck(Document): def before_insert(self): - attendance_exist = frappe.db.get_value(self.doctype, { - 'employee':self.employee, 'date':self.date, 'roster_type':self.roster_type - }, ["name", "workflow_state"], as_dict=1) - if attendance_exist: - frappe.throw(f"""{self.doctype} already exist for {self.employee} on {self.date} with name {attendance_exist.name}""") - employee = frappe.get_doc("Employee", self.employee) - # set shift assignment - shift_assignment = frappe.db.get_value("Shift Assignment", { - "employee":self.employee, "start_date":self.date, "roster_type":self.roster_type, "status":"Active", "docstatus":1}, - ["name", "start_date", "start_datetime", "end_datetime"], - as_dict=1 - ) + self.validate_duplicate() + # Get shift assignment for the date and roster type + shift_assignment = self.get_shift_assignment() + # Set shift assignment to the attendance check if shift_assignment: self.shift_assignment = shift_assignment.name self.start_time = shift_assignment.start_datetime self.end_time = shift_assignment.end_datetime - - # check checkin logs - checkins = frappe.db.sql(f""" - SELECT ec.name, ec.owner, ec.creation, ec.modified, ec.modified_by, - ec.docstatus, ec.idx, ec.employee, ec.employee_name, ec.log_type, - ec.late_entry, ec.early_exit, ec.time, ec.date, ec.skip_auto_attendance, - ec.shift_actual_start, ec.shift_actual_end, ec.shift_assignment, - ec.operations_shift, ec.shift_type, ec.roster_type, ec.operations_site, - ec.project, ec.company, ec.operations_role, ec.post_abbrv,ec.shift_permission, - ec.actual_time, - MIN(CASE WHEN ec.log_type = 'IN' THEN ec.time END) AS earliest_time, - MAX(CASE WHEN ec.log_type = 'OUT' THEN ec.time END) AS latest_time, - MIN(CASE WHEN ec.log_type = 'IN' THEN ec.name END) AS in_name, - MAX(CASE WHEN ec.log_type = 'OUT' THEN ec.name END) AS out_name - FROM - `tabEmployee Checkin` ec - WHERE - ec.shift_assignment="{self.shift_assignment}" - GROUP BY - ec.shift_assignment; - """, as_dict=1) - if checkins: - if checkins[0].in_name: - self.checkin_record=checkins[0].in_name - if checkins[0].out_name: - self.checkout_record=checkins[0].out_name - - attendance_request = frappe.db.sql(f""" - SELECT * FROM `tabAttendance Request` - WHERE '{self.date}' BETWEEN from_date AND to_date - AND docstatus=1 - """, as_dict=1) - if attendance_request: + # Get checkin records for the + checkins = self.get_checkins_details() + # Set check in recods to attendance check + if checkins and len(checkins)>0: + self.checkin_record=checkins[0].in_name if checkins[0].in_name else "" + self.checkout_record=checkins[0].out_name if checkins[0].out_name else "" + + attendance_request = self.get_attendance_request() + if attendance_request and len(attendance_request)>0: self.attendance_request=attendance_request[0].name - # check shift permission - shift_permission = frappe.db.get_value("Shift Permission", { - "employee":self.employee, "date":self.date, "roster_type":self.roster_type, "docstatus":["!=", 0]}, - ["name"] - ) + # Get shift permission + shift_permission = self.get_shift_permission() + # Set shift permission details if shift_permission: - self.shift_permission = shift_permission + self.shift_permission = shift_permission.name self.has_shift_permissions = 1 - # get approver + # Set approver + self.set_attedance_check_approver() + + def validate_duplicate(self): + attendance_exist = frappe.db.get_value( + self.doctype, + { + 'employee':self.employee, + 'date':self.date, + 'roster_type':self.roster_type + }, + ["name", "workflow_state"], + as_dict=1 + ) + if attendance_exist: + msg = f"""Attendance Check already exist for {self.employee} on {self.date} with name {attendance_exist.name}""" + frappe.throw(msg) + + def get_shift_assignment(self): + return frappe.db.get_value( + "Shift Assignment", + { + "employee":self.employee, + "start_date":self.date, + "roster_type":self.roster_type, + "status":"Active", + "docstatus":1 + }, + ["name", "start_date", "start_datetime", "end_datetime"], + as_dict=1 + ) + + def get_checkins_details(self): + return frappe.db.sql(f""" + SELECT + MIN(CASE WHEN ec.log_type = 'IN' THEN ec.name END) AS in_name, + MAX(CASE WHEN ec.log_type = 'OUT' THEN ec.name END) AS out_name + FROM + `tabEmployee Checkin` ec + WHERE + ec.shift_assignment="{self.shift_assignment}" + GROUP BY + ec.shift_assignment; + """, as_dict=1) + + def get_attendance_request(self): + return frappe.db.sql(f""" + select + name + from + `tabAttendance Request` + where + '{self.date}' + between + from_date + and + to_date + and + docstatus=1 + """, as_dict=1) + + def get_shift_permission(self): + return frappe.db.get_value( + "Shift Permission", + { + "employee":self.employee, + "date":self.date, + "roster_type":self.roster_type, + "docstatus":["!=", 0] + }, + ["name"], + as_dict=1 + ) + + def set_attedance_check_approver(self): + employee = frappe.db.get_value( + "Employee", + {"name": self.employee}, + ["reports_to", "shift", "site"], + as_dict=1 + ) if employee.reports_to: self.reports_to = employee.reports_to if employee.shift: @@ -93,486 +139,509 @@ def after_insert(self): def validate(self): + self.validate_is_replaced_shift_assignment() self.validate_justification() + def validate_is_replaced_shift_assignment(self): + if self.attendance_status and self.attendance_status != "Absent" and self.shift_assignment: + if frappe.db.get_value("Shift Assignment", self.shift_assignment, "is_replaced") == 1: + frappe.throw(_(f"{self.employee_name} was replaced for this shift and cannot be marked present.")) + def validate_justification(self): - ''' - The method is used to validate the justification and its dependend fields - ''' + # The method is used to validate the justification and its dependend fields if self.attendance_status == 'Present': if not self.justification: frappe.throw("Please select Justification") - if self.justification != "Other": - self.other_reason = "" - if self.justification == "Other": if not self.other_reason: frappe.throw("Please write the other Reason") - - if self.justification != "Mobile isn't supporting the app": - self.mobile_brand = "" - self.mobile_model = "" - + else: + self.other_reason = "" if self.justification == "Mobile isn't supporting the app": if not self.mobile_brand: frappe.throw("Please select mobile brand") if not self.mobile_model: frappe.throw("Please Select Mobile Model") - - """if self.justification not in ["Invalid media content","Out-of-site location", "User not assigned to shift", "Other"]: - self.screenshot = "" + else: + self.mobile_brand = "" + self.mobile_model = "" if self.justification in ["Invalid media content","Out-of-site location", "User not assigned to shift"]: if not self.screenshot: frappe.throw("Please Attach ScreenShot") - """ + else: + self.screenshot = "" else: self.justification = "" - """if self.justification == "Approved by Administrator": - if not check_attendance_manager(email=frappe.session.user): - frappe.throw("Only the Attendance manager can select 'Approved by Administrator' ") - """ - - - def validate_unscheduled_check(self): - #If Check is unscheduled,confirmed user roles before submitting - if self.is_unscheduled: - if check_attendance_manager(frappe.session.user): - return - elif "Shift Supervisor" in frappe.get_roles(frappe.session.user) or "Site Supervisor" in frappe.get_roles(frappe.session.user): - shift_assignments = frappe.db.get_list("Shift Assignment", filters={'docstatus':1,'start_date':self.date,'employee':self.employee}, fields="*") - if not shift_assignments: - frappe.throw(f"No Shift Assignments found for {self.employee_name} on {self.date} Please create one") - else: - frappe.throw("Only Shift/Site Supervisors and Attendance Managers have permission to submit unscheduled Attendance Checks") + if self.justification == "Approved by Administrator" and not check_attendance_manager(email=frappe.session.user): + frappe.throw("Only the Attendance manager can select 'Approved by Administrator' ") def on_submit(self): + if not self.attendance_status: + frappe.throw(_('To Approve the record set Attendance Status')) shift_working = frappe.db.get_value("Employee", self.employee, "shift_working") - if self.attendance_status == "Present" and shift_working: - self.validate_unscheduled_check() if self.attendance_status == "On Leave": self.check_on_leave_record() if self.attendance_status == "Day Off" and shift_working: - validate_day_off(self,convert=0) - self.validate_justification_and_attendance_status() - self.mark_attendance() - - def mark_attendance(self): - if self.workflow_state == 'Approved': - comment = "" - logs = [] - check_attendance = frappe.db.get_value('Attendance', - {'attendance_date': self.date, 'employee': self.employee, 'docstatus': ['<', 2], - 'roster_type':self.roster_type - }, ["status", "name"], as_dict=1) - comment = "Created from Attendance Check" - if check_attendance: - if check_attendance.status == "Absent": - if self.attendance_status == "Absent": - pass - elif self.attendance_status in ["Present", "Day Off", "On Leave"]: - working_hours=0 - if self.shift_assignment: - working_hours = frappe.db.get_value("Operations Shift", - frappe.db.get_value("Shift Assignment", self.shift_assignment, 'shift'), 'duration') - else: - working_hours = 8 if self.attendance_status == 'Present' else 0 - frappe.db.sql(f"""UPDATE `tabAttendance` - SET status = '{self.attendance_status}', - reference_doctype="{self.doctype}", - reference_docname="{self.name}", - modified="{str(now())}", - working_hours={working_hours}, - modified_by="{frappe.session.user}", - comment="{comment}" - WHERE name = "{check_attendance.name}" - """ - ) - frappe.db.commit() - else: - att = frappe.new_doc("Attendance") - att.employee = self.employee - att.employee_name = self.employee_name - att.attendance_date = self.date - att.status = self.attendance_status - att.roster_type = self.roster_type - att.reference_doctype = "Attendance Check" - att.reference_docname = self.name - att.comment = comment - if not frappe.db.get_value("Employee", self.employee, "attendance_by_timesheet"): - if self.shift_assignment: - att.shift_assignment = self.shift_assignment - else: - shift_assignment = frappe.db.exists("Shift Assignment", { - 'employee':self.employee, 'start_date':self.date, 'roster_type':self.roster_type - }) - if shift_assignment: - att.shift_assignment = shift_assignment - if att.shift_assignment and att.status=='Present': - att.working_hours = frappe.db.get_value("Operations Shift", - frappe.db.get_value("Shift Assignment", att.shift_assignment, 'shift'), 'duration') - if att.status != 'Day Off': - if not att.working_hours: - att.working_hours = 8 if self.attendance_status == 'Present' else 0 - att.insert(ignore_permissions=True) - att.submit() - for i in logs: - frappe.db.set_value("Employee Checkin", i.name, "attendance", att.name) - - - def validate_justification_and_attendance_status(self): - if not self.attendance_status: - frappe.throw(_('To Approve the record set Attendance Status')) + self.validate_day_off() + if self.attendance_status != "On Leave": + self.mark_attendance() def check_on_leave_record(self): - submited_leave_record = frappe.db.sql(f"""SELECT leave_type - FROM `tabLeave Application` - WHERE employee = '{self.employee}' - AND '{self.date}' >= from_date AND '{self.date}' <= to_date - AND workflow_state = 'Approved' - AND docstatus = 1 - """,as_dict=True) - - if submited_leave_record: - return - else: - draft_leave_records = frappe.db.sql(f"""select employee_name,leave_approver_name,name - FROM `tabLeave Application` - WHERE employee = '{self.employee}' - AND '{self.date}' >= from_date AND '{self.date}' <= to_date - AND docstatus = 0 - """,as_dict=True) - if draft_leave_records: + if self.attendance_status == "On Leave": + draft_leave_records = self.get_draft_leave_records() + if draft_leave_records and len(draft_leave_records) > 0: doc_url = get_url_to_form('Leave Application',draft_leave_records[0].get('name')) - error_template = frappe.render_template('one_fm/templates/emails/attendance_check_alert.html',context={'doctype':'Leave Application','current_user':frappe.session.user,'date':self.date,'approver':draft_leave_records[0].get('leave_approver_name'),'page_link':doc_url,'employee_name':self.employee_name}) + error_template = frappe.render_template( + 'one_fm/templates/emails/attendance_check_alert.html', + context={ + 'doctype':'Leave Application', + 'current_user':frappe.session.user, + 'date':self.date, + 'approver':draft_leave_records[0].get('leave_approver_name'), + 'page_link':doc_url, + 'employee_name':self.employee_name + } + ) frappe.throw(error_template) else: + link_to_new_leave = frappe.utils.get_url('/app/leave-application/new-leave-application-1') frappe.throw(f""" -

Please note that a Leave Application has not been created for {self.employee_name}.

-
- To create a leave application Click Here +

Please note that a Leave Application has not been created for {self.employee_name}.

+
+ To create a leave application + + Click Here + """) + def get_draft_leave_records(self): + return frappe.db.sql(f""" + select + employee_name, leave_approver_name, name + from + `tabLeave Application` + where + employee = '{self.employee}' + and + '{self.date}' >= from_date + and + '{self.date}' <= to_date + and + docstatus = 0 + """, + as_dict=True + ) + def validate_day_off(self): + if self.attendance_status == "Day Off": + # Check if shift request for that day exists + shift_request = self.get_shift_request() + if shift_request: + workflow_state = shift_request[0].get("workflow_state") + if workflow_state in {"Draft", "Pending Approval"}: + doc_url = get_url_to_form('Shift Request',shift_request[0].get('name')) + approver_full_name = frappe.db.get_value("User", shift_request[0].get('approver'), 'full_name') + error_template = frappe.render_template( + "one_fm/templates/emails/attendance_check_alert.html", + context={ + "doctype":"Shift Request", + "current_user":frappe.session.user, + "date":self.date, + "approver":approver_full_name, + "page_link":doc_url, + "employee_name":self.employee_name + } + ) + frappe.throw(error_template) + elif workflow_state == "Approved": + return + + # Cancelled or shift request not created at all + link_to_new_shift_request = frappe.utils.get_url('/app/shift-request/new-shift-request-1') + frappe.throw(f""" +

+ Please note that a shift request has not been created for + {self.employee_name} on {self.date} +

+
+ To create a Shift Request + + Click Here + + """) + + def get_shift_request(self): + return frappe.db.sql(f""" + select + name, approver, workflow_state + from + `tabShift Request` + where + employee = '{self.employee}' + and + from_date <= '{self.date}' + and + to_date >='{self.date}' + """, + as_dict=1 + ) -def get_attendance_by_timesheet_employees(employees,attendance_date): - #Get the applicable employees if the current date falls on a public holiday - try: - default_company = frappe.defaults.get_defaults().company - cond = '' - if not default_company: - default_company = frappe.get_last_doc("Company").name - holiday_lists = frappe.db.sql(f"""SELECT h.parent from `tabHoliday` h - WHERE h.holiday_date = '{attendance_date}' - """, as_dict=1) - if holiday_lists: - default_holiday_list = frappe.get_value("Company",default_company,'default_holiday_list') - holiday_lists =[i.parent for i in holiday_lists] - if default_holiday_list in holiday_lists: - if len(holiday_lists)>1: - cond += f" and holiday_list not in {tuple(holiday_lists)}" - else: - cond += f" and holiday_list !='{holiday_lists[0]}'" + def mark_attendance(self): + if self.workflow_state == 'Approved': + attendance = self.get_existing_attendance() + if attendance and len(attendance) > 0: + self.update_existing_attendance_record(attendance) else: - if len(holiday_lists)>1: - cond+=f" and holiday_list is Not NULL and holiday_list not in {tuple(holiday_lists)}" - else: - cond+=f" and holiday_list is Not NULL and holiday_list != '{holiday_lists[0]}'" - if employees: - if len(employees)==1: - cond+=f" and name !='{employees[0]}'" - else: - cond+= f" and name not in {tuple(employees)}" - - ts_employees = frappe.db.sql(f"""SELECT name from `tabEmployee` where status = "Active" AND date_of_joining <= '{attendance_date}' and attendance_by_timesheet = 1 {cond}""",as_dict=1) - return [i.name for i in ts_employees] - return [i.name for i in frappe.get_all("Employee",{"status":"Active",'date_of_joining':['<=',attendance_date],'attendance_by_timesheet':1,'name':['NOT IN',employees]},['name'])] - except: - frappe.log_error(title = "Attendance Check Error for Timesheet",message = frappe.get_traceback()) - #Ensure that a value is returned regardless of what happens here - return [] + self.create_new_attendance_record() + + def get_existing_attendance(self): + return frappe.db.get_value( + "Attendance", + { + "attendance_date": self.date, + "employee": self.employee, + "docstatus": ["<", 2], + "roster_type": self.roster_type + }, + ["status", "name"], + as_dict=1 + ) + def update_existing_attendance_record(self, attendance): + if attendance.status != self.attendance_status: + working_hours = self.get_shift_working_hours(self.shift_assignment) + frappe.db.sql(f""" + update + `tabAttendance` + set + status = '{self.attendance_status}', + reference_doctype='{self.doctype}', + reference_docname='{self.name}', + modified='{str(now())}', + working_hours={working_hours}, + modified_by='{frappe.session.user}', + comment="Created from Attendance Check" + where + name = '{attendance.name}' + """) + frappe.db.commit() + + def create_new_attendance_record(self): + attendance = frappe.new_doc("Attendance") + attendance.employee = self.employee + attendance.employee_name = self.employee_name + attendance.attendance_date = self.date + attendance.status = self.attendance_status + attendance.roster_type = self.roster_type + attendance.reference_doctype = self.doctype + attendance.reference_docname = self.name + attendance.comment = "Created from Attendance Check" + + if not frappe.db.get_value("Employee", self.employee, "attendance_by_timesheet"): + # Set shift assignmet to attendance recod + if self.shift_assignment: + attendance.shift_assignment = self.shift_assignment + else: + shift_assignment = frappe.db.exists("Shift Assignment", { + 'employee':self.employee, 'start_date':self.date, 'roster_type':self.roster_type + }) + if shift_assignment: + attendance.shift_assignment = shift_assignment + + if attendance.shift_assignment and attendance.status=='Present': + attendance.working_hours = self.get_shift_working_hours(attendance.shift_assignment) + if not attendance.working_hours and attendance.status != 'Day Off': + attendance.working_hours = 8 if self.attendance_status == 'Present' else 0 + attendance.insert(ignore_permissions=True) + attendance.submit() + + def get_shift_working_hours(self, shift_assignment=False): + working_hours=0 + if shift_assignment: + shift = frappe.db.get_value("Shift Assignment", shift_assignment, 'shift') + if shift: + working_hours = frappe.db.get_value("Operations Shift", shift, 'duration') + else: + working_hours = 8 if self.attendance_status == 'Present' else 0 + return working_hours def create_attendance_check(attendance_date=None): if production_domain(): if not attendance_date: attendance_date = add_days(today(), -1) + attendance_date = getdate(attendance_date) + + # Create attendance check for absentees for the date + absentees = get_absentees_on_date(attendance_date) + insert_attendance_check_records(absentees, attendance_date) + + # Create attendance check for employee who is shift working but no attendance marked on the date + attendance_not_marked_shift_employees = get_attendance_not_marked_shift_employees(attendance_date) + if attendance_not_marked_shift_employees: + insert_attendance_check_records(attendance_not_marked_shift_employees, attendance_date, True) + +def get_absentees_on_date(attendance_date): + return frappe.get_all("Attendance", + filters={ + 'docstatus': 1, + 'status': 'Absent', + 'attendance_date': attendance_date, + "employee": ["not in", frappe.db.get_list('Shift Permission', filters={'date': attendance_date}, pluck='employee')] + }, + fields=[ + "employee", + "roster_type", + "name as attendance", + "comment as attendance_comment", + "shift_assignment", + "status as attendance_status" + ] + ) - absentees = frappe.get_all("Attendance", filters={ - 'docstatus':1, - 'status':'Absent', - 'attendance_date':attendance_date}, - fields="*" - ) - - attendance_by_timesheet = 0 - - for count, i in enumerate(absentees): - try: - - doc = frappe.get_doc({ - "doctype":"Attendance Check", - "employee":i.employee, - "roster_type":i.roster_type, - "date":i.attendance_date, - "attendance_by_timesheet": attendance_by_timesheet, - "attendance":i.name, - "attendance_comment":i.comment, - "shift_assignment":i.shift_assignment, - "attendance_marked":1, - "marked_attendance_status":i.status - }).insert(ignore_permissions=1) - except Exception as e: - if not "Attendance Check already exist for" in str(e): - frappe.log_error(message=frappe.get_traceback(), title="Attendance Check Creation") - if count%10==0: - frappe.db.commit() - frappe.db.commit() - - # create for no shift but active shift based employees - attendance_list = [i.employee for i in frappe.db.get_list("Attendance", { - "attendance_date":attendance_date})] +def get_attendance_not_marked_shift_employees(attendance_date): + # Fetch the list of employees, attendance marked for the date and basic roster + attendance_list = frappe.db.get_list("Attendance", + filters={ + "attendance_date": attendance_date, + "roster_type": "Basic", + "status": ["not in", ["Absent"]], + "docstatus": 1 + }, + fields=["employee"] + ) + employees = [attendance.employee for attendance in attendance_list] - no_shifts = frappe.db.get_list("Employee", { + # Fetch all the employees who is shift working but no attendance marked + return frappe.db.get_list("Employee", + filters={ "shift_working":1, "status":"Active", - "name": ["NOT IN", attendance_list], + "name": ["not in", employees], "date_of_joining": ["<=", attendance_date] - } - ) - if no_shifts: - for count, i in enumerate(no_shifts): - try: - if not frappe.db.exists("Attendance", { - 'attendance_date':attendance_date, - 'employee':i.name} - ): - doc = frappe.get_doc({ - "doctype":"Attendance Check", - "employee":i.name, - "roster_type":"Basic", - 'is_unscheduled':1, - "date":attendance_date - }).insert(ignore_permissions=1) - except Exception as e: - if not "Attendance Check already exist for" in str(e): - frappe.log_error(message=frappe.get_traceback(), title="Attendance Check Creation") - if count%10==0: - frappe.db.commit() - frappe.db.commit() - - - no_timesheet = frappe.db.sql(f"""SELECT emp.employee FROM `tabEmployee` emp - WHERE emp.attendance_by_timesheet = 1 - AND emp.status ='Active' - AND emp.employee_name != 'Test Employee' - AND emp.date_of_joining <= '{attendance_date}' - AND emp.name NOT IN (SELECT employee from `tabTimesheet` WHERE start_date='{attendance_date}') - AND '{attendance_date}' NOT IN (SELECT holiday_date from `tabHoliday` h WHERE h.parent = emp.holiday_list AND h.holiday_date = '{attendance_date}') - AND emp.employee NOT IN (SELECT employee - FROM `tabLeave Application` - Where '{attendance_date}' >= from_date AND '{attendance_date}' <= to_date - AND workflow_state = 'Approved' - AND docstatus = 1) - """, as_dict=1) - - if no_timesheet: - for count, i in enumerate(no_timesheet): - try: - doc = frappe.get_doc({ - "doctype":"Attendance Check", - "employee":i.employee, - "roster_type":'Basic', - "date":attendance_date, - "attendance_by_timesheet": 1, - "attendance_marked":0, - "comment":"No Timesheet Created." - }).insert(ignore_permissions=1) - except Exception as e: - if not "Attendance Check already exist for" in str(e): - frappe.log_error(message=frappe.get_traceback(), title="Attendance Check Creation") - if count%10==0: - frappe.db.commit() - -def approve_attendance_check(): - attendance_checks = frappe.get_all("Attendance Check", filters={ - "date":["<", today()], "workflow_state":"Pending Approval"} + }, + fields=["name as employee"] ) - for i in attendance_checks: - doc = frappe.get_doc("Attendance Check", i.name) - if not doc.justification: - doc.justification = "Approved by Administrator" - if not doc.attendance_status: - doc.attendance_status = "Absent" - doc.workflow_state = "Approved" - try: - doc.submit() - except Exception as e: - if str(e)=="To date can not greater than employee's relieving date": - doc.db_set("Comment", f"Employee exited company on {frappe.db.get_value('Employee', doc.employee, 'relieving_date')}\n{doc.comment or ''}") - -def mark_missing_attendance(attendance_checkin_found): - for i in attendance_checkin_found: +def insert_attendance_check_records(details, attendance_date, is_unscheduled=False): + for count, data in enumerate(details): try: - checkin_record = "" - checkout_record = "" - shift_assignment = "" - att = "" - working_hours = 0 - in_time = '' - out_time = '' - comment = '' - day_off_ot = 0 - - if i.attendance: - att = frappe.get_doc("Attendance", i.attendance) - if i.shift_assignment: - shift_assignment = frappe.get_doc("Shift Assignment", i.shift_assignment) - day_off_ot = frappe.get_value("Employee Schedule", { - 'employee':i.employee, - 'date':i.date, - 'roster_type':i.roster_type - }, 'day_off_ot') - if i.checkin_record: - checkin_record = frappe.get_doc("Employee Checkin", i.checkin_record) - if i.checkout_record: - checkout_record = frappe.get_doc("Employee Checkin", i.checkout_record) - - # calculate working hours - if checkin_record and not checkout_record and shift_assignment: - working_hours = (shift_assignment.end_datetime - checkin_record.time).total_seconds() / (60 * 60) - in_time = checkin_record.time - out_time = shift_assignment.end_datetime - comment = "No checkout record found" - elif checkin_record and checkout_record and shift_assignment: - working_hours = (checkout_record.time - checkin_record.time).total_seconds() / (60 * 60) - in_time = checkin_record.time - out_time = checkout_record.time - if att: - # set values based on existing attendance - if att.status=='Absent': - att.db_set({ - 'working_hours':working_hours, - 'in_time':in_time, - 'out_time':out_time, - 'comment':comment, - 'day_off_ot':day_off_ot, - 'status':'Present' - }) - else: - att = frappe.new_doc("Attendance") - att.employee = i.employee - att.employee_name = i.employee_name - att.attendance_date = i.date - att.status = 'Present' - att.roster_type = i.roster_type - att.shift_assignment = i.shift_assignment - att.in_time = in_time - att.out_time = out_time - att.working_hours = working_hours - att.comment = comment - att.day_off_ot = day_off_ot - att.insert(ignore_permissions=True) - att.submit() - frappe.db.set_value("Employee Checkin", i.checkin_record, "attendance", att.name) - if checkout_record: - frappe.db.set_value("Employee Checkin", i.checkout_record, "attendance", att.name) + attendance_by_timesheet = False + if not is_unscheduled: + attendance_by_timesheet = frappe.db.get_value("Employee", data["employee"], "attendance_by_timesheet") + filters = { + "doctype": "Attendance Check", + "employee": data["employee"], + "date": attendance_date, + "attendance": data["attendance"] if "attendance" in data else "", + "roster_type": data["roster_type"] if "roster_type" in data else "Basic", + 'is_unscheduled': is_unscheduled, + "attendance_by_timesheet": attendance_by_timesheet, + "marked_attendance_status": data["attendance_status"] if "attendance_status" in data else "", + "shift_assignment": data["shift_assignment"] if "shift_assignment" in data else "", + "attendance_marked": 1 if "attendance" in data else 0, + "comment": data["attendance_comment"] if "attendance_comment" in data else "" + } + + doc = frappe.get_doc(filters) + doc.flags.ignore_mandatory = 1 + doc.insert(ignore_permissions=1) except Exception as e: - frappe.log_error(frappe.get_traceback(), 'Attendance Remark') + if not "Attendance Check already exist for" in str(e): + frappe.log_error(message=frappe.get_traceback(), title="Attendance Check Creation") + if count%10==0: + frappe.db.commit() + frappe.db.commit() @frappe.whitelist() def check_attendance_manager(email: str) -> bool: return frappe.db.get_value("Employee", {"user_id": email}) == frappe.db.get_single_value("ONEFM General Setting", "attendance_manager") +def attendance_check_pending_approval_check(): + pending_approval_attendance_checks = get_pending_approval_attendance_check(48) + if pending_approval_attendance_checks and len(pending_approval_attendance_checks) > 0: + # Issue Penalty to the assigned approver + issue_penalty_to_the_assigned_approver(pending_approval_attendance_checks) + # Assign the attendance checks to attendance manager for approval + assign_attendance_manager(pending_approval_attendance_checks) + + frappe.db.commit() -@frappe.whitelist() -def validate_day_off(form,convert=1): - # Validates the existence of a shift request when the attendance status of the attendance - doc = json.loads(form) if convert else form - if doc.get('attendance_status') == "Day Off": - #check if shift request for that day exists - query = f"Select name from `tabShift Request` where docstatus = 1 and assign_day_off = 1 and employee = '{doc.get('employee')}' and from_date <= '{doc.get('date')}' and to_date >='{doc.get('date')}'" - submited_result_set = frappe.db.sql(query,as_dict=1) - if submited_result_set: - return +def get_pending_approval_attendance_check(hours): + # Method to get list of attendance check, which is in panding approval state after a given hours + date_time = datetime.strptime(now(), '%Y-%m-%d %H:%M:%S.%f') - timedelta(hours=hours) + return frappe.db.sql(""" + select + name, _assign as assign_to + from + `tabAttendance Check` + where + creation <= %s + and + docstatus = 0 + + """, (date_time), as_dict=1) + + +def issue_penalty_to_the_assigned_approver(pending_approval_attendance_checks): + try: + approvers = {} + for pending_approval_attendance_check in pending_approval_attendance_checks: + + if pending_approval_attendance_check.get('assign_to'): + assign_to = frappe.parse_json(pending_approval_attendance_check.assign_to) + if assign_to and len(assign_to) > 0: + if assign_to[0] in approvers: + approvers[assign_to[0]] += ", "+pending_approval_attendance_check.name + else: + approvers[assign_to[0]] = pending_approval_attendance_check.name + + penalty_type = frappe.db.get_single_value("ONEFM General Setting", "att_check_approver_penalty_type") + for approver in approvers: + note = "There are attendance check not approved "+approvers[approver] + approver_employee = frappe.db.get_values( + "Employee", + {"user_id": approver}, + ['name', 'employee_name', 'designation'], + as_dict=True + ) + if approver_employee and len(approver_employee)>0: + penalty = frappe.get_doc({ + "doctype": "Penalty", + "penalty_issuance_time": now(), + "recipient_employee": approver_employee[0].name, + "recipient_name": approver_employee[0].employee_name, + "recipient_designation": approver_employee[0].designation, + "recipient_user": approver, + }) + penalty_details = penalty.append("penalty_details") + penalty_details.penalty_type = penalty_type + penalty_details.exact_notes = note + penalty.save(ignore_permissions=True) + except: + frappe.log_error(title = "Error Creating Penalty Documents",message = frappe.get_traceback()) + +def fetch_existing_todos(manager): + """Fetch the existing todos for attendance checks assigned to the attendance manager + Args: + manager (Str): User + """ + existing_todos = frappe.get_all("ToDo",{'allocated_to':manager,'status':'Open','reference_type':'Attendance Check'},['reference_name']) + return [i.reference_name for i in existing_todos] + + +def create_split_query(todos,limit,manager,today,today_datetime): + """ + This is to mitigate max_allowed_packet errors when the query size is too large + Args: + todos (_type_): all todos + limit (int): the number of todos per string + """ + def create_sql_query(sublist): + values = [] + for d in sublist: + vals = f""" + '{manager}_{d.name}', + '{manager}', + 'Attendance Check', + '{d.name}', + "Assignment for Attendance Check {d.name}", + 'Medium', + 'Open', + '{today}', + "Administrator", + "Action", + "Administrator", + '{today_datetime}', + '{today_datetime}' + """ + values.append(f"({vals})") + query = """ INSERT INTO `tabToDo` + (`name`,`allocated_to`, `reference_type`, `reference_name`, + `description`, `priority`,`status`, `date`,`owner`,`type`,`assigned_by`,`creation`, `modified`) + VALUES """ + ', '.join(values) + return query + + split_lists = [todos[i:i + limit] for i in range(0, len(todos), limit)] + + # Creating SQL query strings for each sublist + sql_queries = [create_sql_query(sublist) for sublist in split_lists] + + return sql_queries + +def create_todos(manager,todos): + """Create todos for the attendance manager + Using this approach because there a potential for over 50k entries and timeout + + Args: + manager (str): attenance manager user + todos (list): a list of dicts with todo details + """ + try: + today = frappe.utils.getdate() + today_datetime = frappe.utils.get_datetime() + + if len(todos)>10000: + #Query needs to be split to avoid max query package error + split_query =create_split_query(todos,10000,manager,today,today_datetime) + for each in split_query: + frappe.db.sql(each,values=[]) else: - draft_query = f"Select name,approver from `tabShift Request` where docstatus = 0 and assign_day_off = 1 and employee = '{doc.get('employee')}' and from_date <= '{doc.get('date')}' and to_date >='{doc.get('date')}'" - drafts_result_set = frappe.db.sql(draft_query,as_dict=1) - if drafts_result_set: - - - doc_url = get_url_to_form('Shift Request',drafts_result_set[0].get('name')) - approver_full_name = frappe.db.get_value("User",drafts_result_set[0].get('approver'),'full_name') - error_template = frappe.render_template('one_fm/templates/emails/attendance_check_alert.html',context={'doctype':'Shift Request','current_user':frappe.session.user,'date':doc.date,'approver':approver_full_name,'page_link':doc_url,'employee_name':doc.employee_name}) - frappe.throw(error_template) - else: - #cancelled or shift request not created at all - frappe.throw(f""" -

Please note that a shift request has not been created for {doc.employee_name} on {doc.date}..

-
- To create a Shift Request Click Here - """) - + query = """ + INSERT INTO + `tabToDo` + ( + `name`,`allocated_to`, `reference_type`, `reference_name`,`description`, `priority`, + `status`, `date`, `assigned_by`,`creation`, `modified`,`type`,`owner` + ) + VALUES + """ + query_body = """""" + for each in todos: + query_body+= f""" + ( + "{'_'.join([manager,each.name])}", "{manager}", "{'Attendance Check'}", "{each.name}", + "Assignment for Attendance Check {each.name}", "{'Medium'}", "{'Open'}", '{today}', + "Administrator",'{today_datetime}','{today_datetime}',"Action","Administrator" + ),""" + if query_body: + query += query_body[:-1] + frappe.db.sql(query,values=[]) + frappe.db.commit() + except: + frappe.log_error(title = "Error Assigning to Attendance Manager",message = frappe.get_traceback()) -def assign_attendance_manager_after_48_hours(): - attendance_manager_user = fetch_attendance_manager_user_obj() - if attendance_manager_user: - date_time = datetime.strptime(now(), '%Y-%m-%d %H:%M:%S.%f') - timedelta(hours=48) - attendance_check = attendance_check = frappe.db.sql(""" - SELECT name - FROM `tabAttendance Check` - WHERE creation <= %s - AND docstatus = 0 - AND TIME(creation) <= %s - """, (date_time, date_time.time()), as_list=1) - - if attendance_check: - list_of_names = tuple(chain.from_iterable(attendance_check)) - if list_of_names: - for obj in list_of_names: - add_assignment({ - 'doctype': "Attendance Check", - 'name': obj, - 'assign_to': [attendance_manager_user], - }) +def notify_manager(manager): + """Notify the manager that new todos have been created for them + Args: + manager (str): attendance manager + """ + try: + page_link = frappe.utils.get_url()+f'/app/todo?date={frappe.utils.get_date_str(frappe.utils.getdate())}&allocated_to={manager}' + msg = frappe.render_template('one_fm/templates/emails/attendance_manager_todo_assignment.html', context={"manager": manager,'page_link':page_link}) + sendemail(recipients= [manager], content=msg, subject="Pending Attendance Checks", delayed=False) + except: + frappe.log_error(title = "Error Notifying Attendance Manager",message = frappe.get_traceback()) + + -def check_for_missed(date,schedules,shift_assignments,attendance_requests,all_attendance,checks_to_create): - all_leaves = frappe.db.sql(f"""SELECT name,employee - FROM `tabLeave Application` - Where '{date}' >= from_date AND '{date}' <= to_date - AND workflow_state = 'Approved' - AND docstatus = 1 - """,as_dict=True) - all_shifts_query = f"""Select employee from `tabShift Request` where docstatus = 1 and - from_date <= '{date}' and to_date >='{date}'""" - all_leave_employees = [i.employee for i in all_leaves] - all_shifts = frappe.db.sql(all_shifts_query,as_dict = 1) - shift_request_employees = [i.employee for i in all_shifts] - schedule_employees = [i.employee for i in schedules] - shift_assignments_employees = [i.employee for i in shift_assignments] - attendance_requests_employees = [i.employee for i in attendance_requests] - all_attendance_employees = [i.employee for i in all_attendance] - merged = shift_request_employees+schedule_employees+shift_assignments_employees+\ - all_attendance_employees+attendance_requests_employees+checks_to_create+all_leave_employees - merged_set = set(merged) #Remove Duplicates - merged_tuple = tuple(merged_set) - if len(merged_tuple) == 1: - single_employee = merged_tuple[0] - employees_with_no_docs_query = f""" SELECT name from `tabEmployee` where status = 'Active' AND date_of_joining <= '{date}' AND shift_working = 1 and name = {single_employee} - """ - - #Employees with no shift,schedule,leaves etc - else: - employees_with_no_docs_query = f""" SELECT name from `tabEmployee` where status = 'Active' AND date_of_joining <= '{date}' and shift_working = 1 and name not in {merged_tuple} - """ - result_set = frappe.db.sql(employees_with_no_docs_query,as_dict=1) - return [i.name for i in result_set] if result_set else [] +def assign_attendance_manager(pending_approval_attendance_checks): + attendance_manager_user = fetch_attendance_manager_user() + if attendance_manager_user: + existing_todos = fetch_existing_todos(attendance_manager_user) + filtered_pending_approval_attendance_check = [i for i in pending_approval_attendance_checks if i.name not in existing_todos ] + create_todos(attendance_manager_user,filtered_pending_approval_attendance_check) + notify_manager(attendance_manager_user) + def schedule_attendance_check(): frappe.enqueue(create_attendance_check, queue='long', timeout=7000) diff --git a/one_fm/one_fm/doctype/onefm_general_setting/onefm_general_setting.json b/one_fm/one_fm/doctype/onefm_general_setting/onefm_general_setting.json index f84bd4a69d..231781eef0 100644 --- a/one_fm/one_fm/doctype/onefm_general_setting/onefm_general_setting.json +++ b/one_fm/one_fm/doctype/onefm_general_setting/onefm_general_setting.json @@ -24,6 +24,8 @@ "attendance_manager", "attendance_manager_name", "attendance_check_exclusion", + "column_break_xudp", + "att_check_approver_penalty_type", "employee_section", "employee_master_role", "wiki_page_access", @@ -73,6 +75,7 @@ "options": "ONEFM Document Access Roles Detail" }, { + "collapsible": 1, "fieldname": "section_break_7yvzt", "fieldtype": "Section Break", "label": "Attendance" @@ -136,6 +139,16 @@ "fieldtype": "Check", "label": "Extend User Permissions" }, + { + "fieldname": "column_break_xudp", + "fieldtype": "Column Break" + }, + { + "fieldname": "att_check_approver_penalty_type", + "fieldtype": "Link", + "label": "Attendance Check Approver Penalty Type", + "options": "Penalty Type" + }, { "description": "Table of roles allowed to view employee info", "fieldname": "employee_info_access", @@ -178,4 +191,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/one_fm/one_fm/page/face_recognition/face_recognition.py b/one_fm/one_fm/page/face_recognition/face_recognition.py index d2d3ba817e..6ab594b456 100755 --- a/one_fm/one_fm/page/face_recognition/face_recognition.py +++ b/one_fm/one_fm/page/face_recognition/face_recognition.py @@ -20,14 +20,15 @@ # setup channel for face recognition face_recognition_service_url = frappe.local.conf.face_recognition_service_url -channels = [ - grpc.secure_channel(i, grpc.ssl_channel_credentials()) for i in face_recognition_service_url -] +# channels = [ +# grpc.secure_channel(i, grpc.ssl_channel_credentials()) for i in face_recognition_service_url +# ] # setup stub for face recognition -stubs = [ - facial_recognition_pb2_grpc.FaceRecognitionServiceStub(i) for i in channels -] +# stubs = [ +# facial_recognition_pb2_grpc.FaceRecognitionServiceStub(i) for i in channels +# ] +stubs = list() class NumpyArrayEncoder(JSONEncoder): @@ -151,7 +152,17 @@ def verify(): @frappe.whitelist() def user_within_site_geofence(employee, log_type, user_latitude, user_longitude): """ This method checks if user's given coordinates fall within the geofence radius of the user's assigned site in Shift Assigment. """ - shift = get_current_shift(employee) + shift_exists = get_current_shift(employee) + if shift_exists['type'] == "Early": + # check if user can checkin with the correct time + return response("Resource Not Found", 404, None, f"You are checking in too early, checkin is allowed in {shift_exists['data']} minutes ") + elif shift_exists['type'] == "Late": + return response("Resource Not Found", 404, None, f"You are checking out too late, checkout was allowed {shift_exists['data']} minutes ago ") + elif shift_exists['type'] == "On Time": + shift = shift_exists['data'] + else: + shift = None + date = cstr(getdate()) if shift: if frappe.db.exists("Shift Request", {"employee":employee, 'from_date':['<=',date],'to_date':['>=',date]}): @@ -216,6 +227,7 @@ def update_onboarding_employee(employee): onboard_employee = frappe.get_doc('Onboard Employee', onboard_employee_exist.name) onboard_employee.enrolled = True onboard_employee.enrolled_on = now_datetime() + onboard_employee.flags.ignore_mandatory = True onboard_employee.save(ignore_permissions=True) frappe.db.commit() @@ -251,6 +263,7 @@ def update_onboarding_employee(employee): onboard_employee = frappe.get_doc('Onboard Employee', onboard_employee_exist) onboard_employee.enrolled = True onboard_employee.enrolled_on = now_datetime() + onboard_employee.flags.ignore_mandatory = True onboard_employee.save(ignore_permissions=True) frappe.db.commit() @@ -328,7 +341,16 @@ def check_existing(): if not employee: frappe.throw(_("Please link an employee to the logged in user to proceed further.")) - shift = get_current_shift(employee) + shift_exists = get_current_shift(employee) + if shift_exists['type'] == "Early": + # check if user can checkin with the correct time + return response("Resource Not Found", 404, None, f"You are checking in too early, checkin is allowed in {shift_exists['data']} minutes ") + elif shift_exists['type'] == "Late": + return response("Resource Not Found", 404, None, f"You are checking out too late, checkout was allowed {shift_exists['data']} minutes ago ") + elif shift_exists['type'] == "On Time": + shift = shift_exists['data'] + else: + shift = None #if employee schedule is linked with the previous Checkin doc if shift: diff --git a/one_fm/operations/doctype/employee_checkin_issue/employee_checkin_issue.js b/one_fm/operations/doctype/employee_checkin_issue/employee_checkin_issue.js index 6eaea7899f..c0665c9963 100644 --- a/one_fm/operations/doctype/employee_checkin_issue/employee_checkin_issue.js +++ b/one_fm/operations/doctype/employee_checkin_issue/employee_checkin_issue.js @@ -3,9 +3,9 @@ frappe.ui.form.on('Employee Checkin Issue', { refresh: function(frm) { - if(!frm.doc.issue && frm.doc.workflow_state == 'Approved'){ - frm.add_custom_button(__('Create Issue'), function() { - create_issue(frm); + if(!frm.doc.ticket && frm.doc.workflow_state == 'Approved'){ + frm.add_custom_button(__('Create Ticket'), function() { + create_ticket(frm); }).addClass('btn-primary'); } }, @@ -14,9 +14,24 @@ frappe.ui.form.on('Employee Checkin Issue', { }, employee: function(frm) { get_shift_assignment(frm); + }, + validate: function(frm){ + validate_date(frm); + }, + date: function(frm){ + validate_date(frm); } + }); + +function validate_date(frm){ + if(frm.doc.date < frappe.datetime.now_date()){ + frappe.throw("Date can not be set to a past date") + } + +} + function set_employee_from_the_session_user(frm) { if(frappe.session.user != 'Administrator' && frm.is_new()){ frappe.db.get_value('Employee', {'user_id': frappe.session.user} , 'name', function(r) { @@ -33,12 +48,12 @@ function set_employee_from_the_session_user(frm) { } }; -function create_issue(frm) { +function create_ticket(frm) { frappe.call({ - method: 'create_issue', + method: 'create_hd_ticket', doc: frm.doc, freeze: true, - freeze_message: __("Creating the Issue...!") + freeze_message: __("Creating the Ticket!") }) }; diff --git a/one_fm/operations/doctype/employee_checkin_issue/employee_checkin_issue.json b/one_fm/operations/doctype/employee_checkin_issue/employee_checkin_issue.json index a95f78132d..3d0425e48e 100644 --- a/one_fm/operations/doctype/employee_checkin_issue/employee_checkin_issue.json +++ b/one_fm/operations/doctype/employee_checkin_issue/employee_checkin_issue.json @@ -23,9 +23,10 @@ "column_break_9", "shift", "shift_type", + "roster_type", "section_break_17", "amended_from", - "issue" + "ticket" ], "fields": [ { @@ -160,18 +161,25 @@ "label": "Issue Details", "mandatory_depends_on": "eval:doc.issue_type==\"Other\"" }, + { + "fetch_from": "assigned_shift.roster_type", + "fieldname": "roster_type", + "fieldtype": "Select", + "label": "Roster Type", + "options": "Basic\nOver-Time" + }, { "allow_on_submit": 1, - "fieldname": "issue", + "fieldname": "ticket", "fieldtype": "Link", - "label": "Issue", - "options": "Issue", + "label": "Ticket", + "options": "HD Ticket", "read_only": 1 } ], "is_submittable": 1, "links": [], - "modified": "2023-01-24 12:30:38.577875", + "modified": "2024-04-23 09:04:39.452102", "modified_by": "Administrator", "module": "Operations", "name": "Employee Checkin Issue", diff --git a/one_fm/operations/doctype/employee_checkin_issue/employee_checkin_issue.py b/one_fm/operations/doctype/employee_checkin_issue/employee_checkin_issue.py index a580c1af3f..aa539b13fd 100644 --- a/one_fm/operations/doctype/employee_checkin_issue/employee_checkin_issue.py +++ b/one_fm/operations/doctype/employee_checkin_issue/employee_checkin_issue.py @@ -4,10 +4,11 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -from frappe.utils import getdate, get_datetime, add_to_date, format_date, cstr +from frappe.utils import getdate, get_datetime, add_to_date, format_date from frappe import _ from one_fm.api.notification import get_employee_user_id from hrms.hr.doctype.shift_assignment.shift_assignment import get_shift_details +from one_fm.api.v1.face_recognition import create_checkin_log from one_fm.api.v1.utils import response from one_fm.utils import ( workflow_approve_reject, send_workflow_action_email, get_approver @@ -25,107 +26,114 @@ class ShiftDetailsMissing(frappe.ValidationError): class EmployeeCheckinIssue(Document): def validate(self): self.check_shift_details_value() - self.validate_date() + self.validate_attendance() + self.validate_employee_checkin() self.validate_duplicate_record() - if self.workflow_state in ['Pending', 'Approved']: - self.validate_attendance() - self.validate_employee_checkin() + self.validate_date() + + # This method validates the shift details availability for employee + def check_shift_details_value(self): + if not self.assigned_shift or not self.shift or not self.shift_supervisor or not self.shift_type: + frappe.throw(_("Shift details are missing. Please make sure that the correct date is set."), exc=ShiftDetailsMissing) def validate_attendance(self): - attendance = frappe.db.exists('Attendance',{'attendance_date': self.date, 'employee': self.employee, 'docstatus': 1}) + attendance = frappe.db.exists( + "Attendance", + { + "attendance_date": self.date, + "employee": self.employee, + "docstatus": 1, + "roster_type": self.roster_type + } + ) if attendance: - frappe.throw(_('There is an Attendance {0} exists for the \ - Employee {1} on {2}'.format(attendance, self.employee_name, format_date(self.date))), exc=ExistAttendance) + frappe.throw( + _("Attendance record {0} already exists for {1} having Roster Type {2} on {3}.".format + ( + attendance, self.employee_name, self.roster_type, format_date(self.date) + ) + ), + exc=ExistAttendance + ) def validate_employee_checkin(self): start_date = get_datetime(self.date) end_date = add_to_date(start_date, hours=23.9998) - employee_checkin = frappe.db.exists('Employee Checkin', - {'log_type': self.log_type, 'time': ["between", [start_date, end_date]], 'employee': self.employee} + employee_checkin = frappe.db.exists( + "Employee Checkin", + { + "log_type": self.log_type, + "time": ["between", [start_date, end_date]], + "shift_assignment": self.assigned_shift, + "employee": self.employee + } ) if employee_checkin: - frappe.throw(_('There is an Employee Checkin {0} exists for the \ - Employee {1} on {2}'.format(employee_checkin, self.employee_name, format_date(self.date))), exc=ExistCheckin) + frappe.throw( + _("Employee Checkin {0} of type {1} already exists for the shift assignment {2}".format( + employee_checkin, self.log_type, self.assigned_shift + ) + ), + exc=ExistCheckin + ) - # This method validates the shift details availability for employee - def check_shift_details_value(self): - if not self.assigned_shift or not self.shift or not self.shift_supervisor or not self.shift_type: - frappe.throw(_("Shift details are missing. Please make sure date is correct."), exc=ShiftDetailsMissing) + # This method validates any dublicate ECI for the employee on same day + def validate_duplicate_record(self): + if frappe.db.exists( + "Employee Checkin Issue", + { + "employee": self.employee, + "assigned_shift": self.assigned_shift, + "log_type": self.log_type, + "name": ["not in", [self.name]], + "date": self.date + } + ): + msg = _( + "{employee} has already created Employee Checkin Issue for {log_type} against the same Shift Assignment".format( + employee=self.employee_name, log_type=self.log_type + ) + ) + frappe.throw(msg) # This method validates the ECI date and avoid creating ECI for previous days def validate_date(self): - if self.docstatus==0 and getdate(self.date) < getdate() and self.is_new(): - frappe.throw(_("Oops! You cannot create a Employee Checkin Issue for a previous date.")) + if self.docstatus == 0 and getdate(self.date) < getdate() and self.is_new(): + frappe.throw(_("Oops! You cannot create Employee Checkin Issue for a previous date.")) - # This method validates any dublicate ECI for the employee on same day - def validate_duplicate_record(self): - date = getdate(self.date).strftime('%d-%m-%Y') - if frappe.db.exists("Employee Checkin Issue", {"employee": self.employee, "date":self.date, - "assigned_shift": self.assigned_shift, "log_type": self.log_type, "name": ["not in", [self.name]]}): - frappe.throw(_("{employee} has already created a Employee Checkin Issue for {log_type} on {date}.".format(employee=self.employee_name, log_type=self.log_type, date=date))) + def on_update(self): + if self.workflow_state == "Approved": + create_checkin_log(self.employee, self.log_type, 0, self.latitude, self.longitude, "Mobile App") + self.create_hd_ticket() - @frappe.whitelist() - def create_issue(self): - if not self.issue: - issue_type = False - if frappe.db.exists('Issue Type', {'name': 'Checkin Issue'}): - issue_type = 'Checkin Issue' - else: - issue_type_doc = frappe.get_doc({"doctype": "Issue Type", "__newname": "Checkin Issue"}).insert() - issue_type = issue_type_doc.name - if issue_type: - issue = frappe.new_doc('Issue') - issue.subject = "Employee Checkin Issue - {0}".format(self.issue_type) - issue.raised_by = frappe.session.user - doc_link = frappe.utils.get_url(self.get_url()) - description = issue.subject + "

The user found an issue in the app \ - and recorded in Employee Checkin Issue.

".format(doc_link) - issue.description = description - issue.issue_type = issue_type - issue.save(ignore_permissions=True) - self.issue = issue.name - self.save(ignore_permissions=True) - self.reload() - def on_update(self): - if self.workflow_state == 'Approved': - create_employee_checkin_for_employee_checkin_issue(self) - workflow_approve_reject(self, [get_employee_user_id(self.employee)]) - - if self.workflow_state == 'Pending': - send_workflow_action_email(self, recipients=[get_employee_user_id(self.shift_supervisor)]) - - if self.workflow_state in ['Rejected']: - workflow_approve_reject(self, [get_employee_user_id(self.employee)]) - -def create_employee_checkin_for_employee_checkin_issue(employee_checkin_issue, from_api=False): - """ - Method to create Employee Checkin from the Employee Checkin Issue - args: - employee_checkin_issue: Object of Employee Checkin Issue - """ - # Get shift details for the employee - shift_assignment = frappe.get_doc("Shift Assignment", employee_checkin_issue.assigned_shift) - - employee_checkin = frappe.new_doc('Employee Checkin') - employee_checkin.employee = employee_checkin_issue.employee - employee_checkin.log_type = employee_checkin_issue.log_type - employee_checkin.time = shift_assignment.start_datetime if employee_checkin_issue.log_type == "IN" else shift_assignment.end_datetime - employee_checkin.date = shift_assignment.start_date if employee_checkin_issue.log_type=='IN' else shift_assignment.end_datetime - employee_checkin.skip_auto_attendance = False - employee_checkin.employee_checkin_issue = employee_checkin_issue.name - # The field shift in shift assignment is operations shift - employee_checkin.operations_shift = shift_assignment.shift - employee_checkin.shift_type = shift_assignment.shift_type - # The field shift_type in shift assignment is shift type and in employee checkin the field shift is shift type - employee_checkin.shift = shift_assignment.shift_type - employee_checkin.shift_assignment = employee_checkin_issue.assigned_shift - if employee_checkin_issue.latitude and employee_checkin_issue.longitude: - employee_checkin.device_id = cstr(employee_checkin_issue.latitude)+","+cstr(employee_checkin_issue.longitude) - if from_api: - employee_checkin.flags.ignore_validate = True - employee_checkin.save(ignore_permissions=True) - frappe.db.commit() + @frappe.whitelist() + def create_hd_ticket(self): + if not self.ticket: + ticket_type = self.get_ticket_type() + ticket = frappe.new_doc('HD Ticket') + ticket.subject = "Employee Checkin Issue - {0}".format(self.issue_type) + ticket.raised_by = frappe.session.user + doc_link = frappe.utils.get_url(self.get_url()) + description = ticket.subject + "

The user found an issue in the app \ + and recorded in Employee Checkin Issue.

".format(doc_link) + ticket.description = description + ticket.ticket_type = ticket_type + ticket.save(ignore_permissions=True) + self.ticket = ticket.name + self.save(ignore_permissions=True) + self.reload() + + def get_ticket_type(self): + if not frappe.db.exists('HD Ticket Type', {'name': 'Checkin Issue'}): + ticket_type_doc = frappe.get_doc( + { + "doctype": "HD Ticket Type", + "__newname": "Checkin Issue" + } + ).insert() + return ticket_type_doc.name + return 'Checkin Issue' @frappe.whitelist() def fetch_approver(employee, date=None): @@ -154,32 +162,6 @@ def fetch_approver(employee, date=None): tail_end = "on {0}".format(date) frappe.throw("No shift assignment found for {employee} {tail_end}".format(employee=employee, tail_end=tail_end)) -# Approve pemding employee checkin issue before marking attendance -def approve_open_employee_checkin_issue(start_date, end_date): - try: - employee_checkin_issue_list = frappe.db.sql(f""" - SELECT eci.name FROM `tabEmployee Checkin Issue` eci JOIN `tabShift Assignment` sa - ON sa.name=eci.assigned_shift - WHERE sa.start_date='{start_date}' and sa.end_date='{end_date}' - AND eci.workflow_state='Pending' AND eci.docstatus=0 - """, as_dict=1) - error_list = """""" - for employee_checkin_issue in employee_checkin_issue_list: - try: - # Apply workflow - employee_checkin_issue_doc = frappe.get_doc("Employee Checkin Issue", employee_checkin_issue.name) - employee_checkin_issue_doc.db_set('workflow_state', 'Approved') - employee_checkin_issue_doc.db_set('docstatus', 1) - employee_checkin_issue_doc.add_comment("Info", "This record is System Aprroved") - employee_checkin_issue_doc.reload() - # Create checkin from employee checkin issue - create_employee_checkin_for_employee_checkin_issue(employee_checkin_issue_doc, True) - except Exception as e: - error_list += str(e)+'\n\n' - if error_list:frappe.log_error(error_list, 'Employee Checkin Issue') - except Exception as e: - frappe.log_error(frappe.get_traceback(), 'Employee Checkin Issue') - @frappe.whitelist() def create_checkin_issue(employee, issue_type, log_type, latitude, longitude, reason): try: diff --git a/one_fm/operations/doctype/employee_schedule/employee_schedule.json b/one_fm/operations/doctype/employee_schedule/employee_schedule.json index 9342de65b9..51afaea114 100755 --- a/one_fm/operations/doctype/employee_schedule/employee_schedule.json +++ b/one_fm/operations/doctype/employee_schedule/employee_schedule.json @@ -8,6 +8,7 @@ "field_order": [ "employee", "employee_name", + "employment_type", "department", "column_break_3", "date", @@ -19,6 +20,8 @@ "shift_type", "start_datetime", "end_datetime", + "is_replaced", + "replaced_employee_schedule", "column_break_6", "site", "project", @@ -192,6 +195,27 @@ { "fieldname": "column_break_d2pic", "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "is_replaced", + "fieldtype": "Check", + "label": "Is Replaced" + }, + { + "depends_on": "eval:doc.is_replaced;", + "fieldname": "replaced_employee_schedule", + "fieldtype": "Link", + "label": "Replaced Employee Schedule", + "options": "Employee Schedule" + }, + { + "fetch_from": "employee.employment_type", + "fieldname": "employment_type", + "fieldtype": "Link", + "label": "Employment Type", + "options": "Employment Type", + "read_only": 1 } ], "links": [], diff --git a/one_fm/operations/doctype/shift_permission/shift_permission.js b/one_fm/operations/doctype/shift_permission/shift_permission.js index 10993bc563..20e4237d0d 100755 --- a/one_fm/operations/doctype/shift_permission/shift_permission.js +++ b/one_fm/operations/doctype/shift_permission/shift_permission.js @@ -15,8 +15,15 @@ frappe.ui.form.on('Shift Permission', { get_shift_assignment(frm); }, date: function(frm) { + validate_date(frm); get_shift_assignment(frm); - } + }, + validate: function(frm){ + validate_date(frm) + }, + permission_type: function(frm) { + get_shift_assignment(frm); + }, }); function set_employee_from_the_session_user(frm) { @@ -38,10 +45,10 @@ function set_employee_from_the_session_user(frm) { function set_options_for_permission_type(frm) { if(frm.doc.log_type){ if(frm.doc.log_type == 'IN'){ - frm.set_df_property('permission_type', 'options', ['', 'Arrive Late', 'Forget to Checkin', 'Checkin Issue']); + frm.set_df_property('permission_type', 'options', ['', 'Arrive Late', ]); } else{ - frm.set_df_property('permission_type', 'options', ['', 'Leave Early', 'Forget to Checkout', 'Checkout Issue']); + frm.set_df_property('permission_type', 'options', ['', 'Leave Early', ]); } } else{ @@ -81,3 +88,17 @@ function set_shift_details(frm, name, supervisor, shift, shift_type){ frappe.model.set_value(frm.doctype, frm.docname, "shift", shift); frappe.model.set_value(frm.doctype, frm.docname, "shift_type", shift_type); } + + +var validate_date = (frm) => { + if (frm.doc.date){ + if (frm.doc.date < frappe.datetime.now_date()){ + if (frm.is_new()){ + frappe.throw("Please note that Shift permission cannot be created for a past date") + + } else { + frappe.throw("Please note that Shift permission cannot be updated to a past date") + } + } + } +} diff --git a/one_fm/operations/doctype/shift_permission/shift_permission.json b/one_fm/operations/doctype/shift_permission/shift_permission.json index 22c2554411..fec2b045fc 100755 --- a/one_fm/operations/doctype/shift_permission/shift_permission.json +++ b/one_fm/operations/doctype/shift_permission/shift_permission.json @@ -12,7 +12,6 @@ "date", "column_break_3", "log_type", - "permission_type", "section_break_5", "assigned_shift", "roster_type", @@ -25,9 +24,6 @@ "section_break_12", "arrival_time", "leaving_time", - "latitude", - "column_break_20", - "longitude", "section_break_17", "reason", "amended_from" @@ -85,15 +81,6 @@ "options": "Shift Assignment", "read_only": 1 }, - { - "fieldname": "permission_type", - "fieldtype": "Select", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Permission Type", - "options": "\nArrive Late\nLeave Early\nForget to Checkin\nForget to Checkout\nCheckin Issue\nCheckout Issue", - "reqd": 1 - }, { "fieldname": "shift_supervisor", "fieldtype": "Link", @@ -119,21 +106,21 @@ }, { "default": "Now", - "depends_on": "eval:doc.permission_type == \"Arrive Late\"", + "depends_on": "eval:doc.log_type == \"IN\"", "fieldname": "arrival_time", "fieldtype": "Time", "in_list_view": 1, "label": "Arrival Time", - "mandatory_depends_on": "eval:doc.permission_type == \"Arrive Late\"" + "mandatory_depends_on": "eval:doc.log_type == \"IN\"" }, { "default": "Now", - "depends_on": "eval:doc.permission_type== \"Leave Early\"", + "depends_on": "eval:doc.log_type == \"OUT\"", "fieldname": "leaving_time", "fieldtype": "Time", "in_list_view": 1, "label": "Leaving Time", - "mandatory_depends_on": "eval:doc.permission_type== \"Leave Early\"" + "mandatory_depends_on": "eval:doc.log_type == \"OUT\"" }, { "fieldname": "amended_from", @@ -153,18 +140,6 @@ "label": "Employee Name", "read_only": 1 }, - { - "depends_on": "eval:doc.permission_type == \"Checkin Issue\" || doc.permission_type == \"Checkout Issue\"", - "fieldname": "latitude", - "fieldtype": "Float", - "label": "Latitude" - }, - { - "depends_on": "eval:doc.permission_type == \"Checkin Issue\" || doc.permission_type == \"Checkout Issue\"", - "fieldname": "longitude", - "fieldtype": "Float", - "label": "Longitude" - }, { "fieldname": "section_break_17", "fieldtype": "Section Break" @@ -201,10 +176,6 @@ "options": "\nIN\nOUT", "reqd": 1 }, - { - "fieldname": "column_break_20", - "fieldtype": "Column Break" - }, { "fetch_from": "assigned_shift.roster_type", "fetch_if_empty": 1, diff --git a/one_fm/operations/doctype/shift_permission/shift_permission.py b/one_fm/operations/doctype/shift_permission/shift_permission.py index a0f729b6d7..99c14d88b4 100755 --- a/one_fm/operations/doctype/shift_permission/shift_permission.py +++ b/one_fm/operations/doctype/shift_permission/shift_permission.py @@ -2,13 +2,14 @@ # Copyright (c) 2020, omar jaber and contributors # For license information, please see license.txt from __future__ import unicode_literals +from datetime import datetime import frappe from frappe.model.document import Document -from frappe.utils import getdate, get_datetime, add_to_date, format_date, cstr, now +from frappe.utils import getdate, format_date +from frappe.workflow.doctype.workflow_action.workflow_action import apply_workflow from frappe import _ +from frappe.desk.form.assign_to import add, remove from one_fm.api.notification import create_notification_log, get_employee_user_id -from hrms.hr.doctype.shift_assignment.shift_assignment import get_shift_details -from one_fm.processor import sendemail from one_fm.utils import has_super_user_role, get_approver class PermissionTypeandLogTypeError(frappe.ValidationError): @@ -25,12 +26,11 @@ class ShiftDetailsMissing(frappe.ValidationError): class ShiftPermission(Document): def validate(self): - self.validate_permission_type() self.check_shift_details_value() self.validate_date() self.validate_record() self.validate_approver() - if self.workflow_state in ['Pending', 'Approved']: + if self.workflow_state in ['Pending Approver', 'Approved']: self.validate_attendance() if not self.title: self.title = self.emp_name @@ -39,25 +39,10 @@ def validate_attendance(self): attendance = frappe.db.exists('Attendance',{'attendance_date': self.date, 'employee': self.employee, 'docstatus': 1}) if attendance: frappe.throw(_('There is an Attendance {0} exists for the Employee {1} on {2}'.format(attendance, self.emp_name, format_date(self.date))), exc=ExistAttendance) - - def validate_permission_type(self): - if self.log_type == 'IN' and self.permission_type not in ['Arrive Late', 'Forget to Checkin', 'Checkin Issue']: - frappe.throw(_('Permission Type cannot be {0}. It should be one of "Arrive Late", "Forget to Checkin", "Checkin Issue" for Log Type "IN"'.format(self.permission_type)), - exc = PermissionTypeandLogTypeError) - if self.log_type == 'OUT' and self.permission_type not in ['Leave Early', 'Forget to Checkout', 'Checkout Issue']: - frappe.throw(_('Permission Type cannot be {0}. It should be one of "Leave Early", "Forget to Checkout", "Checkout Issue" for Log Type "OUT"'.format(self.permission_type)), - exc = PermissionTypeandLogTypeError) - if self.permission_type == "Arrive Late": - field_list = [{'Arrival Time':'arrival_time'}] - self.leaving_time = '' - self.set_mandatory_fields(field_list) - if self.permission_type == "Leave Early": - field_list = [{'Leaving Time':'leaving_time'}] - self.arrival_time = '' - self.set_mandatory_fields(field_list) - if self.permission_type not in ['Arrive Late', 'Leave Early']: - self.arrival_time = '' - self.leaving_time = '' + + def on_update(self): + self.update_shift_assignment_checkin() + self.assign_to_owner() # This method validates the shift details availability for employee def check_shift_details_value(self): @@ -66,17 +51,17 @@ def check_shift_details_value(self): # This method validates the permission date and avoid creating permission for previous days def validate_date(self): - if self.docstatus==0 and getdate(self.date) < getdate() and self.is_new(): - frappe.throw(_("Oops! You cannot apply for permission for a previous date.")) + if getdate(self.date) < getdate(): + frappe.throw(_("Please note that shift permission can not be created for past date")) if self.is_new() else frappe.throw("Please note that shift permission can not be updated to a past date") # This method validates any dublicate permission for the employee on same day def validate_record(self): date = getdate(self.date).strftime('%d-%m-%Y') if self.docstatus==0 and frappe.db.exists("Shift Permission", { "employee": self.employee, "date":self.date, "assigned_shift": self.assigned_shift, - "permission_type": self.permission_type, "workflow_state":"Pending", 'name':['!=', self.name] + "workflow_state":"Pending Approver", 'name':['!=', self.name] }): - frappe.throw(_("{employee} has already applied for permission to {type} on {date}.".format(employee=self.emp_name, type=self.permission_type.lower(), date=date))) + frappe.throw(_("{employee} has already applied for permission on {date}.".format(employee=self.emp_name,date=date))) # This method will display the mandatory fields for the user def set_mandatory_fields(self,field_list): @@ -101,8 +86,8 @@ def after_insert(self): def send_notification(self): date = getdate(self.date).strftime('%d-%m-%Y') user = get_employee_user_id(self.shift_supervisor) - subject = _("{employee} has applied for permission to {type} on {date}.".format(employee=self.emp_name, type=self.permission_type.lower(), date=date)) - message = _("{employee} has applied for permission to {type} on {date}.".format(employee=self.emp_name, type=self.permission_type.lower(), date=date)) + subject = _("{employee} has applied for permission on {date}.".format(employee=self.emp_name, date=date)) + message = _("{employee} has applied for permission on {date}.".format(employee=self.emp_name, date=date)) create_notification_log(subject, message, [user], self) def validate_approver(self): @@ -114,45 +99,80 @@ def validate_approver(self): def on_submit(self): employee_user = frappe.get_value('Employee', self.employee, 'user_id') - subject = _('Your shift request {0} has been {1} by {2}'.format(self.name, self.workflow_state, self.approver_name)) + subject = _('Your shift permission {0} has been {1} by {2}'.format(self.name, self.workflow_state, self.approver_name)) if self.workflow_state == 'Approved': - create_employee_checkin_for_shift_permission(self) if employee_user: create_notification_log(subject, subject, [employee_user], self) if self.workflow_state == 'Rejected': message = False if self.log_type == 'IN': - message = _('Your shift request has been rejected, Please checkin once you arrive to the site before [half way mark] or your attendance will be marked absent!') + message = _('Your shift permission has been rejected, Please checkin once you arrive to the site before [half way mark] or your attendance will be marked absent!') if self.log_type == 'OUT': - message = _('Your shift request has been rejected, Please make sure to checkout!') + message = _('Your shift permission has been rejected, Please make sure to checkout!') if message and employee_user: create_notification_log(subject, message, [employee_user], self) def on_cancel(self): pass -def create_employee_checkin_for_shift_permission(shift_permission): - """ - Method to create Employee Checkin from the Shift Permission - args: - shift_permission: Object of Shift Permission - """ - try: - if frappe.db.get_single_value("HR and Payroll Additional Settings", 'validate_shift_permission_on_employee_checkin')\ - and not frappe.db.exists('Employee Checkin', {'shift_permission': shift_permission.name, 'docstatus': 1}): - if shift_permission.permission_type in ["Arrive Late", "Forget to Checkin", "Checkin Issue"] and not shift_permission.log_type: - shift_permission.db_set('log_type', "IN") - elif shift_permission.permission_type in ["Leave Early", "Forget to Checkout", "Checkout Issue"] and not shift_permission.log_type: - shift_permission.db_set('log_type', "OUT") - shift_permission.reload() - if not shift_permission.log_type: - return False - # Get shift details for the employee - create_checkin(shift_permission) - except Exception as e: - frappe.log_error(frappe.get_traceback(), "Shift Permission") + def update_shift_assignment_checkin(self) -> None: + if self.workflow_state == "Approved" and self.get_doc_before_save().workflow_state != "Approved": + if self.assigned_shift: + if self.log_type == "IN": + if self.arrival_time: + date_time = datetime.strptime(self.date + " " + self.arrival_time, '%Y-%m-%d %H:%M:%S') + frappe.db.sql(""" + UPDATE `tabShift Assignment` + SET start_datetime = %s + WHERE name = %s + """, (date_time, self.assigned_shift)) + + frappe.db.sql(""" + UPDATE `tabEmployee Checkin` + SET shift_actual_start = %s, late_entry = 0 + WHERE shift_assignment = %s + AND log_type = %s + """, (date_time, self.assigned_shift, self.log_type)) + + else: + if self.leaving_time: + date_time = datetime.strptime(self.date + " " + self.leaving_time, '%Y-%m-%d %H:%M:%S') + frappe.db.sql(""" + UPDATE `tabShift Assignment` + SET end_datetime = %s + WHERE name = %s + """, (date_time, self.assigned_shift)) + + frappe.db.sql(""" + UPDATE `tabEmployee Checkin` + SET shift_actual_end = %s, early_exit = 0 + WHERE shift_assignment = %s + AND log_type = %s + """, (date_time, self.assigned_shift, self.log_type)) + + frappe.db.commit() + + def assign_to_owner(self): + # Assign back to owner if Shift permission is Returned to Draft state from Pending Approver + if not self.get("__unsaved"): + if self.workflow_state == "Draft" and self.get_doc_before_save().workflow_state == "Pending Approver": + # Remove approver's assignment + remove(self.doctype, self.name, frappe.session.user, ignore_permissions=False) + + # Assign back to document owner + add({ + 'doctype': self.doctype, + 'name': self.name, + 'assign_to': [self.owner], + 'description': (_(f"Shift Permission: {self.name} has been returned to Draft. Please check and review.")) + }) + + if self.workflow_state == "Pending Approver" and self.get_doc_before_save().workflow_state == "Draft": + # Remove doc owner's assignment + remove(self.doctype, self.name, self.owner, ignore_permissions=False) + @frappe.whitelist() def fetch_approver(employee, date=None): if employee: @@ -167,7 +187,7 @@ def fetch_approver(employee, date=None): limit_page_length=1 ) if employee_shift and len(employee_shift) > 0: - approver = get_approver(employee, date) + approver = get_approver(employee) return { 'shift_assignment':employee_shift[0].name, 'approver':approver, @@ -184,8 +204,9 @@ def approve_open_shift_permission(start_date, end_date): shift_permissions = frappe.db.sql(f""" SELECT sp.name FROM `tabShift Permission` sp JOIN `tabShift Assignment` sa ON sa.name=sp.assigned_shift - WHERE sa.start_date='{start_date}' and sa.end_date='{end_date}' - AND sp.workflow_state='Pending' AND sp.docstatus=0 + WHERE sa.start_date ='{start_date}' and sa.end_date <='{end_date}' + AND sa.is_replaced = 0 + AND sp.workflow_state='Pending Approver' AND sp.docstatus=0 """, as_dict=1) # apply workflow error_list = """""" @@ -193,6 +214,7 @@ def approve_open_shift_permission(start_date, end_date): try: shift_permission = frappe.get_doc("Shift Permission", i.name) create_checkin(shift_permission) + apply_workflow(shift_permission, 'Approve') except Exception as e: error_list += str(e)+'\n\n' if error_list:frappe.log_error(error_list, 'Shift Permission') @@ -231,6 +253,7 @@ def create_checkin(shift_permission): }): if not shift_permission.workflow_state == 'Approved': shift_permission.db_set('workflow_state', "Approved") + shift_permission.db_set("docstatus", 1) shift_permission.reload() frappe.db.commit() # Get shift details for the employee shift_assignment = frappe.get_doc("Shift Assignment", shift_permission.assigned_shift) diff --git a/one_fm/overrides/assignment_rule.py b/one_fm/overrides/assignment_rule.py index 818afdac2a..367c188a55 100644 --- a/one_fm/overrides/assignment_rule.py +++ b/one_fm/overrides/assignment_rule.py @@ -1,10 +1,12 @@ from one_fm.utils import get_doctype_mandatory_fields from frappe.workflow.doctype.workflow_action.workflow_action import ( - get_next_possible_transitions, + get_workflow_name, get_workflow_action_url, get_doc_workflow_state ) + +from one_fm.overrides.workflow import get_next_possible_transitions from frappe.model.workflow import ( apply_workflow, get_workflow_state_field diff --git a/one_fm/overrides/attendance.py b/one_fm/overrides/attendance.py index e6d46e305e..ebed53e874 100644 --- a/one_fm/overrides/attendance.py +++ b/one_fm/overrides/attendance.py @@ -8,9 +8,8 @@ from datetime import timedelta, datetime as p_datetime from hrms.hr.doctype.attendance.attendance import * from hrms.hr.utils import validate_active_employee, get_holidays_for_employee -from one_fm.utils import get_holiday_today +from one_fm.utils import get_holiday_today, is_holiday from one_fm.operations.doctype.shift_permission.shift_permission import create_checkin as approve_shift_permission -from one_fm.operations.doctype.employee_checkin_issue.employee_checkin_issue import approve_open_employee_checkin_issue from frappe.model import table_fields from frappe.workflow.doctype.workflow_action.workflow_action import apply_workflow @@ -197,7 +196,7 @@ def update_shift_details_in_attendance(doc): def mark_single_attendance(emp, att_date, roster_type="Basic"): # check if attendance exists # get holiday, employee schedule, shift assignment, employee checkins - + if not frappe.db.exists("Attendance", { 'employee': emp, 'attendance_date':att_date, @@ -251,6 +250,7 @@ def mark_single_attendance(emp, att_date, roster_type="Basic"): else: mark_for_shift_assignment(employee.name, att_date) + def mark_for_shift_assignment(employee, att_date, roster_type='Basic'): shift_assignment = frappe.db.get_value("Shift Assignment", { 'employee':employee, @@ -287,12 +287,18 @@ def mark_for_shift_assignment(employee, att_date, roster_type='Basic'): fields="name, owner, creation, modified, modified_by, docstatus, idx, employee, employee_name, log_type, late_entry, early_exit, time, date, skip_auto_attendance, shift_actual_start, shift_actual_end, shift_assignment, operations_shift, shift_type, shift_permission, actual_time, MAX(time) as time", order_by="time DESC", group_by="shift_assignment" ) + out_checkins = _out_checkins[0] if _out_checkins else frappe._dict({}) + + early_exit = 0 # check if checkin and out exists if (out_checkins and in_checkins): if (out_checkins.time < in_checkins.time): out_checkins = False # The employee checked in, out, in but not out - + + early_exit = not frappe.db.exists("Employee Checkin", {"log_type": "IN", "time": [">", out_checkins.time], "shift_assignment": shift_assignment.name}) if out_checkins.early_exit else 0 + + # start checkin if in_checkins: if ((in_checkins.time - shift_assignment.start_datetime).total_seconds() / (60*60)) > 4: @@ -309,7 +315,7 @@ def mark_for_shift_assignment(employee, att_date, roster_type='Basic'): comment = "" checkin = in_checkins checkout = out_checkins - + create_single_attendance_record(frappe._dict({ 'status': status, 'comment': comment, @@ -319,12 +325,16 @@ def mark_for_shift_assignment(employee, att_date, roster_type='Basic'): 'checkout': checkout, 'shift_assignment':shift_assignment, 'employee':frappe.db.get_value("Employee", employee, ["name", "employee_name", "holiday_list"], as_dict=1), - 'roster_type': roster_type + 'roster_type': roster_type, + "early_exit": early_exit }) ) # working_hours = (out_time - in_time).total_seconds() / (60 * 60) + + + def create_single_attendance_record(record): if not frappe.db.exists("Attendance", { 'employee':record.employee.name, @@ -355,8 +365,6 @@ def create_single_attendance_record(record): doc.site = record.shift_assignment.site if record.checkin: if record.checkin.late_entry:doc.late_entry=1 - if record.checkout: - if record.checkout.early_exit:doc.early_exit=1 # check if worked less work_duration = 0 if record.checkin and record.checkout and record.shift_assignment: @@ -421,14 +429,14 @@ def remark_for_active_employees(from_date=None): if i.shift_assignment: shift_assignment = frappe.get_doc("Shift Assignment", i.shift_assignment) checkins = frappe.get_list( - "Employee Checkin", - {"shift_assignment":i.shift_assignment}, + "Employee Checkin", + {"shift_assignment":i.shift_assignment}, "*", order_by="time ASC") if checkins: ins = [d for d in checkins if d.log_type=="IN"]#.sort(key = lambda x:x.time) outs = [d for d in checkins if d.log_type=="OUT"]#.sort(key = lambda x:x.time) - + if ins:ins = ins[0] if outs: outs = outs[-1] @@ -463,7 +471,7 @@ def remark_for_active_employees(from_date=None): 'roster_type': shift_assignment.roster_type }) ) - + def remark_absent_for_employees(employees, date): @@ -485,28 +493,29 @@ def mark_overtime_attendance(from_date, to_date): mark_for_shift_assignment(employee.name, from_date, roster_type='Over-Time') + + def mark_all_attendance(): - from one_fm.operations.doctype.shift_permission.shift_permission import approve_open_shift_permission - start_date = add_days(getdate(), -1) - end_date = getdate() - approve_open_shift_permission(str(start_date), str(end_date)) - approve_open_employee_checkin_issue(str(start_date), str(end_date)) - frappe.enqueue(mark_open_timesheet_and_create_attendance) - frappe.enqueue(mark_leave_attendance) - frappe.enqueue(mark_daily_attendance, start_date=start_date, end_date=end_date, timeout=4000, queue='long') + from one_fm.operations.doctype.shift_permission.shift_permission import approve_open_shift_permission + start_date = add_days(getdate(), -1) + end_date = getdate() + frappe.enqueue(approve_open_shift_permission, start_date=str(start_date), end_date=str(end_date)) + frappe.enqueue(approve_pending_employee_checkin_issue) + frappe.enqueue(mark_open_timesheet_and_create_attendance) + frappe.enqueue(mark_daily_attendance, start_date=start_date, end_date=end_date, timeout=4000, queue='long') def mark_daily_attendance(start_date, end_date): try: creation = now() owner = frappe.session.user naming_series = 'HR-ATT-.YYYY.-' - existing_attendance = [i.employee for i in frappe.get_list("Attendance", { + existing_attendance = frappe.get_list("Attendance", { 'attendance_date':start_date, - 'roster_type':'Basic', 'status':['IN', ['Present', 'Holiday', 'On Leave', - 'Work From Home', 'On Hold', 'Day Off']] + 'roster_type':'Basic', + 'status':['IN', ['Present', 'Holiday', 'On Leave','Work From Home', 'On Hold', 'Day Off']] }, - "employee" - )] + pluck="employee" + ) query = """ INSERT INTO `tabAttendance` (`name`, `naming_series`,`employee`, `employee_name`, `working_hours`, `status`, `shift`, `in_time`, `out_time`, @@ -521,24 +530,24 @@ def mark_daily_attendance(start_date, end_date): # Mark Holiday Attendance holiday_attendance_employee = frappe.db.sql(f""" SELECT e.name, e.employee_name, e.company, e.department, - h.description from `tabEmployee` e ,`tabHoliday List` hl + h.description from `tabEmployee` e ,`tabHoliday List` hl INNER JOIN `tabHoliday` h ON h.parent = hl.name - WHERE e.holiday_list = hl.name + WHERE e.holiday_list = hl.name AND h.holiday_date = '{start_date}' AND h.weekly_off=0 AND '{start_date}' BETWEEN hl.from_date AND hl.to_date AND e.status='Active' """, as_dict=1) - - + + if holiday_attendance_employee: for i in holiday_attendance_employee: if not i.name in existing_attendance: try: frappe.db.sql(f""" DELETE FROM `tabAttendance` WHERE employee="{i.name}" AND - attendance_date="{start_date}" + attendance_date="{start_date}" AND roster_type="Basic" AND status="Absent" """) @@ -552,15 +561,16 @@ def mark_daily_attendance(start_date, end_date): "{i.department}", 0, 0, "", "", "Basic", {1}, "{owner}", "{owner}", "{creation}", "{creation}", "{i.description}" ),""" + today = getdate() # Mark DayOff Attendance #Find Employees with no schedule but have Day Off in the company holiday. Mainly for head office employees day_off_no_schedule = frappe.db.sql(f""" SELECT e.name, e.employee_name, e.company, e.department, - h.description from `tabEmployee` e ,`tabHoliday List` hl + h.description from `tabEmployee` e ,`tabHoliday List` hl INNER JOIN `tabHoliday` h ON h.parent = hl.name - WHERE e.holiday_list = hl.name + WHERE e.holiday_list = hl.name AND h.holiday_date = '{start_date}' AND h.weekly_off=1 AND e.attendance_by_timesheet = 0 @@ -570,16 +580,16 @@ def mark_daily_attendance(start_date, end_date): AND e.date_of_joining < '{today}' """, as_dict=1) - - + + day_off_employee = frappe.db.sql(f""" - SELECT e.name, e.employee_name, e.company, e.department, es.name as es_name from `tabEmployee` e + SELECT e.name, e.employee_name, e.company, e.department, es.name as es_name from `tabEmployee` e INNER JOIN `tabEmployee Schedule` es ON es.employee =e.name - WHERE date='{start_date}' + WHERE date='{start_date}' AND es.employee_availability='Day Off' AND e.attendance_by_timesheet = 0 AND e.status='Active' - + """, as_dict=1 ) @@ -591,7 +601,7 @@ def mark_daily_attendance(start_date, end_date): try: frappe.db.sql(f""" DELETE FROM `tabAttendance` WHERE employee="{i.name}" AND - attendance_date="{start_date}" + attendance_date="{start_date}" AND roster_type="Basic" AND status="Absent" """) @@ -607,7 +617,7 @@ def mark_daily_attendance(start_date, end_date): ),""" attendance_request = frappe.db.sql(f""" - SELECT e.name, e.employee_name, e.company, e.department, ar.name as ar_name from `tabEmployee` e + SELECT e.name, e.employee_name, e.company, e.department, ar.name as ar_name, ar.reason as ar_reason from `tabEmployee` e INNER JOIN `tabAttendance Request` ar ON ar.employee =e.name WHERE '{start_date}' BETWEEN ar.from_date AND ar.to_date AND ar.workflow_state='Approved' @@ -617,13 +627,14 @@ def mark_daily_attendance(start_date, end_date): # create BASIC DAY OFF for i in attendance_request: + status = "Work From Home" if i.ar_reason == "Work From Home" else "Present" if not i.get('ar_name'): i.ar_name = i.description if not i.name in existing_attendance: try: frappe.db.sql(f""" DELETE FROM `tabAttendance` WHERE employee="{i.name}" AND - attendance_date="{start_date}" + attendance_date="{start_date}" AND roster_type="Basic" AND status="Absent" """) @@ -632,12 +643,18 @@ def mark_daily_attendance(start_date, end_date): continue query_body+= f""" ( - "HR-ATT_{start_date}_{i.name}_Basic", "{naming_series}" , "{i.name}", "{i.employee_name}", 0, "Work From Home", '', NULL, + "HR-ATT_{start_date}_{i.name}_Basic", "{naming_series}" , "{i.name}", "{i.employee_name}", 0, "{status}", '', NULL, NULL, "", "", "", "", "{start_date}", "{i.company}", "{i.department}", 0, 0, "", "", "Basic", 1, "{owner}", "{owner}", "{creation}", "{creation}", "Attendance Request - {i.ar_name}" ),""" + else: + att_name = frappe.db.get_value("Attendance", {"employee": i.name, "attendance_date": start_date}, "name") + if att_name: + frappe.db.set_value("Attendance", att_name, "status", status) + + # UPDATE QUERY if query_body: query += query_body[:-1] @@ -673,7 +690,7 @@ def mark_daily_attendance(start_date, end_date): end_date = add_days(start_date, 1) attendance_marking = AttendanceMarking() attendance_marking.get_datetime( - start=p_datetime.strptime(f'{start_date} 00:00:00', '%Y-%m-%d %H:%M:%S'), + start=p_datetime.strptime(f'{start_date} 00:00:00', '%Y-%m-%d %H:%M:%S'), end=p_datetime.strptime(f'{end_date} 00:00:00', '%Y-%m-%d %H:%M:%S'), attendance_type=True, ) @@ -688,7 +705,7 @@ def mark_daily_attendance(start_date, end_date): timeout=7000 ) except Exception as e: - frappe.log_error(frappe.get_traceback(), "Attendance Marking") + frappe.log_error(frappe.get_traceback(), "Attendance Marking") def remark_attendance(start_date, end_date): try: @@ -701,7 +718,7 @@ def remark_attendance(start_date, end_date): 'docstatus':1}, fields='name, employee' ) - missing_attendances = [i.employee for i in shift_assignments if not i.employee in [x.employee for x in attendances]] + missing_attendances = [i.employee for i in shift_assignments if not i.employee in [x.employee for x in attendances]] for employee in missing_attendances: mark_bulk_attendance(employee, start_date, start_date) except: @@ -726,23 +743,55 @@ def update_day_off_ot(attendances): if day_off_ot: att.db_set("day_off_ot", day_off_ot) except: - frappe.log_error(frappe.get_traceback(), "Attendance Marking OT") + frappe.log_error(frappe.get_traceback(), "Attendance Marking OT") def mark_open_timesheet_and_create_attendance(): - timesheets = frappe.get_list("Timesheet", {'workflow_state':'Open', - 'start_date':add_days(getdate(), -1)}) + the_date = add_days(getdate(), -1) + employee_list = frappe.db.get_list("Employee", filters={"status": "Active", "attendance_by_timesheet": 1}, pluck="name") + timesheets = frappe.db.get_list("Timesheet", {'workflow_state':'Pending Approval', + 'start_date': the_date},) + for i in timesheets: try: apply_workflow(frappe.get_doc("Timesheet", i.name), 'Approve') except Exception as e: - pass - + frappe.log_error(frappe.get_traceback(), "Timesheet Marking") + + present_employees = frappe.db.get_list("Timesheet", filters={"start_date": the_date, "workflow_state": "Approved"}, pluck="employee") + for obj in employee_list: + status, message = is_holiday(employee=obj) + if obj not in present_employees: + att = frappe.new_doc("Attendance") + att.employee = obj + att.attendance_date = the_date + att.status = "Absent" if not status else "Day Off" + att.working_hours = 0 + att.reference_doctype = "Timesheet" + try: + att.insert(ignore_permissions=True) + att.submit() + except Exception as e: + frappe.log_error(frappe.get_traceback(), "TImesheet Attendance Marking") + frappe.db.commit() + + +def approve_pending_employee_checkin_issue(): + pending_eci = frappe.db.get_list("Employee Checkin Issue", {"workflow_state": "Pending Approval", "date": add_days(getdate(), -1)}, pluck="name") + if pending_eci: + for obj in pending_eci: + try: + apply_workflow(frappe.get_doc("Employee Checkin Issue", obj), "Approve") + except Exception as e: + frappe.log_error(frappe.get_traceback(), "Error while approving employee checkin issue") + frappe.db.commit() + + def mark_leave_attendance(): try: date = add_days(getdate(), -1) creation = now() - + owner = frappe.session.user naming_series = 'HR-ATT-.YYYY.-' e_list = [] @@ -772,12 +821,20 @@ def mark_leave_attendance(): AND l.status = 'Approved' """, as_dict=1) on_leave_employees = [i for i in on_leave_employees if not i.employee in basic_attendance_employees] - - # create On Hold Attendance + + # create On Hold Attendance if on_leave_employees: for i in on_leave_employees: - basic_attendance_employees.append(i.employee) - + name = f"HR-ATT_{date}_{i.employee}_Basic" + emp = employees_dict.get(i.employee) + query_body+= f""" + ( + "{name}", "{naming_series}","{i.employee}", "{i.employee_name}", "On Leave", '{i.leave_type}', '{i.name}', + "{date}", "{i.company}", "{i.department}","Basic", {1}, "{owner}", + "{owner}", "{creation}", "{creation}", "{i.leave_type}" + ),""" + basic_attendance_employees.append(i.employee) + if query_body: query += query_body[:-1] query += f""" @@ -806,18 +863,21 @@ def mark_timesheet_daily_attendance(timesheet_employees,start_date): Mark all the employees included in the daily attendance schedule """ try: - + query = """ - INSERT INTO `tabAttendance` (`name`, `naming_series`,`employee`, `employee_name`, `working_hours`, `status`, `shift`, `in_time`, `out_time`, - `shift_assignment`, `operations_shift`, `site`, `project`, `attendance_date`, `company`, - `department`, `late_entry`, `early_exit`, `operations_role`, `post_abbrv`, `roster_type`, `docstatus`, `modified_by`, `owner`, - `creation`, `modified`, `comment`) + INSERT INTO + `tabAttendance` + ( + `name`, `naming_series`,`employee`, `employee_name`, `employment_type`, `working_hours`, `status`, + `shift`, `in_time`, `out_time`, `shift_assignment`, `operations_shift`, `site`, `project`, + `attendance_date`, `company`, `department`, `late_entry`, `early_exit`, `operations_role`, + `post_abbrv`, `roster_type`, `docstatus`, `modified_by`, `owner`, `creation`, `modified`, `comment` + ) VALUES - """ employees = frappe.get_all("Employee",filters={"name":["IN",timesheet_employees]},fields="*") employees_dict = frappe._dict() - + for i in employees: employees_dict[i.name] = i owner = frappe.session.user @@ -829,7 +889,7 @@ def mark_timesheet_daily_attendance(timesheet_employees,start_date): name = f"HR-ATT_{start_date}_{i}_Basic" query_body+= f""" ( - "{name}", "{naming_series}", "{i}", "{emp.employee_name}", 0, "Absent", '{emp.shift}', NULL, + "{name}", "{naming_series}", "{i}", "{emp.employee_name}", "{emp.employment_type}", 0, "Absent", '{emp.shift}', NULL, NULL, "NULL", "NULL", "{emp.site}", "{emp.project}", "{start_date}", "{emp.company}", "{emp.department}", 0, 0, "NULL", "NULL", "Basic", {1}, "{owner}", "{owner}", "{creation}", "{creation}", "No Timesheet record found" @@ -841,6 +901,7 @@ def mark_timesheet_daily_attendance(timesheet_employees,start_date): naming_series = VALUES(naming_series), employee = VALUES(employee), employee_name = VALUES(employee_name), + employment_type = VALUES(employment_type), working_hours = VALUES(working_hours), status = VALUES(status), shift = VALUES(shift), @@ -872,7 +933,7 @@ class AttendanceMarking(): This class will be used to mark attendance """ - def __ini__(self): + def __init__(self): self.attendance_type = None self.start = None self.end = None @@ -893,9 +954,9 @@ def mark_shift_attendance(self): if self.attendance_type: client_shifts = frappe.db.sql(f""" SELECT sa.* FROM `tabShift Assignment` sa - JOIN `tabOperations Role` op ON sa.operations_role=op.name - WHERE - sa.start_date='{self.start.date()}' + JOIN `tabOperations Role` op ON sa.operations_role=op.name + WHERE sa.is_replaced = 0 + AND sa.start_date='{self.start.date()}' AND op.attendance_by_client=1 AND op.docstatus=1 ; """, as_dict=1) @@ -905,24 +966,24 @@ def mark_shift_attendance(self): shifts = frappe.db.sql(f""" SELECT sa.* FROM `tabShift Assignment` sa - JOIN `tabOperations Role` op ON sa.operations_role=op.name - WHERE - sa.start_date='{self.start.date()}' + JOIN `tabOperations Role` op ON sa.operations_role=op.name + WHERE sa.is_replaced = 0 + AND sa.start_date='{self.start.date()}' AND op.attendance_by_client=0 AND op.status='Active' """, as_dict=1) non_shifts = frappe.db.sql(f""" SELECT sa.* FROM `tabShift Assignment` sa - JOIN `tabEmployee` e ON sa.employee=e.name - WHERE - sa.end_datetime BETWEEN '{self.start}' AND '{self.end}' + JOIN `tabEmployee` e ON sa.employee=e.name + WHERE sa.is_replaced = 0 + AND sa.end_datetime BETWEEN '{self.start}' AND '{self.end}' AND e.shift_working=0""", as_dict=1) shifts.extend(non_shifts) else: client_shifts = frappe.db.sql(f""" SELECT sa.* FROM `tabShift Assignment` sa - JOIN `tabOperations Role` op ON sa.operations_role=op.name - WHERE - sa.end_datetime BETWEEN '{self.start}' AND '{self.end}' + JOIN `tabOperations Role` op ON sa.operations_role=op.name + WHERE sa.is_replaced = 0 + AND sa.end_datetime BETWEEN '{self.start}' AND '{self.end}' AND op.attendance_by_client=1 AND op.status='Active' ; """, as_dict=1) @@ -932,33 +993,44 @@ def mark_shift_attendance(self): shifts = frappe.db.sql(f""" SELECT sa.* FROM `tabShift Assignment` sa - JOIN `tabOperations Role` op ON sa.operations_role=op.name - WHERE - sa.end_datetime BETWEEN '{self.start}' AND '{self.end}' + JOIN `tabOperations Role` op ON sa.operations_role=op.name + WHERE sa.is_replaced = 0 + AND sa.end_datetime BETWEEN '{self.start}' AND '{self.end}' AND op.attendance_by_client=0 AND op.status='Active' """, as_dict=1) non_shifts = frappe.db.sql(f""" SELECT sa.* FROM `tabShift Assignment` sa - JOIN `tabEmployee` e ON sa.employee=e.name - WHERE - sa.end_datetime BETWEEN '{self.start}' AND '{self.end}' + JOIN `tabEmployee` e ON sa.employee=e.name + WHERE sa.is_replaced = 0 + AND sa.end_datetime BETWEEN '{self.start}' AND '{self.end}' AND e.shift_working=0""", as_dict=1) shifts.extend(non_shifts) + + + if shifts: - checkins = self.get_checkins(tuple([i.name for i in shifts]) if len(shifts)>1 else (shifts[0].name)) + existing_attendance = frappe.get_list("Attendance", { + 'attendance_date':getdate(self.start), + 'shift_assignment':['in',[i.name for i in shifts]], + 'status':['IN', ['Present', 'Holiday', 'On Leave','Work From Home', 'On Hold', 'Day Off']] + }, + pluck="employee" + ) + checkins = self.get_checkins(tuple([i.name for i in shifts]) if len(shifts) > 1 else (shifts[0].name,"")) if checkins: # employees = [i.employee for i in shifts] checked_in_employees = [i.employee for i in checkins] no_checkins = [i for i in shifts if not i.employee in checked_in_employees] - if no_checkins: #create absent + if no_checkins: #create absent if no attendance already exists for i in no_checkins: - try: - record = frappe._dict({**dict(i), **{ - "status":"Absent", "comment":"No checkin record found", "working_hours":0, - "dt":"Shift Assignment"}}) - self.create_attendance(record) - except: - pass + if i.employee not in existing_attendance: + try: + record = frappe._dict({**dict(i), **{ + "status":"Absent", "comment":"No checkin record found", "working_hours":0, + "dt":"Shift Assignment"}}) + self.create_attendance(record) + except: + frappe.log_error(message=frappe.get_traceback(), title=f"Error Marking Attendance for {i.employee}") if checkins: # check for work hours # start checkin for i in checkins: @@ -967,11 +1039,12 @@ def mark_shift_attendance(self): 'employee':i.employee, 'attendance_date':i.shift_actual_start.date(), 'roster_type':i.roster_type, - 'status': ["IN", ["Present", "On Leave", "Holiday", "Day Off", + 'status': ["IN", ["Present", "On Leave", "Holiday", "Day Off", "On Hold", "Work From Home"]] }): total_hours = (i.shift_actual_end - i.shift_actual_start).total_seconds() / (60*60) - half_hour = total_hours/2 + i.early_exit = self.check_early_exit(i) + half_hour = total_hours / 2 working_hours = 0 status = "Absent" comment = "" @@ -1003,53 +1076,93 @@ def mark_shift_attendance(self): except Exception as e: pass + + def check_early_exit(self, checkin: dict) -> bool: + """ + Validates the presence of a checkin record created after the last checkout record if + the last checkout record was set as a early exit + + """ + if checkin: + in_name = checkin.get("in_name") + out_name = checkin.get("out_name") + if in_name and out_name: + early_exit_doc = frappe.db.get_value( + "Employee Checkin", + {"name": out_name, "early_exit": 1, "log_type": "OUT"}, + ["time"], + as_dict=1 + ) + + if early_exit_doc: + check = frappe.db.sql( + """ + SELECT name FROM `tabEmployee Checkin` + WHERE shift_assignment = %s + AND log_type = %s + AND time > %s + """, + (checkin.shift_assignment,"IN",early_exit_doc.time), + as_dict=1 + ) + return not bool(check) + return False + + + + + + def get_checkins(self, shift_assignments): query = f""" - SELECT - ec.name, - ec.owner, - ec.creation, - ec.modified, - ec.modified_by, - ec.docstatus, - ec.idx, - ec.employee, - ec.employee_name, - ec.log_type, + SELECT + ec.name, + ec.owner, + ec.creation, + ec.modified, + ec.modified_by, + ec.docstatus, + ec.idx, + ec.employee, + ec.employee_name, + ec.log_type, ec.late_entry, ec.early_exit, - ec.time, - ec.date, - ec.skip_auto_attendance, - ec.shift_actual_start, - ec.shift_actual_end, - ec.shift_assignment, - ec.operations_shift, + ec.time, + ec.date, + ec.skip_auto_attendance, + ec.shift_actual_start, + ec.shift_actual_end, + ec.shift_assignment, + ec.operations_shift, ec.shift_type, - ec.roster_type, - ec.operations_site, - ec.project, - ec.company, - ec.operations_role, + ec.roster_type, + ec.operations_site, + ec.project, + ec.company, + ec.operations_role, ec.post_abbrv, - ec.shift_permission, - ec.actual_time, + ec.shift_permission, + ec.actual_time, + ec.is_replaced, MIN(CASE WHEN ec.log_type = 'IN' THEN ec.time END) AS earliest_time, MAX(CASE WHEN ec.log_type = 'OUT' THEN ec.time END) AS latest_time, - MIN(CASE WHEN ec.log_type = 'IN' THEN ec.name END) AS in_name, + MIN(CASE WHEN ec.log_type = 'IN' THEN ec.name END) AS in_name, MAX(CASE WHEN ec.log_type = 'OUT' THEN ec.name END) AS out_name - FROM + FROM `tabEmployee Checkin` ec - WHERE + WHERE ec.shift_assignment in {shift_assignments} - GROUP BY + AND ec.is_replaced = 0 + + GROUP BY ec.shift_assignment; """ return frappe.db.sql(query, as_dict=1) def mark_day_off(self): days_off = frappe.get_list("Employee Schedule", { - 'date':self.start.date(), 'employee_availability':'Day Off' + 'date':self.start.date(), 'employee_availability':'Day Off', "is_replaced": 0 }, "*") for i in days_off: try: @@ -1057,18 +1170,18 @@ def mark_day_off(self): 'employee':i.employee, 'attendance_date':i.date, 'roster_type':i.roster_type, - 'status': ["IN", ["Present", "On Leave", "Holiday", "Day Off", + 'status': ["IN", ["Present", "On Leave", "Holiday", "Day Off", "On Hold", "Work From Home"]] }): record = frappe._dict({**dict(i), **{ "status":"Day Off", "comment":f"Employee Schedule - {i.name}", "dt":"Employee Schedule"}}) self.create_attendance(record) - + except Exception as e: pass - + def create_attendance(self, record, attendace_type=None): try: # clear absent @@ -1082,19 +1195,18 @@ def create_attendance(self, record, attendace_type=None): try: frappe.db.sql(f""" DELETE FROM `tabAttendance` WHERE employee="{record.employee}" AND - attendance_date="{_date}" + attendance_date="{_date}" AND roster_type="{record.roster_type}" """) except: pass frappe.db.commit() - doc = frappe._dict({}) doc.doctype = "Attendance" doc.employee = record.employee doc.status = record.status - + doc.attendance_date = _date if record.shift_assignment: doc.shift_assignment = record.shift_assignment @@ -1106,6 +1218,11 @@ def create_attendance(self, record, attendace_type=None): doc.shift = record.shift_type doc.operations_shift = record.shift doc.site = record.site + doc.custom_employment_type = record.custom_employment_type + if record.dt=="Employee Schedule": + doc.custom_employment_type = record.employment_type + if record.dt=="Employee Checkin": + doc.custom_employment_type = frappe.db.get_value("Employee", doc.employee, "employment_type") if record.late_entry: doc.late_entry= record.late_entry if record.early_exit: @@ -1130,7 +1247,7 @@ def create_attendance(self, record, attendace_type=None): frappe.db.set_value("Employee Checkin", record.out_name, 'attendance', doc.name) frappe.db.commit() except Exception as e: - frappe.log_error(message=str(e), title="Hourly Attendance Marking") + frappe.log_error(message=frappe.get_traceback(), title=f"Hourly Attendance Marking - {str(e)}") def run_attendance_marking_hourly(): @@ -1144,8 +1261,8 @@ def mark_day_off_for_yesterday(): start_date = add_days(getdate(), -1) attendance_marking = AttendanceMarking() attendance_marking.get_datetime( - start=p_datetime.strptime(f'{start_date} 00:00:00', '%Y-%m-%d %H:%M:%S'), + start=p_datetime.strptime(f'{start_date} 00:00:00', '%Y-%m-%d %H:%M:%S'), end=p_datetime.strptime(f'{getdate()} 00:00:00', '%Y-%m-%d %H:%M:%S'), attendance_type=True, ) - frappe.enqueue(attendance_marking.mark_day_off, queue="long", timeout=6000) \ No newline at end of file + frappe.enqueue(attendance_marking.mark_day_off, queue="long", timeout=6000) diff --git a/one_fm/overrides/attendance_request.py b/one_fm/overrides/attendance_request.py index 436adbb4cc..678789af91 100644 --- a/one_fm/overrides/attendance_request.py +++ b/one_fm/overrides/attendance_request.py @@ -1,20 +1,20 @@ import frappe, pandas as pd from frappe import _ -from frappe.model.document import Document -from frappe.utils import add_days, date_diff, getdate, nowdate, get_link_to_form, format_date +from frappe.utils import getdate, get_link_to_form, format_date from erpnext.setup.doctype.employee.employee import is_holiday -from hrms.hr.utils import validate_active_employee, validate_dates +from hrms.hr.utils import validate_active_employee from hrms.hr.doctype.attendance_request.attendance_request import AttendanceRequest -from frappe.model.workflow import apply_workflow + +from frappe.desk.form.assign_to import add, remove from one_fm.utils import ( - send_workflow_action_email, get_holiday_today, workflow_approve_reject, get_approver, has_super_user_role + send_workflow_action_email, workflow_approve_reject, get_approver, has_super_user_role ) class AttendanceRequestOverride(AttendanceRequest): def validate(self): validate_active_employee(self.employee) - validate_future_dates(self, self.from_date, self.to_date) + 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): frappe.throw(_("Half day date should be in between from date and to date")) @@ -24,7 +24,7 @@ def before_insert(self): check_for_attendance(self) def set_approver(self): - if not self.approver and self.employee: + if self.employee: self.approver = get_approver(self.employee) if self.approver: approver = frappe.db.get_value( @@ -33,7 +33,8 @@ def set_approver(self): ['user_id', 'employee_name'], as_dict=1 ) - pass + self.approver_name = approver.employee_name + self.approver_user = approver.user_id def on_submit(self): if not self.reports_to(): @@ -54,14 +55,13 @@ def cancel_requested_attendance(self): def on_update(self): self.send_notification() + self.assign_to_owner() def on_update_after_submit(self): self.send_notification() if self.update_request: if self.workflow_state == 'Approved': self.create_attendance() - if self.workflow_state == 'Update Request': - self.cancel_requested_attendance() def get_shift_assignment(self, attendance_date): """ @@ -73,7 +73,7 @@ def get_shift_assignment(self, attendance_date): def create_attendance(self): date_range = pd.date_range(self.from_date, self.to_date) for d in date_range: - if d.date()<= getdate(): + if d.date() <= getdate(): self.mark_attendance(str(d.date())) def get_employee(self): @@ -86,7 +86,7 @@ def mark_attendance(self, attendance_date): working_hours = frappe.db.get_value('Shift Type', shift_assignment.shift_type, 'duration') if shift_assignment else 8 # check if attendance exists attendance_name = super(AttendanceRequestOverride, self).get_attendance_record(attendance_date) - status = "Work From Home" if self.reason == "Work From Home" else "Present" + status = "Present" if self.reason == "On Duty" else "Work From Home" if attendance_name: # update existing attendance, change the status doc = frappe.get_doc("Attendance", attendance_name) @@ -157,8 +157,8 @@ def mark_attendance(self, attendance_date): def send_notification(self): if self.workflow_state in ['Pending Approval']: - send_workflow_action_email(self, [self.approver]) - if self.workflow_state in ['Rejected', 'Approved', 'Update Request', 'Cancelled']: + send_workflow_action_email(self, [self.approver_user]) + if self.workflow_state in ['Rejected', 'Approved', 'Cancelled']: workflow_approve_reject(self, recipients=None) def validate_if_attendance_not_applicable(self, attendance_date): @@ -189,56 +189,65 @@ def validate_if_attendance_not_applicable(self, attendance_date): @frappe.whitelist() def reports_to(self): employee_user = frappe.get_value("Employee", {"name": self.employee}, "user_id") - if frappe.session.user == self.approver or has_super_user_role(employee_user) or ( + if frappe.session.user == self.approver_user or has_super_user_role(employee_user) or ( frappe.session.user == "administrator" ): return True - frappe.msgprint('This Attendance Request can only be approved by the employee supervisor') return False + def assign_to_owner(self): + # Assign back to owner if Attendance Request is Returned to Draft state from Pending Approval + if not self.get("__unsaved"): + if self.workflow_state == "Draft" and self.get_doc_before_save().workflow_state == "Pending Approval": + # Remove approver's assignment + remove(self.doctype, self.name, frappe.session.user, ignore_permissions=False) + + # Assign back to document owner + add({ + 'doctype': self.doctype, + 'name': self.name, + 'assign_to': [self.owner], + 'description': (_(f"Attendance Request: {self.name} has been returned to Draft. Please check and review.")) + }) + if self.workflow_state == "Pending Approval" and self.get_doc_before_save().workflow_state == "Draft": + # Remove doc owner's assignment + remove(self.doctype, self.name, self.owner, ignore_permissions=False) + def check_for_attendance(doc): att = frappe.get_list("Attendance", {"employee": doc.employee, "attendance_date":["between", [doc.from_date, doc.to_date]]}, ['status']) if att: frappe.msgprint("Your attendance is marked for today as "+ att[0].status ) -def validate_future_dates(doc, from_date, to_date): +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"] ) if getdate(from_date) > getdate(to_date): - frappe.throw(_("To date can not be less than from date")) + frappe.throw(_("To date can not be less than from date"), title="Invalid From Date") elif date_of_joining and getdate(from_date) < getdate(date_of_joining): - frappe.throw(_("From date can not be less than employee's joining date")) + frappe.throw(_("From date can not be less than employee's joining date"), title="Invalid From Date") elif relieving_date and getdate(to_date) > getdate(relieving_date): - frappe.throw(_("To date can not greater than employee's relieving date")) - - - -@frappe.whitelist() -def update_request(attendance_request, from_date, to_date): - from_date = getdate(from_date) - to_date = getdate(to_date) - attendance_request_obj = frappe.get_doc('Attendance Request', attendance_request) - validate_future_dates(attendance_request_obj, from_date, to_date) - attendance_request_obj.db_set("from_date", from_date) - attendance_request_obj.db_set("to_date", to_date) - attendance_request_obj.db_set("update_request", True) - apply_workflow(attendance_request_obj, "Update Request") - + frappe.throw(_("To date can not greater than employee's relieving date"), title="Invalid From Date") + if getdate(from_date) < getdate(): + if doc.is_new(): + msg = _("Please note that Attendance Request cannot be created for a past date") + else: + msg = _("Please note that Attendance Request cannot be updated for a past date") + frappe.throw(msg, title="Invalid From Date") def mark_future_attendance_request(): - """ - GET attendance request for the future where date is today - """ - attendance_requests = frappe.db.sql(f""" - SELECT name FROM `tabAttendance Request` - WHERE '{getdate()}' BETWEEN from_date AND to_date - AND docstatus=1 - """, as_dict=1) - for row in attendance_requests: - try: - frappe.get_doc("Attendance Request", row.name).mark_attendance(str(getdate())) - except Exception as e: - frappe.log_error(str(e), 'Attendance Request') + """ + GET attendance request for the future where date is today + """ + attendance_requests = frappe.db.sql(f""" + SELECT name FROM `tabAttendance Request` + WHERE '{getdate()}' BETWEEN from_date AND to_date + AND docstatus=1 + """, as_dict=1) + for row in attendance_requests: + try: + frappe.get_doc("Attendance Request", row.name).mark_attendance(str(getdate())) + except Exception as e: + frappe.log_error(str(e), 'Attendance Request') diff --git a/one_fm/overrides/employee.py b/one_fm/overrides/employee.py index d2a8996253..a79a54b348 100644 --- a/one_fm/overrides/employee.py +++ b/one_fm/overrides/employee.py @@ -493,6 +493,48 @@ def message(self): return self._message +def has_day_off(employee, date): + """ + Checks if an employee has a scheduled day off on a specific date. + Args: + employee (str): The name of the employee to check for a day off. + date (str): The date for which to check if the employee has a scheduled day off. + Format: "YYYY-MM-DD" + Returns: + bool: True if the employee has a scheduled day off on the given date, False otherwise. + """ + if frappe.db.exists( + "Employee Schedule", + { + "employee":employee, + "date":date, + "employee_availability":"Day Off" + } + ): + return True + return False + +def is_employee_on_leave(employee, date): + """ + Checks if an employee is on leave on a specific date. + Args: + employee (str): The name of the employee to check for leave. + date (str): The date for which to check if the employee is on leave. + Format: "YYYY-MM-DD" + Returns: + bool: True if the employee is on leave on the given date, False otherwise. + """ + if frappe.db.exists( + "Attendance", + { + "status": "On Leave", + "docstatus": 1, + "employee": employee, + "attendance_date": date + } + ): + return True + return False diff --git a/one_fm/overrides/employee_checkin.py b/one_fm/overrides/employee_checkin.py index b60f02ffb1..092a4f24a6 100644 --- a/one_fm/overrides/employee_checkin.py +++ b/one_fm/overrides/employee_checkin.py @@ -28,15 +28,17 @@ def fetch_shift(self): pass def validate(self): - validate_active_employee(self.employee) - self.validate_duplicate_log() - if frappe.db.get_single_value("HR and Payroll Additional Settings", 'validate_shift_permission_on_employee_checkin'): - try: + try: + validate_active_employee(self.employee) + self.validate_duplicate_log() + if frappe.db.get_single_value("HR and Payroll Additional Settings", + 'validate_shift_permission_on_employee_checkin'): existing_perm = None checkin_time = get_datetime(self.time) curr_shift = get_current_shift(self.employee) if curr_shift: - curr_shift = curr_shift.as_dict() + current_shift_obj = curr_shift['data'] + curr_shift = current_shift_obj.as_dict() start_date = curr_shift["start_date"].strftime("%Y-%m-%d") existing_perm = frappe.db.sql(f""" select name from `tabShift Permission` where date = '{start_date}' and employee = '{self.employee}' and permission_type = '{perm_map[self.log_type]}' and workflow_state = 'Approved' """, as_dict=1) self.shift_assignment = curr_shift["name"] @@ -57,8 +59,8 @@ def validate(self): self.time = curr_shift["end_datetime"] self.skip_auto_attendance = 0 self.shift_permission = existing_perm[0]["name"] - except Exception as e: - frappe.throw(frappe.get_traceback()) + except Exception as e: + frappe.throw(frappe.get_traceback()) def validate_duplicate_log(self): doc = frappe.db.sql(f""" select name from `tabEmployee Checkin` where employee = '{self.employee}' and time = '{self.time}' and (NOT name = '{self.name}')""", as_dict=1) @@ -79,13 +81,16 @@ def before_insert(self): self.shift_actual_end = sa.end_datetime def after_insert(self): - frappe.db.commit() - self.reload() - if not (self.shift_assignment and self.shift_type and self.operations_shift and self.shift_actual_start and self.shift_actual_end): + try: + frappe.db.commit() + self.reload() + # if not (self.shift_assignment and self.shift_type and self.operations_shift and self.shift_actual_start and self.shift_actual_end): frappe.enqueue(after_insert_background, employee_checkin=self.name) - if self.log_type == "IN": - frappe.enqueue(notify_supervisor_about_late_entry, checkin=self) + if self.log_type == "IN": + frappe.enqueue(notify_supervisor_about_late_entry, checkin=self) + except Exception as e: + frappe.log_error(frappe.get_traceback(), 'Employee Checkin') def exists_checkin(current_shift_assignment, checkin_name, log_type="IN"): ''' @@ -108,23 +113,24 @@ def exists_checkin(current_shift_assignment, checkin_name, log_type="IN"): return False def after_insert_background(employee_checkin): - checkin_doc = frappe.get_doc("Employee Checkin", employee_checkin) + self = frappe.get_doc("Employee Checkin", employee_checkin) try: # update shift if not exists - curr_shift = get_current_shift(checkin_doc.employee) + curr_shift = get_current_shift(self.employee) if curr_shift: + curr_shift = curr_shift['data'] shift_type = frappe.db.sql(f"""SELECT * FROM `tabShift Type` WHERE name='{curr_shift.shift_type}' """, as_dict=1)[0] # calculate entry early_exit = 0 late_entry = 0 - actual_time = str(checkin_doc.time) + actual_time = str(self.time) if not '.' in actual_time: actual_time += '.000000' - - if checkin_doc.log_type=='IN': + + if self.log_type=='IN': if (datetime.strptime(actual_time, '%Y-%m-%d %H:%M:%S.%f') - timedelta(minutes=shift_type.late_entry_grace_period)) > curr_shift.start_datetime: late_entry = 1 - if checkin_doc.log_type=='OUT': + if self.log_type=='OUT': if (datetime.strptime(actual_time, '%Y-%m-%d %H:%M:%S.%f') + timedelta(minutes=shift_type.early_exit_grace_period)) < curr_shift.end_datetime: early_exit = 1 @@ -132,12 +138,12 @@ def after_insert_background(employee_checkin): UPDATE `tabEmployee Checkin` SET shift_assignment="{curr_shift.name}", operations_shift="{curr_shift.shift}", shift_type='{curr_shift.shift_type}', shift='{curr_shift.shift_type}', shift_actual_start="{curr_shift.start_datetime}", shift_actual_end="{curr_shift.end_datetime}", - shift_start="{curr_shift.start_datetime.date()}", shift_end="{curr_shift.end_datetime.date()}", early_exit={early_exit}, - late_entry={late_entry}, date='{curr_shift.start_date if checkin_doc.log_type=='IN' else curr_shift.end_datetime}', + shift_start="{curr_shift.start_datetime}", shift_end="{curr_shift.end_datetime}", early_exit={early_exit}, + late_entry={late_entry}, date='{curr_shift.start_date if self.log_type=='IN' else curr_shift.end_datetime}', operations_site="{curr_shift.site}", post_abbrv="{curr_shift.post_abbrv}", project="{curr_shift.project}", company="{curr_shift.company}", operations_role="{curr_shift.operations_role}", roster_type='{curr_shift.roster_type}' - WHERE name="{checkin_doc.name}" + WHERE name="{self.name}" """ frappe.db.sql(query, values=[], as_dict=1) frappe.db.commit() @@ -151,76 +157,72 @@ def after_insert_background(employee_checkin): start_time = get_datetime(cstr(getdate()) + " 00:00:00") end_time = get_datetime(cstr(getdate()) + " 23:59:59") - log_exist = frappe.db.exists("Employee Checkin", {"log_type": checkin_doc.log_type, "time": [ "between", (start_time, end_time)], "skip_auto_attendance": 0 ,"shift_type": checkin_doc.shift_type, "name": ["!=", checkin_doc.name]}) + log_exist = frappe.db.exists("Employee Checkin", {"log_type": self.log_type, "time": [ "between", (start_time, end_time)], "skip_auto_attendance": 0 ,"shift_type": self.shift_type, "name": ["!=", self.name]}) if not log_exist: # In case of back to back shift - if checkin_doc.shift_type: - shift_type = frappe.get_doc("Shift Type", checkin_doc.shift_type) + if self.shift_type: + shift_type = frappe.get_doc("Shift Type", self.shift_type) curr_shift = frappe._dict({ - 'actual_start': checkin_doc.shift_actual_start, - 'actual_end': checkin_doc.shift_actual_end, - 'end_datetime': checkin_doc.shift_end, - 'start_datetime': checkin_doc.shift_start, + 'actual_start': self.shift_actual_start, + 'actual_end': self.shift_actual_end, + 'end_datetime': self.shift_end, + 'start_datetime': self.shift_start, 'shift_type': shift_type }) - - if curr_shift: - supervisor_user = get_notification_user(checkin_doc, checkin_doc.employee) - distance, radius = validate_location(checkin_doc) + supervisor_user = get_notification_user(self, self.employee) + distance, radius = validate_location(self) message_suffix = _("Location logged is inside the site.") if distance <= radius else _("Location logged is {location}m outside the site location.").format(location=cstr(cint(distance)- radius)) - - if checkin_doc.log_type == "IN" and checkin_doc.skip_auto_attendance == 0: + if self.log_type == "IN" and self.skip_auto_attendance == 0: # LATE: Checkin time is after [Shift Start + Late Grace Entry period] - if shift_type.enable_entry_grace_period == 1 and get_datetime(checkin_doc.time) > (get_datetime(checkin_doc.shift_actual_start) + timedelta(minutes=shift_type.late_entry_grace_period)): - time_diff = get_datetime(checkin_doc.time) - get_datetime(checkin_doc.shift_actual_start) + if shift_type.enable_entry_grace_period == 1 and get_datetime(self.time) > (get_datetime(self.shift_actual_start) + timedelta(minutes=shift_type.late_entry_grace_period)): + time_diff = get_datetime(self.time) - get_datetime(self.shift_actual_start) hrs, mins, secs = cstr(time_diff).split(":") delay = "{hrs} hrs {mins} mins".format(hrs=hrs, mins=mins) if cint(hrs) > 0 else "{mins} mins".format(mins=mins) - subject = _("{employee} has checked in late by {delay}. {location}".format(employee=checkin_doc.employee_name, delay=delay, location=message_suffix)) - message = _("{employee_name} has checked in late by {delay}. {location}

Issue Penalty
".format(employee_name=checkin_doc.employee_name,shift=checkin_doc.operations_shift, date=cstr(checkin_doc.time), employee=checkin_doc.employee, delay=delay, location=message_suffix)) + subject = _("{employee} has checked in late by {delay}. {location}".format(employee=self.employee_name, delay=delay, location=message_suffix)) + message = _("{employee_name} has checked in late by {delay}. {location}

Issue Penalty
".format(employee_name=self.employee_name,shift=self.operations_shift, date=cstr(self.time), employee=self.employee, delay=delay, location=message_suffix)) for_users = [supervisor_user] - create_notification_log(subject, message, for_users, checkin_doc) + create_notification_log(subject, message, for_users, self) - elif checkin_doc.log_type == "IN" and checkin_doc.skip_auto_attendance == 1: - subject = _("Hourly Report: {employee} checked in at {time}. {location}".format(employee=checkin_doc.employee_name, time=checkin_doc.time, location=message_suffix)) - message = _("Hourly Report: {employee} checked in at {time}. {location}".format(employee=checkin_doc.employee_name, time=checkin_doc.time, location=message_suffix)) + elif self.log_type == "IN" and self.skip_auto_attendance == 1: + subject = _("Hourly Report: {employee} checked in at {time}. {location}".format(employee=self.employee_name, time=self.time, location=message_suffix)) + message = _("Hourly Report: {employee} checked in at {time}. {location}".format(employee=self.employee_name, time=self.time, location=message_suffix)) for_users = [supervisor_user] - create_notification_log(subject, message, for_users, checkin_doc) + create_notification_log(subject, message, for_users, self) - elif checkin_doc.log_type == "OUT": + elif self.log_type == "OUT": # Automatic checkout - if not checkin_doc.device_id: + if not self.device_id: title = "Checkin Report" category = "Attendance" - subject = _("Automated Checkout: {employee} forgot to checkout.".format(employee=checkin_doc.employee_name)) - message = _('Review check out '.format(name=checkin_doc.name)) + subject = _("Automated Checkout: {employee} forgot to checkout.".format(employee=self.employee_name)) + message = _('Review check out '.format(name=self.name)) for_users = [supervisor_user] send_notification(title, subject, message, category, for_users) #EARLY: Checkout time is before [Shift End - Early grace exit time] - elif shift_type.enable_exit_grace_period == 1 and checkin_doc.device_id and get_datetime(checkin_doc.time) < (get_datetime(checkin_doc.shift_actual_end) - timedelta(minutes=shift_type.early_exit_grace_period)): - time_diff = get_datetime(checkin_doc.shift_actual_end) - get_datetime(checkin_doc.time) + elif shift_type.enable_exit_grace_period == 1 and self.device_id and get_datetime(self.time) < (get_datetime(self.shift_actual_end) - timedelta(minutes=shift_type.early_exit_grace_period)): + time_diff = get_datetime(self.shift_actual_end) - get_datetime(self.time) hrs, mins, secs = cstr(time_diff).split(":") early = "{hrs} hrs {mins} mins".format(hrs=hrs, mins=mins) if cint(hrs) > 0 else "{mins} mins".format(mins=mins) - subject = _("{employee} has checked out early by {early}. {location}".format(employee=checkin_doc.employee_name, early=early, location=message_suffix)) - message = _("{employee_name} has checked out early by {early}. {location}

Issue Penalty
".format(employee_name=checkin_doc.employee_name, shift=checkin_doc.operations_shift, date=cstr(checkin_doc.time), employee=checkin_doc.employee_name, early=early, location=message_suffix)) + subject = _("{employee} has checked out early by {early}. {location}".format(employee=self.employee_name, early=early, location=message_suffix)) + message = _("{employee_name} has checked out early by {early}. {location}

Issue Penalty
".format(employee_name=self.employee_name, shift=self.operations_shift, date=cstr(self.time), employee=self.employee_name, early=early, location=message_suffix)) for_users = [supervisor_user] - create_notification_log(subject, message, for_users, checkin_doc) + create_notification_log(subject, message, for_users, self) else: # When no shift assigned, supervisor of active shift of the nearest site is sent a notification about unassigned checkin. - location = checkin_doc.device_id - # supervisor = get_closest_location(checkin_doc.time, location) - reporting_manager = frappe.get_value("Employee", {"user_id": checkin_doc.owner}, "reports_to") + location = self.device_id + # supervisor = get_closest_location(self.time, location) + reporting_manager = frappe.get_value("Employee", {"user_id": self.owner}, "reports_to") supervisor = get_employee_user_id(reporting_manager) if supervisor: - subject = _("{employee} has checked in on an unassigned shift".format(employee=checkin_doc.employee_name)) - message = _("{employee} has checked in on an unassigned shift".format(employee=checkin_doc.employee_name)) + subject = _("{employee} has checked in on an unassigned shift".format(employee=self.employee_name)) + message = _("{employee} has checked in on an unassigned shift".format(employee=self.employee_name)) for_users = [supervisor] - create_notification_log(subject, message, for_users, checkin_doc) - - + create_notification_log(subject, message, for_users, self) + @frappe.whitelist() diff --git a/one_fm/overrides/leave_application.py b/one_fm/overrides/leave_application.py index 5319ab1e58..2effff8a0d 100644 --- a/one_fm/overrides/leave_application.py +++ b/one_fm/overrides/leave_application.py @@ -5,8 +5,8 @@ from frappe.utils import get_fullname, nowdate, add_to_date from hrms.hr.doctype.leave_application.leave_application import * from one_fm.processor import sendemail - -from frappe.desk.form.assign_to import add +from frappe.desk.form.assign_to import add,remove +from erpnext.crm.utils import get_open_todos from one_fm.api.notification import create_notification_log from frappe.utils import getdate, date_diff import pandas as pd @@ -486,6 +486,14 @@ def update_attendance_recods(self): frappe.msgprint(_("Attendance are created for the leave Appication {0}!".format(self.name)), alert=True) + +def remove_assignment(attendance_check): + open_todo = get_open_todos("Attendance Check",attendance_check) + if open_todo: + for each in open_todo: + remove("Attendance Check",attendance_check,each.allocated_to,ignore_permissions=1) + + @frappe.whitelist() def get_leave_details(employee, date): allocation_records = get_leave_allocation_records(employee, date) diff --git a/one_fm/overrides/shift_assignment.py b/one_fm/overrides/shift_assignment.py index 3192705c60..d8768e4ecb 100644 --- a/one_fm/overrides/shift_assignment.py +++ b/one_fm/overrides/shift_assignment.py @@ -5,7 +5,7 @@ class ShiftAssignmentOverride(ShiftAssignment): - + def validate(self): self.set_datetime() super(ShiftAssignmentOverride, self).validate() @@ -25,7 +25,7 @@ def before_insert(self): self.set_datetime() if not frappe.db.exists("Employee", {'name':self.employee, 'status':'Active'}): frappe.throw(f"{self.employee} - {self.employee_name} is not active and cannot be assigned to a shift") - + def set_datetime(self): if self.shift_type: shift = frappe.get_doc("Shift Type", self.shift_type) @@ -48,7 +48,7 @@ def get_cut_off(self): 'start':start_cutoff, 'end':end_cutoff, }) - + def can_checkin_out(self): """ Check if user can checkin or our based on earliest and latest in or out. @@ -57,7 +57,7 @@ def can_checkin_out(self): if ((now_datetime() < cutoff.start) or (now_datetime() > cutoff.end)): return False return True - + def after_4hrs(self): """ Check if checkin time has exceeded 4hrs, which mean employee is late. @@ -68,41 +68,54 @@ def after_4hrs(self): return True return False - def check_existing_checking(self): - """API to determine the applicable Log type. - The api checks employee's last lcheckin log type. and determine what next log type needs to be - Returns: - True: The log in was "IN", so his next Log Type should be "OUT". - False: either no log type or last log type is "OUT", so his next Ltg Type should be "IN". + def get_last_checkin_log_type(self): + """ + The method checks employee's last checkin log type + Returns: + The last log_type if a checkin recod exist for the shift assignment + Else return False """ - checkin = frappe.db.get_list("Employee Checkin", filters={ - 'employee':self.employee, 'shift_assignment':self.name, - 'roster_type':self.roster_type - }, - fields='log_type', + checkin = frappe.db.get_list( + "Employee Checkin", + filters={ + "employee":self.employee, + "shift_assignment":self.name, + "shift_actual_start":self.start_datetime, + "shift_actual_end":self.end_datetime, + "roster_type":self.roster_type + }, + fields="log_type", order_by="actual_time DESC" ) - if checkin: - # #For Check IN - if checkin[0].log_type=='OUT': - return "IN" - #For Check OUT - else: - return "OUT" + if checkin and len(checkin) > 0: + return checkin[0].log_type + return False + + def get_next_checkin_log_type(self): + """ + Method to determine the applicable Log type. + The method checks employee's last lcheckin log type. and determine what next log type needs to be + Returns: + The last log_type if a checkin recod exist for the shift assignment + Else return IN + """ + last_log_type = self.get_last_checkin_log_type() + if last_log_type and last_log_type == "IN": + return "OUT" return "IN" def has_overlapping_timings(self) -> bool: """ Accepts two shift types and checks whether their timings are overlapping """ - if datetime.strptime(str(self.start_datetime), '%Y-%m-%d %H:%M:%S').date()>datetime.strptime(today(), '%Y-%m-%d').date(): + if datetime.strptime(str(self.start_datetime), '%Y-%m-%d %H:%M:%S').date() > datetime.strptime(today(), '%Y-%m-%d').date(): frappe.throw(f"Shift cannot be created for date greater than today. Today is {today()}, you requested {self.start_date}") existing_shift = frappe.db.sql(f""" SELECT * FROM `tabShift Assignment` WHERE employee="{self.employee}" AND status='Active' AND docstatus=1 AND ( (start_datetime BETWEEN '{self.start_datetime}' AND '{self.end_datetime}') - OR + OR (end_datetime BETWEEN '{self.start_datetime}' AND '{self.end_datetime}') ) @@ -115,4 +128,3 @@ def has_overlapping_timings(self) -> bool: """) return True return False - \ No newline at end of file diff --git a/one_fm/overrides/shift_request.py b/one_fm/overrides/shift_request.py index 0f6972a07c..a8706e2388 100644 --- a/one_fm/overrides/shift_request.py +++ b/one_fm/overrides/shift_request.py @@ -4,13 +4,14 @@ from frappe import _ from frappe.utils import getdate, today, cstr, add_to_date, add_days, get_url_to_form from frappe.model.workflow import apply_workflow +from frappe.desk.form.assign_to import add as add_assignment, DuplicateToDoError from frappe.utils import getdate, cstr from hrms.hr.utils import share_doc_with_approver from hrms.hr.doctype.shift_request.shift_request import * from one_fm.utils import ( workflow_approve_reject, send_workflow_action_email, get_approver ) -from one_fm.api.notification import get_employee_user_id +from one_fm.api.notification import create_notification_log, get_employee_user_id from one_fm.operations.doctype.operations_shift.operations_shift import get_supervisor_operations_shifts from one_fm.processor import sendemail @@ -19,41 +20,15 @@ class OverlappingShiftError(frappe.ValidationError): pass class ShiftRequestOverride(ShiftRequest): - def validate(self): - # ensure status is not pending - if self.is_new(): - self.status='Draft' - if self.status=='Pending Approval': - self.status == 'Draft' - - process_shift_assignemnt(self) # set shift assignment and employee schedule - def on_submit(self): if self.workflow_state != 'Update Request': - self.db_set("status", self.workflow_state) if self.workflow_state!='Pending Approval' else self.db_set("status", 'Draft') + self.db_set("status", self.workflow_state) if self.workflow_state!='Pending Approver' else self.db_set("status", 'Draft') - def before_save(self): - # Fill 'To' date if not set - if not self.to_date: - self.to_date = self.from_date - - # Validate 'From' date - if not self.assign_day_off: - if getdate(today()) > getdate(self.from_date): - frappe.throw('From Date cannot be before today.') - - send_shift_request_mail(self) def on_update(self): for approver in self.custom_shift_approvers: share_doc_with_approver(self, approver.user) - if self.workflow_state in ['Approved', 'Rejected']: - workflow_approve_reject(self, [get_employee_user_id(self.employee)]) - - if self.workflow_state == 'Draft': - send_workflow_action_email(self,[approver.user for approver in self.custom_shift_approvers]) - validate_shift_overlap(self) def validate_approver(self): if not self.is_new() and self.workflow_state == "Approved": @@ -95,7 +70,7 @@ def cancel_shift_assignment_of_request(self): shift_assignment_doc = frappe.get_doc("Shift Assignment", shift["name"]) shift_assignment_doc.cancel() shift_assignment_doc.delete() - if self.from_date <= cstr(getdate()) <= self.to_date and schedule_exists: + if self.from_date <= getdate() <= self.to_date and schedule_exists: schedule = frappe.get_doc("Employee Schedule",{"employee":self.employee, "date":cstr(getdate())}) if schedule: sa = frappe.get_doc(dict( @@ -113,17 +88,78 @@ def cancel_shift_assignment_of_request(self): )).insert() sa.submit() +def validate(doc, event=None): + # ensure status is not pending + if doc.is_new(): + doc.status = 'Draft' + if doc.status == 'Pending Approver': + doc.status == 'Draft' + if doc.status == 'Draft' and doc.purpose == 'Assign Unrostered Employee': + if check_for_roster(doc): + frappe.throw("Employee Already has been rostered for the given dates") + if doc.status == 'Draft' and doc.purpose == 'Replace Existing Assignment': + if doc.replaced_employee: + shift_assignemnt_exists = frappe.get_list("Shift Assignment", + filters=[['employee', '=', doc.replaced_employee], + ['start_date', 'between', [doc.from_date, doc.to_date]], + ['roster_type', '=', "Basic"]], fields=['name']) + if not shift_assignemnt_exists: + frappe.throw("Employee being replaced does not have existing shift.") + process_shift_assignment(doc) # set shift assignment and employee schedule + + +def shift_request_submit(self): + if self.workflow_state != 'Update Request': + self.db_set("status", self.workflow_state) if self.workflow_state != 'Pending Approver' else self.db_set( + "status", 'Draft') + + +def validate_default_shift(self): + default_shift = frappe.get_value("Employee", self.employee, "default_shift") + if self.shift_type == default_shift: + pass + + +def on_update(doc, event): + if doc.workflow_state in ['Approved', 'Rejected']: + workflow_approve_reject(doc, [get_employee_user_id(doc.employee)]) + + if doc.workflow_state == 'Draft': + send_workflow_action_email(doc,[approver.user for approver in doc.custom_shift_approvers]) + validate_shift_overlap(doc) + + if doc.workflow_state == 'Pending Approver': + assign_approver(doc, doc.approver) + + +def assign_approver(doc, approver_id): + add_assignment({ + 'doctype': doc.doctype, + 'name': doc.name, + 'assign_to': [approver_id], + 'description': + _( + f""" + Please Note that a Shift Request {doc.name} has been submitted.
+ Please review and take necessary actions + """ + ) + }) + def validate_shift_overlap(doc): curr_date = getdate() - shift_assignment = frappe.db.get_list("Shift Assignment", {'employee':doc.employee, 'start_date': doc.from_date, "roster_type": "Basic", 'status':'Active'}, ['shift','start_datetime', 'end_datetime']) - shift_type = frappe.db.get_list("Shift Type",{'name':doc.shift_type}, ['start_time', 'end_time']) + shift_assignment = frappe.db.get_list("Shift Assignment", {'employee': doc.employee, 'start_date': doc.from_date, + "roster_type": "Basic", 'status': 'Active'}, + ['shift', 'start_datetime', 'end_datetime']) + shift_type = frappe.db.get_list("Shift Type", {'name': doc.shift_type}, ['start_time', 'end_time']) - shift_start_time = datetime.datetime.combine(curr_date , (datetime.datetime.min + shift_type[0].start_time).time()) + shift_start_time = datetime.datetime.combine(curr_date, (datetime.datetime.min + shift_type[0].start_time).time()) if shift_type[0].start_time > shift_type[0].end_time: - shift_end_time = datetime.datetime.combine(add_to_date(curr_date, days=1) , (datetime.datetime.min + shift_type[0].end_time).time()) + shift_end_time = datetime.datetime.combine(add_to_date(curr_date, days=1), + (datetime.datetime.min + shift_type[0].end_time).time()) else: - shift_end_time = datetime.datetime.combine(curr_date , (datetime.datetime.min + shift_type[0].end_time).time()) + shift_end_time = datetime.datetime.combine(curr_date, (datetime.datetime.min + shift_type[0].end_time).time()) if doc.roster_type == "Over-Time" and shift_assignment: if shift_start_time < shift_assignment[0].end_datetime: @@ -136,35 +172,173 @@ def validate_shift_overlap(doc): frappe.throw(msg, title=_("Overlapping Shifts"), exc=OverlappingShiftError) +def shift_request_cancel(self): + ''' + Method used to override Shift Request on_cancel + ''' + cancel_shift_assignment_of_request(self) + + def on_update_after_submit(doc, method): if doc.update_request: if doc.workflow_state == 'Update Request': doc.db_set("status", 'Draft') doc.cancel_shift_assignment_of_request() -def process_shift_assignemnt(doc, event=None): - role_abbr = frappe.db.get_value("Operations Role",doc.operations_role,'post_abbrv') - if doc.workflow_state=='Approved' and doc.docstatus==1: - if doc.assign_day_off == 1: + +def process_shift_assignment(doc, event=None): + day_off_ot = 1 if doc.purpose == "Day Off Overtime" else 0 + role_abbr = frappe.db.get_value("Operations Role", doc.operations_role, 'post_abbrv') + shift_worker = frappe.db.get_value("Employee", doc.employee, 'shift_working') + if doc.workflow_state == 'Approved' and doc.docstatus == 1: + if doc.purpose == "Assign Day Off": assign_day_off(doc) - else: - if doc.roster_type == "Basic" and cstr(doc.from_date) == cstr(getdate()): - shift_assignemnt = frappe.get_value("Shift Assignment", {'employee':doc.employee, 'start_date': doc.from_date, 'roster_type':"Basic"}, ['name']) - if shift_assignemnt: - update_shift_assignment(shift_assignemnt, doc ) - else: - create_shift_assignment_from_request(doc) - - # check for existing schedule + elif doc.purpose == 'Replace Existing Assignment': + if doc.roster_type == "Basic" and cstr(doc.from_date) <= cstr(getdate()) <= cstr(doc.to_date): + shift_assignemnt = frappe.get_list("Shift Assignment", filters=[['employee', '=', doc.employee], + ['start_date', 'between', + [doc.from_date, doc.to_date]], + ['roster_type', '=', "Basic"]], + fields=['name']) + replace_shift_assignment(shift_assignemnt, doc) + if shift_worker == 1: + # check for existing schedule + schedule_date_range = [str(i.date()) for i in pd.date_range(start=doc.from_date, end=doc.to_date)] + existing_schedules = frappe.db.sql(f""" SELECT name, date FROM `tabEmployee Schedule` + WHERE employee="{doc.employee}" AND roster_type="{doc.roster_type}" + AND date BETWEEN '{doc.from_date}' AND '{doc.to_date}' """, as_dict=1) + if existing_schedules: + replace_employee_schedule(doc, existing_schedules, schedule_date_range) + elif doc.purpose == 'Assign Unrostered Employee': schedule_date_range = [str(i.date()) for i in pd.date_range(start=doc.from_date, end=doc.to_date)] + new_date_range = [i for i in schedule_date_range] + + for date in new_date_range: + create_employee_schedule_from_request(doc, date) + if date == today(): + shift_assignment = frappe._dict({ + "company": doc.company, + "operations_shift": doc.operations_shift, + "roster_type": doc.roster_type, + "shift_type": doc.shift_type, + "employee": doc.employee, + "from_date": date, + "name": doc.name, + "check_in_site": doc.check_in_site, + "check_out_site": doc.check_out_site, + "operations_role": doc.operations_role + }) + create_shift_assignment_from_request(shift_assignment) + + elif day_off_ot: + validate_day_of_ot(doc) + shift_assignment = frappe.get_list("Shift Assignment", filters=[['employee', '=', doc.employee], + ['start_date', 'between', + [doc.from_date, doc.to_date]], + ['roster_type', '=', "Basic"]], + fields=['name']) + if shift_assignment: + update_shift_assignment(shift_assignment[0].name, doc,day_off_ot=day_off_ot) + else: + create_shift_assignment_from_request(doc,day_off_ot=day_off_ot) + + elif doc.purpose == 'Update Existing Assignment': + if check_for_roster(doc): + if doc.roster_type == "Basic" and cstr(doc.from_date) <= cstr(getdate()) <= cstr(doc.to_date): + shift_assignemnt = frappe.get_list("Shift Assignment", filters=[['employee', '=', doc.employee], + ['start_date', 'between', + [doc.from_date, doc.to_date]], + ['roster_type', '=', "Basic"]], + fields=['name']) + if shift_assignemnt: + update_shift_assignment(shift_assignemnt[0].name, doc) + if shift_worker == 1: + # check for existing schedule + schedule_date_range = [str(i.date()) for i in pd.date_range(start=doc.from_date, end=doc.to_date)] + found_schedules_date = [] + existing_schedules = frappe.db.sql(f""" SELECT name, date FROM `tabEmployee Schedule` + WHERE employee="{doc.employee}" AND roster_type="{doc.roster_type}" + AND date BETWEEN '{doc.from_date}' AND '{doc.to_date}' """, as_dict=1) + if existing_schedules: + # update existing schedule + for es in existing_schedules: + start_time, end_time = frappe.db.get_value("Shift Type", doc.shift_type, + ['start_time', 'end_time']) + end_date = es.date + if start_time > end_time: + end_date = add_days(end_date, 1) + + frappe.db.set_value('Employee Schedule', es.name, { + 'shift': doc.operations_shift, + 'shift_type': doc.shift_type, + 'start_datetime': f"{es.date} {start_time}", + 'end_datetime': f"{end_date} {end_time}", + 'operations_role': doc.operations_role, + 'post_abbrv': role_abbr, + 'employee_availability': 'Working', + 'roster_type': doc.roster_type, + 'department': doc.department, + 'site': doc.site, + 'reference_doctype': doc.doctype, + 'reference_docname': doc.name, + }) + found_schedules_date.append(str(es.date)) + # create new schedule + new_date_range = [i for i in schedule_date_range if not i in found_schedules_date] + if new_date_range: + for date in new_date_range: + if frappe.db.exists("Employee Schedule", {'date': date, 'employee': doc.employee, + 'employee_availability': 'Day Off'}): + es = frappe.get_doc("Employee Schedule", {'date': date, 'employee': doc.employee, + 'employee_availability': 'Day Off'}) + start_time, end_time = frappe.db.get_value("Shift Type", doc.shift_type, + ['start_time', 'end_time']) + end_date = es.date + if start_time > end_time: + end_date = add_days(end_date, 1) + + frappe.db.set_value('Employee Schedule', es.name, { + 'shift': doc.operations_shift, + 'shift_type': doc.shift_type, + 'start_datetime': f"{es.date} {start_time}", + 'end_datetime': f"{end_date} {end_time}", + 'operations_role': doc.operations_role, + 'post_abbrv': role_abbr, + 'employee_availability': 'Working', + 'roster_type': doc.roster_type, + 'department': doc.department, + 'site': doc.site, + 'reference_doctype': doc.doctype, + 'reference_docname': doc.name + } + ) + else: + create_employee_schedule_from_request(doc, d) + + +def validate_day_of_ot(shift_request): + """Setup all the documents that need to be created and modified because of the + approval of a shift request""" + role_abbr = frappe.db.get_value("Operations Role",shift_request.operations_role,'post_abbrv') + total_shift_request_duration = frappe.utils.date_diff(shift_request.to_date,shift_request.from_date)+1 + existing_schedule = frappe.db.sql(f"""SELECT name,date from `tabEmployee Schedule` where employee = '{shift_request.employee}' and date between '{shift_request.from_date}' and '{shift_request.to_date}' and employee_availability = 'Day Off' """,as_dict=1) + if existing_schedule: + if int(total_shift_request_duration)!=int(len(existing_schedule)): + existing_dates = [frappe.utils.get_date_str(i.date) for i in existing_schedule] + schedule_date_range = [str(i.date()) for i in pd.date_range(start=shift_request.from_date, end=shift_request.to_date)] + missing_dates = [i for i in schedule_date_range if i not in existing_dates ] + frappe.throw(f"""Please ensure that an employee schedule for Day Off exists for ALL days in this shift request, + Please create a Day Off schedule for {', '.join(missing_dates)} """) + else: + schedule_date_range = [str(i.date()) for i in pd.date_range(start=shift_request.from_date, end=shift_request.to_date)] found_schedules_date = [] existing_schedules = frappe.db.sql(f""" SELECT name, date FROM `tabEmployee Schedule` - WHERE employee="{doc.employee}" AND roster_type="{doc.roster_type}" - AND date BETWEEN '{doc.from_date}' AND '{doc.to_date}' """, as_dict=1) + WHERE employee="{shift_request.employee}" AND roster_type="{shift_request.roster_type}" + AND date BETWEEN '{shift_request.from_date}' AND '{shift_request.to_date}' """, as_dict=1) if existing_schedules: # update existing schedule for es in existing_schedules: - start_time, end_time = frappe.db.get_value("Shift Type", doc.shift_type, ['start_time', 'end_time']) + start_time, end_time = frappe.db.get_value("Shift Type", shift_request.shift_type, ['start_time', 'end_time']) end_date = es.date if start_time > end_time: end_date = add_days(end_date, 1) @@ -172,94 +346,169 @@ def process_shift_assignemnt(doc, event=None): # validate_operations_post_overfill({es.date: 1}, doc.operations_shift) frappe.db.set_value('Employee Schedule', es.name, { - 'shift':doc.operations_shift, - 'shift_type':doc.shift_type, + 'shift':shift_request.operations_shift, + 'shift_type':shift_request.shift_type, 'start_datetime': f"{es.date} {start_time}", 'end_datetime': f"{end_date} {end_time}", - 'operations_role':doc.operations_role, + 'operations_role':shift_request.operations_role, 'post_abbrv': role_abbr, 'employee_availability':'Working', - 'roster_type':doc.roster_type, - 'department':doc.department, - 'site':doc.site, - 'reference_doctype': doc.doctype, - 'reference_docname': doc.name + 'roster_type':shift_request.roster_type, + 'department':shift_request.department, + 'site':shift_request.site, + 'day_off_ot':1, + 'reference_doctype': shift_request.doctype, + 'reference_docname': shift_request.name }) found_schedules_date.append(str(es.date)) - # create new schedule - new_date_range = [i for i in schedule_date_range if not i in found_schedules_date] - if new_date_range: - for d in new_date_range: - if frappe.db.exists("Employee Schedule", {'date':d, 'employee':doc.employee, 'employee_availability':'Day Off'}): - es = frappe.get_doc("Employee Schedule", {'date':d, 'employee':doc.employee, 'employee_availability':'Day Off'}) - start_time, end_time = frappe.db.get_value("Shift Type", doc.shift_type, ['start_time', 'end_time']) - end_date = es.date - if start_time > end_time: - end_date = add_days(end_date, 1) - - # validate_operations_post_overfill({es.date: 1}, doc.operations_shift) - - frappe.db.set_value('Employee Schedule', es.name, { - 'shift':doc.operations_shift, - 'shift_type':doc.shift_type, - 'start_datetime': f"{es.date} {start_time}", - 'end_datetime': f"{end_date} {end_time}", - 'operations_role':doc.operations_role, - 'post_abbrv': role_abbr, - 'employee_availability':'Working', - 'roster_type':doc.roster_type, - 'department':doc.department, - 'site':doc.site, - 'reference_doctype': doc.doctype, - 'reference_docname': doc.name - } - ) - - else: - schedule = frappe.new_doc("Employee Schedule") - schedule.employee = doc.employee - schedule.date = d - schedule.shift = doc.operations_shift - schedule.shift_type = doc.shift_type - schedule.operations_role = doc.operations_role - schedule.post_abbrv = role_abbr - schedule.employee_availability = 'Working' - schedule.roster_type = doc.roster_type - schedule.department = doc.department - schedule.site = doc.site - schedule.reference_doctype = doc.doctype - schedule.reference_docname = doc.name - schedule.save(ignore_permissions=True) - -def update_shift_assignment(shift_assignemnt,shift_request): + else: + frappe.throw(f"No Day Off set for {shift_request.employee_name} between {shift_request.from_date} and {shift_request.to_date}") + +def update_shift_assignment(shift_assignemnt, shift_request,day_off_ot = False): assignment_doc = frappe.get_doc('Shift Assignment', shift_assignemnt) - project = frappe.db.get_value("Operations Shift", {'name':shift_request.operations_shift}, ['project']) - assignment_doc.db_set("company" , shift_request.company) - assignment_doc.db_set("shift" , shift_request.operations_shift) - assignment_doc.db_set("roster_type" , shift_request.roster_type) - assignment_doc.db_set("shift_type" , shift_request.shift_type) - assignment_doc.db_set("site" , shift_request.site) - assignment_doc.db_set("project" , project) - assignment_doc.db_set("site_location" , shift_request.check_in_site) - assignment_doc.db_set("employee" , shift_request.employee) - assignment_doc.db_set("start_date" , shift_request.from_date) - assignment_doc.db_set("shift_request" , shift_request.name) - assignment_doc.db_set("check_in_site" , shift_request.check_in_site) - assignment_doc.db_set("check_out_site" , shift_request.check_out_site) - shift_type_data = frappe.get_doc("Shift Type",shift_request.shift_type) + project = frappe.db.get_value("Operations Shift", {'name': shift_request.operations_shift}, ['project']) + assignment_doc.db_set("company", shift_request.company) + assignment_doc.db_set("shift", shift_request.operations_shift) + assignment_doc.db_set("roster_type", shift_request.roster_type) + assignment_doc.db_set("shift_type", shift_request.shift_type) + assignment_doc.db_set("site", shift_request.site) + assignment_doc.db_set("project", project) + assignment_doc.db_set("site_location", shift_request.check_in_site) + assignment_doc.db_set("employee", shift_request.employee) + assignment_doc.db_set("start_date", shift_request.from_date) + assignment_doc.db_set("shift_request", shift_request.name) + assignment_doc.db_set("check_in_site", shift_request.check_in_site) + assignment_doc.db_set("check_out_site", shift_request.check_out_site) + shift_type_data = frappe.get_doc("Shift Type", shift_request.shift_type) if shift_type_data: - start_datetime = datetime.datetime.strptime(f"{shift_request.from_date} {(datetime.datetime.min + shift_type_data.start_time).time()}", '%Y-%m-%d %H:%M:%S') + start_datetime = datetime.datetime.strptime( + f"{shift_request.from_date} {(datetime.datetime.min + shift_type_data.start_time).time()}", + '%Y-%m-%d %H:%M:%S') if shift_type_data.end_time.total_seconds() < shift_type_data.start_time.total_seconds(): - end_datetime = datetime.datetime.strptime(f"{add_days(assignment_doc.start_date, 1)} {(datetime.datetime.min + shift_type_data.end_time).time()}", '%Y-%m-%d %H:%M:%S') + end_datetime = datetime.datetime.strptime( + f"{add_days(assignment_doc.start_date, 1)} {(datetime.datetime.min + shift_type_data.end_time).time()}", + '%Y-%m-%d %H:%M:%S') else: - end_datetime = datetime.datetime.strptime(f"{assignment_doc.start_date} {(datetime.datetime.min + shift_type_data.end_time).time()}", '%Y-%m-%d %H:%M:%S') + end_datetime = datetime.datetime.strptime( + f"{assignment_doc.start_date} {(datetime.datetime.min + shift_type_data.end_time).time()}", + '%Y-%m-%d %H:%M:%S') - assignment_doc.db_set("start_datetime" ,start_datetime) - assignment_doc.db_set("end_datetime" , end_datetime) + assignment_doc.db_set("start_datetime", start_datetime) + assignment_doc.db_set("end_datetime", end_datetime) if shift_request.operations_role: - assignment_doc.db_set("operations_role" , shift_request.operations_role) + assignment_doc.db_set("operations_role", shift_request.operations_role) + if day_off_ot: + assignment_doc.db_set("custom_day_off_ot" , 1) -def create_shift_assignment_from_request(shift_request, submit=True): + +def replace_shift_assignment(shift_assignemnt, shift_request): + ''' + Method used to create Shift Assignment from Shift Request + args: + shift_request: Object of shift request + submit: Boolean + ''' + project = frappe.db.get_value("Operations Shift", {'name': shift_request.operations_shift}, ['project']) + + #Replace Existing Assignment + if shift_assignemnt: + assignment_doc = frappe.get_doc('Shift Assignment', shift_assignemnt[0].name) + assignment_doc.db_set("company", shift_request.company) + assignment_doc.db_set("shift", shift_request.operations_shift) + assignment_doc.db_set("roster_type", shift_request.roster_type) + assignment_doc.db_set("shift_type", shift_request.shift_type) + assignment_doc.db_set("site", shift_request.site) + assignment_doc.db_set("project", project) + assignment_doc.db_set("site_location", shift_request.check_in_site) + assignment_doc.db_set("employee", shift_request.employee) + assignment_doc.db_set("start_date", shift_request.from_date) + assignment_doc.db_set("shift_request", shift_request.name) + assignment_doc.db_set("check_in_site", shift_request.check_in_site) + assignment_doc.db_set("check_out_site", shift_request.check_out_site) + else: + assignment_doc = create_shift_assignment_from_request(shift_request) + + replaced_shift_assignment = frappe.get_doc("Shift Assignment", {'employee': shift_request.replaced_employee, + 'start_date': shift_request.from_date, + 'roster_type': "Basic"}) + replaced_shift_assignment.db_set("is_replaced", 1) + replaced_shift_assignment.db_set("replaced_shift_assignment", assignment_doc.name) + + frappe.db.sql(""" + UPDATE `tabEmployee Checkin` + SET is_replaced = 1 + WHERE shift_assignment = %s + """, (replaced_shift_assignment.name,)) + + shift_type_data = frappe.get_doc("Shift Type", shift_request.shift_type) + if shift_type_data: + start_datetime = datetime.datetime.strptime( + f"{shift_request.from_date} {(datetime.datetime.min + shift_type_data.start_time).time()}", + '%Y-%m-%d %H:%M:%S') + if shift_type_data.end_time.total_seconds() < shift_type_data.start_time.total_seconds(): + end_datetime = datetime.datetime.strptime( + f"{add_days(assignment_doc.start_date, 1)} {(datetime.datetime.min + shift_type_data.end_time).time()}", + '%Y-%m-%d %H:%M:%S') + else: + end_datetime = datetime.datetime.strptime( + f"{assignment_doc.start_date} {(datetime.datetime.min + shift_type_data.end_time).time()}", + '%Y-%m-%d %H:%M:%S') + + assignment_doc.db_set("start_datetime", start_datetime) + assignment_doc.db_set("end_datetime", end_datetime) + if shift_request.operations_role: + assignment_doc.db_set("operations_role", shift_request.operations_role) + + #Update Employee Checkin + frappe.db.sql(f"""UPDATE `tabEmployee Checkin` + SET is_replaced = 1, + replaced_employee_checkin = ( + SELECT name + FROM `tabEmployee Checkin` + WHERE employee = '{shift_request.replaced_employee}' + AND shift_assignment = '{replaced_shift_assignment}' + LIMIT 1 + ) + WHERE shift_assignment = '{replaced_shift_assignment}' + AND employee = '{shift_request.replaced_employee}'; + """) + + +def replace_employee_schedule(doc, existing_schedules, schedule_date_range): + try: + # replace existing schedule + for es in existing_schedules: + role_abbr = frappe.db.get_value("Operations Role", doc.operations_role, 'post_abbrv') + start_time, end_time = frappe.db.get_value("Shift Type", doc.shift_type, ['start_time', 'end_time']) + end_date = es.date + if start_time > end_time: + end_date = add_days(end_date, 1) + frappe.db.set_value('Employee Schedule', es.name, { + 'shift': doc.operations_shift, + 'shift_type': doc.shift_type, + 'start_datetime': f"{es.date} {start_time}", + 'end_datetime': f"{end_date} {end_time}", + 'operations_role': doc.operations_role, + 'post_abbrv': role_abbr, + 'employee_availability': 'Working', + 'roster_type': doc.roster_type, + 'department': doc.department, + 'site': doc.site, + 'reference_doctype': doc.doctype, + 'reference_docname': doc.name, + }) + + #Update Existing Employee Schedule + replaced_employee_schedule = frappe.get_doc("Employee Schedule", + {'employee': doc.replaced_employee, 'date': es.date}, ['name']) + replaced_employee_schedule.db_set("is_replaced", 1) + replaced_employee_schedule.db_set("replaced_employee_schedule", es.name) + except Exception as e: + frappe.throw(_("Error Replacing Employee Schedule")) + frappe.log_error(frappe.get_traceback(), "Error Replacing Employee Schedule") + + +def create_shift_assignment_from_request(shift_request, submit=True,day_off_ot = False): ''' Method used to create Shift Assignment from Shift Request args: @@ -276,23 +525,49 @@ def create_shift_assignment_from_request(shift_request, submit=True): assignment_doc.shift_request = shift_request.name assignment_doc.check_in_site = shift_request.check_in_site assignment_doc.check_out_site = shift_request.check_out_site + assignment_doc.custom_day_off_ot = 1 if day_off_ot else 0 + if day_off_ot: + assignment_doc.flags.ignore_validate = True if shift_request.operations_role: assignment_doc.operations_role = shift_request.operations_role + assignment_doc.insert() if submit: assignment_doc.submit() frappe.db.commit() + return assignment_doc + + + +def create_employee_schedule_from_request(doc, date): + schedule = frappe.new_doc("Employee Schedule") + schedule.employee = doc.employee + schedule.date = date + schedule.shift = doc.operations_shift + schedule.shift_type = doc.shift_type + schedule.operations_role = doc.operations_role + # schedule.post_abbrv = frappe.db.get_value("Operations Role", doc.operations_role, 'post_abbrv') + schedule.employee_availability = 'Working' + schedule.roster_type = doc.roster_type + schedule.department = doc.department + schedule.site = doc.site + schedule.reference_doctype = doc.doctype + schedule.reference_docname = doc.name + schedule.save(ignore_permissions=True) + def assign_day_off(shift_request): - shift_assignment = frappe.get_list('Shift Assignment' ,{'employee':shift_request.employee, 'start_date': shift_request.from_date}, ['name']) + shift_assignment = frappe.get_list('Shift Assignment', + {'employee': shift_request.employee, 'start_date': shift_request.from_date}, + ['name', "start_date"]) if shift_assignment: for s in shift_assignment: - shift = frappe.get_doc("Shift Assignment", s.name) - if shift.start_date >= getdate(): - shift.cancel() - shift.delete() + frappe.db.sql(f"""DELETE from `tabEmployee Checkin` WHERE employee='{shift_request.employee}' AND shift_assignment='{s.name}'""") + if s.start_date >= getdate(): + frappe.db.sql(f"""DELETE from `tabShift Assignment` WHERE name='{s.name}'""") - employee_schedule = frappe.get_list('Employee Schedule' ,{'employee':shift_request.employee, 'date': ["between", (shift_request.from_date, shift_request.to_date)]}, ['name']) + employee_schedule = frappe.get_list('Employee Schedule', {'employee': shift_request.employee, 'date': ["between", ( + shift_request.from_date, shift_request.to_date)]}, ['name']) if employee_schedule: for es in employee_schedule: schedule = frappe.get_doc("Employee Schedule", es.name) @@ -312,6 +587,71 @@ def assign_day_off(shift_request): frappe.db.commit() +def cancel_shift_assignment_of_request(shift_request): + ''' + Method used to cancel Shift Assignment of a Shift Request + args: + shift_request: Object of shift request + submit: Boolean + ''' + schedule_exists = frappe.db.exists("Employee Schedule", + {"employee": shift_request.employee, "date": cstr(getdate()), + "employee_availability": "Working"}) + + shift_assignment_list = frappe.get_list( + "Shift Assignment", + { + "employee": shift_request.employee, + "shift_request": shift_request.name, + "docstatus": 1 + } + ) + if shift_assignment_list: + for shift in shift_assignment_list: + shift_assignment_doc = frappe.get_doc("Shift Assignment", shift["name"]) + shift_assignment_doc.cancel() + shift_assignment_doc.delete() + if shift_request.from_date <= getdate() <= shift_request.to_date and schedule_exists: + schedule = frappe.get_doc("Employee Schedule", {"employee": shift_request.employee, "date": cstr(getdate())}) + if schedule: + sa = frappe.get_doc(dict( + doctype='Shift Assignment', + start_date=cstr(getdate()), + employee=schedule.employee, + employee_name=schedule.employee_name, + department=schedule.department, + operations_role=schedule.operations_role, + shift=schedule.shift, + site=schedule.site, + project=schedule.project, + shift_type=schedule.shift_type, + roster_type=schedule.roster_type, + )).insert() + sa.submit() + + +def validate_approver(self): + shift, department = frappe.get_value("Employee", self.employee, ["shift", "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] + + if frappe.db.exists("Employee", self.employee, ["reports_to"]): + report_to = frappe.get_value("Employee", self.employee, ["reports_to"]) + approvers.append(frappe.get_value("Employee", report_to, "user_id")) + + if shift: + shift_supervisor = frappe.get_value("Operations Shift", shift, "supervisor") + approvers.append(frappe.get_value("Employee", shift_supervisor, "user_id")) + + if self.approver not in approvers: + frappe.throw(_("Only Approvers can Approve this Request.")) + + @frappe.whitelist() def fetch_approver(doc): doc = frappe._dict(json.loads(doc)) @@ -325,12 +665,36 @@ def fetch_approver(doc): if approver_user_id: return [approver_user_id] + other_approvers + + +def fill_to_date(doc, method): + if not doc.to_date: + doc.to_date = doc.from_date + + +def validate_from_date(doc, method): + if getdate(today()) > getdate(doc.from_date): + attendance_manager = get_employee_user_id(frappe.db.get_single_value("ONEFM General Setting", "attendance_manager")) + if frappe.session.user == attendance_manager: + return + + if doc.purpose != 'Assign Day Off': + message = "Please note that Shift Requests cannot be created for a past date." if doc.is_new() else "Please note that Shift Requests cannot be updated to a past date." + frappe.throw( + _(message), + title=_("Invalid Start Date"), + ) + + @frappe.whitelist() def update_request(shift_request, from_date, to_date): from_date = getdate(from_date) to_date = getdate(to_date) if getdate(today()) > from_date: - frappe.throw('From Date cannot be before today.') + frappe.throw( + _("Please note that Shift Requests cannot be updated to a past date."), + title=_("Invalid Start Date"), + ) if from_date > to_date: frappe.throw('To Date cannot be before From Date.') shift_request_obj = frappe.get_doc('Shift Request', shift_request) @@ -340,6 +704,7 @@ def update_request(shift_request, from_date, to_date): shift_request_obj.db_set("status", 'Draft') apply_workflow(shift_request_obj, "Update Request") + def _get_employee_from_user(user): employee_docname = frappe.db.get_value("Employee", {"user_id": user}) return employee_docname if employee_docname else None @@ -366,12 +731,13 @@ def get_manager(doctype, employee): return [i.name for i in values] - @frappe.whitelist() def fetch_employee_details(employee): - emp_data = frappe.get_all("Employee",{'name':employee},['employee_name','company','shift','site','project','default_shift','department']) + emp_data = frappe.get_all("Employee", {'name': employee}, + ['employee_name', 'company', 'shift', 'site', 'project', 'default_shift', 'department']) return emp_data[0] if emp_data else [] + @frappe.whitelist() def get_employees(doctype, txt, searchfield, start, page_len, filters): """ @@ -380,28 +746,32 @@ def get_employees(doctype, txt, searchfield, start, page_len, filters): """ - is_master= False + is_master = False default_user_roles = None - employee_master_roles = frappe.get_all("ONEFM Document Access Roles Detail",{'parent':"ONEFM General Setting",'parentfield':"employee_master_role"},['role']) + employee_master_roles = frappe.get_all("ONEFM Document Access Roles Detail", + {'parent': "ONEFM General Setting", 'parentfield': "employee_master_role"}, + ['role']) user_roles = frappe.get_roles(frappe.session.user) or [] - default_user_roles = [i.role for i in employee_master_roles] if employee_master_roles else ['HR Manager',"HR User"] + default_user_roles = [i.role for i in employee_master_roles] if employee_master_roles else ['HR Manager', "HR User"] #if user has any default hr role - if [role for role in user_roles if role in default_user_roles ]: - is_master = True - if is_master: + if [role for role in user_roles if role in default_user_roles]: + is_master = True + if is_master: #If the user has not typed anything on the employee field if not txt: - employees = frappe.db.sql("Select name,employee_name,employee_id from `tabEmployee` where status = 'Active' ") + employees = frappe.db.sql( + "Select name,employee_name,employee_id from `tabEmployee` where status = 'Active' ") return employees else: #If the user has typed anything on the employee field - employees = frappe.db.sql(f"Select name,employee_name,employee_id from `tabEmployee` where status = 'Active' and name like '%{txt}%' or employee_name like '%{txt}%' or employee_id like '%{txt}%' ") + employees = frappe.db.sql( + f"Select name,employee_name,employee_id from `tabEmployee` where status = 'Active' and name like '%{txt}%' or employee_name like '%{txt}%' or employee_id like '%{txt}%' ") return employees else: allowed_employees = [] user = frappe.session.user - if user!="Administrator": + if user != "Administrator": employee_id = _get_employee_from_user(user) if employee_id: employee_base_query = f""" @@ -411,32 +781,34 @@ def get_employees(doctype, txt, searchfield, start, page_len, filters): query = None allowed_employees.append(employee_id) #get all reports to - reports_to = frappe.get_all("Employee",{'reports_to':employee_id,'status':"Active"},'name') + reports_to = frappe.get_all("Employee", {'reports_to': employee_id, 'status': "Active"}, 'name') if reports_to: - allowed_employees+=[i.name for i in reports_to] + allowed_employees += [i.name for i in reports_to] #get all employees in project,shift and site if allowed_employees: - cond_str = f" and name in {tuple(allowed_employees)}" if len(allowed_employees)>1 else f" and name = '{allowed_employees[0]}'" + cond_str = f" and name in {tuple(allowed_employees)}" if len( + allowed_employees) > 1 else f" and name = '{allowed_employees[0]}'" if txt: - cond_str+=f" and (name like '%{txt}%' or employee_name like '%{txt}%' or employee_id like '%{txt}%') " - query=employee_base_query+cond_str - shifts = get_manager('Operations Shift',employee_id) + cond_str += f" and (name like '%{txt}%' or employee_name like '%{txt}%' or employee_id like '%{txt}%') " + query = employee_base_query + cond_str + shifts = get_manager('Operations Shift', employee_id) if shifts: - cond_str = f" and shift in {tuple(shifts)}" if len(shifts)>1 else f" and shift = '{shifts[0]}'" + cond_str = f" and shift in {tuple(shifts)}" if len(shifts) > 1 else f" and shift = '{shifts[0]}'" if txt: - cond_str+=f" and (name like '%{txt}%' or employee_name like '%{txt}%' or employee_id like '%{txt}%') " + cond_str += f" and (name like '%{txt}%' or employee_name like '%{txt}%' or employee_id like '%{txt}%') " query += f""" UNION SELECT name,employee_name,employee_id from `tabEmployee` where status = "Active" {cond_str} """ - sites = get_manager('Operations Site',employee_id) + sites = get_manager('Operations Site', employee_id) if sites: - cond_str = f" and site in {tuple(sites)}" if len(sites)>1 else f" and site = '{sites[0]}'" + cond_str = f" and site in {tuple(sites)}" if len(sites) > 1 else f" and site = '{sites[0]}'" if txt: - cond_str+=f" and (name like '%{txt}%' or employee_name like '%{txt}%' or employee_id like '%{txt}%') " + cond_str += f" and (name like '%{txt}%' or employee_name like '%{txt}%' or employee_id like '%{txt}%') " query += f""" UNION SELECT name,employee_name,employee_id from `tabEmployee` where status = "Active" {cond_str} """ - project = get_manager('Project',employee_id) + project = get_manager('Project', employee_id) if project: - cond_str = f" and project in {tuple(project)}" if len(project)>1 else f" and project = '{project[0]}'" + cond_str = f" and project in {tuple(project)}" if len( + project) > 1 else f" and project = '{project[0]}'" if txt: - cond_str+=f" and (name like '%{txt}%' or employee_name like '%{txt}%' or employee_id like '%{txt}%') " + cond_str += f" and (name like '%{txt}%' or employee_name like '%{txt}%' or employee_id like '%{txt}%') " query += f""" UNION SELECT name,employee_name,employee_id from `tabEmployee` where status = "Active" {cond_str} """ #Check if employee is set in operations shift, operations site or project @@ -447,12 +819,26 @@ def get_employees(doctype, txt, searchfield, start, page_len, filters): return () else: if not txt: - return frappe.db.sql("Select name,employee_name,employee_id from `tabEmployee` where status = 'Active' ") + return frappe.db.sql( + "Select name,employee_name,employee_id from `tabEmployee` where status = 'Active' ") else: return frappe.db.sql("Select name,employee_name,employee_id from `tabEmployee` where status = 'Active' and name \ like '%{txt}%' or employee_name like '%{txt}%' \or employee_id like '%{txt}%' ") +def check_for_roster(doc): + schedule_date_range = [str(i.date()) for i in pd.date_range(start=doc.from_date, end=doc.to_date)] + new_date_range = [i for i in schedule_date_range] + if new_date_range: + for date in new_date_range: + if frappe.db.exists("Employee Schedule", {'date': date, 'employee': doc.employee}): + return True + elif frappe.db.exists("Shift Assignment", {"docstatus": 1, "start_date": date, "employee": doc.employee}): + return True + else: + return False + + @frappe.whitelist() def get_operations_role(doctype, txt, searchfield, start, page_len, filters): shift = filters.get('operations_shift') @@ -463,13 +849,15 @@ def get_operations_role(doctype, txt, searchfield, start, page_len, filters): """.format(shift=shift)) return operations_roles + def has_overlap(shift1, shift2): shift1 = frappe.get_doc("Shift Type", shift1) shift2 = frappe.get_doc("Shift Type", shift2) if shift1.end_time <= shift2.start_time or shift1.start_time >= shift2.end_time: - return True #No Overlap + return True #No Overlap else: - return False #Overlap + return False #Overlap + def daterange(start_date, end_date): start_date = datetime.datetime.strptime(start_date, '%Y-%m-%d') @@ -478,8 +866,8 @@ def daterange(start_date, end_date): yield start_date + datetime.timedelta(n) -def send_shift_request_mail(doc): - if doc.workflow_state == 'Pending Approval': +def send_shift_request_mail(doc, method): + if doc.workflow_state == 'Pending Approver': try: title = f"Urgent Notification: {doc.doctype} Requires Your Immediate Review" context = dict( diff --git a/one_fm/overrides/timesheet.py b/one_fm/overrides/timesheet.py index 4a305b19b3..f9b4c5ed1f 100644 --- a/one_fm/overrides/timesheet.py +++ b/one_fm/overrides/timesheet.py @@ -1,11 +1,12 @@ import frappe import itertools +from frappe.desk.form.assign_to import add as add_assignment from frappe.utils import cstr, flt, add_days, time_diff_in_hours, getdate, get_datetime_in_timezone from calendar import monthrange from hrms.overrides.employee_timesheet import * from frappe import _ from one_fm.processor import sendemail -from one_fm.utils import send_workflow_action_email, get_approver_user +from one_fm.utils import send_workflow_action_email, get_approver_user, get_approver class TimesheetOveride(Timesheet): @@ -21,15 +22,16 @@ def validate(self): self.validate_start_date() def validate_start_date(self): - start_date = getdate(self.start_date) - if start_date > getdate(): - frappe.throw(_("Please note that timesheets cannot be created for a date in the future")) - - def before_insert(self): - self.set_dates() - start_date = getdate(self.start_date) - if start_date < get_datetime_in_timezone("Asia/Kuwait").date(): - frappe.throw(_("Please note that timesheets cannot be created for a previous date.")) + if frappe.session.user != "Administrator": + start_date = getdate(self.start_date) + if start_date > getdate(): + frappe.throw(_("Please note that Timesheets cannot be created for a future date"), title="Invalid Start Date") + if start_date < getdate(): + if self.is_new(): + msg = _("Please note that Timesheets cannot be created for a past date") + else: + msg = _("Please note that Timesheets cannot be updated for a past date") + frappe.throw(msg, title="Invalid Start Date") def before_save(self): if not self.is_new(): @@ -39,7 +41,8 @@ def before_save(self): # 2. Check if value of end_date is changed and it is before current date according to AST. # If any of the above criteria is fulfilled then throw an error. if (self.has_value_changed("start_date") and date_in_ast > self.get('start_date')) or (self.has_value_changed("end_date") and date_in_ast > self.get('end_date')): - frappe.throw(_("Please note that timesheets cannot be updated to a previous date.")) + frappe.throw(_("Please note that timesheets cannot be updated to a previous date."), title="Invalid Start Date") + self.assign_unassign() def set_approver(self): if self.attendance_by_timesheet: @@ -55,10 +58,11 @@ def before_submit(self): frappe.throw("Total Hours cannot be 0 or less.") def on_update(self): - if self.workflow_state == 'Open': + if self.workflow_state == 'Pending Approval': send_workflow_action_email(self, [self.approver]) message = "The timesheet {0} of {1}, Open for your Approval".format(self.name, self.employee_name) create_notification_log("Pending - Workflow Action on Timesheet", message, [self.approver], self) + def on_submit(self): self.validate_mandatory_fields() @@ -66,9 +70,10 @@ def on_submit(self): if self.workflow_state == "Approved": self.check_approver() self.create_attendance() - elif self.workflow_state == "Rejected": + elif self.workflow_state == "Canceled": self.check_approver() self.notify_the_employee() + self.delete_todo() def notify_the_employee(self): timesheet_url = '{1}'.format(frappe.utils.get_link_to_form("Timesheet", self.name), self.name) @@ -124,6 +129,57 @@ def check_approver(self): if frappe.session.user not in [self.approver, "Administrator"]: frappe.throw(_("Only Approver can Approve/Reject the timesheet")) + + + def assign_unassign(self) -> None: + previous_doc = self.get_doc_before_save() if not self.is_new() else self + if previous_doc.workflow_state != self.workflow_state: + self.delete_todo() + if self.workflow_state in {"Draft", "Pending Approval"}: + add_assignment({ + 'doctype': self.doctype, + 'name': self.name, + 'assign_to': [self.owner if self.workflow_state == "Draft" else self.approver], + 'description': (_(self.fetch_description())) + }) + + + def delete_todo(self): + return frappe.db.sql(""" + DELETE FROM `tabToDo` + WHERE reference_type = %s AND reference_name = %s + """, (self.doctype, self.name)) + + + def fetch_description(self): + return f""" +

Here is to inform you that the following { self.doctype }({ self.name }) requires your attention/action. +
+ The details of the request are as follows: +
+

+ + + + + + + + + + + + +
LabelValue
Employee{ self.employee }

+ + """ + +@frappe.whitelist() +def fetch_approver(employee): + approver = get_approver(employee) + if approver: + return frappe.get_value("Employee", approver, ["user_id"]) + def timesheet_automation(start_date=None,end_date=None,project=None): filters = { 'attendance_date': ['between', (start_date, end_date)], @@ -280,3 +336,8 @@ def create_notification_log(subject, message, for_users, reference_doc): # If notification log type is Alert then it will not send email for the log doc.type = 'Alert' doc.insert(ignore_permissions=True) + + + + + diff --git a/one_fm/patches.txt b/one_fm/patches.txt index 6d602a2334..2b28d9673f 100644 --- a/one_fm/patches.txt +++ b/one_fm/patches.txt @@ -82,6 +82,8 @@ one_fm.patches.v15_0.replicate_issue_types_to_hd_ticket_types # one_fm.patches.v15_0.update_timesheet_workflow_sate one_fm.patches.v15_0.delete_duplicate_leave_attendance one_fm.patches.v15_0.update_subcontract_doc +one_fm.patches.v15_0.update_shift_permission_workflow +one_fm.patches.v15_0.remove_assign_day_off_field one_fm.patches.v15_0.update_employee_status one_fm.patches.v15_0.update_employee_id_qr_code_generator one_fm.patches.v15_0.update_religion_field_options @@ -92,4 +94,5 @@ one_fm.patches.v15_0.submit_shift_permissions one_fm.patches.v15_0.update_shift_request one_fm.patches.v15_0.add_task_assignments_to_form_field one_fm.patches.v15_0.disable_wrong_shift_request -one_fm.patches.v15_0.update_job_offer_00057_erf \ No newline at end of file +one_fm.patches.v15_0.update_shift_request_day_off +one_fm.patches.v15_0.update_job_offer_00057_erf diff --git a/one_fm/patches/v15_0/delete_duplicate_leave_attendance.py b/one_fm/patches/v15_0/delete_duplicate_leave_attendance.py index b196f7c8e5..95659cd735 100644 --- a/one_fm/patches/v15_0/delete_duplicate_leave_attendance.py +++ b/one_fm/patches/v15_0/delete_duplicate_leave_attendance.py @@ -24,4 +24,4 @@ def execute() : delete_list.append (atts[1].name) delete_list = str(tuple(delete_list)).replace(',)', ')') - frappe.db.sql(f"""DELETE FROM `tabAttendance` where name IN {delete_list}""") \ No newline at end of file + frappe.db.sql(f"""DELETE FROM `tabAttendance` where name IN {delete_list}""") diff --git a/one_fm/patches/v15_0/delete_field_assign_day_off.py b/one_fm/patches/v15_0/delete_field_assign_day_off.py new file mode 100644 index 0000000000..80c97f2d6b --- /dev/null +++ b/one_fm/patches/v15_0/delete_field_assign_day_off.py @@ -0,0 +1,7 @@ +import frappe + +def execute(): + column = 'assign_day_off' + if column in frappe.db.get_table_columns("Shift Request"): + frappe.db.sql("ALTER TABLE `tabShift Request` drop column {0}".format(column)) + frappe.db.commit() \ No newline at end of file diff --git a/one_fm/patches/v15_0/remove_assign_day_off_field.py b/one_fm/patches/v15_0/remove_assign_day_off_field.py new file mode 100644 index 0000000000..995d9e21df --- /dev/null +++ b/one_fm/patches/v15_0/remove_assign_day_off_field.py @@ -0,0 +1,4 @@ +import frappe + +def execute(): + frappe.db.sql("DELETE from `tabCustom Field` where name = 'Shift Request-assign_day_off'") \ No newline at end of file diff --git a/one_fm/patches/v15_0/update_shift_permission_workflow.py b/one_fm/patches/v15_0/update_shift_permission_workflow.py new file mode 100644 index 0000000000..2ca513b8c9 --- /dev/null +++ b/one_fm/patches/v15_0/update_shift_permission_workflow.py @@ -0,0 +1,9 @@ +import frappe + + +def execute(): + frappe.db.sql(""" + UPDATE `tabShift Permission` + SET workflow_state = "Pending Approver" + WHERE workflow_state = "Pending" + """) \ No newline at end of file diff --git a/one_fm/patches/v15_0/update_shift_request_day_off.py b/one_fm/patches/v15_0/update_shift_request_day_off.py new file mode 100644 index 0000000000..e605ad231c --- /dev/null +++ b/one_fm/patches/v15_0/update_shift_request_day_off.py @@ -0,0 +1,9 @@ +import frappe + + +def execute(): + frappe.db.sql(""" + UPDATE `tabShift Request` + SET purpose = "Assign Day Off" + WHERE assign_day_off = 1 + """) \ No newline at end of file diff --git a/one_fm/patches/v15_0/update_timesheet_workflow_sate.py b/one_fm/patches/v15_0/update_timesheet_workflow_sate.py new file mode 100644 index 0000000000..98b4e12c09 --- /dev/null +++ b/one_fm/patches/v15_0/update_timesheet_workflow_sate.py @@ -0,0 +1,12 @@ +import frappe + + +def execute(): + frappe.db.sql(""" + UPDATE + `tabTimesheet` + SET + workflow_state = 'Pending Approval' + WHERE + workflow_state = 'Open' + """) diff --git a/one_fm/permissions.py b/one_fm/permissions.py index a7e0087884..83b4884926 100644 --- a/one_fm/permissions.py +++ b/one_fm/permissions.py @@ -60,9 +60,8 @@ def get_custom_user_permissions(user=None): if not user or user in ("Administrator", "Guest"): return {} - cached_user_permissions = frappe.cache.hget("user_permissions", user) - - + cached_user_permissions = frappe.cache().hget("user_permissions", user) + if cached_user_permissions is not None: return cached_user_permissions @@ -104,7 +103,7 @@ def add_doc_to_perm(perm, doc_name, is_default): if data: for each in data: out['Employee'].append({'doc':each.name,'applicable_for':None,'is_default':0}) - frappe.cache.hset("user_permissions", user, out) + frappe.cache().hset("user_permissions", user, out) except frappe.db.SQLError as e: if frappe.db.is_table_missing(e): diff --git a/one_fm/public/js/doctype_js/attendance_request.js b/one_fm/public/js/doctype_js/attendance_request.js index 649283aa0f..5f9819058a 100644 --- a/one_fm/public/js/doctype_js/attendance_request.js +++ b/one_fm/public/js/doctype_js/attendance_request.js @@ -1,7 +1,6 @@ frappe.ui.form.on('Attendance Request', { refresh: (frm)=>{ frm.trigger('check_workflow'); - set_update_request_btn(frm); }, validate: (frm) => { validate_from_date(frm); @@ -17,89 +16,17 @@ frappe.ui.form.on('Attendance Request', { }) } }, - employee: (frm)=>{ - // Set approver - frm.events.set_approver(frm); - }, from_date: (frm) =>{ validate_from_date(frm); }, - set_approver: (frm) =>{ - if(frm.doc.employee){ - frappe.call({ - method: 'one_fm.utils.get_approver_user', - args:{ - 'employee':frm.doc.employee - }, - callback: function(r) { - let approver = ""; - if(r.message){ - approver = r.message; - } - frm.set_value("approver", approver); - frm.refresh_field("approver"); - } - }); - } - else{ - frm.set_value("approver", ""); - frm.refresh_field("approver"); - } - } }); -function set_update_request_btn(frm) { - if(frm.doc.docstatus == 1 && frm.doc.workflow_state == 'Approved' && !frm.doc.update_request){ - if(frappe.user.has_role('Shift Supervisor')){ - frm.add_custom_button(__('Update Request'), function() { - update_request(frm); - }); - } - } -}; - -function update_request(frm) { - var dialog = new frappe.ui.Dialog({ - title: 'Update Request', - fields: [ - {fieldtype: "Date", label: "From Date", fieldname: "from_date", reqd: true}, - {fieldtype: "Date", label: "To Date", fieldname: "to_date", reqd: true}, - ], - primary_action_label: __("Update"), - primary_action : function(){ - frappe.confirm( - __('Are you sure to proceed?'), - function(){ - // Yes - frappe.call({ - method: 'one_fm.overrides.attendance_request.update_request', - args: { - attendance_request: frm.doc.name, - from_date: dialog.get_value('from_date'), - to_date: dialog.get_value('to_date'), - }, - callback: function(r) { - if(!r.exc) { - frm.reload_doc(); - } - }, - freaze: true, - freaze_message: __("Update Request ..") - }); - dialog.hide(); - }, - function(){ - // No - dialog.hide(); - } - ); - } - }); - dialog.show(); -}; - validate_from_date = (frm) => { if (frm.doc.from_date < frappe.datetime.now_date()){ - frappe.throw("Atendance Request can not be created for past dates.") + if (frm.is_new()){ + frappe.throw("Atendance Request can not be created for past dates.") + }else { + frappe.throw("Atendance Request can not be updated to a past date.") + } } -} +} \ No newline at end of file diff --git a/one_fm/public/js/doctype_js/employee_checkin.js b/one_fm/public/js/doctype_js/employee_checkin.js index 4a8eb6f444..b0ab8bde4d 100644 --- a/one_fm/public/js/doctype_js/employee_checkin.js +++ b/one_fm/public/js/doctype_js/employee_checkin.js @@ -20,12 +20,10 @@ frappe.ui.form.on('Employee Checkin', { }); -validate_source_of_checkin = (frm) => { - allowed_sources = ['Mobile App', 'Mobile Web'] - if (frappe.session.user!='Administrator'){ - if(!allowed_sources.includes(frm.doc.source)){ - frappe.throw("Employee Checkin can only be via the Mobile App or Mobile Web App") - } +var validate_source_of_checkin = (frm) => { + var allowed_sources = ['Mobile App', 'Mobile Web'] + if(!allowed_sources.includes(frm.doc.source)){ + frappe.throw("Employee Checkin can only be via the Mobile App or Mobile Web App") } } diff --git a/one_fm/public/js/doctype_js/shift_request.js b/one_fm/public/js/doctype_js/shift_request.js index e9d4df3a72..20bbbeffb0 100644 --- a/one_fm/public/js/doctype_js/shift_request.js +++ b/one_fm/public/js/doctype_js/shift_request.js @@ -16,6 +16,7 @@ frappe.ui.form.on('Shift Request', { onload: function(frm){ prefillForm(frm); set_employee_filters(frm) + }, refresh: function(frm) { @@ -44,6 +45,14 @@ frappe.ui.form.on('Shift Request', { }); } + }, + purpose: function(frm){ + update_shift_role(frm); + + }, + replaced_employee: function (frm){ + update_shift_role(frm); + } }); @@ -234,4 +243,33 @@ var prefillForm = frm =>{ } }); } -} \ No newline at end of file +} + +const update_shift_role = (frm) => { + if (frm.doc.purpose == "Replace Existing Assignment" && frm.doc.replaced_employee){ + frappe.call({ + method: "frappe.client.get_list", + args: { + doctype: "Shift Assignment", + fields: ["operations_role", "shift"], + order_by: "modified desc", + filters: { + "employee": frm.doc.replaced_employee + }, + limit_page_length: 1 + }, + callback: function(r) { + if (r.message && r.message.length > 0) { + var last_doc = r.message[0]; + frm.set_value({ + operations_role: last_doc.operations_role, + operations_shift: last_doc.shift + }) + + } + } + } + + ) + } +}; \ No newline at end of file diff --git a/one_fm/templates/emails/attendance_manager_todo_assignment.html b/one_fm/templates/emails/attendance_manager_todo_assignment.html new file mode 100644 index 0000000000..dbffc3e3e7 --- /dev/null +++ b/one_fm/templates/emails/attendance_manager_todo_assignment.html @@ -0,0 +1,18 @@ + + +

Dear {{manager}},


+ +

Please note that there are pending Attendance Checks from that require your attention

+ +

You can view the documents by clicking here .

+ +
+ +

Best regards.

+ + + + + + + diff --git a/one_fm/utils.py b/one_fm/utils.py index 0c342d7c0d..a174be6ad4 100755 --- a/one_fm/utils.py +++ b/one_fm/utils.py @@ -3073,6 +3073,36 @@ def get_approver_user(employee): return frappe.db.get_value("Employee", approver, "user_id") return None + +@frappe.whitelist() +def has_super_user_role(user=None): + ''' + A method to check the user is having super user role + Default it will be the role 'Director' + User having this role can be self approve configured documents like Shift Permission. + The user having this role no need reports to, since it will be the same employee linked to the user. + + args: + user: user ID, eg: employee123@one_fm.com + + return boolean(True if super user role exists in the given user's role list) + ''' + if not user: + user = frappe.session.user + if user: + # get the user roles + user_roles = frappe.get_roles(user) + # Check if the default super user role in the user role list + if "Director" in user_roles: + return True + else: + # Get configured super user in ONEFM General Setting + super_user_role = frappe.db.get_single_value("ONEFM General Setting", "super_user_role") + # Check if the super user role exists in the user role list + if super_user_role and super_user_role in user_roles: + return True + return False + @frappe.whitelist() def get_approver(employee, date=False): ''' @@ -3124,34 +3154,6 @@ def get_approver(employee, date=False): return line_manager -@frappe.whitelist() -def has_super_user_role(user=None): - ''' - A method to check the user is having super user role - Default it will be the role 'Director' - User having this role can be self approve configured documents like Shift Permission. - The user having this role no need reports to, since it will be the same employee linked to the user. - - args: - user: user ID, eg: employee123@one_fm.com - - return boolean(True if super user role exists in the given user's role list) - ''' - if not user: - user = frappe.session.user - if user: - # get the user roles - user_roles = frappe.get_roles(user) - # Check if the default super user role in the user role list - if "Director" in user_roles: - return True - else: - # Get configured super user in ONEFM General Setting - super_user_role = frappe.db.get_single_value("ONEFM General Setting", "super_user_role") - # Check if the super user role exists in the user role list - if super_user_role and super_user_role in user_roles: - return True - return False def get_approver_for_many_employees(supervisor=None): """ @@ -3409,64 +3411,44 @@ def custom_validate_interviewer(self): def get_current_shift(employee): """ Get current shift employee should be logged into + This Method Checks if Employee has a shift and is within the checkin Range. + args: + employee: Employee ID + return dict (type, data) """ - sql = f""" - SELECT * FROM `tabShift Assignment` - WHERE employee="{employee}" AND status="Active" AND docstatus=1 AND - ('{now()}' BETWEEN start_datetime AND end_datetime) - """ - shift = frappe.db.sql(sql, as_dict=1) - if shift: # shift was checked in between start and end time - return frappe.get_doc("Shift Assignment", shift[0]) - else: # we look right and left (right for next shift) - dt = datetime.strptime(now(), '%Y-%m-%d %H:%M:%S.%f') - curtime_plus_1 = dt + timedelta(hours=1) + try: + nowtime = now_datetime() sql = f""" - SELECT * FROM `tabShift Assignment` - WHERE employee="{employee}" AND status="Active" AND docstatus=1 AND - ('{curtime_plus_1}' BETWEEN start_datetime AND end_datetime) - """ - shift = frappe.db.sql(sql, as_dict=1) - if shift: # shift was checked 1hr ahead - return frappe.get_doc("Shift Assignment", shift[0]) - else: - curtime_plus_1 = dt + timedelta(hours=-1) - sql = f""" - SELECT * FROM `tabShift Assignment` - WHERE employee="{employee}" AND status="Active" AND docstatus=1 AND - ('{curtime_plus_1}' BETWEEN start_datetime AND end_datetime) + SELECT sa.*, + DATE_SUB(sa.start_datetime, INTERVAL st.begin_check_in_before_shift_start_time MINUTE) as checkin_time, + DATE_ADD(sa.end_datetime, INTERVAL st.allow_check_out_after_shift_end_time MINUTE) as checkout_time + FROM `tabShift Assignment` sa + JOIN `tabShift Type` st ON sa.shift_type = st.name + WHERE sa.employee="{employee}" + AND sa.status="Active" + AND sa.docstatus=1 + AND (DATE('{nowtime}') = sa.start_date + OR DATE_ADD(DATE('{nowtime}'), INTERVAL 1 DAY) = sa.start_date + OR DATE('{nowtime}') = sa.end_date) """ - shift = frappe.db.sql(sql, as_dict=1) - if shift: # shift was checked 1hr in the past - return frappe.get_doc("Shift Assignment", shift[0]) - return False - -@frappe.whitelist() -def check_existing_checking(shift): - """API to determine the applicable Log type. - The api checks employee's last lcheckin log type. and determine what next log type needs to be - Returns: - True: The log in was "IN", so his next Log Type should be "OUT". - False: either no log type or last log type is "OUT", so his next Ltg Type should be "IN". - """ - checkin = frappe.db.get_list("Employee Checkin", filters={ - 'employee':shift.employee, 'shift_assignment':shift.name, - 'shift_actual_start':shift.start_datetime, - 'shift_actual_end':shift.end_datetime, - 'roster_type':shift.roster_type - }, - fields='log_type', - order_by="actual_time DESC" - ) - if checkin: - # #For Check IN - if checkin[0].log_type=='OUT': - return "IN" - #For Check OUT - else: - return "OUT" - return "IN" + shift = frappe.db.sql(sql, as_dict=1) + if shift: # shift was checked in between start and end time + data = frappe.get_doc("Shift Assignment", shift[0].name) + if shift[0].checkin_time > nowtime: + minutes = int((shift[0].checkin_time - nowtime).total_seconds() / 60) + return {"type":"Early", "data": data, "time": minutes} + elif shift[0].checkout_time < nowtime: + minutes = int((nowtime - shift[0].checkout_time).total_seconds() / 60) + return {"type":"Late", "data": data, "time": minutes} + elif shift[0].checkin_time <= nowtime <= shift[0].checkout_time: + return {"type":"On Time", "data": data, "time": 0} + else: + return False + return False + except Exception as e: + frappe.log_error(frappe.get_traceback(), "Error while getting current shift") + return False @frappe.whitelist() def check_existing(): @@ -3479,22 +3461,24 @@ def check_existing(): employee = frappe.get_value("Employee", {"user_id": frappe.session.user}) if not employee: return response("Employee not found", 404, None, "Employee not found") - curr_shift = get_current_shift(employee) + shift_exists = get_current_shift(employee) + if shift_exists['type'] == "On Time": + curr_shift = shift_exists['data'] if not curr_shift: return response("Employee not found", 404, None, "Employee not found") - log_type = check_existing_checking(curr_shift) + log_type = curr_shift.get_next_checkin_log_type() if log_type=='IN': return response("success", 200, True, "") return response("success", 200, False, "") -def fetch_attendance_manager_user_obj() -> str: - attendance_manager = frappe.get_doc("ONEFM General Setting").get("attendance_manager") +def fetch_attendance_manager_user() -> str: + attendance_manager = frappe.db.get_single_value("ONEFM General Setting", "attendance_manager") if attendance_manager: attendance_manager_user = frappe.db.get_value("Employee", {"name": attendance_manager}, "user_id") - return attendance_manager_user + if attendance_manager_user: + return attendance_manager_user return "" - def custom_toggle_notifications(user: str, enable: bool = False): try: settings = frappe.get_doc("Notification Settings", user) @@ -3640,4 +3624,20 @@ def send_work_anniversary_reminders(): others = [d for d in anniversary_persons if d != person] reminder_text = get_work_anniversary_reminder_text(others) send_work_anniversary_reminder(person_email, reminder_text, others, message, sender) - \ No newline at end of file + + + +def is_holiday(employee, date=None, raise_exception=True): + try: + date = today() if not date else date + holiday_list = get_holiday_list_for_employee(employee.name, raise_exception) + if not holiday_list: + return False, "" + holidays = frappe.db.get_value("Holiday", {"parent": holiday_list, "holiday_date": date}, ["weekly_off"], as_dict=1) + if holidays: + return (True, f"Dear {employee.employee_name}, Today is your day off. Happy Recharging!.") if holidays.weekly_off else (True, "Today is your holiday, have fun") + return False, "" + except Exception as e: + frappe.log_error(frappe.get_traceback(), "Error while validating Holiday") + return False, "" +