Skip to content

Commit 55e907f

Browse files
authored
Merge pull request #1522 from frappe/version-15-hotfix
chore: release v15
2 parents f75977c + 93ac8bf commit 55e907f

File tree

7 files changed

+125
-42
lines changed

7 files changed

+125
-42
lines changed

hrms/hr/doctype/leave_ledger_entry/leave_ledger_entry.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import frappe
66
from frappe import _
77
from frappe.model.document import Document
8-
from frappe.utils import DATE_FORMAT, flt, getdate, today
8+
from frappe.utils import DATE_FORMAT, flt, get_link_to_form, getdate, today
99

1010

1111
class LeaveLedgerEntry(Document):
@@ -40,7 +40,11 @@ def validate_leave_allocation_against_leave_application(ledger):
4040
if leave_application_records:
4141
frappe.throw(
4242
_("Leave allocation {0} is linked with the Leave Application {1}").format(
43-
ledger.transaction_name, ", ".join(leave_application_records)
43+
ledger.transaction_name,
44+
", ".join(
45+
get_link_to_form("Leave Application", application)
46+
for application in leave_application_records
47+
),
4448
)
4549
)
4650

hrms/hr/doctype/shift_assignment/shift_assignment.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,14 @@ def get_shift_assignments(start: str, end: str, filters: str | list | None = Non
171171
if not filters:
172172
filters = []
173173

174-
filters.extend([["start_date", ">=", start], ["end_date", "<=", end], ["docstatus", "=", 1]])
174+
filters.extend([["start_date", "<=", end], ["docstatus", "=", 1]])
175+
176+
or_filters = [["end_date", ">=", start], ["end_date", "is", "not set"]]
175177

176178
return frappe.get_list(
177179
"Shift Assignment",
178180
filters=filters,
181+
or_filters=or_filters,
179182
fields=[
180183
"name",
181184
"start_date",

hrms/hr/doctype/shift_assignment/test_shift_assignment.py

+20-5
Original file line numberDiff line numberDiff line change
@@ -230,17 +230,32 @@ def test_multiple_shift_assignments_for_same_day(self):
230230
def test_calendar(self):
231231
employee1 = make_employee("[email protected]", company="_Test Company")
232232
employee2 = make_employee("[email protected]", company="_Test Company")
233+
employee3 = make_employee("[email protected]", company="_Test Company")
233234

234235
shift_type = setup_shift_type(shift_type="Shift 1", start_time="08:00:00", end_time="12:00:00")
235236
date = getdate()
236-
shift1 = make_shift_assignment(shift_type.name, employee1, date)
237-
make_shift_assignment(shift_type.name, employee2, date)
237+
shift1 = make_shift_assignment(shift_type.name, employee1, date) # 1 day
238+
make_shift_assignment(shift_type.name, employee2, date) # excluded due to employee filter
239+
make_shift_assignment(
240+
shift_type.name, employee3, add_days(date, -3), add_days(date, -2)
241+
) # excluded
242+
shift2 = make_shift_assignment(shift_type.name, employee3, add_days(date, -1), date) # 2 days
243+
shift3 = make_shift_assignment(
244+
shift_type.name, employee3, add_days(date, 1), add_days(date, 2)
245+
) # 2 days
246+
shift4 = make_shift_assignment(
247+
shift_type.name, employee3, add_days(date, 30), add_days(date, 30)
248+
) # 1 day
249+
make_shift_assignment(shift_type.name, employee3, add_days(date, 31)) # excluded
238250

239251
events = get_events(
240-
start=date, end=date, filters=[["Shift Assignment", "employee", "=", employee1, False]]
252+
start=date,
253+
end=add_days(date, 30),
254+
filters=[["Shift Assignment", "employee", "!=", employee2, False]],
241255
)
242-
self.assertEqual(len(events), 1)
243-
self.assertEqual(events[0]["name"], shift1.name)
256+
self.assertEqual(len(events), 6)
257+
for shift in events:
258+
self.assertIn(shift["name"], [shift1.name, shift2.name, shift3.name, shift4.name])
244259

245260
def test_calendar_for_night_shift(self):
246261
employee1 = make_employee("[email protected]", company="_Test Company")

hrms/hr/page/organizational_chart/organizational_chart.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def get_connections(employee: str, lft: int, rgt: int) -> int:
4343
query = (
4444
frappe.qb.from_(Employee)
4545
.select(Count(Employee.name))
46-
.where((Employee.lft > lft) & (Employee.rgt < rgt))
46+
.where((Employee.lft > lft) & (Employee.rgt < rgt) & (Employee.status == "Active"))
4747
).run()
4848

4949
return query[0][0]

hrms/hr/utils.py

+44-32
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
# License: GNU General Public License v3. See license.txt
33

44
import frappe
5-
from frappe import _
5+
from frappe import _, qb
66
from frappe.model.document import Document
7+
from frappe.query_builder import Criterion
8+
from frappe.query_builder.custom import ConstantColumn
79
from frappe.utils import (
810
add_days,
911
comma_and,
@@ -721,59 +723,69 @@ def get_matching_queries(
721723
filter_by_reference_date=None,
722724
from_reference_date=None,
723725
to_reference_date=None,
726+
common_filters=None,
724727
):
725728
"""Returns matching queries for Bank Reconciliation"""
726729
queries = []
727730
if transaction.withdrawal > 0:
728731
if "expense_claim" in document_types:
729732
ec_amount_matching = get_ec_matching_query(
730-
bank_account, company, exact_match, from_date, to_date
733+
bank_account, company, exact_match, from_date, to_date, common_filters
731734
)
732735
queries.extend([ec_amount_matching])
733736

734737
return queries
735738

736739

737-
def get_ec_matching_query(bank_account, company, exact_match, from_date=None, to_date=None):
740+
def get_ec_matching_query(
741+
bank_account, company, exact_match, from_date=None, to_date=None, common_filters=None
742+
):
738743
# get matching Expense Claim query
744+
filters = []
745+
ec = qb.DocType("Expense Claim")
746+
739747
mode_of_payments = [
740748
x["parent"]
741749
for x in frappe.db.get_all(
742750
"Mode of Payment Account", filters={"default_account": bank_account}, fields=["parent"]
743751
)
744752
]
745-
746-
mode_of_payments = "('" + "', '".join(mode_of_payments) + "' )"
747753
company_currency = get_company_currency(company)
748754

749-
filter_by_date = ""
755+
filters.append(ec.docstatus == 1)
756+
filters.append(ec.is_paid == 1)
757+
filters.append(ec.clearance_date.isnull())
758+
filters.append(ec.mode_of_payment.isin(mode_of_payments))
759+
if exact_match:
760+
filters.append(ec.total_sanctioned_amount == common_filters.amount)
761+
else:
762+
filters.append(ec.total_sanctioned_amount.gt(common_filters.amount))
763+
750764
if from_date and to_date:
751-
filter_by_date = f"AND posting_date BETWEEN '{from_date}' AND '{to_date}'"
752-
order_by = "posting_date"
753-
754-
return f"""
755-
SELECT
756-
( CASE WHEN employee = %(party)s THEN 1 ELSE 0 END
757-
+ 1 ) AS rank,
758-
'Expense Claim' as doctype,
759-
name,
760-
total_sanctioned_amount as paid_amount,
761-
'' as reference_no,
762-
'' as reference_date,
763-
employee as party,
764-
'Employee' as party_type,
765-
posting_date,
766-
{company_currency!r} as currency
767-
FROM
768-
`tabExpense Claim`
769-
WHERE
770-
total_sanctioned_amount {'= %(amount)s' if exact_match else '> 0.0'}
771-
AND docstatus = 1
772-
AND is_paid = 1
773-
AND ifnull(clearance_date, '') = ""
774-
AND mode_of_payment in {mode_of_payments}
775-
{filter_by_date}
776-
"""
765+
filters.append(ec.posting_date[from_date:to_date])
766+
767+
ref_rank = frappe.qb.terms.Case().when(ec.employee == common_filters.party, 1).else_(0)
768+
769+
ec_query = (
770+
qb.from_(ec)
771+
.select(
772+
(ref_rank + 1).as_("rank"),
773+
ec.name,
774+
ec.total_sanctioned_amount.as_("paid_amount"),
775+
ConstantColumn("").as_("reference_no"),
776+
ConstantColumn("").as_("reference_date"),
777+
ec.employee.as_("party"),
778+
ConstantColumn("Employee").as_("party_type"),
779+
ec.posting_date,
780+
ConstantColumn(company_currency).as_("currency"),
781+
)
782+
.where(Criterion.all(filters))
783+
)
784+
785+
if from_date and to_date:
786+
ec_query = ec_query.orderby(ec.posting_date)
787+
788+
return ec_query
777789

778790

779791
def notify_bulk_action_status(doctype: str, failure: list, success: list) -> None:

hrms/payroll/doctype/salary_slip/salary_slip.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ def calculate_lwp_ppl_and_absent_days_based_on_attendance(
676676

677677
# skip counting absent on holidays
678678
if not consider_marked_attendance_on_holidays and getdate(d.attendance_date) in holidays:
679-
if d.status == "Absent" or (
679+
if d.status in ["Absent", "Half Day"] or (
680680
d.leave_type
681681
and d.leave_type in leave_type_map.keys()
682682
and not leave_type_map[d.leave_type]["include_holiday"]

hrms/payroll/doctype/salary_slip/test_salary_slip.py

+49
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,55 @@ def test_consider_marked_attendance_on_holidays_with_unmarked_attendance(self):
570570
# no_of_days - period before DOJ
571571
self.assertEqual(ss.payment_days, no_of_days[0] - 3 - 1)
572572

573+
@change_settings(
574+
"Payroll Settings",
575+
{
576+
"payroll_based_on": "Attendance",
577+
"consider_unmarked_attendance_as": "Present",
578+
"include_holidays_in_total_working_days": 1,
579+
"consider_marked_attendance_on_holidays": 0,
580+
},
581+
)
582+
def test_consider_marked_attendance_on_holidays_with_half_day_on_holiday(self):
583+
from erpnext.setup.doctype.holiday_list.holiday_list import is_holiday
584+
585+
no_of_days = get_no_of_days()
586+
month_start_date, month_end_date = get_first_day(nowdate()), get_last_day(nowdate())
587+
joining_date = add_days(month_start_date, 3)
588+
589+
emp_id = make_employee(
590+
591+
status="Active",
592+
date_of_joining=joining_date,
593+
relieving_date=None,
594+
)
595+
596+
for days in range(date_diff(month_end_date, joining_date) + 1):
597+
date = add_days(joining_date, days)
598+
if not is_holiday("Salary Slip Test Holiday List", date):
599+
mark_attendance(emp_id, date, "Present", ignore_validate=True)
600+
601+
# mark half day on holiday
602+
first_sunday = get_first_sunday(for_date=joining_date, find_after_for_date=True)
603+
mark_attendance(emp_id, first_sunday, "Half Day", ignore_validate=True)
604+
605+
ss = make_employee_salary_slip(
606+
emp_id,
607+
"Monthly",
608+
"Test Salary Slip With Holidays Included",
609+
)
610+
611+
self.assertEqual(ss.total_working_days, no_of_days[0])
612+
# no_of_days - period before DOJ
613+
self.assertEqual(ss.payment_days, no_of_days[0] - 3)
614+
615+
# enable consider marked attendance on holidays
616+
frappe.db.set_single_value("Payroll Settings", "consider_marked_attendance_on_holidays", 1)
617+
ss.save()
618+
self.assertEqual(ss.total_working_days, no_of_days[0])
619+
# no_of_days - period before DOJ - 0.5 LWP on holiday (half day present)
620+
self.assertEqual(ss.payment_days, no_of_days[0] - 3 - 0.5)
621+
573622
@change_settings("Payroll Settings", {"include_holidays_in_total_working_days": 1})
574623
def test_payment_days(self):
575624
from hrms.payroll.doctype.salary_structure.test_salary_structure import (

0 commit comments

Comments
 (0)