diff --git a/hrms/controllers/employee_reminders.py b/hrms/controllers/employee_reminders.py index 31231c7418..38fa5488dc 100644 --- a/hrms/controllers/employee_reminders.py +++ b/hrms/controllers/employee_reminders.py @@ -61,9 +61,10 @@ def send_holidays_reminder_in_advance(employee, holidays): employee_doc = frappe.get_doc("Employee", employee) employee_email = get_employee_email(employee_doc) frequency = frappe.db.get_single_value("HR Settings", "frequency") - + sender_email = get_sender_email() email_header = _("Holidays this Month.") if frequency == "Monthly" else _("Holidays this Week.") frappe.sendmail( + sender=sender_email, recipients=[employee_email], subject=_("Upcoming Holidays Reminder"), template="holiday_reminder", @@ -85,10 +86,12 @@ def send_holidays_reminder_in_advance(employee, holidays): # ------------------ def send_birthday_reminders(): """Send Employee birthday reminders if no 'Stop Birthday Reminders' is not set.""" + to_send = int(frappe.db.get_single_value("HR Settings", "send_birthday_reminders")) if not to_send: return + sender = get_sender_email() employees_born_today = get_employees_who_are_born_today() for company, birthday_persons in employees_born_today.items(): @@ -97,7 +100,7 @@ def send_birthday_reminders(): recipients = list(set(employee_emails) - set(birthday_person_emails)) reminder_text, message = get_birthday_reminder_text_and_message(birthday_persons) - send_birthday_reminder(recipients, reminder_text, birthday_persons, message) + send_birthday_reminder(recipients, reminder_text, birthday_persons, message, sender) if len(birthday_persons) > 1: # special email for people sharing birthdays @@ -105,7 +108,7 @@ def send_birthday_reminders(): person_email = person["user_id"] or person["personal_email"] or person["company_email"] others = [d for d in birthday_persons if d != person] reminder_text, message = get_birthday_reminder_text_and_message(others) - send_birthday_reminder(person_email, reminder_text, others, message) + send_birthday_reminder(person_email, reminder_text, others, message, sender) def get_birthday_reminder_text_and_message(birthday_persons): @@ -124,8 +127,9 @@ def get_birthday_reminder_text_and_message(birthday_persons): return reminder_text, message -def send_birthday_reminder(recipients, reminder_text, birthday_persons, message): +def send_birthday_reminder(recipients, reminder_text, birthday_persons, message, sender=None): frappe.sendmail( + sender=sender, recipients=recipients, subject=_("Birthday Reminder"), template="birthday_reminder", @@ -206,6 +210,7 @@ def send_work_anniversary_reminders(): if not to_send: return + sender = get_sender_email() employees_joined_today = get_employees_having_an_event_today("work_anniversary") message = _("A friendly reminder of an important date for our team.") @@ -218,7 +223,7 @@ def send_work_anniversary_reminders(): recipients = list(set(employee_emails) - set(anniversary_person_emails)) reminder_text = get_work_anniversary_reminder_text(anniversary_persons) - send_work_anniversary_reminder(recipients, reminder_text, anniversary_persons, message) + send_work_anniversary_reminder(recipients, reminder_text, anniversary_persons, message, sender) if len(anniversary_persons) > 1: # email for people sharing work anniversaries @@ -226,7 +231,7 @@ def send_work_anniversary_reminders(): person_email = person["user_id"] or person["personal_email"] or person["company_email"] others = [d for d in anniversary_persons if d != person] reminder_text = get_work_anniversary_reminder_text(others) - send_work_anniversary_reminder(person_email, reminder_text, others, message) + send_work_anniversary_reminder(person_email, reminder_text, others, message, sender) def get_work_anniversary_reminder_text(anniversary_persons: list) -> str: @@ -261,8 +266,15 @@ def get_pluralized_years(years): return f"{years} years" -def send_work_anniversary_reminder(recipients, reminder_text, anniversary_persons, message): +def send_work_anniversary_reminder( + recipients, + reminder_text, + anniversary_persons, + message, + sender=None, +): frappe.sendmail( + sender=sender, recipients=recipients, subject=_("Work Anniversary Reminder"), template="anniversary_reminder", @@ -273,3 +285,7 @@ def send_work_anniversary_reminder(recipients, reminder_text, anniversary_person ), header=_("Work Anniversary Reminder"), ) + + +def get_sender_email() -> str | None: + return frappe.db.get_single_value("HR Settings", "sender_email") diff --git a/hrms/hr/doctype/attendance/attendance_list.js b/hrms/hr/doctype/attendance/attendance_list.js index 97132e8204..fbbb93763b 100644 --- a/hrms/hr/doctype/attendance/attendance_list.js +++ b/hrms/hr/doctype/attendance/attendance_list.js @@ -86,6 +86,7 @@ frappe.listview_settings["Attendance"] = { fieldtype: "MultiCheck", options: [], columns: 2, + select_all: true, }, ], primary_action(data) { diff --git a/hrms/hr/doctype/hr_settings/hr_settings.js b/hrms/hr/doctype/hr_settings/hr_settings.js index 6e26a1fa71..b85f4aec4d 100644 --- a/hrms/hr/doctype/hr_settings/hr_settings.js +++ b/hrms/hr/doctype/hr_settings/hr_settings.js @@ -2,6 +2,15 @@ // For license information, please see license.txt frappe.ui.form.on('HR Settings', { + refresh: function (frm) { + frm.set_query("sender", () => { + return { + filters: { + enable_outgoing: 1, + }, + }; + }); + } }); frappe.tour['HR Settings'] = [ diff --git a/hrms/hr/doctype/hr_settings/hr_settings.json b/hrms/hr/doctype/hr_settings/hr_settings.json index 1e9cbe32c4..52567f819a 100644 --- a/hrms/hr/doctype/hr_settings/hr_settings.json +++ b/hrms/hr/doctype/hr_settings/hr_settings.json @@ -12,12 +12,14 @@ "column_break_9", "retirement_age", "reminders_section", - "send_birthday_reminders", "column_break_11", "send_work_anniversary_reminders", - "column_break_18", + "send_birthday_reminders", "send_holiday_reminders", "frequency", + "column_break_hyec", + "sender", + "sender_email", "leave_and_expense_claim_settings", "send_leave_notification", "leave_approval_notification_template", @@ -71,10 +73,6 @@ "fieldtype": "Check", "label": "Expense Approver Mandatory In Expense Claim" }, - { - "fieldname": "column_break_18", - "fieldtype": "Column Break" - }, { "default": "1", "fieldname": "leave_approver_mandatory_in_leave_application", @@ -251,13 +249,31 @@ { "fieldname": "column_break_34", "fieldtype": "Column Break" + }, + { + "fieldname": "sender", + "fieldtype": "Link", + "label": "Sender", + "options": "Email Account" + }, + { + "depends_on": "eval:doc.sender", + "fetch_from": "sender.email_id", + "fieldname": "sender_email", + "fieldtype": "Data", + "label": "Sender Email", + "read_only": 1 + }, + { + "fieldname": "column_break_hyec", + "fieldtype": "Column Break" } ], "icon": "fa fa-cog", "idx": 1, "issingle": 1, "links": [], - "modified": "2022-10-04 17:29:45.926918", + "modified": "2023-11-01 11:06:31.329718", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", @@ -275,5 +291,6 @@ ], "sort_field": "modified", "sort_order": "ASC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/hrms/payroll/doctype/payroll_settings/payroll_settings.js b/hrms/payroll/doctype/payroll_settings/payroll_settings.js index 941464dc51..3154b64620 100644 --- a/hrms/payroll/doctype/payroll_settings/payroll_settings.js +++ b/hrms/payroll/doctype/payroll_settings/payroll_settings.js @@ -2,6 +2,16 @@ // For license information, please see license.txt frappe.ui.form.on('Payroll Settings', { + refresh: function (frm) { + frm.set_query("sender", () => { + return { + filters: { + enable_outgoing: 1, + }, + }; + }); + }, + encrypt_salary_slips_in_emails: function(frm) { let encrypt_state = frm.doc.encrypt_salary_slips_in_emails; frm.set_df_property('password_policy', 'reqd', encrypt_state); diff --git a/hrms/payroll/doctype/payroll_settings/payroll_settings.json b/hrms/payroll/doctype/payroll_settings/payroll_settings.json index c53c92bc72..85c2f93369 100644 --- a/hrms/payroll/doctype/payroll_settings/payroll_settings.json +++ b/hrms/payroll/doctype/payroll_settings/payroll_settings.json @@ -20,6 +20,8 @@ "show_leave_balances_in_salary_slip", "email_section", "email_salary_slip_to_employee", + "sender", + "sender_email", "column_break_iewr", "encrypt_salary_slips_in_emails", "password_policy", @@ -155,13 +157,28 @@ "fieldname": "consider_marked_attendance_on_holidays", "fieldtype": "Check", "label": "Consider Marked Attendance on Holidays" + }, + { + "depends_on": "eval:doc.email_salary_slip_to_employee", + "fieldname": "sender", + "fieldtype": "Link", + "label": "Sender", + "options": "Email Account" + }, + { + "depends_on": "eval:doc.sender", + "fetch_from": "sender.email_id", + "fieldname": "sender_email", + "fieldtype": "Data", + "label": "Sender Email", + "read_only": 1 } ], "icon": "fa fa-cog", "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-09-25 14:03:59.215240", + "modified": "2023-11-01 13:51:04.225492", "modified_by": "Administrator", "module": "Payroll", "name": "Payroll Settings", diff --git a/hrms/payroll/doctype/salary_slip/salary_slip.py b/hrms/payroll/doctype/salary_slip/salary_slip.py index bcb0b08aa9..b1df20296a 100644 --- a/hrms/payroll/doctype/salary_slip/salary_slip.py +++ b/hrms/payroll/doctype/salary_slip/salary_slip.py @@ -1799,6 +1799,7 @@ def get_loan_details(self): "docstatus": 1, "repay_from_salary": 1, "company": self.company, + "status": ("!=", "Closed"), }, ) @@ -1860,6 +1861,7 @@ def email_salary_slip(self): if receiver: email_args = { + "sender": payroll_settings.sender_email, "recipients": [receiver], "message": _(message), "subject": "Salary Slip - from {0} to {1}".format(self.start_date, self.end_date), diff --git a/hrms/payroll/doctype/salary_slip/test_salary_slip.py b/hrms/payroll/doctype/salary_slip/test_salary_slip.py index d159efc2e1..0b9655fc31 100644 --- a/hrms/payroll/doctype/salary_slip/test_salary_slip.py +++ b/hrms/payroll/doctype/salary_slip/test_salary_slip.py @@ -631,6 +631,7 @@ def test_loan_repayment_salary_slip(self): "Test Loan Repayment Salary Structure", "Monthly", employee=applicant, + company="_Test Company", currency="INR", payroll_period=payroll_period, ) @@ -689,6 +690,94 @@ def test_payroll_frequency(self): elif payroll_frequency == "Daily": self.assertEqual(ss.end_date, nowdate()) + def test_loan_write_off_salary_slip(self): + from erpnext.loan_management.doctype.loan.loan import make_loan_write_off + from erpnext.loan_management.doctype.loan.test_loan import ( + create_loan, + create_loan_accounts, + create_loan_type, + create_repayment_entry, + make_loan_disbursement_entry, + ) + from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import ( + process_loan_interest_accrual_for_term_loans, + ) + + from hrms.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure + + applicant = make_employee("test_loan_repayment_salary_slip@salary.com", company="_Test Company") + + create_loan_accounts() + + create_loan_type( + "Personal Loan", + 12000, + 0, + is_term_loan=1, + mode_of_payment="Cash", + disbursement_account="Disbursement Account - _TC", + payment_account="Payment Account - _TC", + loan_account="Loan Account - _TC", + interest_income_account="Interest Income Account - _TC", + penalty_income_account="Penalty Income Account - _TC", + repayment_schedule_type="Monthly as per repayment start date", + ) + + payroll_period = create_payroll_period(name="_Test Payroll Period", company="_Test Company") + + make_salary_structure( + "Test Loan Repayment Salary Structure", + "Monthly", + employee=applicant, + company="_Test Company", + currency="INR", + payroll_period=payroll_period, + ) + + frappe.db.sql( + "delete from tabLoan where applicant = 'test_loan_repayment_salary_slip@salary.com'" + ) + loan = create_loan( + applicant, + "Personal Loan", + 12000, + "Repay Over Number of Periods", + 12, + posting_date=payroll_period.start_date, + ) + loan.repay_from_salary = 1 + loan.submit() + + make_loan_disbursement_entry( + loan.name, loan.loan_amount, disbursement_date=payroll_period.start_date + ) + + process_loan_interest_accrual_for_term_loans( + posting_date=add_months(payroll_period.start_date, 12) + ) + + repayment_entry = create_repayment_entry( + loan.name, applicant, add_months(payroll_period.start_date, 7), 7000 + ) + repayment_entry.submit() + + we = make_loan_write_off( + loan.name, posting_date=add_months(payroll_period.start_date, 8), amount=5000 + ) + we.submit() + + self.assertEqual(frappe.db.get_value("Loan", loan.name, "status"), "Closed") + + ss = make_employee_salary_slip( + applicant, + "Monthly", + "Test Loan Repayment Salary Structure", + posting_date=add_months(payroll_period.start_date, 8), + ) + ss.submit() + + self.assertEqual(ss.total_loan_repayment, 0) + def test_multi_currency_salary_slip(self): from hrms.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure