Skip to content

Commit 282a301

Browse files
NagariaHussainmergify[bot]
authored andcommitted
feat: Employee reminders (#25735)
* feat: Add reminders section to HR Settings * refactor: Extract generic function for getting Employees * feat: Employee Work Anniversary Reminder * feat: Daily Holiday Reminder * fix: Unnecessary params and replace [] with .get() * test: Daily Holiday Reminders * test: is_holiday basic tests * refactor: Move employee reminders code to separate module * feat: Add advance reminder to HR settings * feat: Advance Holiday Reminders * refactor: get_holidays_for_employee * feat: Email holiday reminders in advance + tests * fix: Remove unused import * refactor: HR Setting Reminder Section * refactor: Remove Daily Holiday Reminders feat * feat: Reminder miss warning * fix: Failing test and function name change * chore: Add patch for field rename * chore: Rename frequency label * fix: Failing patch test * fix: sider and removed description of fields * fix: email alignment Co-authored-by: pateljannat <[email protected]> Co-authored-by: Jannat Patel <[email protected]> (cherry picked from commit 24b2a31) # Conflicts: # erpnext/hooks.py # erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py # erpnext/hr/doctype/employee/employee.py # erpnext/hr/doctype/employee/test_employee.py # erpnext/hr/doctype/hr_settings/hr_settings.json # erpnext/hr/doctype/hr_settings/hr_settings.py # erpnext/hr/doctype/upload_attendance/upload_attendance.py # erpnext/hr/utils.py # erpnext/patches.txt # erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py # erpnext/payroll/doctype/payroll_period/payroll_period.py # erpnext/payroll/doctype/salary_slip/salary_slip.py # erpnext/public/js/utils.js # erpnext/setup/doctype/employee/employee_reminders.py # erpnext/setup/doctype/employee/test_employee_reminders.py
1 parent c6c7d78 commit 282a301

File tree

18 files changed

+3921
-0
lines changed

18 files changed

+3921
-0
lines changed

erpnext/hooks.py

+11
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,11 @@
436436
"erpnext.crm.doctype.opportunity.opportunity.auto_close_opportunity",
437437
"erpnext.controllers.accounts_controller.update_invoice_status",
438438
"erpnext.accounts.doctype.fiscal_year.fiscal_year.auto_create_fiscal_year",
439+
<<<<<<< HEAD
440+
=======
441+
"erpnext.hr.doctype.employee.employee_reminders.send_work_anniversary_reminders",
442+
"erpnext.hr.doctype.employee.employee_reminders.send_birthday_reminders",
443+
>>>>>>> 24b2a31581 (feat: Employee reminders (#25735))
439444
"erpnext.projects.doctype.task.task.set_tasks_as_overdue",
440445
"erpnext.stock.doctype.serial_no.serial_no.update_maintenance_status",
441446
"erpnext.buying.doctype.supplier_scorecard.supplier_scorecard.refresh_scorecards",
@@ -466,6 +471,12 @@
466471
"erpnext.crm.utils.open_leads_opportunities_based_on_todays_event",
467472
"erpnext.assets.doctype.asset.depreciation.post_depreciation_entries",
468473
],
474+
"weekly": [
475+
"erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_weekly"
476+
],
477+
"monthly": [
478+
"erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_monthly"
479+
],
469480
"monthly_long": [
470481
"erpnext.accounts.deferred_revenue.process_deferred_accounting",
471482
"erpnext.accounts.utils.auto_create_exchange_rate_revaluation_monthly",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
3+
# For license information, please see license.txt
4+
5+
from __future__ import unicode_literals
6+
import frappe
7+
from frappe import _
8+
from frappe.utils import date_diff, add_days, getdate, cint, format_date
9+
from frappe.model.document import Document
10+
from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, validate_active_employee, \
11+
create_additional_leave_ledger_entry, get_holiday_dates_for_employee
12+
13+
class CompensatoryLeaveRequest(Document):
14+
15+
def validate(self):
16+
validate_active_employee(self.employee)
17+
validate_dates(self, self.work_from_date, self.work_end_date)
18+
if self.half_day:
19+
if not self.half_day_date:
20+
frappe.throw(_("Half Day Date is mandatory"))
21+
if not getdate(self.work_from_date)<=getdate(self.half_day_date)<=getdate(self.work_end_date):
22+
frappe.throw(_("Half Day Date should be in between Work From Date and Work End Date"))
23+
validate_overlap(self, self.work_from_date, self.work_end_date)
24+
self.validate_holidays()
25+
self.validate_attendance()
26+
if not self.leave_type:
27+
frappe.throw(_("Leave Type is madatory"))
28+
29+
def validate_attendance(self):
30+
attendance = frappe.get_all('Attendance',
31+
filters={
32+
'attendance_date': ['between', (self.work_from_date, self.work_end_date)],
33+
'status': 'Present',
34+
'docstatus': 1,
35+
'employee': self.employee
36+
}, fields=['attendance_date', 'status'])
37+
38+
if len(attendance) < date_diff(self.work_end_date, self.work_from_date) + 1:
39+
frappe.throw(_("You are not present all day(s) between compensatory leave request days"))
40+
41+
def validate_holidays(self):
42+
holidays = get_holiday_dates_for_employee(self.employee, self.work_from_date, self.work_end_date)
43+
if len(holidays) < date_diff(self.work_end_date, self.work_from_date) + 1:
44+
if date_diff(self.work_end_date, self.work_from_date):
45+
msg = _("The days between {0} to {1} are not valid holidays.").format(frappe.bold(format_date(self.work_from_date)), frappe.bold(format_date(self.work_end_date)))
46+
else:
47+
msg = _("{0} is not a holiday.").format(frappe.bold(format_date(self.work_from_date)))
48+
49+
frappe.throw(msg)
50+
51+
def on_submit(self):
52+
company = frappe.db.get_value("Employee", self.employee, "company")
53+
date_difference = date_diff(self.work_end_date, self.work_from_date) + 1
54+
if self.half_day:
55+
date_difference -= 0.5
56+
leave_period = get_leave_period(self.work_from_date, self.work_end_date, company)
57+
if leave_period:
58+
leave_allocation = self.get_existing_allocation_for_period(leave_period)
59+
if leave_allocation:
60+
leave_allocation.new_leaves_allocated += date_difference
61+
leave_allocation.validate()
62+
leave_allocation.db_set("new_leaves_allocated", leave_allocation.total_leaves_allocated)
63+
leave_allocation.db_set("total_leaves_allocated", leave_allocation.total_leaves_allocated)
64+
65+
# generate additional ledger entry for the new compensatory leaves off
66+
create_additional_leave_ledger_entry(leave_allocation, date_difference, add_days(self.work_end_date, 1))
67+
68+
else:
69+
leave_allocation = self.create_leave_allocation(leave_period, date_difference)
70+
self.db_set("leave_allocation", leave_allocation.name)
71+
else:
72+
frappe.throw(_("There is no leave period in between {0} and {1}").format(format_date(self.work_from_date), format_date(self.work_end_date)))
73+
74+
def on_cancel(self):
75+
if self.leave_allocation:
76+
date_difference = date_diff(self.work_end_date, self.work_from_date) + 1
77+
if self.half_day:
78+
date_difference -= 0.5
79+
leave_allocation = frappe.get_doc("Leave Allocation", self.leave_allocation)
80+
if leave_allocation:
81+
leave_allocation.new_leaves_allocated -= date_difference
82+
if leave_allocation.new_leaves_allocated - date_difference <= 0:
83+
leave_allocation.new_leaves_allocated = 0
84+
leave_allocation.validate()
85+
leave_allocation.db_set("new_leaves_allocated", leave_allocation.total_leaves_allocated)
86+
leave_allocation.db_set("total_leaves_allocated", leave_allocation.total_leaves_allocated)
87+
88+
# create reverse entry on cancelation
89+
create_additional_leave_ledger_entry(leave_allocation, date_difference * -1, add_days(self.work_end_date, 1))
90+
91+
def get_existing_allocation_for_period(self, leave_period):
92+
leave_allocation = frappe.db.sql("""
93+
select name
94+
from `tabLeave Allocation`
95+
where employee=%(employee)s and leave_type=%(leave_type)s
96+
and docstatus=1
97+
and (from_date between %(from_date)s and %(to_date)s
98+
or to_date between %(from_date)s and %(to_date)s
99+
or (from_date < %(from_date)s and to_date > %(to_date)s))
100+
""", {
101+
"from_date": leave_period[0].from_date,
102+
"to_date": leave_period[0].to_date,
103+
"employee": self.employee,
104+
"leave_type": self.leave_type
105+
}, as_dict=1)
106+
107+
if leave_allocation:
108+
return frappe.get_doc("Leave Allocation", leave_allocation[0].name)
109+
else:
110+
return False
111+
112+
def create_leave_allocation(self, leave_period, date_difference):
113+
is_carry_forward = frappe.db.get_value("Leave Type", self.leave_type, "is_carry_forward")
114+
allocation = frappe.get_doc(dict(
115+
doctype="Leave Allocation",
116+
employee=self.employee,
117+
employee_name=self.employee_name,
118+
leave_type=self.leave_type,
119+
from_date=add_days(self.work_end_date, 1),
120+
to_date=leave_period[0].to_date,
121+
carry_forward=cint(is_carry_forward),
122+
new_leaves_allocated=date_difference,
123+
total_leaves_allocated=date_difference,
124+
description=self.reason
125+
))
126+
allocation.insert(ignore_permissions=True)
127+
allocation.submit()
128+
return allocation

0 commit comments

Comments
 (0)