diff --git a/hrms/hr/doctype/leave_allocation/leave_allocation.js b/hrms/hr/doctype/leave_allocation/leave_allocation.js index 633a209389..a11b61f5d4 100755 --- a/hrms/hr/doctype/leave_allocation/leave_allocation.js +++ b/hrms/hr/doctype/leave_allocation/leave_allocation.js @@ -26,7 +26,7 @@ frappe.ui.form.on("Leave Allocation", { }, refresh: function (frm) { - if (frm.doc.docstatus === 1 && frm.doc.expired) { + if (frm.doc.docstatus === 1 && !frm.doc.expired) { var valid_expiry = moment(frappe.datetime.get_today()).isBetween( frm.doc.from_date, frm.doc.to_date, diff --git a/hrms/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/hrms/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index 0f87255457..dd1718aef3 100644 --- a/hrms/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/hrms/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -1,7 +1,6 @@ # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - import frappe from frappe import _ from frappe.model.document import Document @@ -185,6 +184,12 @@ def get_remaining_leaves(allocation): @frappe.whitelist() def expire_allocation(allocation, expiry_date=None): """expires non-carry forwarded allocation""" + import json + + if isinstance(allocation, str): + allocation = json.loads(allocation) + allocation = frappe.get_doc("Leave Allocation", allocation["name"]) + leaves = get_remaining_leaves(allocation) expiry_date = expiry_date if expiry_date else allocation.to_date diff --git a/hrms/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py b/hrms/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py index c8084769b1..668b758665 100644 --- a/hrms/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py +++ b/hrms/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py @@ -1,9 +1,44 @@ # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -# import frappe +import frappe from frappe.tests.utils import FrappeTestCase +from frappe.utils.data import add_to_date, today + +from erpnext.setup.doctype.employee.test_employee import make_employee + +from hrms.hr.doctype.leave_ledger_entry.leave_ledger_entry import expire_allocation class TestLeaveLedgerEntry(FrappeTestCase): - pass + def setUp(self): + emp_id = make_employee("test_leave_allocation@salary.com", company="_Test Company") + self.employee = frappe.get_doc("Employee", emp_id) + + def test_expire_allocation(self): + import json + + allocation = { + "doctype": "Leave Allocation", + "__islocal": 1, + "employee": self.employee.name, + "employee_name": self.employee.employee_name, + "leave_type": "_Test Leave Type", + "from_date": today(), + "to_date": add_to_date(today(), days=30), + "new_leaves_allocated": 5, + "docstatus": 1, + } + + allocation = frappe.get_doc(allocation).save() + + expire_allocation(json.dumps(allocation.as_dict())) + allocation.reload() + + self.assertEqual(allocation.expired, 1) + + def tearDown(self): + frappe.db.rollback() + + +test_dependencies = ["Employee", "Leave Type"] diff --git a/hrms/hr/report/employee_advance_summary/employee_advance_summary.js b/hrms/hr/report/employee_advance_summary/employee_advance_summary.js index 68178eb843..6d2be8e306 100644 --- a/hrms/hr/report/employee_advance_summary/employee_advance_summary.js +++ b/hrms/hr/report/employee_advance_summary/employee_advance_summary.js @@ -15,7 +15,7 @@ frappe.query_reports["Employee Advance Summary"] = { fieldname: "from_date", label: __("From Date"), fieldtype: "Date", - default: frappe.defaults.get_user_default("year_start_date"), + default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1], width: "80", }, { diff --git a/hrms/hr/report/employee_leave_balance/employee_leave_balance.js b/hrms/hr/report/employee_leave_balance/employee_leave_balance.js index b87e60e13e..6e2964af6d 100644 --- a/hrms/hr/report/employee_leave_balance/employee_leave_balance.js +++ b/hrms/hr/report/employee_leave_balance/employee_leave_balance.js @@ -8,14 +8,12 @@ frappe.query_reports["Employee Leave Balance"] = { label: __("From Date"), fieldtype: "Date", reqd: 1, - default: frappe.defaults.get_default("year_start_date"), }, { fieldname: "to_date", label: __("To Date"), fieldtype: "Date", reqd: 1, - default: frappe.defaults.get_default("year_end_date"), }, { label: __("Company"), diff --git a/hrms/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.js b/hrms/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.js index ee6ad2af3c..0f8e753291 100644 --- a/hrms/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.js +++ b/hrms/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.js @@ -58,11 +58,17 @@ frappe.query_reports["Monthly Attendance Sheet"] = { fieldtype: "Select", options: ["", "Branch", "Grade", "Department", "Designation"], }, + { + fieldname: "include_company_descendants", + label: __("Include Company Descendants"), + fieldtype: "Check", + default: 1, + }, { fieldname: "summarized_view", label: __("Summarized View"), fieldtype: "Check", - Default: 0, + default: 0, }, ], onload: function () { diff --git a/hrms/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/hrms/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py index e74bac6a82..9823a30ab9 100644 --- a/hrms/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py +++ b/hrms/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py @@ -9,6 +9,7 @@ from frappe import _ from frappe.query_builder.functions import Count, Extract, Sum from frappe.utils import cint, cstr, getdate +from frappe.utils.nestedset import get_descendants_of Filters = frappe._dict @@ -31,6 +32,11 @@ def execute(filters: Filters | None = None) -> tuple: if not (filters.month and filters.year): frappe.throw(_("Please select month and year.")) + if filters.company: + filters.companies = [filters.company] + if filters.include_company_descendants: + filters.companies.extend(get_descendants_of("Company", filters.company)) + attendance_map = get_attendance_map(filters) if not attendance_map: frappe.msgprint(_("No attendance records found."), alert=True, indicator="orange") @@ -223,7 +229,7 @@ def get_attendance_map(filters: Filters) -> dict: for d in attendance_list: if d.status == "On Leave": - leave_map.setdefault(d.employee, []).append(d.day_of_month) + leave_map.setdefault(d.employee, {}).setdefault(d.shift, []).append(d.day_of_month) continue if d.shift is None: @@ -234,13 +240,14 @@ def get_attendance_map(filters: Filters) -> dict: # leave is applicable for the entire day so all shifts should show the leave entry for employee, leave_days in leave_map.items(): - # no attendance records exist except leaves - if employee not in attendance_map: - attendance_map.setdefault(employee, {}).setdefault(None, {}) + for assigned_shift, days in leave_days.items(): + # no attendance records exist except leaves + if employee not in attendance_map: + attendance_map.setdefault(employee, {}).setdefault(assigned_shift, {}) - for day in leave_days: - for shift in attendance_map[employee].keys(): - attendance_map[employee][shift][day] = "On Leave" + for day in days: + for shift in attendance_map[employee].keys(): + attendance_map[employee][shift][day] = "On Leave" return attendance_map @@ -257,7 +264,7 @@ def get_attendance_records(filters: Filters) -> list[dict]: ) .where( (Attendance.docstatus == 1) - & (Attendance.company == filters.company) + & (Attendance.company.isin(filters.companies)) & (Extract("month", Attendance.attendance_date) == filters.month) & (Extract("year", Attendance.attendance_date) == filters.year) ) @@ -288,7 +295,7 @@ def get_employee_related_details(filters: Filters) -> tuple[dict, list]: Employee.company, Employee.holiday_list, ) - .where(Employee.company == filters.company) + .where(Employee.company.isin(filters.companies)) ) if filters.employee: @@ -305,7 +312,7 @@ def get_employee_related_details(filters: Filters) -> tuple[dict, list]: emp_map = {} if group_by: - group_key = lambda d: d[group_by] # noqa + group_key = lambda d: "" if d[group_by] is None else d[group_by] # noqa for parameter, employees in groupby(sorted(employee_details, key=group_key), key=group_key): group_by_param_values.append(parameter) emp_map.setdefault(parameter, frappe._dict()) @@ -466,7 +473,7 @@ def get_attendance_summary_and_days(employee: str, filters: Filters) -> tuple[di .where( (Attendance.docstatus == 1) & (Attendance.employee == employee) - & (Attendance.company == filters.company) + & (Attendance.company.isin(filters.companies)) & (Extract("month", Attendance.attendance_date) == filters.month) & (Extract("year", Attendance.attendance_date) == filters.year) ) @@ -479,7 +486,7 @@ def get_attendance_summary_and_days(employee: str, filters: Filters) -> tuple[di .where( (Attendance.docstatus == 1) & (Attendance.employee == employee) - & (Attendance.company == filters.company) + & (Attendance.company.isin(filters.companies)) & (Extract("month", Attendance.attendance_date) == filters.month) & (Extract("year", Attendance.attendance_date) == filters.year) ) @@ -543,7 +550,7 @@ def get_leave_summary(employee: str, filters: Filters) -> dict[str, float]: .where( (Attendance.employee == employee) & (Attendance.docstatus == 1) - & (Attendance.company == filters.company) + & (Attendance.company.isin(filters.companies)) & ((Attendance.leave_type.isnotnull()) | (Attendance.leave_type != "")) & (Extract("month", Attendance.attendance_date) == filters.month) & (Extract("year", Attendance.attendance_date) == filters.year) @@ -577,7 +584,7 @@ def get_entry_exits_summary(employee: str, filters: Filters) -> dict[str, float] .where( (Attendance.docstatus == 1) & (Attendance.employee == employee) - & (Attendance.company == filters.company) + & (Attendance.company.isin(filters.companies)) & (Extract("month", Attendance.attendance_date) == filters.month) & (Extract("year", Attendance.attendance_date) == filters.year) ) diff --git a/hrms/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py b/hrms/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py index 71fa9745e1..ad2066cd6f 100644 --- a/hrms/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py +++ b/hrms/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py @@ -15,7 +15,7 @@ make_holiday_list, make_leave_application, ) -from hrms.tests.test_utils import get_first_day_for_prev_month +from hrms.tests.test_utils import create_company, get_first_day_for_prev_month class TestMonthlyAttendanceSheet(FrappeTestCase): @@ -41,6 +41,9 @@ def test_monthly_attendance_sheet_report(self): mark_attendance(self.employee, previous_month_first + relativedelta(days=1), "Present") mark_attendance(self.employee, previous_month_first + relativedelta(days=2), "On Leave") + employee_on_leave_with_shift = make_employee("employee@leave.com", company=self.company) + mark_attendance(employee_on_leave_with_shift, previous_month_first, "On Leave", "Day Shift") + filters = frappe._dict( { "month": previous_month_first.month, @@ -50,14 +53,14 @@ def test_monthly_attendance_sheet_report(self): ) report = execute(filters=filters) - record = report[1][0] datasets = report[3]["data"]["datasets"] absent = datasets[0]["values"] present = datasets[1]["values"] leaves = datasets[2]["values"] # ensure correct attendance is reflected on the report - self.assertEqual(self.employee, record.get("employee")) + self.assertEqual(self.employee, report[1][0].get("employee")) + self.assertEqual("Day Shift", report[1][1].get("shift")) self.assertEqual(absent[0], 1) self.assertEqual(present[1], 1) self.assertEqual(leaves[2], 1) @@ -205,6 +208,11 @@ def test_attendance_with_group_by_filter(self): mark_attendance(self.employee, previous_month_first + relativedelta(days=2), "On Leave") mark_attendance(self.employee, previous_month_first + relativedelta(days=3), "Present") + departmentless_employee = make_employee( + "emp@departmentless.com", company=self.company, department=None + ) + mark_attendance(departmentless_employee, previous_month_first + relativedelta(days=3), "Present") + filters = frappe._dict( { "month": previous_month_first.month, @@ -276,6 +284,35 @@ def test_attendance_with_employee_filter(self): self.assertEqual(present[1], 1) self.assertEqual(leaves[2], 1) + def test_attendance_with_company_filter(self): + create_company("Test Parent Company", is_group=1) + create_company("Test Child Company", is_group=1, parent_company="Test Parent Company") + create_company("Test Grandchild Company", parent_company="Test Child Company") + + employee1 = make_employee("test_employee@parent.com", company="Test Parent Company") + employee2 = make_employee("test_employee@child.com", company="Test Child Company") + employee3 = make_employee("test_employee@grandchild.com", company="Test Grandchild Company") + + previous_month_first = get_first_day_for_prev_month() + mark_attendance(employee1, previous_month_first, "Present") + mark_attendance(employee2, previous_month_first, "Present") + mark_attendance(employee3, previous_month_first, "Present") + + filters = frappe._dict( + { + "month": previous_month_first.month, + "year": previous_month_first.year, + "company": "Test Parent Company", + "include_company_descendants": 1, + } + ) + report = execute(filters=filters) + self.assertEqual(len(report[1]), 3) + + filters.include_company_descendants = 0 + report = execute(filters=filters) + self.assertEqual(len(report[1]), 1) + def test_attendance_with_employee_filter_and_summarized_view(self): previous_month_first = get_first_day_for_prev_month() diff --git a/hrms/payroll/report/income_tax_computation/income_tax_computation.py b/hrms/payroll/report/income_tax_computation/income_tax_computation.py index cd27e90c5d..d6a814ed23 100644 --- a/hrms/payroll/report/income_tax_computation/income_tax_computation.py +++ b/hrms/payroll/report/income_tax_computation/income_tax_computation.py @@ -442,7 +442,7 @@ def get_data_for_eval(self, emp: str, emp_details: dict) -> tuple: salary_slip = frappe.new_doc("Salary Slip") salary_slip.employee = emp salary_slip.salary_structure = emp_details.salary_structure - salary_slip.start_date = self.payroll_period_start_date + salary_slip.start_date = max(self.payroll_period_start_date, emp_details.date_of_joining) salary_slip.payroll_frequency = frappe.db.get_value( "Salary Structure", emp_details.salary_structure, "payroll_frequency" ) diff --git a/hrms/tests/test_utils.py b/hrms/tests/test_utils.py index 8658c32c5b..595782c543 100644 --- a/hrms/tests/test_utils.py +++ b/hrms/tests/test_utils.py @@ -88,7 +88,7 @@ def add_date_to_holiday_list(date: str, holiday_list: str) -> None: holiday_list.save() -def create_company(name: str = "_Test Company"): +def create_company(name: str = "_Test Company", is_group: 0 | 1 = 0, parent_company: str | None = None): if frappe.db.exists("Company", name): return frappe.get_doc("Company", name) @@ -98,6 +98,8 @@ def create_company(name: str = "_Test Company"): "company_name": name, "default_currency": "INR", "country": "India", + "is_group": is_group, + "parent_company": parent_company, } ).insert()