16.0-negative-leaves-and-allocation #13

Merged
laetitiadacosta merged 5 commits from 16.0-negative-leaves-and-allocation into 16.0 2025-11-24 10:00:02 +00:00
20 changed files with 546 additions and 73 deletions

View File

@@ -1,43 +0,0 @@
==========================================================
allow_negative_leave_and_allocation_hr_holidays_attendance
==========================================================
manage heritance of Duration in TimeOffCard
Installation
============
The module self-installs when ``allow_negative_leave_and_allocation`` and ``hr_holidays_attendance``.
Known issues / Roadmap
======================
None yet.
Bug Tracker
===========
Bugs are tracked on `our issues website <https://github.com/elabore-coop/allow_negative_leave_and_allocation_hr_holidays_attendance/issues>`_. In case of
trouble, please check there if your issue has already been
reported. If you spotted it first, help us smashing it by providing a
detailed and welcomed feedback.
Credits
=======
Contributors
------------
* `Elabore <mailto:laetitia.dacosta@elabore.coop>`
Funders
-------
The development of this module has been financially supported by:
* Elabore (https://elabore.coop)
Maintainer
----------
This module is maintained by Elabore.

View File

@@ -1,11 +0,0 @@
<template>
<t t-name="allow_negative_hr_holidays_attendance.TimeOffCard" t-inherit="hr_holidays.TimeOffCard" t-inherit-mode="extension" owl="1">
<xpath expr="//t[@t-set='duration']" position="replace">
<t t-set="duration" t-value="props.requires_allocation
? (props.data['allows_negative'] ? data.usable_remaining_leaves : data.virtual_remaining_leaves)
: data.overtime_deductible
? data.usable_remaining_leaves
: data.virtual_leaves_taken" />
</xpath>
</t>
</template>

View File

@@ -27,7 +27,7 @@ None yet.
Bug Tracker Bug Tracker
=========== ===========
Bugs are tracked on `our issues website <https://github.com/elabore-coop/allow_negative_leave_and_allocation/issues>`_. In case of Bugs are tracked on `our issues website <https://git.elabore.coop/Elabore/hr-tools/issues>`_. In case of
trouble, please check there if your issue has already been trouble, please check there if your issue has already been
reported. If you spotted it first, help us smashing it by providing a reported. If you spotted it first, help us smashing it by providing a
detailed and welcomed feedback. detailed and welcomed feedback.
@@ -39,7 +39,7 @@ Contributors
------------ ------------
* `Alusage : Nicolas JEUDY` * `Alusage : Nicolas JEUDY`
* `Elabore <mailto:laetitia.dacosta@elabore.coop>` * `Elabore <mailto:contact@elabore.coop>`
Funders Funders
------- -------

View File

@@ -1,6 +1,6 @@
{ {
"name": "hr_employee_stats_sheet", "name": "hr_employee_stats_sheet",
"version": "16.0.2.0.0", "version": "16.0.2.1.0",
"description": "Add global sheet for employee stats", "description": "Add global sheet for employee stats",
"summary": "Add global sheet for employee stats", "summary": "Add global sheet for employee stats",
"author": "Nicolas JEUDY", "author": "Nicolas JEUDY",
@@ -8,7 +8,7 @@
"license": "LGPL-3", "license": "LGPL-3",
"category": "Human Resources", "category": "Human Resources",
"depends": [ "depends": [
"allow_negative_leave_and_allocation", "hr_negative_leave",
"base", "base",
"hr", "hr",
"hr_holidays", "hr_holidays",

View File

@@ -0,0 +1,44 @@
===================================
allow_negative_leave_and_allocation
===================================
allow negative leaves, manage negative leave balances and negative allocations
Installation
============
Use Odoo normal module installation procedure to install
``allow_negative_leave_and_allocation``.
Known issues / Roadmap
======================
None yet.
Bug Tracker
===========
Bugs are tracked on `our issues website <https://git.elabore.coop/Elabore/hr-tools/issues>`_. In case of
trouble, please check there if your issue has already been
reported. If you spotted it first, help us smashing it by providing a
detailed and welcomed feedback.
Credits
=======
Contributors
------------
* `Elabore <mailto:contact@elabore.coop>`
Funders
-------
The development of this module has been financially supported by:
* Elabore (https://elabore.coop)
Maintainer
----------
This module is maintained by Elabore.

View File

@@ -0,0 +1 @@
from . import models

View File

@@ -1,30 +1,28 @@
# Copyright 2025 Elabore () # Copyright 2024 Elabore ()
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{ {
"name": "allow_negative_leave_and_allocation_hr_holidays_attendance", "name": "hr_negative_leave",
"version": "16.0.1.0.0", "version": "16.0.3.0.0",
"author": "Elabore", "author": "Elabore",
"website": "https://elabore.coop", "website": "https://elabore.coop",
"maintainer": "Elabore", "maintainer": "Elabore",
"license": "AGPL-3", "license": "AGPL-3",
"category": "HR", "category": "hr",
"summary": "manage heritance of Duration in TimeOffCard", "summary": "allow negative leaves, manage negative leave balances and negative allocations",
# any module necessary for this one to work correctly # any module necessary for this one to work correctly
"depends": [ "depends": [
"base","allow_negative_leave_and_allocation","hr_holidays_attendance", "base","hr_holidays",
], ],
"qweb": [], "qweb": [],
"external_dependencies": { "external_dependencies": {
"python": [], "python": [],
}, },
# always loaded # always loaded
"data": [], "data": [
"assets": { "views/hr_leave_type_views.xml",
'web.assets_backend': [ "views/hr_leave_views.xml",
'allow_negative_leave_and_allocation_hr_holidays_attendance/static/src/xml/time_off_card.xml',
], ],
},
# only loaded in demonstration mode # only loaded in demonstration mode
"demo": [], "demo": [],
"js": [], "js": [],
@@ -32,6 +30,6 @@
"installable": True, "installable": True,
# Install this module automatically if all dependency have been previously # Install this module automatically if all dependency have been previously
# and independently installed. Used for synergetic or glue modules. # and independently installed. Used for synergetic or glue modules.
"auto_install": True, "auto_install": False,
"application": False, "application": False,
} }

View File

@@ -0,0 +1,54 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * hr_negative_leave
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-31 08:25+0000\n"
"PO-Revision-Date: 2025-10-31 08:25+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: hr_negative_leave
#: model:ir.model.fields,field_description:hr_negative_leave.field_hr_leave_type__allows_negative
msgid "Allow Negative Leaves"
msgstr "Autoriser les demandes et les soldes de congés négatifs"
#. module: hr_negative_leave
#: model_terms:ir.ui.view,arch_db:hr_negative_leave.hr_leave_type_negative_leave
msgid "Allow negative"
msgstr "Autoriser les soldes négatifs"
#. module: hr_negative_leave
#: model:ir.model.fields,help:hr_negative_leave.field_hr_leave_type__allows_negative
msgid ""
"If checked, users request can exceed the allocated days and balance can go "
"in negative."
msgstr ""
#. module: hr_negative_leave
#: model:ir.model.fields,field_description:hr_negative_leave.field_hr_leave_type__remaining_leaves_allowing_negative
msgid "Remaining Leaves when Negative Allowed"
msgstr ""
#. module: hr_negative_leave
#: model:ir.model.fields,field_description:hr_negative_leave.field_hr_leave__smart_search
#: model:ir.model.fields,field_description:hr_negative_leave.field_hr_leave_type__smart_search
msgid "Smart Search"
msgstr ""
#. module: hr_negative_leave
#: model:ir.model,name:hr_negative_leave.model_hr_leave
msgid "Time Off"
msgstr "Congés"
#. module: hr_negative_leave
#: model:ir.model,name:hr_negative_leave.model_hr_leave_type
msgid "Time Off Type"
msgstr "Type de congés"

View File

@@ -0,0 +1,54 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * hr_negative_leave
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-31 08:23+0000\n"
"PO-Revision-Date: 2025-10-31 08:23+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: hr_negative_leave
#: model:ir.model.fields,field_description:hr_negative_leave.field_hr_leave_type__allows_negative
msgid "Allow Negative Leaves"
msgstr ""
#. module: hr_negative_leave
#: model_terms:ir.ui.view,arch_db:hr_negative_leave.hr_leave_type_negative_leave
msgid "Allow negative"
msgstr ""
#. module: hr_negative_leave
#: model:ir.model.fields,help:hr_negative_leave.field_hr_leave_type__allows_negative
msgid ""
"If checked, users request can exceed the allocated days and balance can go "
"in negative."
msgstr ""
#. module: hr_negative_leave
#: model:ir.model.fields,field_description:hr_negative_leave.field_hr_leave_type__remaining_leaves_allowing_negative
msgid "Remaining Leaves when Negative Allowed"
msgstr ""
#. module: hr_negative_leave
#: model:ir.model.fields,field_description:hr_negative_leave.field_hr_leave__smart_search
#: model:ir.model.fields,field_description:hr_negative_leave.field_hr_leave_type__smart_search
msgid "Smart Search"
msgstr ""
#. module: hr_negative_leave
#: model:ir.model,name:hr_negative_leave.model_hr_leave
msgid "Time Off"
msgstr ""
#. module: hr_negative_leave
#: model:ir.model,name:hr_negative_leave.model_hr_leave_type
msgid "Time Off Type"
msgstr ""

View File

@@ -0,0 +1 @@
from . import hr_leave_type, hr_leave

View File

@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
# Copyright (c) 2005-2006 Axelor SARL. (http://www.axelor.com)
from odoo import api, models
class HrLeave(models.Model):
_inherit = "hr.leave"
@api.constrains('state', 'number_of_days', 'holiday_status_id')
def _check_holidays(self):
# Keep only leaves that do not allow negative balances
to_check = self.filtered(lambda h: not h.holiday_status_id.allows_negative)
if to_check:
super(HrLeave, to_check)._check_holidays()

View File

@@ -0,0 +1,228 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import datetime
from collections import defaultdict
from datetime import time, timedelta
from odoo import api, fields, models
from odoo.tools.translate import _
from odoo.addons.resource.models.resource import Intervals
class HolidaysType(models.Model):
_inherit = "hr.leave.type"
# negative time off
allows_negative = fields.Boolean(string='Allow Negative Leaves',
help="If checked, users request can exceed the allocated days and balance can go in negative.")
@api.depends('requires_allocation')
def _compute_valid(self):
res = super()._compute_valid()
for holiday_type in res:
#if negative is allowed, then the holiday type is valid in any case
if not holiday_type.has_valid_allocation:
holiday_type.has_valid_allocation = holiday_type.allows_negative
#overwrite _get_employees_days_per_allocation() from hr_holidays module
def _get_employees_days_per_allocation(self, employee_ids, date=None):
Review

Tu peux ajouter des tests pour ce compute please ?

On se serait vite rendu compte qu'il manque qqch dans la formulation
if holiday_type.virtual_remaining_leaves < 0 (holiday_type.max_leaves - holiday_type.virtual_leaves_taken) != holiday_type.virtual_remaining_leaves:

(à priori un and ?)

Tu peux ajouter des tests pour ce compute please ? On se serait vite rendu compte qu'il manque qqch dans la formulation `if holiday_type.virtual_remaining_leaves < 0 (holiday_type.max_leaves - holiday_type.virtual_leaves_taken) != holiday_type.virtual_remaining_leaves:` (à priori un `and` ?)
Review

Vu ensemble, remaining_leaves_allowing_negative à supprimer.

Vu ensemble, remaining_leaves_allowing_negative à supprimer.
Review

c'est un reliquat de code qui n'est plus utilisé nulle part et que j'ai oublié de supprimer, c'est maintenant fait

c'est un reliquat de code qui n'est plus utilisé nulle part et que j'ai oublié de supprimer, c'est maintenant fait
if not date:
date = fields.Date.to_date(self.env.context.get('default_date_from')) or fields.Date.context_today(self)
leaves_domain = [
('employee_id', 'in', employee_ids),
('state', 'in', ['confirm', 'validate1', 'validate']),
('holiday_status_id', 'in', self.ids)
]
if self.env.context.get("ignore_future"):
leaves_domain.append(('date_from', '<=', date))
leaves = self.env['hr.leave'].search(leaves_domain)
allocations = self.env['hr.leave.allocation'].with_context(active_test=False).search([
Review

en faisant ça tu mets sous le tapis toute cette condition

                allocation = self.env['hr.leave.allocation'].search([
                    ('holiday_status_id', '=', holiday_type.id),
                    ('employee_id', '=', employee_id),
                    '|',
                    ('date_to', '>=', date_to),
                    '&',
                    ('date_to', '=', False),
                    ('date_from', '<=', date_from)])
                holiday_type.has_valid_allocation = bool(allocation)

qu'on retrouve dans le _compute_valid(), non ?

en faisant ça tu mets sous le tapis toute cette condition ``` allocation = self.env['hr.leave.allocation'].search([ ('holiday_status_id', '=', holiday_type.id), ('employee_id', '=', employee_id), '|', ('date_to', '>=', date_to), '&', ('date_to', '=', False), ('date_from', '<=', date_from)]) holiday_type.has_valid_allocation = bool(allocation) ``` qu'on retrouve dans le `_compute_valid()`, non ?
Review

Vu ensemble : mettre un petit commentaire qui explique que has_valid_allocation n'a plus de sens si on accepte les congés négatifs

Vu ensemble : mettre un petit commentaire qui explique que has_valid_allocation n'a plus de sens si on accepte les congés négatifs
Review

vu en visio avec @mondot , j'ai ajouté un commentaire pour expliquer le code

vu en visio avec @mondot , j'ai ajouté un commentaire pour expliquer le code
('employee_id', 'in', employee_ids),
('state', 'in', ['validate']),
Review

on pourrait ajouter
# Check for 'Modification for leaves allowing negative' to see the modifications

on pourrait ajouter `# Check for 'Modification for leaves allowing negative' to see the modifications`
('holiday_status_id', 'in', self.ids),
Review

J'ai du mal à comprendre cette maxi fonction. Je veux bien qu'on voit ça ensemble.

J'ai du mal à comprendre cette maxi fonction. Je veux bien qu'on voit ça ensemble.
])
# The allocation_employees dictionary groups the allocations based on the employee and the holiday type
# The structure is the following:
# - KEYS:
# allocation_employees
# |--employee_id
# |--holiday_status_id
# - VALUES:
# Intervals with the start and end date of each allocation and associated allocations within this interval
allocation_employees = defaultdict(lambda: defaultdict(list))
### Creation of the allocation intervals ###
for holiday_status_id in allocations.holiday_status_id:
for employee_id in employee_ids:
allocation_intervals = Intervals([(
fields.datetime.combine(allocation.date_from, time.min),
fields.datetime.combine(allocation.date_to or datetime.date.max, time.max),
allocation)
for allocation in allocations.filtered(lambda allocation: allocation.employee_id.id == employee_id and allocation.holiday_status_id == holiday_status_id)])
allocation_employees[employee_id][holiday_status_id] = allocation_intervals
# The leave_employees dictionary groups the leavess based on the employee and the holiday type
# The structure is the following:
# - KEYS:
# leave_employees
# |--employee_id
# |--holiday_status_id
# - VALUES:
# Intervals with the start and end date of each leave and associated leave within this interval
leaves_employees = defaultdict(lambda: defaultdict(list))
leave_intervals = []
### Creation of the leave intervals ###
if leaves:
for holiday_status_id in leaves.holiday_status_id:
for employee_id in employee_ids:
leave_intervals = Intervals([(
fields.datetime.combine(leave.date_from, time.min),
fields.datetime.combine(leave.date_to, time.max),
leave)
for leave in leaves.filtered(lambda leave: leave.employee_id.id == employee_id and leave.holiday_status_id == holiday_status_id)])
leaves_employees[employee_id][holiday_status_id] = leave_intervals
# allocation_days_consumed is a dictionary to map the number of days/hours of leaves taken per allocation
# The structure is the following:
# - KEYS:
# allocation_days_consumed
# |--employee_id
# |--holiday_status_id
# |--allocation
# |--virtual_leaves_taken
# |--leaves_taken
# |--virtual_remaining_leaves
# |--remaining_leaves
# |--max_leaves
# |--closest_allocation_to_expire
# - VALUES:
# Integer representing the number of (virtual) remaining leaves, (virtual) leaves taken or max leaves for each allocation.
# The unit is in hour or days depending on the leave type request unit
allocations_days_consumed = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: 0))))
company_domain = [('company_id', 'in', list(set(self.env.company.ids + self.env.context.get('allowed_company_ids', []))))]
### Existing leaves assigned to allocations ###
if leaves_employees:
for employee_id, leaves_interval_by_status in leaves_employees.items():
for holiday_status_id in leaves_interval_by_status:
days_consumed = allocations_days_consumed[employee_id][holiday_status_id]
if allocation_employees[employee_id][holiday_status_id]:
allocations = allocation_employees[employee_id][holiday_status_id] & leaves_interval_by_status[holiday_status_id]
available_allocations = self.env['hr.leave.allocation']
for allocation_interval in allocations._items:
available_allocations |= allocation_interval[2]
# Consume the allocations that are close to expiration first
sorted_available_allocations = available_allocations.filtered('date_to').sorted(key='date_to')
sorted_available_allocations += available_allocations.filtered(lambda allocation: not allocation.date_to)
leave_intervals = leaves_interval_by_status[holiday_status_id]._items
sorted_allocations_with_remaining_leaves = self.env['hr.leave.allocation']
for leave_interval in leave_intervals:
leaves = leave_interval[2]
for leave in leaves:
if leave.leave_type_request_unit in ['day', 'half_day']:
leave_duration = leave.number_of_days
leave_unit = 'days'
else:
leave_duration = leave.number_of_hours_display
leave_unit = 'hours'
if holiday_status_id.requires_allocation != 'no':
for available_allocation in sorted_available_allocations:
# if the allocation is not valid for the leave period, continue
if (available_allocation.date_to and available_allocation.date_to < leave.date_from.date()) \
or (available_allocation.date_from > leave.date_to.date()):
continue
# calculate the number of days/hours for this allocation (allocation days/hours - leaves already taken)
virtual_remaining_leaves = (available_allocation.number_of_days if leave_unit == 'days' else available_allocation.number_of_hours_display) - allocations_days_consumed[employee_id][holiday_status_id][available_allocation]['virtual_leaves_taken']
#############################################
# Modification for leaves allowing negative #
#############################################
# if negative is allowed for this leave type, we can exceed the number of available days in this allocation
if holiday_status_id.allows_negative:
max_leaves = leave_duration
else:
# if negative is not allowed for this leave type, then we cannot exceed the allocation amount
# the max leaves for this allocation is the minimum between the remaining available days and the leave duration
max_leaves = min(virtual_remaining_leaves, leave_duration)
####################################################
# END OF Modification for leaves allowing negative #
####################################################
# the new calculation of days taken for this allocation is previous taken + max_leaves (which can never exceed the allocation total)
days_consumed[available_allocation]['virtual_leaves_taken'] += max_leaves
if leave.state == 'validate':
days_consumed[available_allocation]['leaves_taken'] += max_leaves
leave_duration -= max_leaves
# Check valid allocations with still availabe leaves on it
if days_consumed[available_allocation]['virtual_remaining_leaves'] > 0 and available_allocation.date_to and available_allocation.date_to > date:
sorted_allocations_with_remaining_leaves |= available_allocation
if leave_duration > 0:
# There are not enough allocation for the number of leaves
days_consumed[False]['virtual_remaining_leaves'] -= leave_duration
else:
days_consumed[False]['virtual_leaves_taken'] += leave_duration
if leave.state == 'validate':
Review
                                    ###########################################
                                    # END OF Modification for leaves allowing negative #
                                    ###########################################
########################################### # END OF Modification for leaves allowing negative # ###########################################
Review

ok

ok
days_consumed[False]['leaves_taken'] += leave_duration
# no need to sort the allocations again
allocations_days_consumed[employee_id][holiday_status_id][False]['closest_allocation_to_expire'] = sorted_allocations_with_remaining_leaves[0] if sorted_allocations_with_remaining_leaves else False
# Future available leaves
future_allocations_date_from = fields.datetime.combine(date, time.min)
future_allocations_date_to = fields.datetime.combine(date, time.max) + timedelta(days=5*365)
for employee_id, allocation_intervals_by_status in allocation_employees.items():
employee = self.env['hr.employee'].browse(employee_id)
for holiday_status_id, intervals in allocation_intervals_by_status.items():
if not intervals:
continue
future_allocation_intervals = intervals & Intervals([(
future_allocations_date_from,
future_allocations_date_to,
self.env['hr.leave'])])
search_date = date
closest_allocations = self.env['hr.leave.allocation']
for interval in intervals._items:
closest_allocations |= interval[2]
allocations_with_remaining_leaves = self.env['hr.leave.allocation']
for interval_from, interval_to, interval_allocations in future_allocation_intervals._items:
if interval_from.date() > search_date:
continue
interval_allocations = interval_allocations.filtered('active')
if not interval_allocations:
continue
# If no end date to the allocation, consider the number of days remaining as infinite
employee_quantity_available = (
employee._get_work_days_data_batch(interval_from, interval_to, compute_leaves=False, domain=company_domain)[employee_id]
if interval_to != future_allocations_date_to
else {'days': float('inf'), 'hours': float('inf')}
)
reached_remaining_days_limit = False
for allocation in interval_allocations:
if allocation.date_from > search_date:
continue
days_consumed = allocations_days_consumed[employee_id][holiday_status_id][allocation]
if allocation.type_request_unit in ['day', 'half_day']:
quantity_available = employee_quantity_available['days']
remaining_days_allocation = (allocation.number_of_days - days_consumed['virtual_leaves_taken'])
else:
quantity_available = employee_quantity_available['hours']
remaining_days_allocation = (allocation.number_of_hours_display - days_consumed['virtual_leaves_taken'])
#TODO leave allocation allowing negative not yet handled here
if quantity_available <= remaining_days_allocation:
search_date = interval_to.date() + timedelta(days=1)
days_consumed['max_leaves'] = allocation.number_of_days if allocation.type_request_unit in ['day', 'half_day'] else allocation.number_of_hours_display
if not reached_remaining_days_limit:
days_consumed['virtual_remaining_leaves'] += min(quantity_available, remaining_days_allocation)
days_consumed['remaining_leaves'] = days_consumed['max_leaves'] - days_consumed['leaves_taken']
if remaining_days_allocation >= quantity_available:
reached_remaining_days_limit = True
# Check valid allocations with still availabe leaves on it
if days_consumed['virtual_remaining_leaves'] > 0 and allocation.date_to and allocation.date_to > date:
allocations_with_remaining_leaves |= allocation
allocations_sorted = sorted(allocations_with_remaining_leaves, key=lambda a: a.date_to)
allocations_days_consumed[employee_id][holiday_status_id][False]['closest_allocation_to_expire'] = allocations_sorted[0] if allocations_sorted else False
return allocations_days_consumed

View File

@@ -0,0 +1 @@
from . import test_hr_negative_leave

View File

@@ -0,0 +1,91 @@
from datetime import date, timedelta
from odoo.tests import tagged
from odoo.tests.common import TransactionCase
from odoo.exceptions import UserError
@tagged('post_install', '-at_install')
class TestHrNegativeLeave(TransactionCase):
def setUp(self):
super().setUp()
# create a simple employee
self.employee = self.env['hr.employee'].create({
'name': 'NegTest Employee',
})
# create a user
self.user = self.env['res.users'].create({
'name': 'Test user',
'login': 'test user',
'employee_id': self.employee.id,
})
# prepare a leave type and an allocation with 1 day available
self.leave_type = self.env['hr.leave.type'].create({
'name': 'NegTest Type',
'request_unit': 'day',
'requires_allocation': 'yes',
'allows_negative': False,
})
self.allocation = self.env['hr.leave.allocation'].create({
'name': 'Alloc 1d',
'employee_id': self.employee.id,
'holiday_status_id': self.leave_type.id,
'number_of_days': 1.0,
'date_from': date.today() - timedelta(days=1),
'allocation_type': 'regular',
})
def test_negative_not_allowed_raises(self):
self.allocation.action_validate()
#self.leave_type._compute_leaves()
"""If the leave type does NOT allow negative, trying to confirm a leave
that exceeds allocations should raise a UserError."""
leave = self.env['hr.leave'].create({
'name': 'Too many days',
'employee_id': self.employee.id,
'holiday_status_id': self.leave_type.id,
'request_date_from': date.today(),
'request_date_to': date.today() + timedelta(days=1),
'number_of_days': 2.0,
'state': 'draft',
})
# self.leave_type._compute_leaves()
with self.assertRaises(UserError):
leave.write({'state': 'validate'})
def test_negative_allowed_allows_excess(self):
self.env.user = self.user
self.env.user.employee_id = self.employee
self.allocation.action_validate()
"""If the leave type allows negative, confirming a leave that exceeds
allocations must NOT raise an error."""
# flip the flag on the leave type
self.leave_type.allows_negative = True
leave = self.env['hr.leave'].create({
'name': 'Too many days',
'employee_id': self.employee.id,
'holiday_status_id': self.leave_type.id,
'date_from': date.today(),
'date_to': date.today() + timedelta(days=1),
'number_of_days': 2.0,
'state': 'draft',
})
# should not raise
leave.write({'state': 'confirm'})
# check remaining leaves is negative
self.leave_type._compute_leaves()
# Allocated in time off popup (Alloué)
self.assertEqual(self.leave_type.max_leaves, 1, "max_leaves should be 1",)
# Approuved in time off popup (Approuvé)
self.assertEqual(self.leave_type.virtual_leaves_taken, 2, "virtual_leaves_taken should be 2",)
# Remaining in time off popup (Restants)
self.assertEqual(self.leave_type.max_leaves - self.leave_type.virtual_leaves_taken, -1, "remaining leaves should display in timeoff popup -1",)

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hr_leave_type_negative_leave" model="ir.ui.view">
<field name="name">hr.leave.type.negative.leave</field>
<field name="model">hr.leave.type</field>
<field name="inherit_id" ref="hr_holidays.edit_holiday_status_form" />
<field name="arch" type="xml">
<xpath expr="//group[@name='allocation_validation']" position="after">
<group name="negative_leave" id="negative_leave" colspan="4"
string="Allow negative"
attrs="{'invisible':[('requires_allocation', '=', 'no')]}"
>
<field name="allows_negative" />
</group>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,22 @@
<odoo>
<record id="hr_leave_view_negative_leave" model="ir.ui.view">
<field name="name">hr.leave.view.negative.leave</field>
<field name="model">hr.leave</field>
<field name="inherit_id" ref="hr_holidays.hr_leave_view_form" />
<field name="arch" type="xml">
<xpath expr="//field[@name='holiday_status_id']" position="attributes">
<attribute name="domain">[
'|',
('requires_allocation', '=', 'no'),
'&amp;',
('has_valid_allocation', '=', True),
'|',
('allows_negative', '=', True),
'&amp;',
('virtual_remaining_leaves', '&gt;', 0),
('allows_negative', '=', False),
]</attribute>
</xpath>
</field>
</record>
</odoo>

View File

@@ -29,7 +29,7 @@ None yet.
Bug Tracker Bug Tracker
=========== ===========
Bugs are tracked on `our issues website <https://github.com/elabore-coop/allow_negative_leave_and_allocation/issues>`_. In case of Bugs are tracked on `our issues website <https://git.elabore.coop/Elabore/hr-tools/issues>`_. In case of
trouble, please check there if your issue has already been trouble, please check there if your issue has already been
reported. If you spotted it first, help us smashing it by providing a reported. If you spotted it first, help us smashing it by providing a
detailed and welcomed feedback. detailed and welcomed feedback.