LEAVES ALLOCATIONS: CUSTOMIZATION

This commit is contained in:
Pranay 2025-01-24 11:43:58 +05:30
parent 9138f20aba
commit ac90760034
2 changed files with 221 additions and 112 deletions

View File

@ -17,7 +17,7 @@ class hrLeaveAccrualLevel(models.Model):
('monthly', 'Monthly'), ('monthly', 'Monthly'),
('yearly', 'Yearly'), ('yearly', 'Yearly'),
], default='daily', required=True, string="Frequency") ], default='daily', required=True, string="Frequency")
emp_type = fields.Many2one('hr.contract.type', "Employee Type", tracking=True) emp_type = fields.Many2many('hr.contract.type', string="Employee Type", tracking=True)
max_start_count = fields.Integer( max_start_count = fields.Integer(
@ -98,16 +98,17 @@ class hrTimeoffAllocation(models.Model):
for level in level_ids: for level in level_ids:
# Calculate the current frequency # Calculate the current frequency
run_allocation = self._handel_weekly_frequency(level)
if run_allocation:
if level.emp_type:
level_filtered_employees = employees.filtered(lambda emp: emp.emp_type == level.emp_type)
else:
level_filtered_employees = employees
qualified_employees = level_filtered_employees.filtered(lambda emp: self._emp_filter_by_level(emp, level))
# After filtering, we create the leave allocation for each employee if level.emp_type:
for emp in qualified_employees: level_filtered_employees = employees.filtered(lambda emp: emp.emp_type.id in level.emp_type.ids)
else:
level_filtered_employees = employees
qualified_employees = level_filtered_employees.filtered(lambda emp: self._emp_filter_by_level(emp, level))
# After filtering, we create the leave allocation for each employee
for emp in qualified_employees:
run_allocation = self._handel_weekly_frequency(level,emp)
if run_allocation:
allocations = self.env['hr.leave.allocation'].sudo().search([('employee_id','=',emp.id),('holiday_status_id','=',accrual.time_off_type_id.id),('state','=','validate')]) allocations = self.env['hr.leave.allocation'].sudo().search([('employee_id','=',emp.id),('holiday_status_id','=',accrual.time_off_type_id.id),('state','=','validate')])
leaves = self.env['hr.leave'].sudo().search([('employee_id','=',emp.id),('holiday_status_id','=',accrual.time_off_type_id.id),('state','not in',['draft','refuse','cancel'])]) leaves = self.env['hr.leave'].sudo().search([('employee_id','=',emp.id),('holiday_status_id','=',accrual.time_off_type_id.id),('state','not in',['draft','refuse','cancel'])])
emp_leave_balance = sum(allocation.number_of_days for allocation in allocations) - sum(leave.number_of_days for leave in leaves) emp_leave_balance = sum(allocation.number_of_days for allocation in allocations) - sum(leave.number_of_days for leave in leaves)
@ -115,7 +116,7 @@ class hrTimeoffAllocation(models.Model):
continue continue
self._create_leave_allocation(emp, level, accrual) self._create_leave_allocation(emp, level, accrual)
def _handel_weekly_frequency(self,level): def _handel_weekly_frequency(self,level,emp):
today_date = datetime.today().date() today_date = datetime.today().date()
if level.level_frequency == 'weekly': if level.level_frequency == 'weekly':
weekday_map = { weekday_map = {
@ -125,14 +126,13 @@ class hrTimeoffAllocation(models.Model):
elif level.level_frequency == 'daily': elif level.level_frequency == 'daily':
return True return True
elif level.level_frequency == 'monthly': elif level.level_frequency == 'monthly':
return True if level.first_day_display == str(today_date.day) else False return True if level.first_day_display == str(today_date.day) or (emp.doj and ((emp.doj + timedelta(days=2)) == today_date)) else False
elif level.level_frequency == 'yearly': elif level.level_frequency == 'yearly':
month_map = { month_map = {
'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4, 'may': 5, 'jun': 6, 'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4, 'may': 5, 'jun': 6,
'jul': 7, 'aug': 8, 'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12 'jul': 7, 'aug': 8, 'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12
} }
return True if level.first_day_display == str(today_date.day) and today_date.month == month_map.get(level.yearly_month) else False return True if (level.first_day_display == str(today_date.day) and today_date.month == month_map.get(level.yearly_month)) or (emp.doj and ((emp.doj + timedelta(days=2)) == today_date)) else False
else: else:
return True return True
@ -140,11 +140,29 @@ class hrTimeoffAllocation(models.Model):
""" """
Create leave allocation for a qualified employee based on the accrual level and added value. Create leave allocation for a qualified employee based on the accrual level and added value.
""" """
today_date = datetime.today().date()
number_of_days = level.added_value
if employee.doj and ((employee.doj + timedelta(days=2)) == today_date):
if level.level_frequency == 'monthly':
if employee.doj.day <= 10:
number_of_days = level.added_value
else:
number_of_days = level.added_value/2
elif level.level_frequency == 'yearly':
start_month = int(level.yearly_month)
joining_month = employee.doj.month
# Compute remaining months in the allocation cycle
remaining_months = (start_month - joining_month) % 12 or 12
# Calculate proportional leaves
number_of_days = (level.added_value / 12) * remaining_months
if employee.doj.day > 10:
number_of_days = number_of_days - ((level.added_value / 12)/2)
self.env['hr.leave.allocation'].sudo().create({ self.env['hr.leave.allocation'].sudo().create({
'employee_id': employee.id, 'employee_id': employee.id,
'holiday_status_id': accrual.time_off_type_id.id, 'holiday_status_id': accrual.time_off_type_id.id,
'date_from': fields.Date.today(), 'date_from': fields.Date.today(),
'number_of_days': level.added_value, 'number_of_days': number_of_days,
'allocation_type': 'auto_allocation' 'allocation_type': 'auto_allocation'
}).action_approve() }).action_approve()
@ -238,10 +256,40 @@ class HRLeave(models.Model):
"\nThe status is 'Approved', when time off request is approved by manager." + "\nThe status is 'Approved', when time off request is approved by manager." +
"\nThe status is 'Cancelled', when time off request is cancelled.") "\nThe status is 'Cancelled', when time off request is cancelled.")
def _check_validity(self):
for rec in self:
if rec.holiday_status_id.limit_leave_requests:
if rec.holiday_status_id.limit_request_count and rec.holiday_status_id.limit_request_type and rec.holiday_status_id.limit_emp_type and rec.holiday_status_id.limit_request_count >= 0:
if rec.employee_id.id in rec.holiday_status_id.limit_emp_type.ids:
time_frame = {
'week': timedelta(weeks=1),
'month': timedelta(days=30),
'year': timedelta(days=365),
}.get(rec.holiday_status_id.limit_request_type, timedelta(weeks=1)) # Default to 1 week
restriction_start_date = datetime.now() - time_frame
# Count the leave requests made by the employee within the restriction period
leave_count = self.env['hr.leave'].search_count([
('employee_id', '=', rec.employee_id.id),
('state', 'not in', ['cancel', 'refuse', 'draft']), # Adjust states if needed
('holiday_status_id', '=', rec.holiday_status_id.id),
('request_date_from', '>=', restriction_start_date),
('id','!=',rec.id)
])
if leave_count >= rec.holiday_status_id.limit_request_count:
raise ValidationError(_(
"You have exceeded the maximum allowed leave requests (%s) for the selected period (%s)."
) % (rec.holiday_status_id.limit_request_count, rec.holiday_status_id.limit_request_type))
return super(HRLeave, self)._check_validity()
def action_draft(self): def action_draft(self):
for rec in self: for rec in self:
if rec.employee_id.user_id.id != self.env.user.id: if rec.employee_id.user_id.id != self.env.user.id:
raise ValidationError(_("Only employee can submit his own leave")) raise ValidationError(_("Only employee can submit his own leave"))
self._check_validity() self._check_validity()
rec.state = 'confirm' rec.state = 'confirm'
@ -285,3 +333,16 @@ class HRLeaveType(models.Model):
('day', 'Day'), ('day', 'Day'),
('half_day', 'Half Day')], default='day', string='Take Time Off in', required=True) ('half_day', 'Half Day')], default='day', string='Take Time Off in', required=True)
request_unit = fields.Selection(related="request_unit_type",store=True) request_unit = fields.Selection(related="request_unit_type",store=True)
limit_leave_requests = fields.Boolean(string='Limit Leave Requests', default=False)
limit_request_count = fields.Integer(
"limit Count",
help="Defines the minimum number of leave requests after which the restriction will apply. For example, set 1 to start restrictions after the first request.",
default="1")
limit_request_type = fields.Selection(
[('week', 'Week'),
('month', 'Month'),
('year', 'Year')],
default='day', string="Limit Type", required=True,
help="Specifies the type of time period (days, months, or years) for applying the leave request")
limit_emp_type = fields.Many2many('hr.contract.type', string="Employee Type")

