Skip to content

Commit e776663

Browse files
authored
Merge pull request #4106 from frappe/version-16-hotfix
2 parents 886684e + 58ef03b commit e776663

File tree

9 files changed

+266
-69
lines changed

9 files changed

+266
-69
lines changed

hrms/hr/doctype/attendance/attendance.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
# License: GNU General Public License v3. See license.txt
33

44

5+
from datetime import date
6+
57
import frappe
68
from frappe import _
79
from frappe.model.document import Document
10+
from frappe.query_builder.terms import ValueWrapper
811
from frappe.utils import (
912
add_days,
1013
cint,
@@ -249,10 +252,11 @@ def publish_update(self):
249252

250253

251254
@frappe.whitelist()
252-
def get_events(start, end, filters=None):
255+
def get_events(start: date | str, end: date | str, filters: str | list | None = None) -> list[dict]:
253256
employee = frappe.db.get_value("Employee", {"user_id": frappe.session.user})
254257
if not employee:
255258
return []
259+
256260
if isinstance(filters, str):
257261
import json
258262

@@ -270,7 +274,7 @@ def add_attendance(filters):
270274
"Attendance",
271275
fields=[
272276
"name",
273-
"'Attendance' as doctype",
277+
ValueWrapper("Attendance").as_("doctype"),
274278
"attendance_date",
275279
"employee_name",
276280
"status",
@@ -279,7 +283,7 @@ def add_attendance(filters):
279283
filters=filters,
280284
)
281285
for record in attendance:
282-
record["title"] = f"{record.employee_name} : {record.status}"
286+
record["title"] = f"{record['employee_name']} : {record['status']}"
283287
return attendance
284288

285289

@@ -338,7 +342,7 @@ def mark_attendance(
338342

339343

340344
@frappe.whitelist()
341-
def mark_bulk_attendance(data):
345+
def mark_bulk_attendance(data: str | dict):
342346
import json
343347

344348
if isinstance(data, str):
@@ -348,11 +352,11 @@ def mark_bulk_attendance(data):
348352
frappe.throw(_("Please select a date."))
349353
return
350354

351-
for date in data.unmarked_days:
355+
for attendance_date in data.unmarked_days:
352356
doc_dict = {
353357
"doctype": "Attendance",
354358
"employee": data.employee,
355-
"attendance_date": get_datetime(date),
359+
"attendance_date": get_datetime(attendance_date),
356360
"status": data.status,
357361
"half_day_status": "Absent" if data.status == "Half Day" else None,
358362
"shift": data.shift,

hrms/hr/doctype/attendance/test_attendance.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from hrms.hr.doctype.attendance.attendance import (
2323
DuplicateAttendanceError,
2424
OverlappingShiftAttendanceError,
25+
get_events,
2526
get_unmarked_days,
2627
mark_attendance,
2728
)
@@ -268,5 +269,29 @@ def test_duplicate_attendance_when_created_from_checkins_and_tool(self):
268269
)
269270
self.assertEqual(len(attendances), 1)
270271

272+
def test_get_events_returns_attendance(self):
273+
employee = make_employee("calendar.user@example.com", company="_Test Company")
274+
275+
attendance_name = mark_attendance(employee, getdate(), status="Present")
276+
attendance = frappe.get_value("Attendance", attendance_name, "status")
277+
278+
self.assertEqual(attendance, "Present")
279+
280+
frappe.set_user("calendar.user@example.com")
281+
try:
282+
events = get_events(start=getdate(), end=getdate())
283+
finally:
284+
frappe.set_user("Administrator")
285+
286+
self.assertTrue(events)
287+
attendance_events = [e for e in events if e.get("doctype") == "Attendance"]
288+
self.assertTrue(attendance_events)
289+
self.assertEqual(attendance_events[0].get("status"), "Present")
290+
self.assertEqual(
291+
attendance_events[0].get("employee_name"),
292+
frappe.db.get_value("Employee", employee, "employee_name"),
293+
)
294+
self.assertEqual(attendance_events[0].get("attendance_date"), getdate())
295+
271296
def tearDown(self):
272297
frappe.db.rollback()

hrms/hr/doctype/employee_onboarding/employee_onboarding.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
"default": "Pending",
7171
"fieldname": "boarding_status",
7272
"fieldtype": "Select",
73-
"label": "Status",
73+
"label": "Boarding Status",
7474
"options": "Pending\nIn Process\nCompleted",
7575
"read_only": 1
7676
},
@@ -174,11 +174,11 @@
174174
],
175175
"is_submittable": 1,
176176
"links": [],
177-
"modified": "2024-03-27 13:09:39.939109",
177+
"modified": "2026-02-05 13:46:25.874832",
178178
"modified_by": "Administrator",
179179
"module": "HR",
180180
"name": "Employee Onboarding",
181-
"naming_rule": "Expression (old style)",
181+
"naming_rule": "Expression",
182182
"owner": "Administrator",
183183
"permissions": [
184184
{
@@ -209,9 +209,10 @@
209209
"write": 1
210210
}
211211
],
212+
"row_format": "Dynamic",
212213
"sort_field": "creation",
213214
"sort_order": "DESC",
214215
"states": [],
215216
"title_field": "employee_name",
216217
"track_changes": 1
217-
}
218+
}

