diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1cc6649f62..52dfd03c45 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,7 +70,7 @@ jobs: run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts - name: Cache pip - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }} @@ -79,7 +79,7 @@ jobs: ${{ runner.os }}- - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -94,7 +94,7 @@ jobs: id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - - uses: actions/cache@v2 + - uses: actions/cache@v4 id: yarn-cache with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} diff --git a/hrms/hr/doctype/employee_checkin/employee_checkin.js b/hrms/hr/doctype/employee_checkin/employee_checkin.js index 2d86442b1f..9b9a2a98c3 100644 --- a/hrms/hr/doctype/employee_checkin/employee_checkin.js +++ b/hrms/hr/doctype/employee_checkin/employee_checkin.js @@ -23,22 +23,27 @@ frappe.ui.form.on("Employee Checkin", { add_fetch_shift_button(frm) { if (frm.doc.attendace) return; frm.add_custom_button(__("Fetch Shift"), function () { - const previous_shift = frm.doc.shift; frappe.call({ method: "fetch_shift", doc: frm.doc, freeze: true, freeze_message: __("Fetching Shift"), callback: function () { - if (previous_shift === frm.doc.shift) return; - frm.dirty(); - frm.save(); - frappe.show_alert({ - message: __("Shift has been successfully updated to {0}.", [ - frm.doc.shift, - ]), - indicator: "green", - }); + if (frm.doc.shift) { + frappe.show_alert({ + message: __("Shift has been successfully updated to {0}.", [ + frm.doc.shift, + ]), + indicator: "green", + }); + frm.dirty(); + frm.save(); + } else { + frappe.show_alert({ + message: __("No valid shift found for log time"), + indicator: "orange", + }); + } }, }); }); diff --git a/hrms/hr/doctype/employee_checkin/test_employee_checkin.py b/hrms/hr/doctype/employee_checkin/test_employee_checkin.py index 98f7388a6d..79371a7583 100644 --- a/hrms/hr/doctype/employee_checkin/test_employee_checkin.py +++ b/hrms/hr/doctype/employee_checkin/test_employee_checkin.py @@ -583,6 +583,34 @@ def test_bulk_fetch_shift(self): # shift does not change since attendance is already marked self.assertEqual(log2.shift, shift1.name) + def test_bulk_fetch_shift_if_shift_settings_change_for_the_same_shift(self): + emp1 = make_employee("bulkemp1@example.com", company="_Test Company") + emp2 = make_employee("bulkemp2@example.com", company="_Test Company") + + # 8 - 12, + shift = setup_shift_type(shift_type="Test Bulk Shift") + date = getdate() + make_shift_assignment(shift.name, emp1, date) + make_shift_assignment(shift.name, emp2, date) + + timestamp = datetime.combine(date, get_time("08:00:00")) + # shift actual start is `current date 07:00:00` + log1 = make_checkin(emp1, timestamp) + self.assertEqual(log1.shift_actual_start, datetime.combine(date, get_time("07:00:00"))) + log2 = make_checkin(emp2, timestamp) + self.assertEqual(log2.shift_actual_start, datetime.combine(date, get_time("07:00:00"))) + + # change shift settings like check in buffer from 60 minutes to 120 minutes + # so now shift actual start is `current date 06:00:00` + shift.begin_check_in_before_shift_start_time = 120 + shift.save() + bulk_fetch_shift([log1.name, log2.name]) + # shift changes according to the new assignment + log1.reload() + self.assertEqual(log1.shift_actual_start, datetime.combine(date, get_time("06:00:00"))) + log2.reload() + self.assertEqual(log2.shift_actual_start, datetime.combine(date, get_time("06:00:00"))) + def make_n_checkins(employee, n, hours_to_reverse=1): logs = [make_checkin(employee, now_datetime() - timedelta(hours=hours_to_reverse, minutes=n + 1))] diff --git a/hrms/hr/doctype/full_and_final_statement/full_and_final_statement.py b/hrms/hr/doctype/full_and_final_statement/full_and_final_statement.py index dd1a4ba2e6..4d334c6ecd 100644 --- a/hrms/hr/doctype/full_and_final_statement/full_and_final_statement.py +++ b/hrms/hr/doctype/full_and_final_statement/full_and_final_statement.py @@ -6,6 +6,11 @@ from frappe.model.document import Document from frappe.utils import flt, get_link_to_form, today +from hrms.hr.doctype.full_and_final_statement.full_and_final_statement_loan_utils import ( + cancel_loan_repayment, + process_loan_accrual, +) + class FullandFinalStatement(Document): def before_insert(self): @@ -22,8 +27,12 @@ def before_submit(self): self.validate_settlement("receivables") self.validate_assets() + def on_submit(self): + process_loan_accrual(self) + def on_cancel(self): self.ignore_linked_doctypes = ("GL Entry",) + cancel_loan_repayment(self) def validate_relieving_date(self): if not self.relieving_date: diff --git a/hrms/hr/doctype/full_and_final_statement/full_and_final_statement_loan_utils.py b/hrms/hr/doctype/full_and_final_statement/full_and_final_statement_loan_utils.py new file mode 100644 index 0000000000..530e6ece2b --- /dev/null +++ b/hrms/hr/doctype/full_and_final_statement/full_and_final_statement_loan_utils.py @@ -0,0 +1,101 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from typing import TYPE_CHECKING + +import frappe +from frappe import _ + +from hrms.payroll.doctype.salary_slip.salary_slip_loan_utils import if_lending_app_installed + +if TYPE_CHECKING: + from hrms.payroll.doctype.full_and_final_statement.full_and_final_statement import FullandFinalStatement + + +@if_lending_app_installed +def process_loan_accrual(doc: "FullandFinalStatement"): + from lending.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import ( + make_loan_interest_accrual_entry, + ) + from lending.loan_management.doctype.loan_repayment.loan_repayment import ( + calculate_amounts, + create_repayment_entry, + get_pending_principal_amount, + ) + + loan_receivables = [] + for receivable in doc.receivables: + if receivable.component != "Loan": + continue + + loan_receivables.append(receivable.reference_document) + + for loan in loan_receivables: + loan_doc = frappe.get_doc("Loan", loan) + loan_repayment_schedule = frappe.get_doc("Loan Repayment Schedule", {"loan": loan, "docstatus": 1}) + if loan_repayment_schedule.repayment_schedule: + amounts = [] + for repayment_schedule in loan_repayment_schedule.repayment_schedule: + amounts = calculate_amounts(loan, doc.transaction_date, "Normal Repayment") + pending_principal_amount = get_pending_principal_amount(loan_doc) + if not repayment_schedule.is_accrued: + args = frappe._dict( + { + "loan": loan, + "applicant_type": loan_doc.applicant_type, + "applicant": loan_doc.applicant, + "interest_income_account": loan_doc.interest_income_account, + "loan_account": loan_doc.loan_account, + "pending_principal_amount": amounts["pending_principal_amount"], + "payable_principal": repayment_schedule.principal_amount, + "interest_amount": repayment_schedule.interest_amount, + "total_pending_interest_amount": pending_principal_amount, + "penalty_amount": amounts["penalty_amount"], + "posting_date": doc.transaction_date, + "repayment_schedule_name": repayment_schedule.name, + "accrual_type": "Regular", + "due_date": doc.transaction_date, + } + ) + make_loan_interest_accrual_entry(args) + frappe.db.set_value("Repayment Schedule", repayment_schedule.name, "is_accrued", 1) + + repayment_entry = create_repayment_entry( + loan, + doc.employee, + doc.company, + doc.transaction_date, + loan_doc.loan_product, + "Normal Repayment", + amounts["interest_amount"], + amounts["pending_principal_amount"], + receivable.amount, + ) + + repayment_entry.save() + repayment_entry.submit() + + +@if_lending_app_installed +def cancel_loan_repayment(doc: "FullandFinalStatement"): + loan_receivables = [] + for receivable in doc.receivables: + if receivable.component != "Loan": + continue + + loan_receivables.append(receivable.reference_document) + + for loan in loan_receivables: + posting_date = frappe.utils.getdate(doc.transaction_date) + loan_repayment = frappe.get_doc( + "Loan Repayment", {"against_loan": loan, "docstatus": 1, "posting_date": posting_date} + ) + + if loan_repayment: + loan_repayment.cancel() + + loan_interest_accruals = frappe.get_all( + "Loan Interest Accrual", filters={"loan": loan, "docstatus": 1, "posting_date": posting_date} + ) + for accrual in loan_interest_accruals: + frappe.get_doc("Loan Interest Accrual", accrual.name).cancel() diff --git a/hrms/setup.py b/hrms/setup.py index 383b5118c0..ced82e2da9 100644 --- a/hrms/setup.py +++ b/hrms/setup.py @@ -595,6 +595,7 @@ def remove_lending_docperms_from_ess(): if row.document_type in loan_docperms: doc.user_doctypes.remove(row) + doc.flags.ignore_links = True doc.save(ignore_permissions=True)