View File

@ -16,7 +16,7 @@
<xpath expr="//div[@name='carryover']" position="after"> <xpath expr="//div[@name='carryover']" position="after">
<field name="time_off_type_id" required="1"/> <field name="time_off_type_id" required="1"/>
<div> <div>
<strong>Allocation Starts</strong> <strong>Allocation Starts Running </strong>
<field name="accrual_start_count" style="width: 2rem"/> <field name="accrual_start_count" style="width: 2rem"/>
<field name="accrual_start_type" style="width: 4.75rem"/> <field name="accrual_start_type" style="width: 4.75rem"/>
<strong>after employee joining date</strong> <strong>after employee joining date</strong>
@ -27,96 +27,124 @@
</xpath> </xpath>
<xpath expr="//field[@name='level_ids']/kanban" position="replace"> <xpath expr="//field[@name='level_ids']/kanban" position="replace">
<kanban default_order="sequence"> <kanban default_order="sequence">
<field name="sequence"/> <field name="sequence"/>
<field name="action_with_unused_accruals"/> <field name="action_with_unused_accruals"/>
<field name="accrual_validity_count"/> <field name="accrual_validity_count"/>
<field name="accrual_validity_type"/> <field name="accrual_validity_type"/>
<field name="cap_accrued_time"/> <field name="cap_accrued_time"/>
<field name="accrual_validity"/> <field name="accrual_validity"/>
<templates> <templates>
<div t-name="card" class="bg-transparent border-0"> <div t-name="card" class="bg-transparent border-0">
<div class="o_hr_holidays_body"> <div class="o_hr_holidays_body">
<div class="o_hr_holidays_timeline text-center"> <div class="o_hr_holidays_timeline text-center">
<t t-if="record.start_count.raw_value &gt; 0"> <t t-if="record.start_count.raw_value &gt; 0">
Experience between <field name="start_count"/> <field name="start_type"/> and <field name="max_start_count"/> <field name="max_start_type"/> Experience between
<field name="start_count"/>
<field name="start_type"/>
and
<field name="max_start_count"/>
<field name="max_start_type"/>
</t>
<t t-else="">
initially
</t>
</div>
<t t-if="!read_only_mode">
<a type="edit" t-attf-class="oe_kanban_action text-black">
<t t-call="level_content"/>
</a>
</t>
<t t-else="">
<t t-call="level_content"/>
</t>
</div>
</div>
<t t-name="level_content">
<div class="o_hr_holidays_card">
<div class="content container" style="width: 560px;">
<div class="row w-100">
<div class="pe-0 me-0" style="width: 6rem;">
<field name="added_value" invisible="1"/>
<span t-out="record.added_value.raw_value"/>
<field name="added_value_type"/>,
</div>
<div class="col-auto m-0 p-0">
<field name="level_frequency" class="ms-1"/>
<t t-if="record.level_frequency.raw_value === 'weekly'">
on
<field name="week_day"/>
</t>
<t t-elif="record.level_frequency.raw_value === 'monthly'">
on the
<field name="first_day"/>
day of the month
</t>
<t t-elif="record.level_frequency.raw_value === 'bimonthly'">
on the
<field name="first_day"/>
and on the
<field name="second_day"/>
days of the months
</t>
<t t-elif="record.level_frequency.raw_value === 'biyearly'">
on the
<field name="first_month_day"/>
<field name="first_month"/>
and on the
<field name="second_month_day"/>
<field name="second_month"/>
</t>
<t t-elif="record.level_frequency.raw_value === 'yearly'">
on
<field name="yearly_day"/>
<field name="yearly_month"/>
</t>
</div>
</div>
<div class="row text-muted">
<div class="pe-0 me-0" style="width: 6rem;">
Cap:
</div>
<div class="col-3 m-0 ps-1 d-flex">
<t t-if="record.cap_accrued_time.raw_value and record.maximum_leave.raw_value &gt; 0">
<field name="maximum_leave" widget="float_without_trailing_zeros"/>
<field class="ms-1" name="added_value_type"/>
</t> </t>
<t t-else=""> <t t-else="">
initially Unlimited
</t> </t>
</div> </div>
<t t-if="!read_only_mode">
<a type="edit" t-attf-class="oe_kanban_action text-black">
<t t-call="level_content"/>
</a>
</t>
<t t-else="">
<t t-call="level_content"/>
</t>
</div> </div>
</div> <div class="row text-muted" invisible="1">
<t t-name="level_content"> <div class="pe-0 me-0" style="width: 6rem;">
<div class="o_hr_holidays_card"> Carry over:
<div class="content container" style="width: 560px;"> </div>
<div class="row w-100"> <div class="col-6 m-0 ps-1">
<div class="pe-0 me-0" style="width: 6rem;"> <t t-if="record.action_with_unused_accruals.raw_value === 'all'">all
<field name="added_value" invisible="1"/> <span invisible="not accrual_validity">
<span t-out="record.added_value.raw_value"/> <field name="added_value_type"/>, - Valid for
</div> <field name="accrual_validity_count"/>
<div class="col-auto m-0 p-0"> <field name="accrual_validity_type"/>
<field name="level_frequency" class="ms-1"/> </span>
<t t-if="record.level_frequency.raw_value === 'weekly'"> </t>
on <field name="week_day"/> <t t-elif="record.action_with_unused_accruals.raw_value === 'maximum'">
</t> up to
<t t-elif="record.level_frequency.raw_value === 'monthly'"> <field name="postpone_max_days"/>
on the <field name="first_day"/> day of the month <t t-esc="record.added_value_type.raw_value"/>
</t> <span invisible="not accrual_validity">
<t t-elif="record.level_frequency.raw_value === 'bimonthly'"> - Valid for
on the <field name="first_day"/> and on the <field name="second_day"/> days of the months <field name="accrual_validity_count"/>
</t> <field name="accrual_validity_type"/>
<t t-elif="record.level_frequency.raw_value === 'biyearly'"> </span>
on the <field name="first_month_day"/> <field name="first_month"/> and on the <field name="second_month_day"/> <field name="second_month"/> </t>
</t> <t t-else="">no</t>
<t t-elif="record.level_frequency.raw_value === 'yearly'">
on <field name="yearly_day"/> <field name="yearly_month"/>
</t>
</div>
</div>
<div class="row text-muted">
<div class="pe-0 me-0" style="width: 6rem;">
Cap:
</div>
<div class="col-3 m-0 ps-1 d-flex">
<t t-if="record.cap_accrued_time.raw_value and record.maximum_leave.raw_value &gt; 0">
<field name="maximum_leave" widget="float_without_trailing_zeros"/> <field class="ms-1" name="added_value_type"/>
</t>
<t t-else="">
Unlimited
</t>
</div>
</div>
<div class="row text-muted" invisible="1">
<div class="pe-0 me-0" style="width: 6rem;">
Carry over:
</div>
<div class="col-6 m-0 ps-1">
<t t-if="record.action_with_unused_accruals.raw_value === 'all'">all
<span invisible="not accrual_validity">
- Valid for <field name="accrual_validity_count"/> <field name="accrual_validity_type"/>
</span>
</t>
<t t-elif="record.action_with_unused_accruals.raw_value === 'maximum'">up to <field name="postpone_max_days"/> <t t-esc="record.added_value_type.raw_value"/>
<span invisible="not accrual_validity">
- Valid for <field name="accrual_validity_count"/> <field name="accrual_validity_type"/>
</span>
</t>
<t t-else="">no</t>
</div>
</div>
</div> </div>
</div> </div>
</t> </div>
</templates> </div>
</kanban> </t>
</templates>
</kanban>
</xpath> </xpath>
</field> </field>
</record> </record>
@ -174,15 +202,17 @@
<field name="inherit_id" ref="hr_holidays.hr_accrual_level_view_form"/> <field name="inherit_id" ref="hr_holidays.hr_accrual_level_view_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//group[@name='accrue']" position="replace"> <xpath expr="//group[@name='accrue']" position="replace">
<group name="accrue" col="1" width="800px"> <group name="accrue" col="1" width="800px">
<div class="o_td_label"> <div class="o_td_label">
<label for="added_value" string="Employee accrue"/> <label for="added_value" string="Employee accrue"/>
</div> </div>
<div> <div>
<field name="accrued_gain_time" invisible="1"/> <field name="accrued_gain_time" invisible="1"/>
<field name="can_modify_value_type" invisible="1"/> <field name="can_modify_value_type" invisible="1"/>
<field name="added_value" widget="float_without_trailing_zeros" style="width: 4rem" class="me-1"/> <field name="added_value" widget="float_without_trailing_zeros" style="width: 4rem"
<field name="added_value_type" style="width: 3.4rem" nolabel="1" readonly="not can_modify_value_type"/> class="me-1"/>
<field name="added_value_type" style="width: 3.4rem" nolabel="1"
readonly="not can_modify_value_type"/>
</div> </div>
<div style="width: 5rem"/> <div style="width: 5rem"/>
<div name="daily" invisible="level_frequency != 'daily'"> <div name="daily" invisible="level_frequency != 'daily'">
@ -190,18 +220,22 @@
</div> </div>
<div name="weekly" invisible="level_frequency != 'weekly'"> <div name="weekly" invisible="level_frequency != 'weekly'">
<field name="level_frequency" style="width: 4.5rem;"/> <field name="level_frequency" style="width: 4.5rem;"/>
<label for="week_day" string="on" class="me-1"/><field name="week_day" style="width: 6.6rem"/> <label for="week_day" string="on" class="me-1"/>
<field name="week_day" style="width: 6.6rem"/>
</div> </div>
<div name="monthly" invisible="level_frequency != 'monthly'"> <div name="monthly" invisible="level_frequency != 'monthly'">
<field name="level_frequency" style="width: 4.5rem"/> <field name="level_frequency" style="width: 4.5rem"/>
<label for="first_day_display" string="on the" class="me-1"/> <label for="first_day_display" string="on the" class="me-1"/>
<field name="first_day_display" required="1" style="width: 4rem"/> <field name="first_day_display" required="1" style="width: 4rem"/>
of the month of the month (or 2 days after employee joining date)
</div> </div>
<div name="yearly" invisible="level_frequency != 'yearly'"> <div name="yearly" invisible="level_frequency != 'yearly'">
<field name="level_frequency" style="width: 4rem"/> <field name="level_frequency" style="width: 4rem"/>
<label for="yearly_day_display" string="on the" class="me-1"/> <label for="yearly_day_display" string="on the" class="me-1"/>
<field name="yearly_day_display" required="1" style="width: 4rem"/> of <field name="yearly_month" required="1" style="width: 5.4rem"/> <field name="yearly_day_display" required="1" style="width: 4rem"/>
of
<field name="yearly_month" required="1" style="width: 5.4rem"/>
(or 2 days after employee joining date)
</div> </div>
</group> </group>
</xpath> </xpath>
@ -211,22 +245,27 @@
<label for="start_count" string="Employee total Experience"/> <label for="start_count" string="Employee total Experience"/>
</div> </div>
<div>Min <div>Min
<field name="start_count" style="width: 2rem" required="max_start_count > 0 or max_start_type != False"/> <field name="start_count" style="width: 2rem"
<field name="start_type" style="width: 4.75rem" required="max_start_count > 0 or max_start_type != False"/> required="max_start_count > 0 or max_start_type != False"/>
<field name="start_type" style="width: 4.75rem"
required="max_start_count > 0 or max_start_type != False"/>
&amp; &amp;
Max of Max of
<field name="max_start_count" style="width: 2rem" required="start_count > 0 or start_type != False"/> <field name="max_start_count" style="width: 2rem"
<field name="max_start_type" style="width: 4.75rem" required="start_count > 0 or start_type != False"/> required="start_count > 0 or start_type != False"/>
<field name="max_start_type" style="width: 4.75rem"
required="start_count > 0 or start_type != False"/>
Experience is required Experience is required
</div> </div>
</group> </group>
<group name="emp_types"> <group name="emp_types">
<div class="o_td_label"> <div class="o_td_label">
<label for="emp_type" string="Employee Type"/> <label for="emp_type" string="Employee Type" widget="many2many_tags"/>
</div> </div>
<div> <div>
<field name="emp_type" style="width: 4" required="max_start_count > 0 or max_start_type != False"/> <field name="emp_type" style="width: 4" widget="many2many_tags"
required="1"/>
</div> </div>
</group> </group>
@ -258,6 +297,15 @@
</xpath> </xpath>
<xpath expr="//field[@name='request_unit']" position="after"> <xpath expr="//field[@name='request_unit']" position="after">
<field name="request_unit_type"/> <field name="request_unit_type"/>
<field name="limit_leave_requests"/>
<div invisible="not limit_leave_requests">
<strong>Eligible to apply upto </strong>
<field name="limit_request_count" style="width: 2rem" required="limit_leave_requests == True" invisible="not limit_leave_requests"/>
<strong> Leaves Per </strong>
<field name="limit_request_type" style="width: 4.75rem" required="limit_leave_requests == True" invisible="not limit_leave_requests"/>
<strong> during</strong>
<field name="limit_emp_type" widget="many2many_tags" required="limit_leave_requests == True" invisible="not limit_leave_requests"/>
</div>
</xpath> </xpath>
</field> </field>
</record> </record>