hrms/hr/doctype/employee_onboarding/employee_onboarding_list.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ frappe.listview_settings["Employee Onboarding"] = {
55
return [
66
__(doc.boarding_status),
77
frappe.utils.guess_colour(doc.boarding_status),
8-
"status,=," + doc.boarding_status,
8+
"boarding_status,=," + doc.boarding_status,
99
];
1010
},
1111
};

hrms/hr/report/employee_advance_summary/employee_advance_summary.py

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# For license information, please see license.txt
33

44

5+
from pypika import Order
6+
57
import frappe
68
from frappe import _, msgprint
79

@@ -27,6 +29,7 @@ def execute(filters=None):
2729
advance.advance_amount,
2830
advance.paid_amount,
2931
advance.claimed_amount,
32+
advance.return_amount,
3033
advance.status,
3134
advance.currency,
3235
]
@@ -80,6 +83,13 @@ def get_columns():
8083
"options": "currency",
8184
"width": 120,
8285
},
86+
{
87+
"label": _("Returned Amount"),
88+
"fieldname": "return_amount",
89+
"fieldtype": "Currency",
90+
"options": "currency",
91+
"width": 120,
92+
},
8393
{"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 120},
8494
{
8595
"label": _("Currency"),
@@ -92,31 +102,42 @@ def get_columns():
92102
]
93103

94104

95-
def get_conditions(filters):
96-
conditions = ""
105+
def get_advances(filters):
106+
EmployeeAdvance = frappe.qb.DocType("Employee Advance")
107+
108+
query = (
109+
frappe.qb.from_(EmployeeAdvance)
110+
.select(
111+
EmployeeAdvance.name,
112+
EmployeeAdvance.employee,
113+
EmployeeAdvance.paid_amount,
114+
EmployeeAdvance.status,
115+
EmployeeAdvance.advance_amount,
116+
EmployeeAdvance.claimed_amount,
117+
EmployeeAdvance.return_amount,
118+
EmployeeAdvance.company,
119+
EmployeeAdvance.posting_date,
120+
EmployeeAdvance.purpose,
121+
EmployeeAdvance.currency,
122+
)
123+
.where(EmployeeAdvance.docstatus < 2)
124+
)
97125

98126
if filters.get("employee"):
99-
conditions += "and employee = %(employee)s"
127+
query = query.where(EmployeeAdvance.employee == filters.employee)
128+
100129
if filters.get("company"):
101-
conditions += " and company = %(company)s"
130+
query = query.where(EmployeeAdvance.company == filters.company)
131+
102132
if filters.get("status"):
103-
conditions += " and status = %(status)s"
104-
if filters.get("from_date"):
105-
conditions += " and posting_date>=%(from_date)s"
106-
if filters.get("to_date"):
107-
conditions += " and posting_date<=%(to_date)s"
133+
query = query.where(EmployeeAdvance.status == filters.status)
108134

109-
return conditions
135+
if filters.get("from_date"):
136+
query = query.where(EmployeeAdvance.posting_date >= filters.from_date)
110137

138+
if filters.get("to_date"):
139+
query = query.where(EmployeeAdvance.posting_date <= filters.to_date)
111140

112-
def get_advances(filters):
113-
conditions = get_conditions(filters)
114-
return frappe.db.sql(
115-
"""select name, employee, paid_amount, status, advance_amount, claimed_amount, company,
116-
posting_date, purpose, currency
117-
from `tabEmployee Advance`
118-
where docstatus<2 %s order by posting_date, name desc"""
119-
% conditions,
120-
filters,
121-
as_dict=1,
141+
return query.orderby(EmployeeAdvance.posting_date, EmployeeAdvance.name, order=Order.desc).run(
142+
as_dict=True
122143
)

hrms/hr/report/shift_attendance/shift_attendance.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ frappe.query_reports["Shift Attendance"] = {
5959
fieldtype: "Check",
6060
default: 1,
6161
},
62+
{
63+
fieldname: "include_attendance_without_checkins",
64+
label: __("Include Shift Attendance Without Checkins"),
65+
fieldtype: "Check",
66+
default: 0,
67+
},
6268
],
6369
formatter: (value, row, column, data, default_formatter) => {
6470
value = default_formatter(value, row, column, data);

hrms/hr/report/shift_attendance/shift_attendance.py

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55

66
import frappe
77
from frappe import _
8+
from frappe.query_builder import Criterion
89
from frappe.utils import cint, flt, format_datetime, format_duration
910

11+
from erpnext.accounts.utils import build_qb_match_conditions
12+
1013

1114
def execute(filters=None):
1215
columns = get_columns()
@@ -130,9 +133,10 @@ def get_columns():
130133

131134

132135
def get_data(filters):
133-
query = get_query(filters)
134-
data = query.run(as_dict=True)
136+
data = get_attendance_with_checkins(filters)
135137
data = update_data(data, filters)
138+
if filters.include_attendance_without_checkins:
139+
data.extend(get_attendance_without_checkins(filters))
136140
return data
137141

138142

@@ -209,15 +213,41 @@ def get_chart_data(data):
209213
return chart
210214

211215

212-
def get_query(filters):
216+
def get_attendance_with_checkins(filters):
213217
attendance = frappe.qb.DocType("Attendance")
214218
checkin = frappe.qb.DocType("Employee Checkin")
215219
shift_type = frappe.qb.DocType("Shift Type")
216220

217221
query = (
218-
frappe.qb.from_(attendance)
222+
get_base_attendance_query(filters)
219223
.inner_join(checkin)
220224
.on(checkin.attendance == attendance.name)
225+
.select(
226+
checkin.shift_start,
227+
checkin.shift_end,
228+
checkin.shift_actual_start,
229+
checkin.shift_actual_end,
230+
shift_type.enable_late_entry_marking,
231+
shift_type.late_entry_grace_period,
232+
shift_type.enable_early_exit_marking,
233+
shift_type.early_exit_grace_period,
234+
)
235+
)
236+
for field in filters:
237+
if field == "late_entry" and not filters.consider_grace_period:
238+
query = query.where(attendance.in_time > checkin.shift_start)
239+
elif field == "early_exit" and not filters.consider_grace_period:
240+
query = query.where(attendance.out_time < checkin.shift_end)
241+
result = query.run(as_dict=True)
242+
return result
243+
244+
245+
def get_base_attendance_query(filters):
246+
attendance = frappe.qb.DocType("Attendance")
247+
shift_type = frappe.qb.DocType("Shift Type")
248+
249+
query = (
250+
frappe.qb.from_(attendance)
221251
.inner_join(shift_type)
222252
.on(attendance.shift == shift_type.name)
223253
.select(
@@ -234,36 +264,39 @@ def get_query(filters):
234264
attendance.early_exit,
235265
attendance.department,
236266
attendance.company,
237-
checkin.shift_start,
238-
checkin.shift_end,
239-
checkin.shift_actual_start,
240-
checkin.shift_actual_end,
241-
shift_type.enable_late_entry_marking,
242-
shift_type.late_entry_grace_period,
243-
shift_type.enable_early_exit_marking,
244-
shift_type.early_exit_grace_period,
245267
)
246268
.where(attendance.docstatus == 1)
247269
.groupby(attendance.name)
248270
)
249271

250-
for filter in filters:
251-
if filter == "from_date":
272+
for field in filters:
273+
if field == "from_date":
252274
query = query.where(attendance.attendance_date >= filters.from_date)
253-
elif filter == "to_date":
275+
elif field == "to_date":
254276
query = query.where(attendance.attendance_date <= filters.to_date)
255-
elif filter == "consider_grace_period":
277+
elif field in ["consider_grace_period", "include_attendance_without_checkins"]:
256278
continue
257-
elif filter == "late_entry" and not filters.consider_grace_period:
258-
query = query.where(attendance.in_time > checkin.shift_start)
259-
elif filter == "early_exit" and not filters.consider_grace_period:
260-
query = query.where(attendance.out_time < checkin.shift_end)
261279
else:
262-
query = query.where(attendance[filter] == filters[filter])
280+
query = query.where(attendance[field] == filters[field])
263281

282+
query = query.where(Criterion.all(build_qb_match_conditions("Attendance")))
264283
return query
265284

266285

286+
def get_attendance_without_checkins(filters):
287+
attendance = frappe.qb.DocType("Attendance")
288+
checkin = frappe.qb.DocType("Employee Checkin")
289+
290+
query = (
291+
get_base_attendance_query(filters)
292+
.left_join(checkin)
293+
.on(checkin.attendance == attendance.name)
294+
.where(checkin.attendance.isnull())
295+
)
296+
result = query.run(as_dict=True)
297+
return result
298+
299+
267300
def update_data(data, filters):
268301
for d in data:
269302
update_late_entry(d, filters.consider_grace_period)

0 commit comments

Comments
 (0)