Skip to content

feat(Attendance): Half Day Present / Half Day Leave #2933

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 22 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1a937e9
feat: new field in attendance doctype to indicate the status of other…
asmitahase Mar 6, 2025
02f557e
feat: update conditions for duplicate attendance record validation
asmitahase Mar 17, 2025
3a9f5b1
feat: update half day attendance with employee checkin details
asmitahase Mar 17, 2025
5b472ca
fix: filters to get existing half day attendance
asmitahase Mar 24, 2025
e6d2858
feat: update existing half day attenadnce created from employee check…
asmitahase Mar 24, 2025
1b044ff
feat: link two half day leaves to attendance document
asmitahase Mar 31, 2025
d6089f4
fix: null check on leave records while creating attendance
asmitahase Apr 1, 2025
aaa08a5
fix: update all attendance status from update attendance, let the val…
asmitahase Apr 1, 2025
2afa972
fix: grave mistake of using insert instead of save
asmitahase Apr 1, 2025
ece4461
fix: possible values for half_day_status
asmitahase Apr 1, 2025
299e5a5
fix: update attendance status and leave application details after sub…
asmitahase Apr 1, 2025
3ba3ab9
test: modified exisitng tests to accomodate half-day feature
asmitahase Apr 1, 2025
934f9c5
fix: duplicate attendance validation condition
asmitahase Apr 1, 2025
f03c1a6
refactor: show shift and attendance request fields only of they exist
asmitahase Apr 1, 2025
a9b9f76
fix: set half_day_status as None instead of empty string
asmitahase Apr 1, 2025
c8769d5
fix: show validation error if user tries to create a leave applicatio…
asmitahase Apr 1, 2025
5e68de7
Revert "fix: possible values for half_day_status"
asmitahase Apr 1, 2025
33dcdcf
fix: mark status of other half as present when leave application is c…
asmitahase Apr 1, 2025
a62541d
feat: link checkins to exisitng half day attendance and update the de…
asmitahase Apr 1, 2025
43fb630
fix: using uninitiated variable
asmitahase Apr 1, 2025
a480b25
fix: modify query to get leave summary
asmitahase Apr 1, 2025
a6c6fbe
test: fixed leave type not found error
asmitahase Apr 1, 2025
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
13 changes: 12 additions & 1 deletion hrms/hr/doctype/attendance/attendance.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ frappe.ui.form.on("Attendance", {
return {
query: "erpnext.controllers.queries.employee_query",
};
});
}),
(frm.fields_dict["leave_details"].grid.get_field("leave_application").get_query =
function (doc) {
return {
filters: {
company: doc.company,
employee: doc.employee,
from_date: ["<=", doc.attendance_date],
to_date: [">=", doc.attendance_date],
},
};
});
},
});
61 changes: 35 additions & 26 deletions hrms/hr/doctype/attendance/attendance.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@
"naming_series",
"employee",
"employee_name",
"working_hours",
"status",
"leave_type",
"leave_application",
"half_day_status",
"column_break0",
"attendance_date",
"company",
Expand All @@ -25,9 +23,12 @@
"in_time",
"out_time",
"column_break_18",
"late_entry",
"working_hours",
"early_exit",
"amended_from"
"late_entry",
"amended_from",
"section_break_vxxb",
"leave_details"
],
"fields": [
{
Expand Down Expand Up @@ -90,25 +91,6 @@
"reqd": 1,
"search_index": 1
},
{
"depends_on": "eval:in_list([\"On Leave\", \"Half Day\"], doc.status)",
"fieldname": "leave_type",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Leave Type",
"mandatory_depends_on": "eval:in_list([\"On Leave\", \"Half Day\"], doc.status)",
"oldfieldname": "leave_type",
"oldfieldtype": "Link",
"options": "Leave Type"
},
{
"fieldname": "leave_application",
"fieldtype": "Link",
"label": "Leave Application",
"no_copy": 1,
"options": "Leave Application",
"read_only": 1
},
{
"fieldname": "column_break0",
"fieldtype": "Column Break",
Expand Down Expand Up @@ -146,12 +128,14 @@
"read_only": 1
},
{
"depends_on": "shift",
"fieldname": "shift",
"fieldtype": "Link",
"label": "Shift",
"options": "Shift Type"
},
{
"depends_on": "attendance_request",
"fieldname": "attendance_request",
"fieldtype": "Link",
"label": "Attendance Request",
Expand All @@ -170,12 +154,14 @@
},
{
"default": "0",
"depends_on": "shift",
"fieldname": "late_entry",
"fieldtype": "Check",
"label": "Late Entry"
},
{
"default": "0",
"depends_on": "shift",
"fieldname": "early_exit",
"fieldtype": "Check",
"label": "Early Exit"
Expand All @@ -202,13 +188,35 @@
{
"fieldname": "column_break_18",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.status==\"Half Day\";",
"fieldname": "half_day_status",
"fieldtype": "Select",
"label": "Status for Other Half",
"no_copy": 1,
"options": "\nPresent\nAbsent"
},
{
"depends_on": "eval:in_list([\"On Leave\", \"Half Day\"], doc.status)",
"fieldname": "leave_details",
"fieldtype": "Table",
"label": "Leave Details",
"mandatory_depends_on": "eval:in_list([\"On Leave\", \"Half Day\"], doc.status)",
"no_copy": 1,
"options": "Leave Application Detail"
},
{
"fieldname": "section_break_vxxb",
"fieldtype": "Section Break"
}
],
"grid_page_length": 50,
"icon": "fa fa-ok",
"idx": 1,
"is_submittable": 1,
"links": [],
"modified": "2025-01-31 11:45:54.846562",
"modified": "2025-04-01 12:58:32.491823",
"modified_by": "Administrator",
"module": "HR",
"name": "Attendance",
Expand Down Expand Up @@ -267,10 +275,11 @@
"share": 1
}
],
"row_format": "Dynamic",
"search_fields": "employee,employee_name,attendance_date,status",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"title_field": "employee_name",
"track_changes": 1
}
}
47 changes: 18 additions & 29 deletions hrms/hr/doctype/attendance/attendance.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class OverlappingShiftAttendanceError(frappe.ValidationError):


