\n"
+" % if object.company_id\n"
" Sent by ${object.company_id.name}\n"
" % if 'website_url' in object.event_id and object.event_id.website_url:\n"
" \n"
" Discover all our events.\n"
" % endif\n"
+" % endif\n"
"
\n"
"
\n"
"
\n"
@@ -502,11 +504,13 @@ msgid "
\n"
"
\n"
"
\n"
+" % if object.company_id:\n"
" Sent by ${object.company_id.name}\n"
" % if 'website_url' in object.event_id and object.event_id.website_url:\n"
" \n"
" Discover all our events.\n"
" % endif\n"
+" % endif\n"
"
\n"
"
\n"
"
\n"
diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py
index 886472e8aba7e..f4086bb20066f 100644
--- a/addons/hr_holidays/models/hr_leave.py
+++ b/addons/hr_holidays/models/hr_leave.py
@@ -658,7 +658,7 @@ def action_validate(self):
elif holiday.holiday_type == 'company':
employees = self.env['hr.employee'].search([('company_id', '=', self.mode_company_id.id)])
else:
- holiday.department_id.member_ids
+ employees = holiday.department_id.member_ids
for employee in employees:
values = holiday._prepare_holiday_values(employee)
leaves += self.with_context(
diff --git a/addons/hr_holidays/tests/__init__.py b/addons/hr_holidays/tests/__init__.py
index 68ef444542894..9510f75b34bf1 100644
--- a/addons/hr_holidays/tests/__init__.py
+++ b/addons/hr_holidays/tests/__init__.py
@@ -6,3 +6,4 @@
from . import test_hr_leave_type
from . import test_accrual_allocations
from . import test_change_department
+from . import test_leave_requests
diff --git a/addons/hr_holidays/tests/test_access_rights.py b/addons/hr_holidays/tests/test_access_rights.py
index cf4e8f76ad8d8..ca651a69010ef 100644
--- a/addons/hr_holidays/tests/test_access_rights.py
+++ b/addons/hr_holidays/tests/test_access_rights.py
@@ -6,7 +6,7 @@
from odoo import tests
from odoo.addons.hr_holidays.tests.common import TestHrHolidaysBase
-from odoo.exceptions import AccessError, UserError
+from odoo.exceptions import AccessError, ValidationError, UserError
from odoo.tools import mute_logger
@@ -117,6 +117,24 @@ def test_leave_update_hr_by_user_other(self):
with self.assertRaises(AccessError):
other_leave.sudo(self.user_employee_id).write({'name': 'Crocodile Dundee is my man'})
+ # ----------------------------------------
+ # Creation
+ # ----------------------------------------
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_creation_for_other_user(self):
+ """ Employee cannot creates a leave request for another employee """
+ HolidaysEmployeeGroup = self.env['hr.leave'].sudo(self.user_employee_id)
+ with self.assertRaises(AccessError):
+ HolidaysEmployeeGroup.create({
+ 'name': 'Hol10',
+ 'employee_id': self.employee_hruser_id,
+ 'holiday_status_id': self.leave_type.id,
+ 'date_from': (datetime.today() - relativedelta(days=1)),
+ 'date_to': datetime.today(),
+ 'number_of_days': 1,
+ })
+
# ----------------------------------------
# Reset
# ----------------------------------------
@@ -137,7 +155,7 @@ def test_leave_reset_by_manager(self):
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_leave_reset_by_manager_other(self):
- """ Manager may not reset other leaves """
+ """ Manager may reset other leaves """
self.employee_leave.sudo(self.user_hrmanager).action_draft()
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
@@ -187,19 +205,23 @@ def test_leave_reset_by_user_other(self):
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_leave_validation_hr_by_manager(self):
""" Manager validates hr-only leaves """
+ self.assertEqual(self.employee_leave.state, 'confirm')
self.employee_leave.sudo(self.user_hrmanager_id).action_approve()
+ self.assertEqual(self.employee_leave.state, 'validate')
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_leave_validation_hr_by_officer_department(self):
""" Officer validates hr-only leaves for co-workers """
+ self.assertEqual(self.employee_leave.state, 'confirm')
self.employee_leave.sudo(self.user_hruser).action_approve()
+ self.assertEqual(self.employee_leave.state, 'validate')
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_leave_validation_hr_by_officer_no_department(self):
""" Officer validates hr-only leaves for workers from no department and with no manager """
self.employee_hruser.write({'department_id': False})
- # TDE FIXME: not sure for this one
- # self.employee_leave.sudo(self.user_hruser).action_approve()
+ with self.assertRaises(AccessError):
+ self.employee_leave.sudo(self.user_hruser).action_approve()
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_leave_validation_hr_by_officer_other_department_with_manager(self):
@@ -207,6 +229,7 @@ def test_leave_validation_hr_by_officer_other_department_with_manager(self):
self.employee_hruser.write({'department_id': self.hr_dept.id})
with self.assertRaises(AccessError):
self.employee_leave.sudo(self.user_hruser).action_approve()
+ self.assertEqual(self.employee_leave.state, 'confirm')
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_leave_validation_hr_by_officer_other_department_wo_manager(self):
@@ -214,6 +237,7 @@ def test_leave_validation_hr_by_officer_other_department_wo_manager(self):
self.employee_hruser.write({'department_id': self.hr_dept.id})
with self.assertRaises(AccessError):
self.employee_leave.sudo(self.user_hruser).action_approve()
+ self.assertEqual(self.employee_leave.state, 'confirm')
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_leave_validation_hr_by_officer_other_department_manager(self):
@@ -221,7 +245,9 @@ def test_leave_validation_hr_by_officer_other_department_manager(self):
self.employee_hruser.write({'department_id': self.hr_dept.id})
self.employee_leave.sudo().department_id.write({'manager_id': self.employee_hruser.id})
+ self.assertEqual(self.employee_leave.state, 'confirm')
self.employee_leave.sudo(self.user_hruser).action_approve()
+ self.assertEqual(self.employee_leave.state, 'validate')
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
def test_leave_validation_hr_by_user(self):
@@ -232,6 +258,36 @@ def test_leave_validation_hr_by_user(self):
with self.assertRaises(UserError):
self.employee_leave.sudo(self.user_employee_id).write({'state': 'validate'})
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_validate_by_manager(self):
+ """ Manager (who has no manager) validate its own leaves """
+ manager_leave = self.env['hr.leave'].sudo(self.user_hrmanager_id).create({
+ 'name': 'Hol manager',
+ 'holiday_status_id': self.leave_type.id,
+ 'employee_id': self.employee_hrmanager_id,
+ 'date_from': (datetime.today() + relativedelta(days=15)),
+ 'date_to': (datetime.today() + relativedelta(days=16)),
+ 'number_of_days': 1,
+ })
+ self.assertEqual(manager_leave.state, 'confirm')
+ manager_leave.action_approve()
+ self.assertEqual(manager_leave.state, 'validate')
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_leave_validate_by_manager_2(self):
+ """ Manager (who has also a manager) validate its own leaves """
+ manager_leave2 = self.env['hr.leave'].sudo(self.user_hrmanager_2_id).create({
+ 'name': 'Hol manager2',
+ 'holiday_status_id': self.leave_type.id,
+ 'employee_id': self.employee_hrmanager_2_id,
+ 'date_from': (datetime.today() + relativedelta(days=15)),
+ 'date_to': (datetime.today() + relativedelta(days=16)),
+ 'number_of_days': 1,
+ })
+ self.assertEqual(manager_leave2.state, 'confirm')
+ manager_leave2.action_approve()
+ self.assertEqual(manager_leave2.state, 'validate')
+
# ----------------------------------------
# Validation: one validation, manager
# ----------------------------------------
diff --git a/addons/hr_holidays/tests/test_holidays_flow.py b/addons/hr_holidays/tests/test_holidays_flow.py
index 51bcafe049cb4..08e1be1b20dca 100644
--- a/addons/hr_holidays/tests/test_holidays_flow.py
+++ b/addons/hr_holidays/tests/test_holidays_flow.py
@@ -13,29 +13,11 @@
class TestHolidaysFlow(TestHrHolidaysBase):
@mute_logger('odoo.addons.base.models.ir_model', 'odoo.models')
- def test_00_leave_request_flow(self):
- """ Testing leave request flow """
+ def test_00_leave_request_flow_unlimited(self):
+ """ Testing leave request flow: unlimited type of leave request """
Requests = self.env['hr.leave']
- Allocations = self.env['hr.leave.allocation']
HolidaysStatus = self.env['hr.leave.type']
- def _check_holidays_status(holiday_status, ml, lt, rl, vrl):
- self.assertEqual(holiday_status.max_leaves, ml,
- 'hr_holidays: wrong type days computation')
- self.assertEqual(holiday_status.leaves_taken, lt,
- 'hr_holidays: wrong type days computation')
- self.assertEqual(holiday_status.remaining_leaves, rl,
- 'hr_holidays: wrong type days computation')
- self.assertEqual(holiday_status.virtual_remaining_leaves, vrl,
- 'hr_holidays: wrong type days computation')
-
- # HrUser creates some holiday statuses -> crash because only HrManagers should do this
- with self.assertRaises(AccessError):
- HolidaysStatus.sudo(self.user_hruser_id).create({
- 'name': 'UserCheats',
- 'allocation_type': 'no',
- })
-
# HrManager creates some holiday statuses
HolidayStatusManagerGroup = HolidaysStatus.sudo(self.user_hrmanager_id)
HolidayStatusManagerGroup.create({
@@ -43,42 +25,24 @@ def _check_holidays_status(holiday_status, ml, lt, rl, vrl):
'allocation_type': 'no',
'categ_id': self.env['calendar.event.type'].sudo(self.user_hrmanager_id).create({'name': 'NotLimitedMeetingType'}).id
})
- self.holidays_status_1 = HolidayStatusManagerGroup.create({
+ self.holidays_status_hr = HolidayStatusManagerGroup.create({
'name': 'NotLimitedHR',
'allocation_type': 'no',
'validation_type': 'hr',
})
- self.holidays_status_2 = HolidayStatusManagerGroup.create({
- 'name': 'Limited',
- 'allocation_type': 'fixed',
- 'validation_type': 'both',
- })
- self.holidays_status_3 = HolidayStatusManagerGroup.create({
+ self.holidays_status_manager = HolidayStatusManagerGroup.create({
'name': 'NotLimitedManager',
'allocation_type': 'no',
'validation_type': 'manager',
})
- self.holiday_status_4 = HolidayStatusManagerGroup.create({
- 'name': 'TimeNotLimited',
- 'allocation_type': 'no',
- 'validation_type': 'manager',
- 'validity_start': fields.Datetime.from_string('2017-01-01 00:00:00'),
- 'validity_stop': fields.Datetime.from_string('2017-06-01 00:00:00'),
- })
-
- # --------------------------------------------------
- # Case1: unlimited type of leave request
- # --------------------------------------------------
- # Employee creates a leave request for another employee -> should crash
HolidaysEmployeeGroup = Requests.sudo(self.user_employee_id)
- Requests.search([('name', '=', 'Hol10')]).unlink()
# Employee creates a leave request in a no-limit category hr manager only
hol1_employee_group = HolidaysEmployeeGroup.create({
'name': 'Hol11',
'employee_id': self.employee_emp_id,
- 'holiday_status_id': self.holidays_status_1.id,
+ 'holiday_status_id': self.holidays_status_hr.id,
'date_from': (datetime.today() - relativedelta(days=1)),
'date_to': datetime.today(),
'number_of_days': 1,
@@ -87,9 +51,6 @@ def _check_holidays_status(holiday_status, ml, lt, rl, vrl):
hol1_manager_group = hol1_employee_group.sudo(self.user_hrmanager_id)
self.assertEqual(hol1_user_group.state, 'confirm', 'hr_holidays: newly created leave request should be in confirm state')
- # Employee validates its leave request -> should not work
- self.assertEqual(hol1_manager_group.state, 'confirm', 'hr_holidays: employee should not be able to validate its own leave request')
-
# HrUser validates the employee leave request -> should work
hol1_user_group.action_approve()
self.assertEqual(hol1_manager_group.state, 'validate', 'hr_holidays: validated leave request should be in validate state')
@@ -98,7 +59,7 @@ def _check_holidays_status(holiday_status, ml, lt, rl, vrl):
hol12_employee_group = HolidaysEmployeeGroup.create({
'name': 'Hol12',
'employee_id': self.employee_emp_id,
- 'holiday_status_id': self.holidays_status_3.id,
+ 'holiday_status_id': self.holidays_status_manager.id,
'date_from': (datetime.today() + relativedelta(days=12)),
'date_to': (datetime.today() + relativedelta(days=13)),
'number_of_days': 1,
@@ -107,47 +68,48 @@ def _check_holidays_status(holiday_status, ml, lt, rl, vrl):
hol12_manager_group = hol12_employee_group.sudo(self.user_hrmanager_id)
self.assertEqual(hol12_user_group.state, 'confirm', 'hr_holidays: newly created leave request should be in confirm state')
- # Employee validates its leave request -> should not work
- self.assertEqual(hol12_user_group.state, 'confirm', 'hr_holidays: employee should not be able to validate its own leave request')
-
# HrManager validate the employee leave request
hol12_manager_group.action_approve()
self.assertEqual(hol1_user_group.state, 'validate', 'hr_holidays: validates leave request should be in validate state')
- # --------------------------------------------------
- # Case2: limited type of leave request
- # --------------------------------------------------
- # Employee creates a new leave request at the same time -> crash, avoid interlapping
- with self.assertRaises(ValidationError):
- HolidaysEmployeeGroup.create({
- 'name': 'Hol21',
- 'employee_id': self.employee_emp_id,
- 'holiday_status_id': self.holidays_status_1.id,
- 'date_from': (datetime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M'),
- 'date_to': datetime.today(),
- 'number_of_days': 1,
- })
+ @mute_logger('odoo.addons.base.models.ir_model', 'odoo.models')
+ def test_01_leave_request_flow_limited(self):
+ """ Testing leave request flow: limited type of leave request """
+ Requests = self.env['hr.leave']
+ Allocations = self.env['hr.leave.allocation']
+ HolidaysStatus = self.env['hr.leave.type']
+
+ def _check_holidays_status(holiday_status, ml, lt, rl, vrl):
+ self.assertEqual(holiday_status.max_leaves, ml,
+ 'hr_holidays: wrong type days computation')
+ self.assertEqual(holiday_status.leaves_taken, lt,
+ 'hr_holidays: wrong type days computation')
+ self.assertEqual(holiday_status.remaining_leaves, rl,
+ 'hr_holidays: wrong type days computation')
+ self.assertEqual(holiday_status.virtual_remaining_leaves, vrl,
+ 'hr_holidays: wrong type days computation')
- # Employee creates a leave request in a limited category -> crash, not enough days left
- with self.assertRaises(ValidationError):
- HolidaysEmployeeGroup.create({
- 'name': 'Hol22',
- 'employee_id': self.employee_emp_id,
- 'holiday_status_id': self.holidays_status_2.id,
- 'date_from': (datetime.today() + relativedelta(days=1)).strftime('%Y-%m-%d %H:%M'),
- 'date_to': (datetime.today() + relativedelta(days=2)),
- 'number_of_days': 1,
- })
+ # HrManager creates some holiday statuses
+ HolidayStatusManagerGroup = HolidaysStatus.sudo(self.user_hrmanager_id)
+ HolidayStatusManagerGroup.create({
+ 'name': 'WithMeetingType',
+ 'allocation_type': 'no',
+ 'categ_id': self.env['calendar.event.type'].sudo(self.user_hrmanager_id).create({'name': 'NotLimitedMeetingType'}).id
+ })
- # Clean transaction
- Requests.search([('name', 'in', ['Hol21', 'Hol22'])]).unlink()
+ self.holidays_status_limited = HolidayStatusManagerGroup.create({
+ 'name': 'Limited',
+ 'allocation_type': 'fixed',
+ 'validation_type': 'both',
+ })
+ HolidaysEmployeeGroup = Requests.sudo(self.user_employee_id)
# HrUser allocates some leaves to the employee
aloc1_user_group = Allocations.sudo(self.user_hruser_id).create({
'name': 'Days for limited category',
'employee_id': self.employee_emp_id,
- 'holiday_status_id': self.holidays_status_2.id,
+ 'holiday_status_id': self.holidays_status_limited.id,
'number_of_days': 2,
})
# HrUser validates the first step
@@ -156,14 +118,14 @@ def _check_holidays_status(holiday_status, ml, lt, rl, vrl):
# HrManager validates the second step
aloc1_user_group.sudo(self.user_hrmanager_id).action_validate()
# Checks Employee has effectively some days left
- hol_status_2_employee_group = self.holidays_status_2.sudo(self.user_employee_id)
+ hol_status_2_employee_group = self.holidays_status_limited.sudo(self.user_employee_id)
_check_holidays_status(hol_status_2_employee_group, 2.0, 0.0, 2.0, 2.0)
# Employee creates a leave request in the limited category, now that he has some days left
hol2 = HolidaysEmployeeGroup.create({
'name': 'Hol22',
'employee_id': self.employee_emp_id,
- 'holiday_status_id': self.holidays_status_2.id,
+ 'holiday_status_id': self.holidays_status_limited.id,
'date_from': (datetime.today() + relativedelta(days=2)).strftime('%Y-%m-%d %H:%M'),
'date_to': (datetime.today() + relativedelta(days=3)),
'number_of_days': 1,
@@ -201,15 +163,6 @@ def _check_holidays_status(holiday_status, ml, lt, rl, vrl):
self.assertEqual(hol2.state, 'draft',
'hr_holidays: resetting should lead to draft state')
- # HrManager changes the date and put too much days -> crash when confirming
- hol2_manager_group.write({
- 'date_from': (datetime.today() + relativedelta(days=4)).strftime('%Y-%m-%d %H:%M'),
- 'date_to': (datetime.today() + relativedelta(days=7)),
- 'number_of_days': 4,
- })
- with self.assertRaises(ValidationError):
- hol2_manager_group.action_confirm()
-
employee_id = self.ref('hr.employee_admin')
# cl can be of maximum 20 days for employee_admin
hol3_status = self.env.ref('hr_holidays.holiday_status_cl').with_context(employee_id=employee_id)
@@ -236,75 +189,6 @@ def _check_holidays_status(holiday_status, ml, lt, rl, vrl):
# Check left days for casual leave: 19 days left
_check_holidays_status(hol3_status, 20.0, 1.0, 19.0, 19.0)
- Requests.create({
- 'name': 'Sick Leave',
- 'holiday_status_id': self.holiday_status_4.id,
- 'date_from': fields.Datetime.from_string('2017-03-03 06:00:00'),
- 'date_to': fields.Datetime.from_string('2017-03-11 19:00:00'),
- 'employee_id': employee_id,
- 'number_of_days': 1,
- }).unlink()
-
- with self.assertRaises(ValidationError):
- Requests.create({
- 'name': 'Sick Leave',
- 'holiday_status_id': self.holiday_status_4.id,
- 'date_from': fields.Datetime.from_string('2017-07-03 06:00:00'),
- 'date_to': fields.Datetime.from_string('2017-07-11 19:00:00'),
- 'employee_id': employee_id,
- 'number_of_days': 1,
- })
-
- hol41 = HolidaysEmployeeGroup.create({
- 'name': 'Hol41',
- 'employee_id': self.employee_emp_id,
- 'holiday_status_id': self.holidays_status_1.id,
- 'date_from': (datetime.today() + relativedelta(days=9)).strftime('%Y-%m-%d %H:%M'),
- 'date_to': (datetime.today() + relativedelta(days=10)),
- 'number_of_days': 1,
- })
-
- # A simple user should be able to reset it's own leave
- hol41.action_draft()
- hol41.unlink()
-
- hol42 = Requests.sudo(self.user_hrmanager_id).create({
- 'name': 'Hol41',
- 'employee_id': self.employee_hrmanager_id,
- 'holiday_status_id': self.holidays_status_1.id,
- 'date_from': (datetime.today() + relativedelta(days=9)).strftime('%Y-%m-%d %H:%M'),
- 'date_to': (datetime.today() + relativedelta(days=10)),
- 'number_of_days': 1,
- })
-
- # A manager should be able to reset someone else's leave
- hol42.action_draft()
- hol42.unlink()
-
- # Manager should be able to approve it's own leave
- hol51 = HolidaysEmployeeGroup.sudo(self.user_hrmanager_2_id).create({
- 'name': 'Hol51',
- 'employee_id': self.employee_hrmanager_2_id,
- 'holiday_status_id': self.holidays_status_1.id,
- 'date_from': (datetime.today() + relativedelta(days=15)).strftime('%Y-%m-%d %H:%M'),
- 'date_to': (datetime.today() + relativedelta(days=16)),
- 'number_of_days': 1,
- })
-
- hol51.action_approve()
-
- # Unless there is not manager above
- hol52 = HolidaysEmployeeGroup.sudo(self.user_hrmanager_id).create({
- 'name': 'Hol52',
- 'employee_id': self.employee_hrmanager_id,
- 'holiday_status_id': self.holidays_status_1.id,
- 'date_from': (datetime.today() + relativedelta(days=15)).strftime('%Y-%m-%d %H:%M'),
- 'date_to': (datetime.today() + relativedelta(days=16)),
- 'number_of_days': 1,
- })
-
- hol52.action_approve()
-
def test_10_leave_summary_reports(self):
# Print the HR Holidays(Summary Department) Report through the wizard
ctx = {
diff --git a/addons/hr_holidays/tests/test_hr_leave_type.py b/addons/hr_holidays/tests/test_hr_leave_type.py
index 495d1aa2e4bf7..dfe1b77713d30 100644
--- a/addons/hr_holidays/tests/test_hr_leave_type.py
+++ b/addons/hr_holidays/tests/test_hr_leave_type.py
@@ -4,6 +4,8 @@
from datetime import datetime
from dateutil.relativedelta import relativedelta
+from odoo.exceptions import AccessError
+
from odoo.addons.hr_holidays.tests.common import TestHrHolidaysBase
@@ -30,3 +32,11 @@ def test_time_type(self):
self.env['resource.calendar.leaves'].search([('holiday_id', '=', leave_1.id)]).time_type,
'leave'
)
+
+ def test_type_creation_right(self):
+ # HrUser creates some holiday statuses -> crash because only HrManagers should do this
+ with self.assertRaises(AccessError):
+ self.env['hr.leave.type'].sudo(self.user_hruser_id).create({
+ 'name': 'UserCheats',
+ 'allocation_type': 'no',
+ })
diff --git a/addons/hr_holidays/tests/test_leave_requests.py b/addons/hr_holidays/tests/test_leave_requests.py
new file mode 100644
index 0000000000000..8dad86eae3999
--- /dev/null
+++ b/addons/hr_holidays/tests/test_leave_requests.py
@@ -0,0 +1,152 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
+
+from odoo import fields
+from odoo.exceptions import ValidationError
+from odoo.tools import mute_logger
+
+from odoo.addons.hr_holidays.tests.common import TestHrHolidaysBase
+
+class TestLeaveRequests(TestHrHolidaysBase):
+
+ def _check_holidays_status(self, holiday_status, ml, lt, rl, vrl):
+ self.assertEqual(holiday_status.max_leaves, ml,
+ 'hr_holidays: wrong type days computation')
+ self.assertEqual(holiday_status.leaves_taken, lt,
+ 'hr_holidays: wrong type days computation')
+ self.assertEqual(holiday_status.remaining_leaves, rl,
+ 'hr_holidays: wrong type days computation')
+ self.assertEqual(holiday_status.virtual_remaining_leaves, vrl,
+ 'hr_holidays: wrong type days computation')
+
+ def setUp(self):
+ super(TestLeaveRequests, self).setUp()
+
+ # Make sure we have the rights to create, validate and delete the leaves, leave types and allocations
+ LeaveType = self.env['hr.leave.type'].sudo(self.user_hrmanager_id).with_context(tracking_disable=True)
+
+ self.holidays_type_1 = LeaveType.create({
+ 'name': 'NotLimitedHR',
+ 'allocation_type': 'no',
+ 'validation_type': 'hr',
+ })
+ self.holidays_type_2 = LeaveType.create({
+ 'name': 'Limited',
+ 'allocation_type': 'fixed',
+ 'validation_type': 'hr',
+ })
+ self.holidays_type_3 = LeaveType.create({
+ 'name': 'TimeNotLimited',
+ 'allocation_type': 'no',
+ 'validation_type': 'manager',
+ 'validity_start': fields.Datetime.from_string('2017-01-01 00:00:00'),
+ 'validity_stop': fields.Datetime.from_string('2017-06-01 00:00:00'),
+ })
+
+ self.set_employee_create_date(self.employee_emp_id, '2010-02-03 00:00:00')
+ self.set_employee_create_date(self.employee_hruser_id, '2010-02-03 00:00:00')
+
+ def set_employee_create_date(self, id, newdate):
+ """ This method is a hack in order to be able to define/redefine the create_date
+ of the employees.
+ This is done in SQL because ORM does not allow to write onto the create_date field.
+ """
+ self.env.cr.execute("""
+ UPDATE
+ hr_employee
+ SET create_date = '%s'
+ WHERE id = %s
+ """ % (newdate, id))
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_overlapping_requests(self):
+ """ Employee cannot create a new leave request at the same time, avoid interlapping """
+ self.env['hr.leave'].sudo(self.user_employee_id).create({
+ 'name': 'Hol11',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_type_1.id,
+ 'date_from': (datetime.today() - relativedelta(days=1)),
+ 'date_to': datetime.today(),
+ 'number_of_days': 1,
+ })
+
+ with self.assertRaises(ValidationError):
+ self.env['hr.leave'].sudo(self.user_employee_id).create({
+ 'name': 'Hol21',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_type_1.id,
+ 'date_from': (datetime.today() - relativedelta(days=1)),
+ 'date_to': datetime.today(),
+ 'number_of_days': 1,
+ })
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_limited_type_no_days(self):
+ """ Employee creates a leave request in a limited category but has not enough days left """
+
+ with self.assertRaises(ValidationError):
+ self.env['hr.leave'].sudo(self.user_employee_id).create({
+ 'name': 'Hol22',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_type_2.id,
+ 'date_from': (datetime.today() + relativedelta(days=1)).strftime('%Y-%m-%d %H:%M'),
+ 'date_to': (datetime.today() + relativedelta(days=2)),
+ 'number_of_days': 1,
+ })
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_limited_type_days_left(self):
+ """ Employee creates a leave request in a limited category and has enough days left """
+ aloc1_user_group = self.env['hr.leave.allocation'].sudo(self.user_hruser_id).create({
+ 'name': 'Days for limited category',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_type_2.id,
+ 'number_of_days': 2,
+ })
+ aloc1_user_group.action_approve()
+
+ holiday_status = self.holidays_type_2.sudo(self.user_employee_id)
+ self._check_holidays_status(holiday_status, 2.0, 0.0, 2.0, 2.0)
+
+ hol = self.env['hr.leave'].sudo(self.user_employee_id).create({
+ 'name': 'Hol11',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_type_2.id,
+ 'date_from': (datetime.today() - relativedelta(days=2)),
+ 'date_to': datetime.today(),
+ 'number_of_days': 2,
+ })
+
+ holiday_status.invalidate_cache()
+ self._check_holidays_status(holiday_status, 2.0, 0.0, 2.0, 0.0)
+
+ hol.sudo(self.user_hrmanager_id).action_approve()
+
+ self._check_holidays_status(holiday_status, 2.0, 2.0, 0.0, 0.0)
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_accrual_validity_time_valid(self):
+ """ Employee ask leave during a valid validity time """
+ self.env['hr.leave'].sudo(self.user_employee_id).create({
+ 'name': 'Valid time period',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_type_3.id,
+ 'date_from': fields.Datetime.from_string('2017-03-03 06:00:00'),
+ 'date_to': fields.Datetime.from_string('2017-03-11 19:00:00'),
+ 'number_of_days': 1,
+ })
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_accrual_validity_time_not_valid(self):
+ """ Employee ask leav during a not valid validity time """
+ with self.assertRaises(ValidationError):
+ self.env['hr.leave'].sudo(self.user_employee_id).create({
+ 'name': 'Sick Leave',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_type_3.id,
+ 'date_from': fields.Datetime.from_string('2017-07-03 06:00:00'),
+ 'date_to': fields.Datetime.from_string('2017-07-11 19:00:00'),
+ 'number_of_days': 1,
+ })
diff --git a/addons/l10n_be_invoice_bba/__manifest__.py b/addons/l10n_be_invoice_bba/__manifest__.py
index 274fd996fc6d6..53c121f9de631 100644
--- a/addons/l10n_be_invoice_bba/__manifest__.py
+++ b/addons/l10n_be_invoice_bba/__manifest__.py
@@ -11,17 +11,18 @@
'description': """
Add Structured Communication to customer invoices.
----------------------------------------------------------------------------------------------------------------------
+--------------------------------------------------
-Using BBA structured communication simplifies the reconciliation between invoices and payments.
+Using BBA structured communication simplifies the reconciliation between invoices and payments.
You can select the structured communication as payment communication in Invoicing/Accounting settings.
Three algorithms are suggested:
+
1) Random : +++RRR/RRRR/RRRDD+++
**R..R =** Random Digits, **DD =** Check Digits
2) Date : +++DOY/YEAR/SSSDD+++
**DOY =** Day of the Year, **SSS =** Sequence Number, **DD =** Check Digits
3) Customer Reference +++RRR/RRRR/SSSDDD+++
- **R..R =** Customer Reference without non-numeric characters, **SSS =** Sequence Number, **DD =** Check Digits
+ **R..R =** Customer Reference without non-numeric characters, **SSS =** Sequence Number, **DD =** Check Digits
""",
'depends': ['account', 'l10n_be'],
'data' : [
diff --git a/addons/mail/data/mail_data.xml b/addons/mail/data/mail_data.xml
index 2c7b833452d0f..9553cc5b69edc 100644
--- a/addons/mail/data/mail_data.xml
+++ b/addons/mail/data/mail_data.xml
@@ -88,11 +88,14 @@
diff --git a/addons/mail/i18n/mail.pot b/addons/mail/i18n/mail.pot
index beb83dc09ebc5..80b1399e13995 100644
--- a/addons/mail/i18n/mail.pot
+++ b/addons/mail/i18n/mail.pot
@@ -4533,7 +4533,8 @@ msgstr ""
#. module: mail
#. openerp-web
-#: code:addons/mail/static/src/xml/thread.xml:397
+#: code:addons/mail/static/src/xml/thread.xml:390
+#: model_terms:ir.ui.view,arch_db:mail.message_notification_email
#: model_terms:ir.ui.view,arch_db:mail.view_mail_search
#: selection:mail.mail,state:0
#: selection:mail.notification,email_status:0
@@ -5566,6 +5567,7 @@ msgstr ""
#. module: mail
#. openerp-web
#: code:addons/mail/static/src/xml/activity.xml:43
+#: model_terms:ir.ui.view,arch_db:mail.message_notification_email
#: model_terms:ir.ui.view,arch_db:mail.view_mail_form
#, python-format
msgid "by"
diff --git a/addons/mail/models/mail_activity.py b/addons/mail/models/mail_activity.py
index 4602fe1ff8edf..d7829ae0a1a49 100644
--- a/addons/mail/models/mail_activity.py
+++ b/addons/mail/models/mail_activity.py
@@ -422,8 +422,10 @@ def activity_format(self):
@api.model
def get_activity_data(self, res_model, domain):
- res = self.env[res_model].search(domain)
- activity_domain = [('res_id', 'in', res.ids), ('res_model', '=', res_model)]
+ activity_domain = [('res_model', '=', res_model)]
+ if domain:
+ res = self.env[res_model].search(domain)
+ activity_domain.append(('res_id', 'in', res.ids))
grouped_activities = self.env['mail.activity'].read_group(
activity_domain,
['res_id', 'activity_type_id', 'res_name:max(res_name)', 'ids:array_agg(id)', 'date_deadline:min(date_deadline)'],
@@ -443,7 +445,6 @@ def get_activity_data(self, res_model, domain):
state = self._compute_state_from_date(group['date_deadline'], self.user_id.sudo().tz)
activity_data[res_id][activity_type_id] = {
'count': group['__count'],
- 'domain': group['__domain'],
'ids': group['ids'],
'state': state,
'o_closest_deadline': group['date_deadline'],
diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py
index 497f32fe6288c..17d11c89401b4 100644
--- a/addons/mail/models/mail_channel.py
+++ b/addons/mail/models/mail_channel.py
@@ -340,7 +340,7 @@ def message_receive_bounce(self, email, partner, mail_id=None):
@api.multi
def _notify_email_recipients(self, message, recipient_ids):
# Excluded Blacklisted
- whitelist = self.env['res.partner'].sudo().search([('id', 'in', recipient_ids), ('is_blacklisted', '=', False)])
+ whitelist = self.env['res.partner'].sudo().search([('id', 'in', recipient_ids)]).filtered(lambda p: not p.is_blacklisted)
# real mailing list: multiple recipients (hidden by X-Forge-To)
if self.alias_domain and self.alias_name:
return {
diff --git a/addons/mail/models/mail_followers.py b/addons/mail/models/mail_followers.py
index ccb245e18d33d..f4f16c2e9f01b 100644
--- a/addons/mail/models/mail_followers.py
+++ b/addons/mail/models/mail_followers.py
@@ -114,14 +114,14 @@ def _get_recipient_data(self, records, subtype_id, pids=None, cids=None):
partner.active as active, partner.partner_share as pshare, NULL as ctype,
users.notification_type AS notif, array_agg(groups.id) AS groups
FROM res_partner partner
- LEFT JOIN res_users users ON users.partner_id = partner.id
+ LEFT JOIN res_users users ON users.partner_id = partner.id AND users.active
LEFT JOIN res_groups_users_rel groups_rel ON groups_rel.uid = users.id
LEFT JOIN res_groups groups ON groups.id = groups_rel.gid
WHERE EXISTS (
SELECT partner_id FROM sub_followers
WHERE sub_followers.channel_id IS NULL
AND sub_followers.partner_id = partner.id
- AND (sub_followers.internal <> TRUE OR partner.partner_share <> TRUE)
+ AND (coalesce(sub_followers.internal, false) <> TRUE OR coalesce(partner.partner_share, false) <> TRUE)
) %s
GROUP BY partner.id, users.notification_type
UNION
@@ -148,7 +148,7 @@ def _get_recipient_data(self, records, subtype_id, pids=None, cids=None):
partner.active as active, partner.partner_share as pshare, NULL as ctype,
users.notification_type AS notif, NULL AS groups
FROM res_partner partner
-LEFT JOIN res_users users ON users.partner_id = partner.id
+LEFT JOIN res_users users ON users.partner_id = partner.id AND users.active
WHERE partner.id IN %s"""
params.append(tuple(pids))
if cids:
diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py
index aea140127672f..6a50a19834774 100644
--- a/addons/mail/models/mail_mail.py
+++ b/addons/mail/models/mail_mail.py
@@ -375,9 +375,6 @@ def _send(self, auto_commit=False, raise_exception=False, smtp_session=None):
# /!\ can't use mail.state here, as mail.refresh() will cause an error
# see revid:odo@openerp.com-20120622152536-42b2s28lvdv3odyr in 6.1
mail._postprocess_sent_message(success_pids=success_pids, failure_type=failure_type)
- except UnicodeEncodeError as exc:
- _logger.exception('UnicodeEncodeError on text "%s" while processing mail ID %r.', exc.object, mail.id)
- raise MailDeliveryException(_("Mail Delivery Failed"), "Invalid text: %s" % exc.object)
except MemoryError:
# prevent catching transient MemoryErrors, bubble up to notify user or abort cron job
# instead of marking the mail as failed
@@ -398,10 +395,13 @@ def _send(self, auto_commit=False, raise_exception=False, smtp_session=None):
mail.write({'state': 'exception', 'failure_reason': failure_reason})
mail._postprocess_sent_message(success_pids=success_pids, failure_reason=failure_reason, failure_type='UNKNOWN')
if raise_exception:
- if isinstance(e, AssertionError):
- # get the args of the original error, wrap into a value and throw a MailDeliveryException
- # that is an except_orm, with name and value as arguments
- value = '. '.join(e.args)
+ if isinstance(e, (AssertionError, UnicodeEncodeError)):
+ if isinstance(e, UnicodeEncodeError):
+ value = "Invalid text: %s" % e.object
+ else:
+ # get the args of the original error, wrap into a value and throw a MailDeliveryException
+ # that is an except_orm, with name and value as arguments
+ value = '. '.join(e.args)
raise MailDeliveryException(_("Mail Delivery Failed"), value)
raise
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index a6ad7be4ee51e..a4feb61b3fb83 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -1169,7 +1169,7 @@ def message_route(self, message, message_dict, model=None, thread_id=None, custo
final_recipient_data = tools.decode_message_header(dsn, 'Final-Recipient')
partner_address = final_recipient_data.split(';', 1)[1].strip()
if partner_address:
- partners = partners.sudo().search([('email', 'like', partner_address)])
+ partners = partners.sudo().search([('email', '=', partner_address)])
for partner in partners:
partner.message_receive_bounce(partner_address, partner, mail_id=bounced_mail_id)
diff --git a/addons/mail/static/src/js/composers/basic_composer.js b/addons/mail/static/src/js/composers/basic_composer.js
index a6cdb39e2de9b..8a533cd451557 100644
--- a/addons/mail/static/src/js/composers/basic_composer.js
+++ b/addons/mail/static/src/js/composers/basic_composer.js
@@ -211,7 +211,8 @@ var BasicComposer = Widget.extend({
* displayed to the user. If none of them match, then it will fetch for more
* partner suggestions (@see _mentionFetchPartners).
*
- * @param {$.Deferred