Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions hrms/hr/doctype/attendance/attendance.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
from frappe.utils import (
add_days,
cint,
create_batch,
cstr,
format_date,
get_datetime,
get_link_to_form,
getdate,
nowdate,
)
from frappe.utils.background_jobs import get_job

import hrms
from hrms.hr.doctype.shift_assignment.shift_assignment import has_overlapping_timings
Expand Down Expand Up @@ -347,6 +349,23 @@ def mark_bulk_attendance(data):
if not data.unmarked_days:
frappe.throw(_("Please select a date."))
return
if len(data.unmarked_days) > 10 or frappe.flags.test_bg_job:
job_id = f"process_bulk_attendance_for_employee_{data.employee}"
job = frappe.enqueue(
process_bulk_attendance_in_batches, data=data, job_id=job_id, timeout=600, deduplicate=True
)
if job:
message = _(
"Bulk attendance marking is queued with a background job. It may take a while. You can monitor the job status {0}"
).format(get_link_to_form("RQ Job", job.id, label="here"))
else:
message = _(
"Bulk attendance marking is already in progress for employee {0}. You can monitor the job status {1}"
).format(frappe.bold(data.employee), get_link_to_form("RQ Job", get_job(job_id).id, label="here"))
frappe.msgprint(message, allow_dangerous_html=True)
else:
process_bulk_attendance_in_batches(data)
frappe.msgprint(_("Attendance marked successfully."), alert=True)

for date in data.unmarked_days:
doc_dict = {
Expand All @@ -360,6 +379,31 @@ def mark_bulk_attendance(data):
attendance.submit()


def process_bulk_attendance_in_batches(data, chunk_size=20):
savepoint = "mark_bulk_attendance"
for days in create_batch(data.unmarked_days, chunk_size):
for attendance_date in days:
try:
frappe.db.savepoint(savepoint)
doc_dict = {
"doctype": "Attendance",
"employee": data.employee,
"attendance_date": getdate(attendance_date),
"status": data.status,
"half_day_status": "Absent" if data.status == "Half Day" else None,
"shift": data.shift,
}
attendance = frappe.get_doc(doc_dict).insert()
attendance.submit()
except (DuplicateAttendanceError, OverlappingShiftAttendanceError, Exception):
if not frappe.flags.in_test:
frappe.db.rollback(save_point=savepoint)
continue

if not frappe.flags.in_test:
frappe.db.commit() # nosemgrep


@frappe.whitelist()
def get_unmarked_days(employee, from_date, to_date, exclude_holidays=0):
joining_date, relieving_date = frappe.get_cached_value(
Expand Down
10 changes: 0 additions & 10 deletions hrms/hr/doctype/attendance/attendance_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ frappe.listview_settings["Attendance"] = {
return [__(doc.status), "orange", "status,=," + doc.status];
}
},

onload: function (list_view) {
let me = this;

Expand Down Expand Up @@ -109,15 +108,6 @@ frappe.listview_settings["Attendance"] = {
args: {
data: data,
},
callback: function (r) {
if (r.message === 1) {
frappe.show_alert({
message: __("Attendance Marked"),
indicator: "blue",
});
cur_dialog.hide();
}
},
});
},
);
Expand Down
32 changes: 32 additions & 0 deletions hrms/hr/doctype/attendance/test_attendance.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
getdate,
nowdate,
)
from frappe.utils.user import add_role

from erpnext.setup.doctype.employee.test_employee import make_employee

Expand All @@ -24,6 +25,7 @@
OverlappingShiftAttendanceError,
get_unmarked_days,
mark_attendance,
mark_bulk_attendance,
)
from hrms.tests.test_utils import get_first_sunday

Expand Down Expand Up @@ -241,5 +243,35 @@ def test_duplicate_attendance_when_created_from_checkins_and_tool(self):
)
self.assertEqual(len(attendances), 1)

def test_bulk_attendance_marking_through_bg(self):
user1 = "test_bg1@example.com"
user2 = "test_bg2@example.com"
employee1 = make_employee("test_bg1@example.com", company="_Test Company")
employee2 = make_employee("test_bg2@example.com", company="_Test Company")
add_role(user1, "HR Manager")
add_role(user2, "HR Manager")
frappe.flags.test_bg_job = True
frappe.set_user(user1)
data1 = frappe._dict(unmarked_days=[getdate()], employee=employee1, status="Present", shift="")
data2 = frappe._dict(unmarked_days=[getdate()], employee=employee2, status="Present", shift="")
mark_bulk_attendance(data1)
self.assertStartsWith(
frappe.message_log[-1].message, "Bulk attendance marking is queued with a background job."
)
frappe.set_user(user2)
mark_bulk_attendance(data1)
self.assertStartsWith(
frappe.message_log[-1].message, "Bulk attendance marking is already in progress for employee"
)
mark_bulk_attendance(data2)
self.assertStartsWith(
frappe.message_log[-1].message, "Bulk attendance marking is queued with a background job."
)
frappe.flags.test_bg_job = False
mark_bulk_attendance(data2)
frappe.set_user("Administrator")
attendance_records = frappe.get_all("Attendance", {"employee": employee2})
self.assertEqual(len(attendance_records), 1)

def tearDown(self):
frappe.db.rollback()
Loading