Files
enterprise-addons/project_enterprise/tests/gantt_reschedule_dates_common.py
T

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
},
})