Files

1025 lines
51 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from odoo.fields import Command
from odoo.tests.common import users, tagged, freeze_time
from .gantt_reschedule_dates_common import ProjectEnterpriseGanttRescheduleCommon, fake_now
@tagged('gantt_reschedule', 'post_install', '-at_install')
@freeze_time(fake_now)
class TestGanttRescheduleOnTasks(ProjectEnterpriseGanttRescheduleCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.task_1_date_deadline -= timedelta(hours=2)
cls.task_1.write({"date_deadline": cls.task_1_date_deadline, "allocated_hours": 1})
def test_gantt_reschedule_date_not_active(self):
""" This test purpose is to ensure that auto shift date feature is not active when it shouldn't:
* When the task has no project (either the moved task or the linked one (depend_on_ids or dependent_ids).
* When the calculated planned_start_date is prior to now.
"""
def test_task_3_dates_unchanged(task_1_new_planned_dates, failed_message, domain_force=False, **context):
task_1 = self.task_1.with_context(context) if domain_force else self.task_1.with_context(**context)
self.gantt_reschedule_consume_buffer(self.task_1, task_1_new_planned_dates)
self.assertEqual(self.task_3_planned_date_begin, self.task_3.planned_date_begin, failed_message)
self.assertEqual(self.task_3_date_deadline, self.task_3.date_deadline, failed_message)
task_1.write({
'planned_date_begin': self.task_1_planned_date_begin,
'date_deadline': self.task_1_date_deadline,
})
self.task_4.depend_on_ids = [Command.clear()]
# Checks that no date shift is made when the moved task has no project_id
failed_message = "The auto shift date feature should not be triggered after having moved a task that " \
"does not have a project_id."
project_id = self.task_1.project_id
self.task_1.write({
'project_id': False
})
test_task_3_dates_unchanged(self.task_1_date_gantt_reschedule_trigger(), failed_message)
self.task_1.write({
'project_id': project_id.id
})
# Checks that no date shift is made when the linked task has no project_id
failed_message = "The auto shift date feature should not be triggered on tasks (depend_on_ids/dependent_ids) " \
"that do have a project_id."
project_id = self.task_3.project_id
self.task_3.write({
'project_id': False
})
test_task_3_dates_unchanged(self.task_1_date_gantt_reschedule_trigger(), failed_message)
self.task_3.write({
'project_id': project_id.id
})
# Checks that no date shift is made when the new planned_date is prior to the current datetime.
with freeze_time(self.task_1_no_date_gantt_reschedule_trigger()['planned_date_begin'] + relativedelta(weeks=1)):
failed_message = "The auto shift date feature should not trigger any changes when the new planned_date " \
"is prior to the current datetime."
test_task_3_dates_unchanged(self.task_1_date_gantt_reschedule_trigger(), failed_message)
def test_gantt_reschedule_forward_dependent_task_consume_and_maintain_buffer(self):
"""
All tasks in this test have allocated_hours, so their duration is computed according to the allocated hours
Initial state:
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Task 1 │ --> │ Task 3 │ --> │ Task 4 │ --> │ Task 5 │ --> │ Task 6 │
│ │ │ │ │ │ │02/08 8H │ │ │
│ 24/06 │ │ 24/06 │ │ 30/06 │ │ -> │ │ 04/08 │
│ 9H > 10H│ │13H > 15H│ │15H > 17H│ │03/08 17H│ │ 8H->17H │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
buffer(1, 3) = 2 hours (10H -> 12H), lunch hour from 12H -> 13H is not counted
buffer(3, 4) = 32 hours
buffer(4, 5) = 0
buffer(5, 6) = 0
This test purpose is to ensure that task 3 that depends on a task 1 is shifted forward when task 1 advances in 2 ways:
1- consume buffer: task 3 can start directly (when possible) after task 1, tasks 4, 5, 6 should not be replanned as there no
conflicts to solve.
2- maintain buffer: task 3 should start after min = buffer = 2H after the end of task 1, tasks 4, 5 and 6 should move as the buffer should
be maintained
┌─────────┐
│ Task 1 │
│ │
│ 24/06 │
│13H > 14H│
└─────────┘
|
| ┌─────────┐
| │ Task 3 │
-->│ │ task 3 should move to the right with its children 4, 5 and 6
│ 24/06 │ =>
│13H > 15H│
└─────────┘
"""
moved_tasks = self.task_3 | self.task_1
not_moved_tasks = self.task_4 | self.task_5 | self.task_6
old_vals = {
task.name: (task.planned_date_begin, task.date_deadline)
for task in moved_tasks
}
# 1- consume buffer
res = self.gantt_reschedule_consume_buffer(self.task_1, self.task_1_date_gantt_reschedule_trigger())
self.assert_old_tasks_vals(res, 'success', 'Tasks rescheduled', moved_tasks, old_vals)
failed_message = "The auto shift date feature should move forward a dependent tasks."
self.assertEqual(self.task_3.planned_date_begin, datetime(2021, 6, 24, 14), failed_message)
self.assertEqual(self.task_3.date_deadline, datetime(2021, 6, 24, 16), failed_message)
self.assertEqual(self.task_1.planned_date_begin, datetime(2021, 6, 24, 13), failed_message)
self.assertEqual(self.task_1.date_deadline, datetime(2021, 6, 24, 14), failed_message)
self.assert_task_not_replanned(not_moved_tasks, self.project_pigs_intial_dates())
moved_tasks.action_rollback_scheduling(res['old_vals_per_pill_id'])
self.assertEqual((self.task_3.planned_date_begin, self.task_3.date_deadline), old_vals[self.task_3.name])
self.assertEqual((self.task_1.planned_date_begin, self.task_1.date_deadline), old_vals[self.task_1.name])
# 2- maintain buffer
moved_tasks |= not_moved_tasks
for task in not_moved_tasks:
old_vals[task.name] = (task.planned_date_begin, task.date_deadline)
res = self.gantt_reschedule_maintain_buffer(self.task_1, self.task_1_date_gantt_reschedule_trigger())
self.assert_old_tasks_vals(res, 'success', 'Tasks rescheduled', moved_tasks, old_vals)
self.assertEqual(self.task_1.planned_date_begin, datetime(2021, 6, 24, 13), failed_message)
self.assertEqual(self.task_1.date_deadline, datetime(2021, 6, 24, 14), failed_message)
self.assertEqual(self.task_3.planned_date_begin, datetime(2021, 6, 24, 16), failed_message + "tasks 3 should start after buffer = 2 hours after the end of task 1")
self.assertEqual(self.task_3.date_deadline, datetime(2021, 6, 25, 9), failed_message + "tasks 3 should start after buffer = 2 hours after the end of task 1")
self.assertEqual(self.task_4.planned_date_begin, datetime(2021, 8, 2, 9), failed_message + """
buffer between task 3 and 4 = 32H
we need to wait 31h in month 6 (7 hours on day 25 and 24 Hours on the last week)
month 7 is off for the company
1 hours on monday 02/08
""")
self.assertEqual(self.task_4.date_deadline, self.task_5.planned_date_begin, failed_message + "buffer = 0 between task 4 and 5 as month 07 was off")
self.assertEqual(self.task_5.date_deadline, self.task_6.planned_date_begin, failed_message + "buffer = 0 between task 5 and 6")
def test_gantt_reschedule_backward_dependent_task_consume_and_maintain_buffer(self):
"""
All tasks in this test have allocated_hours, so their duration is computed according to the allocated hours
This test purpose is to ensure that a task A that depends on a task B is shifted backward, up to before
B planned_date_start field value.
┌─────────┐
task 5 should move │ Task 5 │
to the left with | 02/08 8H|
its ancestors │ -> │
<= │ │-------
│03/08 17H│ |
└─────────┘ |
|------------------
| ┌─────────┐
| │ Task 6 │
-->│ │
│ 03/08 │
│08H > 17H│
└─────────┘
"""
moved_tasks = self.task_6 | self.task_5 | self.task_4
not_moved_tasks = self.task_3 | self.task_1
old_vals = {
task.name: (task.planned_date_begin, task.date_deadline)
for task in moved_tasks
}
task_6_vals = {
"planned_date_begin": '2021-08-03 08:00:00',
"date_deadline": '2021-08-03 17:00:00'
}
# 1- consume buffer
res = self.gantt_reschedule_consume_buffer(self.task_6, task_6_vals)
self.assert_old_tasks_vals(res, 'success', 'Tasks rescheduled', moved_tasks, old_vals)
failed_message = "The auto shift date feature should move forward a dependent tasks."
self.assertEqual(self.task_6.planned_date_begin, datetime(2021, 8, 3, 8), failed_message)
self.assertEqual(self.task_6.date_deadline, datetime(2021, 8, 3, 17), failed_message)
self.assertEqual(self.task_5.planned_date_begin, datetime(2021, 6, 30, 8), failed_message)
self.assertEqual(self.task_5.date_deadline, datetime(2021, 8, 2, 17), failed_message)
self.assert_task_not_replanned(not_moved_tasks, self.project_pigs_intial_dates())
moved_tasks.action_rollback_scheduling(res['old_vals_per_pill_id'])
for task in moved_tasks:
self.assertEqual((task.planned_date_begin, task.date_deadline), old_vals[task.name])
"""
2- maintain buffer
buffer(5, 6) = 0
buffer(4, 5) = 0
buffer(4, 3) = 32H => as task 4 were moved day in the past, task 3 moved also 1 day in the past to keep the buffer
buffer(1, 3) = 2H => task 1 will also follow by moving one day in the past to keep the buffer
INFO: it's one day in the back by chance because it's still in a valid period (task 5 and task 6 are counter examples)
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Task 1 │ --> │ Task 3 │ --> │ Task 4 │ --> │ Task 5 │ --> │ Task 6 │
│ │ │ │ │ │ │30/06 8H │ │ │
│ 23/06 │ │ 23/06 │ │ 29/06 │ │ -> │ │ 03/08 │
│ 9H > 10H│ │13H > 15H│ │15H > 17H│ │02/08 17H│ │ 8H->17H │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
"""
moved_tasks |= not_moved_tasks
for task in not_moved_tasks:
old_vals[task.name] = (task.planned_date_begin, task.date_deadline)
res = self.gantt_reschedule_maintain_buffer(self.task_6, task_6_vals)
self.assert_old_tasks_vals(res, 'success', 'Tasks rescheduled', moved_tasks, old_vals)
self.assertEqual(self.task_6.planned_date_begin, datetime(2021, 8, 3, 8), failed_message)
self.assertEqual(self.task_6.date_deadline, datetime(2021, 8, 3, 17), failed_message)
self.assertEqual(self.task_5.planned_date_begin, datetime(2021, 6, 30, 8), failed_message)
self.assertEqual(self.task_5.date_deadline, datetime(2021, 8, 2, 17), failed_message + "buffer = 0 between task 5 and 6, and month 07 is off")
for task in self.task_1 | self.task_3 | self.task_4:
self.assertEqual(task.planned_date_begin, old_vals[task.name][0] + timedelta(days=-1), failed_message)
self.assertEqual(task.date_deadline, old_vals[task.name][1] + timedelta(days=-1), failed_message)
def test_gantt_reschedule_forward_dependent_task_maintain_buffer_with_missing_start_date_on_task(self):
"""
Tests forward rescheduling with maintain buffer when a dependent task (Task 3) has no planned start date.
Task 1 move to the right, but Task 3 and its children 4, 5 and 6 remains unchanged due to Task 3
missing planned start date.
"""
self.task_3.planned_date_begin = False
task3_date_deadline = self.task_3.date_deadline
res = self.gantt_reschedule_maintain_buffer(self.task_1, self.task_1_date_gantt_reschedule_trigger())
self.assertEqual(self.task_1.planned_date_begin, datetime(2021, 6, 24, 13))
self.assertEqual(self.task_1.date_deadline, datetime(2021, 6, 24, 14))
# Task 3 and its children 4, 5 and 6 remains unchanged
self.assertFalse(self.task_3.planned_date_begin)
self.assertEqual(self.task_3.date_deadline, task3_date_deadline)
self.assert_task_not_replanned(self.task_4 | self.task_5 | self.task_6, self.project_pigs_intial_dates())
self.assertEqual(res['message'], "Tasks rescheduled")
def test_gantt_reschedule_backward_dependent_task_maintain_buffer_with_missing_date_deadline_on_task(self):
"""
Tests backward rescheduling with maintain buffer when a depends on task (Task 4) has no date deadline.
Task 6 -> task 5 should move to the left but Task 4 and its parent 3 and 1 remains unchanged due to
Task 4 missing date deadline.
"""
self.task_4.date_deadline = False
task4_date_begin = self.task_4.planned_date_begin
task_6_vals = {
"planned_date_begin": '2021-08-03 08:00:00',
"date_deadline": '2021-08-03 17:00:00'
}
res = self.gantt_reschedule_maintain_buffer(self.task_6, task_6_vals)
self.assertEqual(self.task_6.planned_date_begin, datetime(2021, 8, 3, 8))
self.assertEqual(self.task_6.date_deadline, datetime(2021, 8, 3, 17))
self.assertEqual(self.task_5.planned_date_begin, datetime(2021, 6, 30, 8))
self.assertEqual(self.task_5.date_deadline, datetime(2021, 8, 2, 17))
# Task 4 and its parent 3 and 1 remains unchanged
self.assertFalse(self.task_4.date_deadline)
self.assertEqual(self.task_4.planned_date_begin, task4_date_begin)
self.assert_task_not_replanned(self.task_3 | self.task_1, self.project_pigs_intial_dates())
self.assertEqual(res['message'], "Tasks rescheduled")
def test_gantt_rechedule_forward_maintain_buffer_multiple_dependencies(self):
"""
Tests buffers are respected when rescheduling a task forward in a multidependencies scenario:
┌───────────┐
│ Task 1 │
│ 24/06 │──────────────────┐
│ 9H > 10H │ │
└───────────┘ │ ┌───────────┐
├──>│ Task 4 │
┌───────────┐ │ │ 30/06 │
│ Task 3 │──┘ │ 13H > 15H │
│ 24/06 │ └───────────┘
│ 13H > 15H │
└───────────┘
"""
self.task_4.write({
'depend_on_ids': [Command.link(self.task_1.id), Command.link(self.task_3.id)],
'planned_date_begin': '2021-06-30 13:00:00',
'date_deadline': '2021-06-30 15:00:00',
})
self.task_3.write({'depend_on_ids': False})
task_1_new_vals = {
"planned_date_begin": '2021-06-24 10:00:00',
"date_deadline": '2021-06-24 11:00:00',
}
self.gantt_reschedule_maintain_buffer(self.task_1, task_1_new_vals)
self.assertEqual(self.task_1.planned_date_begin, datetime(2021, 6, 24, 10))
self.assertEqual(self.task_1.date_deadline, datetime(2021, 6, 24, 11))
self.assertEqual(self.task_4.planned_date_begin, datetime(2021, 6, 30, 14))
self.assertEqual(self.task_4.date_deadline, datetime(2021, 6, 30, 16))
self.assertEqual(self.task_3.planned_date_begin, datetime(2021, 6, 24, 13), "Task 3 should not move")
self.assertEqual(self.task_3.date_deadline, datetime(2021, 6, 24, 15), "Task 3 should not move")
def test_gantt_rechedule_backward_maintain_buffer_multiple_dependencies(self):
"""
Tests that all buffers are respected rescheduling a task backward in a multidependencies scenario:
┌───────────┐ ┌───────────┐
│ Task 1 │ │ Task 4 │
│ 24/06 │───────┬──────────────────>│ 30/06 │
│ 9H > 10H │ │ │ 15H > 17H │
└───────────┘ │ └───────────┘
│ ┌───────────┐
│ │ Task 3 │
└──>│ 24/06 │
│ 13H > 15H │
└───────────┘
"""
self.task_1.write({'dependent_ids': [Command.link(self.task_3.id), Command.link(self.task_4.id)]})
self.task_3.write({'dependent_ids': False})
task_4_new_vals = {
"planned_date_begin": '2021-06-30 14:00:00',
"date_deadline": '2021-06-30 16:00:00',
}
self.gantt_reschedule_maintain_buffer(self.task_4, task_4_new_vals)
self.assertEqual(self.task_4.planned_date_begin, datetime(2021, 6, 30, 14))
self.assertEqual(self.task_4.date_deadline, datetime(2021, 6, 30, 16))
self.assertEqual(self.task_1.planned_date_begin, datetime(2021, 6, 24, 8))
self.assertEqual(self.task_1.date_deadline, datetime(2021, 6, 24, 9))
self.assertEqual(self.task_3.planned_date_begin, datetime(2021, 6, 24, 13), "Task 3 should not move")
self.assertEqual(self.task_3.date_deadline, datetime(2021, 6, 24, 15), "Task 3 should not move")
@users('admin')
def test_gantt_reschedule_with_allocated_hours(self):
""" This test purpose is to ensure that the task planned_date_fields (begin/end) are calculated accordingly to
the allocated_hours if any. So if a dependent task has to be move forward up to before an unavailable period
of time and that its allocated_hours is such that the date_deadline would fall into that unavailable
period, then the date_deadline will be push forward after the unavailable period so that the
allocated_hours constraint is met.
task 3 is planned from 13H to 15H but with allocated hours = 8, when it will be moved, it will be planned
in coherence with its allocated hours
"""
self.task_3.allocated_hours = 8
self.gantt_reschedule_consume_buffer(self.task_1, self.task_1_date_gantt_reschedule_trigger())
failed_message = ("The auto shift date feature should take the allocated_hours into account and update the"
"date_deadline accordingly when moving a task forward.")
self.assertEqual((self.task_3.planned_date_begin, self.task_3.date_deadline), (datetime(2021, 6, 24, 14, 0), datetime(2021, 6, 25, 14, 0)), failed_message + "8 hours between dates")
@users('admin')
def test_gantt_reschedule_without_allocated_hours(self):
""" This test purpose is to ensure that the interval made by the task planned_date_fields (begin/end) is
preserved when no allocated_hours is set.
task 3 allocated hours = 0, task duration when moving it will be 2H as the difference between dates
"""
self.task_3.write({
'allocated_hours': 0,
})
self.gantt_reschedule_consume_buffer(self.task_1, self.task_1_date_gantt_reschedule_trigger())
failed_message = ("The auto shift date feature should take the allocated_hours into account and update the"
"date_deadline accordingly when moving a task forward.")
self.assertEqual((self.task_3.planned_date_begin, self.task_3.date_deadline), (datetime(2021, 6, 24, 14, 0), datetime(2021, 6, 24, 16, 0)), failed_message + "2 hours between dates")
def test_gantt_reschedule_project_user(self):
""" This test purpose is to ensure that the project user has the sufficient rights to trigger a gantt
reschedule.
"""
new_task_6_planned_date_begin = self.task_5_planned_date_begin + timedelta(hours=1)
self.gantt_reschedule_consume_buffer(self.task_6, {
'planned_date_begin': new_task_6_planned_date_begin.strftime('%Y-%m-%d %H:%M:%S'),
'date_deadline': (new_task_6_planned_date_begin + (self.task_6_date_deadline - self.task_6_planned_date_begin)).strftime('%Y-%m-%d %H:%M:%S'),
})
failed_message = "The auto shift date feature should handle correctly dependencies cascades."
self.assertEqual(self.task_5.date_deadline,
new_task_6_planned_date_begin, failed_message)
self.assertEqual(self.task_5.planned_date_begin,
datetime(year=2021, month=6, day=29, hour=9), failed_message)
self.assertEqual(self.task_4.date_deadline,
self.task_5.planned_date_begin, failed_message)
self.assertEqual(self.task_4.planned_date_begin,
datetime(year=2021, month=6, day=28, hour=16), failed_message)
@users('admin')
def test_project2_reschedule_cascading_forward(self):
"""
This test concerns project2 tasks, when task 0 is moved ahead of task 8.
As tasks 1, 2, 3 are children of 0, they should be done after it,
they should also be moved forward in one of this 2 valid orders
1- ['0', '1', '2', '3']
2- ['0', '1', '3', '2']
but it will be usually the first option as task 3 is the first in the dependent_ids of task 1
All the other tasks should not move.
"""
self.project2_task_1.dependent_ids = self.project2_task_1.dependent_ids.sorted(key=lambda t: t.name, reverse=True)
self.gantt_reschedule_consume_buffer(self.project2_task_0, {
"planned_date_begin": "2024-03-18 08:00:00",
"date_deadline": "2024-03-18 12:00:00",
})
self.assert_task_not_replanned(
self.project2_task_4 | self.project2_task_5 | self.project2_task_6 | self.project2_task_7 | self.project2_task_8 | self.project2_task_9 |
self.project2_task_10 | self.project2_task_11 | self.project2_task_12 | self.project2_task_13 | self.project2_task_14,
self.initial_dates,
)
self.assert_new_dates(
self.project2_task_0,
datetime(year=2024, month=3, day=18, hour=8),
datetime(year=2024, month=3, day=18, hour=12),
)
self.assert_new_dates(
self.project2_task_1,
datetime(year=2024, month=3, day=18, hour=13),
datetime(year=2024, month=3, day=18, hour=17),
"task 1 duration = 4 Hours. 4 hours will be planned on 18/03/2024 from 13H to 17H"
)
self.assert_new_dates(
self.project2_task_2,
datetime(year=2024, month=3, day=19, hour=8),
datetime(year=2024, month=3, day=19, hour=10),
"task 2 duration = 2 Hours. 2 hours will be planned on 19/03/2024 from 8H to 10H"
)
self.assert_new_dates(
self.project2_task_3,
datetime(year=2024, month=3, day=19, hour=10),
datetime(year=2024, month=3, day=19, hour=16),
"task 3 duration = 5 Hours. 5 hours will be planned on 19/03/2024 from 10H to 16H"
)
@users('admin')
def test_project2_reschedule_cascading_backward(self):
"""
This test concerns project2 tasks, when the left arrow is clicked. task 8 should be moved behind task 0
As tasks 4, 6, 5, 7 are ancestors for 8 and should be done before it, they should be moved backward.
A valid topo order can be:
1- ['8', '7', '5', '6', '4']
2- ['8', '7', '6', '4', '5']
it will be usually 2 as task 5 is the first in the depend_on_ids of task 7.
9 and 10 should not be impacted as they are not ancestors of 8.
We shouldn't have conflicts with 11, 12, 13 and 14 that are already planned in the past
All the other tasks should not move.
"""
self.project2_task_7.depend_on_ids = self.project2_task_7.depend_on_ids.sorted(key=lambda t: t.name)
self.gantt_reschedule_consume_buffer(self.project2_task_8, {
"planned_date_begin": "2024-02-27 10:00:00",
"date_deadline": "2024-02-29 17:00:00",
})
self.assert_task_not_replanned(
self.project2_task_0 | self.project2_task_1 | self.project2_task_2 | self.project2_task_3 | self.project2_task_9 |
self.project2_task_10 | self.project2_task_11 | self.project2_task_12 | self.project2_task_13 | self.project2_task_14,
self.initial_dates,
)
self.assert_new_dates(
self.project2_task_8,
datetime(year=2024, month=2, day=27, hour=10),
datetime(year=2024, month=2, day=29, hour=17),
)
self.assert_new_dates(
self.project2_task_7,
datetime(year=2024, month=2, day=26, hour=15),
datetime(year=2024, month=2, day=27, hour=10),
"""
task 7 duration = 4 Hours.
2 hours available on 27/02/2024 from 8H to 10H.
2 hours available on 26/02/2024 from 15H to 17H.
"""
)
self.assert_new_dates(
self.project2_task_6,
datetime(year=2024, month=2, day=21, hour=13),
datetime(year=2024, month=2, day=23, hour=17),
"""
task 6 duration = 20 Hours.
no hours available in 26/02/2024 as task 7 was planned from 15H to 17H
and task 14 planned from 8H to 15H
16 hours will be planned on 22/02 and 23/02
4 hours will be planned on 21/02 from 13H to 17H
"""
)
self.assert_new_dates(
self.project2_task_4,
datetime(year=2024, month=2, day=19, hour=13),
datetime(year=2024, month=2, day=21, hour=12),
"""
task 4 duration = 16 Hours
4 Hours on 19/02
8 hours on 20/02
4 hours on 21/02 from 8H to 12H
"""
)
self.assert_new_dates(
self.project2_task_5, datetime(year=2024, month=2, day=15, hour=13),
datetime(year=2024, month=2, day=19, hour=12),
"""
task 5 duration = 16 Hours.
4 hours will be planned on 19/02
8 hours will be planned on 16/02
4 hours will be planned on 15 from 13H to 17H
"""
)
@users('admin')
def test_project2_reschedule_cascading_backward_no_planning_in_the_past(self):
"""
This test concerns project2 tasks, when the left arrow is clicked. task 8 should be moved behind task 0
As tasks 4, 6, 5, 7 are ancestors for 8 and should be done before it, they should be moved backward.
As we can't plan taks in the past and there are no available intervals to plan, so they should be
planned starting from now and it will create conflicts
fake now (01/04) 07/04
| |
| |
| |
[ 7 ]--->[8]----->[0]->[1]->[2]
[ 6 ]
[ 4 ]
[ 5 ]
"""
self.project2_task_0.planned_date_begin = datetime(year=2021, month=4, day=7, hour=8)
res = self.gantt_reschedule_consume_buffer(self.project2_task_8, {
"planned_date_begin": "2021-04-05 11:00:00",
"date_deadline": "2021-04-06 17:00:00",
})
moved_tasks = self.project2_task_8 + self.project2_task_7 + self.project2_task_6 + self.project2_task_5 + self.project2_task_4
self.assert_old_tasks_vals(res, 'info',
'Some tasks were scheduled concurrently, resulting in a conflict due to the limited availability of the assignees. The planned dates for these tasks may not align with their allocated hours.',
moved_tasks, self.initial_dates
)
self.assert_new_dates(
self.project2_task_8,
datetime(year=2021, month=4, day=5, hour=11),
datetime(year=2021, month=4, day=6, hour=17),
)
self.assert_new_dates(
self.project2_task_7,
datetime(year=2021, month=4, day=2, hour=16),
datetime(year=2021, month=4, day=5, hour=11),
"""
task 7 duration = 4 Hours.
5 hours to plan on 05/04 from 8H to 11H
1 hour to plan on 02/04 from 16H to 17H
"""
)
self.assert_new_dates(
self.project2_task_5,
datetime(year=2021, month=4, day=1, hour=8),
datetime(year=2021, month=4, day=2, hour=17),
"""
task 5 duration = 16 Hours.
No available slot to plan it, so it will be planned in conflict starting from the first available slot 01/04/2021
"""
)
self.assert_new_dates(
self.project2_task_6,
datetime(year=2021, month=4, day=1, hour=8),
datetime(year=2021, month=4, day=5, hour=12),
"""
task 6 duration = 20 Hours.
No available slot to plan it, so it will be planned in conflict starting from the first available slot 01/04/2021
"""
)
self.assert_new_dates(
self.project2_task_4,
datetime(year=2021, month=4, day=1, hour=8),
datetime(year=2021, month=4, day=2, hour=17),
"""
task 4 duration = 16 Hours.
No available slot to plan it, so it will be planned in conflict starting from the first available slot 01/04/2021
"""
)
moved_tasks.action_rollback_scheduling(res['old_vals_per_pill_id'])
self.assert_task_not_replanned(moved_tasks, self.initial_dates)
@users('admin')
def test_compute_task_duration(self):
"""
when task allocated_hours = 0, duration is computed as the intersection of work intervals and [planned_date_begin, date_deadline]
example: task 6 is planned from datetime(2024, 3, 9, 8, 0) to datetime(2024, 3, 13, 12, 0)
duration is 20:
- 0 on 09/03 and 10/03 as it's weekend
- 8 on 11/03
- 8 on 12/03
- 4 from 8H to 12H
"""
durations = self.project2_task_6._get_tasks_durations(self.user_projectuser, 'planned_date_begin', 'date_deadline')
self.assertEqual(durations[self.project2_task_6.id], 20 * 3600)
@users('admin')
def test_backward_cross_project(self):
"""
When the move back/for{ward} concerns 2 tasks from 2 different projects, it's done
exactly like the previous cases (tasks belonging to same project). There is nothing
special for this case.
Test dependencies Project [5]--- ---->[6]--- -->[8]
| | | |
project_pigs | [4]- --->[7]-
|_________________|
"""
self.project2_task_7.dependent_ids = self.project2_task_7.dependent_ids.sorted(key=lambda t: t.name)
(self.project2_task_0 + self.project2_task_4 + self.project2_task_7).project_id = self.project_pigs.id
self.gantt_reschedule_consume_buffer(self.project2_task_8, {
"planned_date_begin": "2024-02-27 10:00:00",
"date_deadline": "2024-02-29 17:00:00",
})
self.assert_task_not_replanned(
self.project2_task_0 | self.project2_task_1 | self.project2_task_2 | self.project2_task_3 | self.project2_task_9 |
self.project2_task_10 | self.project2_task_11 | self.project2_task_12 | self.project2_task_13 | self.project2_task_14,
self.initial_dates,
)
self.assert_new_dates(
self.project2_task_8,
datetime(year=2024, month=2, day=27, hour=10),
datetime(year=2024, month=2, day=29, hour=17),
"""
task 8 duration = 13 Hours.
Only 2 hours available on 29/02/2024 because task 12 and 13 are planned from 9H to 16H.
Only 5 hours available on 28/02/2024 because task 11 is planned from 8H to 11H.
The remaining 6 hours will be planned in 27/02/2024 from 10H
"""
)
self.assert_new_dates(
self.project2_task_7,
datetime(year=2024, month=2, day=26, hour=15),
datetime(year=2024, month=2, day=27, hour=10),
"""
task 8 duration = 4 Hours.
2 hours available on 27/02/2024 from 8H to 10H.
2 hours available on 26/02/2024 from 15H to 17H.
"""
)
self.assert_new_dates(
self.project2_task_5,
datetime(year=2024, month=2, day=15, hour=13),
datetime(year=2024, month=2, day=19, hour=12),
"""
task 5 duration = 16 Hours.
day 17/02 and 18/02 are weekend
4 hours planned on day 15/02
8 hours planned on day 16/02
4 hours planned on day 19/02
"""
)
self.assert_new_dates(
self.project2_task_6,
datetime(year=2024, month=2, day=21, hour=13),
datetime(year=2024, month=2, day=23, hour=17),
"""
task 6 duration = 20 Hours.
4 Hours on 21/02, 16 Hours on 22/02 and 23/02
"""
)
self.assert_new_dates(
self.project2_task_4,
datetime(year=2024, month=2, day=19, hour=13),
datetime(year=2024, month=2, day=21, hour=12),
"""
task 4 duration = 16 Hours
4 Hours on 19/02
8 hours on 20/02
4 hours on 21/02 from 8H to 12H
"""
)
def test_move_forward_with_multi_users(self):
"""
-------------------------------------------------
| |
v |
Raouf 1 [0]->[ 1 ]------>[2]----------->[5] |->[7] [8]
| | ^ --------|----
| | | | |
Raouf 2 | -->[3] | | ----->[9]----->[10]
| | |
| | |
Raouf 3 and 2 ---------->[4]---->[6]
When we move 0 in front of 8:
- 3, 4 can be planned in // just after the end of 1
- 2, 6 can be planned in // just after the end of 4
- 5 planned after 2
- 9, 10 should wait for 5 to be planned even if there are available slots before
- 7 planned after 6
"""
self.project2_task_8.write({
'depend_on_ids': [Command.unlink(self.project2_task_7.id)],
})
(self.project2_task_4 + self.project2_task_6).write({
'user_ids': [self.user2.id, self.user1.id],
})
(self.project2_task_9 + self.project2_task_10).write({
'user_ids': [self.user1.id],
})
self.project2_task_2.write({
'dependent_ids': [Command.link(self.project2_task_5.id)],
'depend_on_ids': [Command.link(self.project2_task_4.id), Command.unlink(self.project2_task_7.id)],
})
self.project2_task_5.write({
'dependent_ids': [Command.unlink(self.project2_task_7.id)]
})
self.project2_task_1.write({
'dependent_ids': [Command.link(self.project2_task_4.id)]
})
res = self.gantt_reschedule_consume_buffer(self.project2_task_0, {
"planned_date_begin": "2024-03-18 08:00:00",
"date_deadline": "2024-03-18 12:00:00",
})
moved_tasks = self.project2.task_ids - self.project2_task_8 - self.project2_task_12 - self.project2_task_13 - self.project2_task_14 - self.project2_task_11
self.assert_old_tasks_vals(res, 'success', 'Tasks rescheduled', moved_tasks, self.initial_dates)
self.assert_new_dates(
self.project2_task_0,
datetime(year=2024, month=3, day=18, hour=8),
datetime(year=2024, month=3, day=18, hour=12),
)
self.assert_new_dates(
self.project2_task_1,
datetime(year=2024, month=3, day=18, hour=13),
datetime(year=2024, month=3, day=18, hour=17),
)
self.assert_new_dates(
self.project2_task_3,
datetime(year=2024, month=3, day=19, hour=8),
datetime(year=2024, month=3, day=19, hour=14),
)
self.assert_new_dates(
self.project2_task_4,
datetime(year=2024, month=3, day=19, hour=8),
datetime(year=2024, month=3, day=20, hour=17),
)
self.assert_new_dates(
self.project2_task_2,
datetime(year=2024, month=3, day=21, hour=8),
datetime(year=2024, month=3, day=21, hour=10),
)
self.assert_new_dates(
self.project2_task_6,
datetime(year=2024, month=3, day=21, hour=8),
datetime(year=2024, month=3, day=25, hour=12),
"allocated_hours = 20"
)
self.assert_new_dates(
self.project2_task_7,
datetime(year=2024, month=3, day=25, hour=13),
datetime(year=2024, month=3, day=25, hour=17),
)
self.assert_new_dates(
self.project2_task_5,
datetime(year=2024, month=3, day=21, hour=10),
datetime(year=2024, month=3, day=25, hour=10),
)
self.assert_new_dates(
self.project2_task_9,
datetime(year=2024, month=3, day=25, hour=13),
datetime(year=2024, month=3, day=25, hour=17),
"""
task 9 should start after task 5 which is its blocking task and after task 6
as user Raouf 2 was busy doing it.
"""
)
self.assert_new_dates(
self.project2_task_10,
datetime(year=2024, month=3, day=26, hour=8),
datetime(year=2024, month=3, day=26, hour=12),
)
moved_tasks.action_rollback_scheduling(res['old_vals_per_pill_id'])
self.assert_task_not_replanned(moved_tasks, self.initial_dates)
def test_move_backward_with_multi_users(self):
"""
----------------------------------------------------------------
| |
v |
Armande [13] [0]->[ 1 ]------>[2]----------->[5] |->[7]---------->[8]
| | ^ --------|----- ^
| | | | | |
Raouf 2 | -->[3] | | ----->[9]------------>[10]
| | |
| | |
Raouf 3 & Raouf 2 ---------->[4]---->[6]
When we move 8 before 13:
- all the ancestors of 8 should move before 8 (0, 1, 4, 2, 6, 7, 5, 9, 10, 8)
"""
self.project2_task_7.dependent_ids = self.project2_task_7.dependent_ids.sorted(key=lambda t: t.name)
self.project2_task_8.write({
'depend_on_ids': [Command.link(self.project2_task_10.id), Command.link(self.project2_task_7.id)],
'dependent_ids': [Command.unlink(self.project2_task_0.id), Command.link(self.project2_task_13.id)],
})
(self.project2_task_4 + self.project2_task_6).write({
'user_ids': [self.user2.id, self.user1.id],
})
(self.project2_task_9 + self.project2_task_10).write({
'user_ids': [self.user1.id],
})
self.project2_task_2.write({
'dependent_ids': [Command.link(self.project2_task_5.id)],
'depend_on_ids': [Command.unlink(self.project2_task_7.id), Command.link(self.project2_task_4.id)],
})
self.project2_task_5.write({
'dependent_ids': [Command.unlink(self.project2_task_7.id)]
})
self.project2_task_1.write({
'dependent_ids': [Command.link(self.project2_task_4.id)]
})
res = self.gantt_reschedule_consume_buffer(self.project2_task_8, {
"planned_date_begin": "2024-02-27 09:00:00",
"date_deadline": "2024-02-29 09:00:00",
})
moved_tasks = self.project2.task_ids - self.project2_task_13 - self.project2_task_3 - self.project2_task_11 - self.project2_task_14 - self.project2_task_12
self.assert_old_tasks_vals(res, 'success', 'Tasks rescheduled', moved_tasks, self.initial_dates)
self.assert_new_dates(
self.project2_task_0,
datetime(year=2024, month=2, day=14, hour=9),
datetime(year=2024, month=2, day=14, hour=14),
)
self.assert_new_dates(
self.project2_task_1,
datetime(year=2024, month=2, day=14, hour=14),
datetime(year=2024, month=2, day=15, hour=9),
)
self.assert_new_dates(
self.project2_task_4,
datetime(year=2024, month=2, day=15, hour=9),
datetime(year=2024, month=2, day=19, hour=9),
)
self.assert_new_dates(
self.project2_task_2,
datetime(year=2024, month=2, day=21, hour=15),
datetime(year=2024, month=2, day=21, hour=17),
)
self.assert_new_dates(
self.project2_task_6,
datetime(year=2024, month=2, day=19, hour=9),
datetime(year=2024, month=2, day=21, hour=14),
)
self.assert_new_dates(
self.project2_task_7,
datetime(year=2024, month=2, day=21, hour=14),
datetime(year=2024, month=2, day=27, hour=9),
)
self.assert_new_dates(
self.project2_task_5,
datetime(year=2024, month=2, day=22, hour=8),
datetime(year=2024, month=2, day=23, hour=17),
)
self.assert_new_dates(
self.project2_task_9,
datetime(year=2024, month=2, day=26, hour=9),
datetime(year=2024, month=2, day=26, hour=14),
)
self.assert_new_dates(
self.project2_task_10,
datetime(year=2024, month=2, day=26, hour=14),
datetime(year=2024, month=2, day=27, hour=9),
)
self.assert_new_dates(
self.project2_task_8,
datetime(year=2024, month=2, day=27, hour=9),
datetime(year=2024, month=2, day=29, hour=9),
)
moved_tasks.action_rollback_scheduling(res['old_vals_per_pill_id'])
self.assert_task_not_replanned(moved_tasks, self.initial_dates)
def test_web_gantt_write(self):
users = self.user_projectuser + self.user_projectmanager
self.task_1.write({'user_ids': users.ids})
self.task_2.write({'user_ids': self.user_projectuser.ids})
tasks = self.task_1 + self.task_2
tasks.web_gantt_write({'user_ids': self.user_projectmanager.ids})
self.assertEqual(self.task_1.user_ids, users, "The assignees set on Task 1 should remain the same if the new assigne was in fact already in the assigness of the task.")
self.assertEqual(self.task_2.user_ids, self.user_projectmanager, "The assignees set on Task 2 should be the new one and the user initially assinged should be unassigned.")
tasks.web_gantt_write({'user_ids': False})
self.assertFalse(tasks.user_ids, "No assignees should be set on the both tasks")
tasks.web_gantt_write({'user_ids': self.user_portal.ids})
self.assertEqual(self.task_1.user_ids, self.user_portal, "User portal should be assigned to Task 1.")
self.assertEqual(self.task_2.user_ids, self.user_portal, "User portal should be assigned to Task 2.")
tasks.web_gantt_write({'user_ids': users.ids})
self.assertEqual(self.task_1.user_ids, users, "Project user and Prohect manager should be assigned to the task 1 and portal user should be assigned.")
self.assertEqual(self.task_2.user_ids, users, "Project user and Prohect maanger should be assigned to the task 2 and portal user should be assigned.")
def test_project_web_gantt_write(self):
self.project_goats.write({
'user_id': self.user_projectmanager.id,
})
self.project_pigs.write({
'user_id': self.user_projectmanager.id,
'date_start': '2021-09-27',
'date': '2021-09-28'
})
self.project_goats.web_gantt_write({'user_id': False})
self.assertEqual(
self.project_goats.user_id,
self.user_projectmanager,
"Project without a schedule should retain its assignee when `user_id` is set to False"
)
self.project_pigs.web_gantt_write({'user_id': False})
self.assertFalse(
self.project_pigs.user_id,
"Scheduled project should have its assignee removed when `user_id` is set to False."
)
def test_gantt_reschedule_dependent_task_maintain_buffer_with_date_deadline_on_task(self):
"""
Tests rescheduling with maintain buffer when we change the deadline of the task (task 1) on which other
tasks depend. In the front end, this comes down to resizing the pill of task 1.
If the deadline is set forward (postponed), then the dependent task should have its start date and deadline
postponed as well. If the deadline is set backward, as we keep the buffer, the dependent tasks should have their
deadline moved backwards as well.
"""
# FORWARD
# original dates:
# task 1: 24/6 9am -> 10am
# task 3: 24/6 1pm -> 3pm
# gap between task 1 and 3: 2 working hours
self.gantt_reschedule_maintain_buffer(self.task_1, {'date_deadline': str(self.task_1.date_deadline + relativedelta(hours=12))})
self.assert_new_dates(
self.task_1, datetime(2021, 6, 24, 9), datetime(2021, 6, 24, 22))
self.assert_new_dates(
self.task_3, datetime(2021, 6, 25, 10), datetime(2021, 6, 25, 12),
"2 working hours gap maintained between 8 and 10")
self.assert_new_dates(
self.task_4, datetime(2021, 8, 2, 13), datetime(2021, 8, 2, 15),
"2 working hours gap maintained between 10 and 13")
# BACKWARD
self.gantt_reschedule_maintain_buffer(self.task_1, {'date_deadline': str(self.task_1.date_deadline - relativedelta(hours=12))})
self.assert_new_dates(
self.task_1, datetime(2021, 6, 24, 9), datetime(2021, 6, 24, 10))
# restoring initial dates for task 3 and 4
self.assert_new_dates(
self.task_3, datetime(2021, 6, 24, 13), datetime(2021, 6, 24, 15),
"Original date should be restored.")
self.assert_new_dates(
self.task_4, datetime(2021, 6, 30, 15), datetime(2021, 6, 30, 17),
"Original date should be restored.")
def test_gantt_shrinking_a_single_task_deadline(self):
"""
Test shrinking only the deadline of a single task without predecessors should
not crash with ValueError: max() arg is an empty sequence.
"""
og_begin = self.task_1.planned_date_begin
og_deadline = self.task_1.date_deadline
shrunk_deadline = og_deadline - timedelta(minutes=60)
self.gantt_reschedule_consume_buffer(
self.task_1,
{"date_deadline": shrunk_deadline.strftime("%Y-%m-%d %H:%M:%S")},
)
# Shrinking task_1's deadline should only affect its deadline, not its begin
self.assertEqual(self.task_1.planned_date_begin, og_begin)
self.assertEqual(self.task_1.date_deadline, shrunk_deadline)
# No predecessors to move, so nothing else should change
self.assert_task_not_replanned(
self.task_3 | self.task_4 | self.task_5 | self.task_6,
self.project_pigs_intial_dates(),
)