class Attendance(Document):
def before_insert(self):
self.half_day_status = None if self.half_day_status == "" else self.half_day_status

def validate(self):
from erpnext.controllers.status_updater import validate_status

Expand Down Expand Up @@ -83,6 +86,7 @@ def get_duplicate_attendance_record(self) -> str | None:
& (Attendance.docstatus < 2)
& (Attendance.attendance_date == self.attendance_date)
& (Attendance.name != self.name)
& (Attendance.half_day_status.isnull())
)
.for_update()
)
Expand Down Expand Up @@ -159,37 +163,22 @@ def check_leave_record(self):
& (LeaveApplication.docstatus == 1)
)
).run(as_dict=True)

if leave_record:
for d in leave_record:
self.leave_type = d.leave_type
self.leave_application = d.name
if d.half_day_date == getdate(self.attendance_date):
self.status = "Half Day"
frappe.msgprint(
_("Employee {0} on Half day on {1}").format(
self.employee, format_date(self.attendance_date)
)
)
else:
self.status = "On Leave"
frappe.msgprint(
_("Employee {0} is on Leave on {1}").format(
self.employee, format_date(self.attendance_date)
)
)

if self.status in ("On Leave", "Half Day"):
if not leave_record:
frappe.msgprint(
_("No leave record found for employee {0} on {1}").format(
self.employee, format_date(self.attendance_date)
),
alert=1,
if len(leave_record) > 1:
self.status = "On Leave"
elif leave_record[0].half_day_date == getdate(self.attendance_date):
self.status = "Half Day"
self.half_day_status = "Absent"
self.set("leave_details", [])
for record in leave_record:
self.append(
"leave_details",
{
"leave_application": record["name"],
"leave_type": record["leave_type"],
"half_day": record["half_day"],
},
)
elif self.leave_type:
self.leave_type = None
self.leave_application = None

def validate_employee(self):
emp = frappe.db.sql(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def validate_attendance(self):
filters={
"attendance_date": ["between", (self.work_from_date, self.work_end_date)],
"status": ("in", ["Present", "Work From Home", "Half Day"]),
"half_day_status": ("!=", "Absent"),
"docstatus": 1,
"employee": self.employee,
},
Expand Down
64 changes: 49 additions & 15 deletions hrms/hr/doctype/employee_checkin/employee_checkin.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,21 +223,39 @@ def mark_attendance_and_link_log(
elif attendance_status in ("Present", "Absent", "Half Day"):
try:
frappe.db.savepoint("attendance_creation")
attendance = frappe.new_doc("Attendance")
attendance.update(
{
"doctype": "Attendance",
"employee": employee,
"attendance_date": attendance_date,
"status": attendance_status,
"working_hours": working_hours,
"shift": shift,
"late_entry": late_entry,
"early_exit": early_exit,
"in_time": in_time,
"out_time": out_time,
}
).submit()
if attendance_status == "Half Day" and (
attendance := get_existing_half_day_attendance(employee, attendance_date)
):
frappe.db.set_value(
"Attendance",
attendance.name,
{
"half_day_status": "Present",
"working_hours": working_hours,
"shift": shift,
"late_entry": late_entry,
"early_exit": early_exit,
"in_time": in_time,
"out_time": out_time,
},
)
else:
attendance = frappe.new_doc("Attendance")
attendance.update(
{
"doctype": "Attendance",
"employee": employee,
"attendance_date": attendance_date,
"status": attendance_status,
"working_hours": working_hours,
"shift": shift,
"late_entry": late_entry,
"early_exit": early_exit,
"in_time": in_time,
"out_time": out_time,
"half_day_status": "Absent" if attendance_status == "Half Day" else None,
}
).submit()

if attendance_status == "Absent":
attendance.add_comment(
Expand All @@ -254,6 +272,22 @@ def mark_attendance_and_link_log(
frappe.throw(_("{} is an invalid Attendance Status.").format(attendance_status))


def get_existing_half_day_attendance(employee, attendance_date):
attendance_name = frappe.db.exists(
"Attendance",
{
"employee": employee,
"attendance_date": attendance_date,
"status": "Half Day",
"half_day_status": "Absent",
},
)
if attendance_name:
attenadance_doc = frappe.get_doc("Attendance", attendance_name)
return attenadance_doc
return None


def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
"""Given a set of logs in chronological order calculates the total working hours based on the parameters.
Zero is returned for all invalid cases.
Expand Down
Loading
Loading