357 lines
16 KiB
Python
357 lines
16 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
from odoo.fields import Command
|
|
from odoo.addons.project.tests.test_project_base import TestProjectCommon
|
|
from odoo.addons.web_gantt.models.models import Base
|
|
from odoo.tests import new_test_user
|
|
|
|
|
|
# As the writing of the new planned_dates is only made when planned_date_start is in the future,
|
|
# we need to cheat during the tests
|
|
fake_now = datetime(2021, 4, 1)
|
|
|
|
|
|
class ProjectEnterpriseGanttRescheduleCommon(TestProjectCommon):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
"""
|
|
project structure
|
|
|
|
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
|
|
│ Task 1 │ --> │ Task 3 │ --> │ Task 4 │ --> │ Task 5 │ --> │ Task 6 │
|
|
│ │ │ │ │ │ │02/08 8H │ │ │
|
|
│ 24/06 │ │ 24/06 │ │ 30/06 │ │ -> │ │ 04/08 │
|
|
│ 9H > 12H│ │13H > 15H│ │15H > 17H│ │03/08 17H│ │ 8H->17H │
|
|
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
|
|
|
|
1 --> 3: 3 blocked by 1
|
|
|
|
"""
|
|
def get_overlapping_delta():
|
|
return cls.task_3_planned_date_begin - cls.task_1_date_deadline + timedelta(hours=1)
|
|
|
|
super().setUpClass()
|
|
|
|
test_additional_context = {'mail_create_nolog': True, 'tracking_disable': True, 'mail_notrack': True}
|
|
test_context = dict(cls.env.context, **test_additional_context)
|
|
cls.env = cls.env(context=test_context)
|
|
cls.maxDiff = None
|
|
|
|
cls.ProjectTask = cls.env['project.task']
|
|
cls.dependency_field_name = 'depend_on_ids'
|
|
cls.dependency_inverted_field_name = 'dependent_ids'
|
|
cls.start_date_field_name = 'planned_date_begin'
|
|
cls.stop_date_field_name = 'date_deadline'
|
|
cls.Settings = cls.env["res.config.settings"]
|
|
cls.project_pigs.write({
|
|
'allow_task_dependencies': True,
|
|
})
|
|
cls.task_1 = cls.task_1.with_context(**test_context)
|
|
cls.task_1_planned_date_begin = datetime(2021, 6, 24, 9, 0, 0)
|
|
cls.task_1_date_deadline = datetime(2021, 6, 24, 12, 0, 0)
|
|
cls.task_1.write({
|
|
'planned_date_begin': cls.task_1_planned_date_begin,
|
|
'date_deadline': cls.task_1_date_deadline,
|
|
'allocated_hours': 3.0,
|
|
})
|
|
cls.task_3_planned_date_begin = datetime(2021, 6, 24, 13, 0, 0)
|
|
cls.task_3_date_deadline = datetime(2021, 6, 24, 15, 0, 0)
|
|
cls.task_3 = cls.ProjectTask.create({
|
|
'name': 'Pigs UserTask 3',
|
|
'user_ids': cls.user_projectuser,
|
|
'project_id': cls.project_pigs.id,
|
|
'depend_on_ids': [Command.link(cls.task_1.id)],
|
|
'planned_date_begin': cls.task_3_planned_date_begin,
|
|
'date_deadline': cls.task_3_date_deadline,
|
|
'allocated_hours': 2.0,
|
|
})
|
|
cls.task_4_planned_date_begin = datetime(2021, 6, 30, 15, 0, 0)
|
|
cls.task_4_date_deadline = datetime(2021, 6, 30, 17, 0, 0)
|
|
cls.task_4 = cls.ProjectTask.create({
|
|
'name': 'Pigs UserTask 4',
|
|
'user_ids': cls.user_projectuser,
|
|
'project_id': cls.project_pigs.id,
|
|
'depend_on_ids': [Command.link(cls.task_3.id)],
|
|
'planned_date_begin': cls.task_4_planned_date_begin,
|
|
'date_deadline': cls.task_4_date_deadline,
|
|
'allocated_hours': 2.0,
|
|
})
|
|
cls.task_5_planned_date_begin = datetime(2021, 8, 2, 8, 0, 0)
|
|
cls.task_5_date_deadline = datetime(2021, 8, 3, 17, 0, 0)
|
|
cls.task_5 = cls.ProjectTask.create({
|
|
'name': 'Pigs UserTask 5',
|
|
'user_ids': cls.user_projectuser,
|
|
'project_id': cls.project_pigs.id,
|
|
'depend_on_ids': [Command.link(cls.task_4.id)],
|
|
'planned_date_begin': cls.task_5_planned_date_begin,
|
|
'date_deadline': cls.task_5_date_deadline,
|
|
'allocated_hours': 16.0,
|
|
})
|
|
cls.task_6_planned_date_begin = datetime(2021, 8, 4, 8, 0, 0)
|
|
cls.task_6_date_deadline = datetime(2021, 8, 4, 17, 0, 0)
|
|
cls.task_6 = cls.ProjectTask.create({
|
|
'name': 'Pigs UserTask 6',
|
|
'user_ids': cls.user_projectuser,
|
|
'project_id': cls.project_pigs.id,
|
|
'depend_on_ids': [Command.link(cls.task_5.id)],
|
|
'planned_date_begin': cls.task_6_planned_date_begin,
|
|
'date_deadline': cls.task_6_date_deadline,
|
|
'allocated_hours': 8.0,
|
|
})
|
|
|
|
cls.project_pigs_intial_dates = lambda cls: {
|
|
task.name: (task.planned_date_begin, task.date_deadline)
|
|
for task in cls.project_pigs.task_ids
|
|
}
|
|
|
|
cls.task_1_date_gantt_reschedule_trigger = lambda cls: {
|
|
'planned_date_begin': (cls.task_1.planned_date_begin + get_overlapping_delta()).strftime('%Y-%m-%d %H:%M:%S'),
|
|
'date_deadline': (cls.task_1.date_deadline + get_overlapping_delta()).strftime('%Y-%m-%d %H:%M:%S'),
|
|
}
|
|
cls.task_1_no_date_gantt_reschedule_trigger = lambda cls: {
|
|
'planned_date_begin': cls.task_1.planned_date_begin + get_overlapping_delta() - timedelta(hours=1),
|
|
'date_deadline': cls.task_1.date_deadline + get_overlapping_delta() - timedelta(hours=1),
|
|
}
|
|
cls.calendar_40h = cls.env['resource.calendar'].create({
|
|
'name': '40h calendar',
|
|
'attendance_ids': [
|
|
(0, 0, {
|
|
'name': 'Monday Morning', 'dayofweek': '0',
|
|
'hour_from': 8, 'hour_to': 12,
|
|
'day_period': 'morning'}
|
|
),
|
|
(0, 0, {
|
|
'name': 'Monday Lunch', 'dayofweek': '0',
|
|
'hour_from': 12, 'hour_to': 13,
|
|
'day_period': 'lunch'}
|
|
),
|
|
(0, 0, {
|
|
'name': 'Monday Evening', 'dayofweek': '0',
|
|
'hour_from': 13, 'hour_to': 17,
|
|
'day_period': 'afternoon'}
|
|
),
|
|
(0, 0, {
|
|
'name': 'Tuesday Morning', 'dayofweek': '1',
|
|
'hour_from': 8, 'hour_to': 12,
|
|
'day_period': 'morning'}
|
|
),
|
|
(0, 0, {
|
|
'name': 'Tuesday Lunch', 'dayofweek': '1',
|
|
'hour_from': 12, 'hour_to': 13,
|
|
'day_period': 'lunch'}
|
|
),
|
|
(0, 0, {
|
|
'name': 'Tuesday Evening', 'dayofweek': '1',
|
|
'hour_from': 13, 'hour_to': 17,
|
|
'day_period': 'afternoon'}
|
|
),
|
|
(0, 0, {
|
|
'name': 'Wednesday Morning', 'dayofweek': '2',
|
|
'hour_from': 8, 'hour_to': 12,
|
|
'day_period': 'morning'}
|
|
),
|
|
(0, 0, {
|
|
'name': 'Wednesday Lunch', 'dayofweek': '2',
|
|
'hour_from': 12, 'hour_to': 13,
|
|
'day_period': 'lunch'}
|
|
),
|
|
(0, 0, {
|
|
'name': 'Wednesday Evening', 'dayofweek': '2',
|
|
'hour_from': 13, 'hour_to': 17,
|
|
'day_period': 'afternoon'}
|
|
),
|
|
(0, 0, {
|
|
'name': 'Thursday Morning', 'dayofweek': '3',
|
|
'hour_from': 8, 'hour_to': 12,
|
|
'day_period': 'morning'}
|
|
),
|
|
(0, 0, {
|
|
'name': 'Thursday Lunch', 'dayofweek': '3',
|
|
'hour_from': 12, 'hour_to': 13,
|
|
'day_period': 'lunch'}
|
|
),
|
|
(0, 0, {
|
|
'name': 'Thursday Evening', 'dayofweek': '3',
|
|
'hour_from': 13, 'hour_to': 17,
|
|
'day_period': 'afternoon'}
|
|
),
|
|
(0, 0, {
|
|
'name': 'Friday Morning', 'dayofweek': '4',
|
|
'hour_from': 8, 'hour_to': 12,
|
|
'day_period': 'morning'}
|
|
),
|
|
(0, 0, {
|
|
'name': 'Friday Lunch', 'dayofweek': '4',
|
|
'hour_from': 12, 'hour_to': 13,
|
|
'day_period': 'lunch'}
|
|
),
|
|
(0, 0, {
|
|
'name': 'Friday Evening', 'dayofweek': '4',
|
|
'hour_from': 13, 'hour_to': 17,
|
|
'day_period': 'afternoon'}
|
|
),
|
|
],
|
|
'tz': 'UTC',
|
|
})
|
|
cls.annual_holiday = cls.env['resource.calendar.leaves'].create({
|
|
'name': 'Building leave',
|
|
'resource_id': False,
|
|
'calendar_id': cls.calendar_40h.id,
|
|
'date_from': datetime(2021, 7, 1, 0, 0, 0),
|
|
'date_to': datetime(2021, 7, 31, 23, 59, 59),
|
|
})
|
|
cls.env.company.write({
|
|
'resource_calendar_id': cls.calendar_40h.id,
|
|
})
|
|
cls.user1 = new_test_user(cls.env, login='raouf2')
|
|
cls.user2 = new_test_user(cls.env, login='raouf3')
|
|
|
|
"""
|
|
Second project structure (with more nodes and use cases)
|
|
|
|
[4]->[6]
|
|
|
|
|
[14] [13] v
|
|
[11] [12] --->[0]->[1]->[2] [5]->[7]->[8]-----------------
|
|
| | | |
|
|
| v v |
|
|
| [3] [9]->[10] |
|
|
| |
|
|
------------------------------------------------------
|
|
|
|
[0]->[1] means 1 blocked by 0
|
|
"""
|
|
cls.project2 = cls.env['project.project'].create({
|
|
'name': 'Test dependencies Project',
|
|
'type_ids': [Command.create({'name': 'To Do'})],
|
|
'allow_task_dependencies': True,
|
|
})
|
|
|
|
cls.initial_dates = {
|
|
'0': (datetime(2024, 3, 1, 8, 0), datetime(2024, 3, 1, 12, 0)),
|
|
'1': (datetime(2024, 3, 1, 13, 0), datetime(2024, 3, 1, 17, 0)),
|
|
'2': (datetime(2024, 3, 4, 8, 0), datetime(2024, 3, 4, 10, 0)),
|
|
'3': (datetime(2024, 3, 2, 11, 0), datetime(2024, 3, 2, 17, 0)),
|
|
'4': (datetime(2024, 3, 5, 8, 0), datetime(2024, 3, 6, 17, 0)),
|
|
'5': (datetime(2024, 3, 7, 8, 0), datetime(2024, 3, 8, 17, 0)),
|
|
'6': (datetime(2024, 3, 9, 8, 0), datetime(2024, 3, 13, 12, 0)),
|
|
'7': (datetime(2024, 3, 13, 13, 0), datetime(2024, 3, 13, 17, 0)),
|
|
'8': (datetime(2024, 3, 15, 8, 0), datetime(2024, 3, 16, 14, 0)),
|
|
'9': (datetime(2024, 3, 14, 8, 0), datetime(2024, 3, 14, 12, 0)),
|
|
'10': (datetime(2024, 3, 14, 13, 0), datetime(2024, 3, 14, 17, 0)),
|
|
'11': (datetime(2024, 2, 28, 8, 0), datetime(2024, 2, 28, 11, 0)),
|
|
'12': (datetime(2024, 2, 29, 15, 0), datetime(2024, 2, 29, 16, 0)),
|
|
'13': (datetime(2024, 2, 29, 9, 0), datetime(2024, 2, 29, 15, 0)),
|
|
'14': (datetime(2024, 2, 26, 8, 0), datetime(2024, 2, 26, 15, 0)),
|
|
}
|
|
|
|
vals_list = []
|
|
for name, dates in cls.initial_dates.items():
|
|
vals_list.append({
|
|
"name": name,
|
|
"user_ids": cls.user_projectuser,
|
|
"project_id": cls.project2.id,
|
|
"planned_date_begin": dates[0],
|
|
"date_deadline": dates[1],
|
|
})
|
|
|
|
cls.project2_task_0, \
|
|
cls.project2_task_1, \
|
|
cls.project2_task_2, \
|
|
cls.project2_task_3, \
|
|
cls.project2_task_4, \
|
|
cls.project2_task_5, \
|
|
cls.project2_task_6, \
|
|
cls.project2_task_7, \
|
|
cls.project2_task_8, \
|
|
cls.project2_task_9, \
|
|
cls.project2_task_10, \
|
|
cls.project2_task_11, \
|
|
cls.project2_task_12, \
|
|
cls.project2_task_13, \
|
|
cls.project2_task_14 = cls.ProjectTask.create(vals_list)
|
|
|
|
cls.project2_task_0.write({
|
|
'depend_on_ids': [Command.link(cls.project2_task_8.id)],
|
|
'dependent_ids': [Command.link(cls.project2_task_1.id)],
|
|
})
|
|
cls.project2_task_1.write({
|
|
'dependent_ids': [Command.link(cls.project2_task_2.id), Command.link(cls.project2_task_3.id)]
|
|
})
|
|
cls.project2_task_3.write({
|
|
'allocated_hours': 5,
|
|
})
|
|
cls.project2_task_4.write({
|
|
'allocated_hours': 16,
|
|
})
|
|
cls.project2_task_5.write({
|
|
'allocated_hours': 16,
|
|
})
|
|
cls.project2_task_6.write({
|
|
'depend_on_ids': [Command.link(cls.project2_task_4.id)],
|
|
'allocated_hours': 20,
|
|
})
|
|
cls.project2_task_7.write({
|
|
'depend_on_ids': [Command.link(cls.project2_task_5.id), Command.link(cls.project2_task_6.id)],
|
|
})
|
|
cls.project2_task_8.write({
|
|
'depend_on_ids': [Command.link(cls.project2_task_7.id)],
|
|
'allocated_hours': 13,
|
|
})
|
|
cls.project2_task_9.write({
|
|
'depend_on_ids': [Command.link(cls.project2_task_5.id)],
|
|
})
|
|
cls.project2_task_10.write({
|
|
'depend_on_ids': [Command.link(cls.project2_task_9.id)],
|
|
})
|
|
cls.is_module_timesheet_grid_installed = hasattr(cls.env['project.task'], 'allow_timesheets')
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.env.user.has_group('.')
|
|
self.env.user.has_group('base.group_system')
|
|
|
|
@classmethod
|
|
def gantt_reschedule_maintain_buffer(cls, record, data):
|
|
return cls.ProjectTask.web_gantt_reschedule(
|
|
data,
|
|
Base._WEB_GANTT_RESCHEDULE_MAINTAIN_BUFFER,
|
|
record.id,
|
|
cls.dependency_field_name, cls.dependency_inverted_field_name,
|
|
cls.start_date_field_name, cls.stop_date_field_name
|
|
)
|
|
|
|
@classmethod
|
|
def gantt_reschedule_consume_buffer(cls, record, data):
|
|
return cls.ProjectTask.web_gantt_reschedule(
|
|
data,
|
|
Base._WEB_GANTT_RESCHEDULE_CONSUME_BUFFER,
|
|
record.id,
|
|
cls.dependency_field_name, cls.dependency_inverted_field_name,
|
|
cls.start_date_field_name, cls.stop_date_field_name
|
|
)
|
|
|
|
def assert_task_not_replanned(cls, tasks, initial_dates):
|
|
for task in tasks:
|
|
cls.assertEqual((task.planned_date_begin, task.date_deadline), initial_dates[task.name], f"task {task.name} should not be replanned")
|
|
|
|
def assert_new_dates(cls, task, planned_date_begin, date_deadline, message=None):
|
|
cls.assertEqual(task.planned_date_begin, planned_date_begin, message)
|
|
cls.assertEqual(task.date_deadline, date_deadline, message)
|
|
|
|
def assert_old_tasks_vals(cls, res, _type, message, moved_tasks, initial_dates):
|
|
cls.assertDictEqual(res, {
|
|
'type': _type,
|
|
'message': message,
|
|
'old_vals_per_pill_id': {
|
|
task.id: {
|
|
'planned_date_begin': initial_dates[task.name][0],
|
|
'date_deadline': initial_dates[task.name][1],
|
|
} for task in moved_tasks
|
|
},
|
|
})
|