1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/test_web_gantt/tests/test_reschedule.py
2024-12-10 09:04:09 +07:00

421 lines
22 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta
from odoo import Command
from .common import TestWebGantt
class TestWebGanttReschedule(TestWebGantt):
def test_reschedule_forward(self):
""" This test purpose is to ensure that the forward rescheduling is properly working. """
self.gantt_reschedule_forward(self.pill_1, self.pill_2)
self.assertEqual(
self.pill_1[self.date_stop_field_name], self.pill_2[self.date_start_field_name],
'Pill 1 should move forward up to start of Pill 2.'
)
def test_reschedule_backward(self):
""" This test purpose is to ensure that the backward rescheduling is properly working. """
self.gantt_reschedule_backward(self.pill_1, self.pill_2)
self.assertEqual(
self.pill_2[self.date_start_field_name], self.pill_1[self.date_stop_field_name],
'Pill 2 should move backward up to end of Pill 1.'
)
def test_prevent_action_on_non_dependent_records(self):
""" This test purpose is to ensure that non-dependent records are not rescheduled. """
self.pill_3_start_date = self.pill_2_start_date + timedelta(days=1)
self.pill_3_stop_date = self.pill_3_start_date + timedelta(hours=8)
self.pill_3 = self.create_pill('Pill 3', self.pill_3_start_date, self.pill_3_stop_date, [self.pill_2.id])
# Non-dependent records should not be rescheduled.
with self.assertRaises(ValueError):
self.gantt_reschedule_forward(self.pill_1, self.pill_3)
# Non-dependent records should not be rescheduled.
with self.assertRaises(ValueError):
self.gantt_reschedule_backward(self.pill_1, self.pill_3)
def test_not_in_past(self):
""" This test purpose is to ensure that:
* Records in the past are not rescheduled.
* Records are not rescheduled in the past.
* A notification is returned when trying to reschedule a record in the past.
"""
a_day_in_the_past = datetime.now().replace(minute=0, second=0, microsecond=0) - timedelta(days=1)
self.pill_in_past = self.create_pill(
'Pill in past', a_day_in_the_past, a_day_in_the_past + timedelta(hours=8), [self.pill_1.id])
# Records in the past should not be rescheduled.
with self.assertRaises(ValueError):
self.gantt_reschedule_backward(self.pill_in_past, self.pill_1)
yesterday_midnight = datetime.now().replace(minute=0, second=0, microsecond=0) - timedelta(days=1)
self.pill_1.write({
self.date_start_field_name: yesterday_midnight,
self.date_stop_field_name: yesterday_midnight + timedelta(hours=8),
})
result = self.gantt_reschedule_backward(self.pill_1, self.pill_2)
self.assertEqual(
self.pill_2[self.date_start_field_name], self.pill_2_start_date,
'Records should not be rescheduled in the past.'
)
is_ir_actions_client_type = result and result['type'] and result['type'] == 'ir.actions.client'
is_notification_tag = result and result['tag'] and result['tag'] == 'display_notification'
self.assertTrue(
is_ir_actions_client_type and is_notification_tag,
'A notification should be returned when trying to reschedule a record in the past.'
)
def test_cascade_forward(self):
""" This test purpose is to ensure that the rescheduling is correctly cascading when performed forward.
All master records are rescheduled when performing a forward reschedule.
Non-conflicting slave records are not rescheduled.
"""
self.gantt_reschedule_forward(self.pill_2, self.pill_3)
self.assertEqual(
self.pill_1[self.date_stop_field_name], self.pill_2[self.date_start_field_name],
'Reschedule of Pill 2 should have cascaded to Pill 1 which should move forward up to start of Pill 2.'
)
self.assertEqual(
(self.pill_3[self.date_start_field_name], self.pill_3[self.date_stop_field_name]),
(self.pill_3_start_date, self.pill_3_stop_date),
'Reschedule of Pill 2 towards Pill 3 should not have altered Pill 3.'
)
self.assertEqual(
(self.pill_4[self.date_start_field_name], self.pill_4[self.date_stop_field_name]),
(self.pill_4_start_date, self.pill_4_stop_date),
'Forward reschedule of Pill 2 should not have an impact on Pill 4.'
)
def test_cascade_backward(self):
""" This test purpose is to ensure that the rescheduling is correctly cascading when performed backward.
All slave records are rescheduled when performing a backward reschedule.
Non-conflicting master records are not rescheduled.
"""
self.gantt_reschedule_backward(self.pill_2, self.pill_3)
self.assertEqual(
self.pill_4[self.date_start_field_name], self.pill_3[self.date_stop_field_name],
'Reschedule of Pill 3 should have cascaded to Pill 4 which should move backward up to start of Pill 3.'
)
self.assertEqual(
(self.pill_2[self.date_start_field_name], self.pill_2[self.date_stop_field_name]),
(self.pill_2_start_date, self.pill_2_stop_date),
'Reschedule of Pill 3 towards Pill 2 should not have altered Pill 2.'
)
self.assertEqual(
(self.pill_1[self.date_start_field_name], self.pill_1[self.date_stop_field_name]),
(self.pill_1_start_date, self.pill_1_stop_date),
'Backward reschedule of Pill 3 should not have an impact on Pill 1.'
)
def test_conflicting_cascade_forward(self):
""" This test purpose is to ensure that, when rescheduling forward:
* All conflicts between a record that would have been rescheduled forward and its slave records are
resolved.
* Non-conflicting slave records are not rescheduled.
"""
self.pill_2_slave_in_conflict = self.create_pill(
'Pill in conflict with Pill 2', self.pill_1_start_date, self.pill_1_stop_date, [self.pill_2.id]
)
self.pill_2_slave_not_in_conflict = self.create_pill(
'Pill not in conflict with Pill 2', self.pill_4_start_date, self.pill_4_stop_date, [self.pill_2.id]
)
self.pill_3_slave_in_conflict = self.create_pill(
'Pill in conflict with Pill 3', self.pill_1_start_date, self.pill_1_stop_date, [self.pill_3.id]
)
self.pill_3_slave_not_in_conflict = self.create_pill(
'Pill not in conflict with Pill 3', self.pill_4_start_date, self.pill_4_stop_date, [self.pill_3.id]
)
self.gantt_reschedule_forward(self.pill_3, self.pill_4)
self.assertEqual(
self.pill_3_slave_in_conflict[self.date_start_field_name],
self.pill_3[self.date_stop_field_name],
'pill_3_slave_in_conflict should have been rescheduled once pill_3 had been rescheduled.'
)
self.assertEqual(
self.pill_3_slave_not_in_conflict[self.date_start_field_name],
self.pill_4_start_date,
'pill_3_slave_not_in_conflict should not have been reschedule once pill_3 had been rescheduled.'
)
self.assertEqual(
self.pill_2_slave_in_conflict[self.date_start_field_name],
self.pill_2[self.date_stop_field_name],
'pill_2_slave_in_conflict should have been rescheduled once pill_2 had been rescheduled.'
)
self.assertEqual(
self.pill_2_slave_not_in_conflict[self.date_start_field_name],
self.pill_4_start_date,
'pill_2_slave_not_in_conflict should not have been reschedule once pill_2 had been rescheduled.'
)
def test_conflicting_cascade_backward(self):
""" This test purpose is to ensure that, when rescheduling backward:
* All conflicts between a record that would have been rescheduled backward and its master records are
resolved.
* Non-conflicting master records are not rescheduled.
"""
self.pill_3_master_in_conflict = self.create_pill(
'Pill in conflict with Pill 3', self.pill_4_start_date, self.pill_4_stop_date
)
self.pill_3_master_not_in_conflict = self.create_pill(
'Pill not in conflict with Pill 3', self.pill_1_start_date, self.pill_1_stop_date
)
self.pill_3[self.dependency_field_name] = [
Command.link(self.pill_3_master_in_conflict.id),
Command.link(self.pill_3_master_not_in_conflict.id),
]
self.pill_2_master_in_conflict = self.create_pill(
'Pill in conflict with Pill 2', self.pill_4_start_date, self.pill_4_stop_date
)
self.pill_2_master_not_in_conflict = self.create_pill(
'Pill not in conflict with Pill 2', self.pill_1_start_date, self.pill_1_stop_date
)
self.pill_2[self.dependency_field_name] = [
Command.link(self.pill_2_master_in_conflict.id),
Command.link(self.pill_2_master_not_in_conflict.id),
]
self.gantt_reschedule_backward(self.pill_1, self.pill_2)
self.assertEqual(
self.pill_2_master_in_conflict[self.date_stop_field_name],
self.pill_2[self.date_start_field_name],
'pill_2_master_in_conflict should have been rescheduled once pill_2 had been rescheduled.'
)
self.assertEqual(
self.pill_2_master_not_in_conflict[self.date_stop_field_name],
self.pill_1_stop_date,
'pill_2_master_not_in_conflict should not have been reschedule once pill_2 had been rescheduled.'
)
self.assertEqual(
self.pill_3_master_in_conflict[self.date_stop_field_name],
self.pill_3[self.date_start_field_name],
'pill_3_master_in_conflict should have been rescheduled once pill_3 had been rescheduled.'
)
self.assertEqual(
self.pill_3_master_not_in_conflict[self.date_stop_field_name],
self.pill_1_stop_date,
'pill_3_master_not_in_conflict should not have been reschedule once pill_3 had been rescheduled.'
)
def check_dependencies(self):
""" Assert that each pill start_date is later than the ones of its master pills. """
for pill in self.pills:
for master_pill in pill[self.dependency_field_name]:
self.assertTrue(master_pill[self.date_start_field_name] < pill[self.date_start_field_name])
def get_start_date_for_pill(self, pill):
""" Returns the start date for the provided pill.
:param pill: The pill the start date has to be returned for.
"""
if pill == self.pill_1:
return self.pill_1_start_date
if pill == self.pill_2:
return self.pill_2_start_date
if pill == self.pill_3:
return self.pill_3_start_date
if pill == self.pill_4:
return self.pill_4_start_date
if pill == self.pill_5:
return self.pill_5_start_date
if pill == self.pill_6:
return self.pill_6_start_date
return False
def check_pills_have_not_been_rescheduled(self, pills, trigger_pill):
""" Checks that pills, other than trigger_pill, have not been rescheduled.
:param pills: Pills to check.
:param trigger_pill: Pill that originated the rescheduling.
"""
pill_number = pills.index(trigger_pill) + 1
for pill in pills:
if pill != trigger_pill:
self.assertEqual(
pill[self.date_start_field_name], self.get_start_date_for_pill(pill),
f'Pill {pill_number} should hot have been rescheduled as the algorithm should not have '
f'solve the loop.'
)
def non_straight_dependency_alert_1_test(self, trigger_pill_number):
""" Ensures that situation as described bellow return a notification and only first move is performed
as the resolution is not supported. Instead of risking altering the data with remaining conflicts
(as the number of situation and crossed links can be unlimited), we just rollback all changes as
soon as we detect that we have a `loop` (not meaning circular dependency).
---------------- 6
/ /
1 - 2 - 3 - 4 - 5
with a 7 (linked to the pill corresponding to trigger_pill_number)
"""
self.pill_5_start_date = self.pill_4_start_date + timedelta(days=1)
self.pill_5_stop_date = self.pill_5_start_date + timedelta(hours=8)
self.pill_5 = self.create_pill('Pill 5', self.pill_5_start_date, self.pill_5_stop_date, [self.pill_4.id])
self.pill_6_start_date = self.pill_5_start_date + timedelta(days=1)
self.pill_6_stop_date = self.pill_6_start_date + timedelta(hours=8)
self.pill_6 = self.create_pill(
'Pill 6', self.pill_6_start_date, self.pill_6_stop_date, [self.pill_5.id, self.pill_1.id]
)
pills = [self.pill_1, self.pill_2, self.pill_3, self.pill_4, self.pill_5, self.pill_6]
trigger_pill = pills[trigger_pill_number - 1]
self.pill_7_start_date = self.pill_6_start_date + timedelta(days=1)
self.pill_7_stop_date = self.pill_7_start_date + timedelta(hours=8)
self.pill_7 = self.create_pill('Pill 7', self.pill_7_start_date, self.pill_7_stop_date, [trigger_pill.id])
result = self.gantt_reschedule_forward(trigger_pill, self.pill_7)
self.assertTrue(
result and result['type'] and result['type'] == 'ir.actions.client',
'The rescheduling should return a notification when the algorithm cannot solve the loop.'
)
self.assertEqual(
trigger_pill[self.date_stop_field_name], self.pill_7[self.date_start_field_name],
'The first move should be saved when the algorithm cannot solve the loop.'
)
self.check_pills_have_not_been_rescheduled(pills, trigger_pill)
def test_non_straight_dependency_alert_1_a(self):
""" Ensures that rescheduling 1 towards 7, in the situation as described bellow, returns a notification and
only first move is performed as the resolution is not supported.
________________________________ 7
/ 5 - 6
---------------- 6 / Placing 1, 2 and 6, 3 and 5,
/ / => 1 - 7 with next step being placing 4
1 - 2 - 3 - 4 - 5 \
2 - 3
"""
self.non_straight_dependency_alert_1_test(1)
def test_non_straight_dependency_alert_1_c(self):
""" Ensures that rescheduling 6 towards 7, in the situation as described bellow, returns a notification and
only first move is performed as the resolution is not supported.
__________ 7
/ 2 7
---------------- 6 / / Placing 6, 1 and 5, 2 and 4,
/ / => 1 - 6 with next step being placing 3
1 - 2 - 3 - 4 - 5 /
4 - 5
"""
self.non_straight_dependency_alert_1_test(6)
def non_straight_dependency_alert_2_test(self, trigger_pill_number):
""" Ensures that situation as described bellow raises a Validation error as the resolution is not supported.
Instead of risking altering the data with remaining conflicts (as the number of situation and crossed
links can be unlimited), we just rollback all changes as soon as we detect that we have a `loop`
(not meaning circular dependency).
THIS TEST IS NOT A DUPLICATE OF test_non_straight_dependency_alert_1_* !!! The current algorythm need to
be tested with both an odd and an even number of elements in a `loop`.
-----------5
/ /
1 - 2 - 3 - 4
with a 7 (linked to the pill corresponding to trigger_pill_number)
Notice that if 3 is linked, the resolution will be performed smoothly => 3 -> (2, 4) => (1, 5) without
conflicts.
"""
self.pill_5_start_date = self.pill_4_start_date + timedelta(days=1)
self.pill_5_stop_date = self.pill_5_start_date + timedelta(hours=8)
self.pill_5 = self.create_pill(
'Pill 5', self.pill_5_start_date, self.pill_5_stop_date, [self.pill_4.id, self.pill_1.id]
)
pills = [self.pill_1, self.pill_2, self.pill_3, self.pill_4, self.pill_5]
trigger_pill = pills[trigger_pill_number - 1]
self.pill_7_start_date = self.pill_5_start_date + timedelta(weeks=1)
self.pill_7_stop_date = self.pill_7_start_date + timedelta(hours=8)
self.pill_7 = self.create_pill('Pill 7', self.pill_7_start_date, self.pill_7_stop_date, [trigger_pill.id])
result = self.gantt_reschedule_forward(trigger_pill, self.pill_7)
self.assertTrue(
result and result['type'] and result['type'] == 'ir.actions.client',
'The rescheduling should return a notification when the algorithm cannot solve the loop.'
)
self.assertEqual(
trigger_pill[self.date_stop_field_name], self.pill_7[self.date_start_field_name],
'The first move should be saved when the algorithm cannot solve the loop.'
)
self.check_pills_have_not_been_rescheduled(pills, trigger_pill)
def test_non_straight_dependency_alert_2_a(self):
""" Ensures that rescheduling 1 towards 7, in the situation as described bellow, returns a notification and
only first move is performed as the resolution is not supported.
______________________ 7
/ 4 - 5 Placing 1, 2 and 5, 3 and 4,
-------------5 / with next step being replacing 3 and 4
/ / => 1 - 7 as in conflict.
1 - 2 - 3 - 4 \
2 - 3
"""
self.non_straight_dependency_alert_2_test(1)
def test_non_straight_dependency_alert_2_b(self):
""" Ensures that rescheduling 5 towards 7, in the situation as described bellow, returns a notification and
only first move is performed as the resolution is not supported.
______________ 7
/ 2 7
------------ 5 / / Placing 5, 1 and 4, 2 and 3,
/ / => 1 - 5 with next step being placing 2 and 3
1 - 2 - 3 - 4 / as in conflict.
3 - 4
"""
self.non_straight_dependency_alert_2_test(5)
def test_direction_shift_when_in_conflict(self):
""" This test purpose is to ensure that the correct record is moved when rescheduling forward / backward and
depending on the fact the records are in conflicts or not (master_record stop_date_field is later than the
slave_record start_date_field means they are in conflict).
"""
self.gantt_reschedule_forward(self.pill_1, self.pill_2)
self.assertGreater(
self.pill_1[self.date_start_field_name], self.pill_1_start_date,
'Pill 1 should have been rescheduled forward.'
)
self.assertEqual(
self.pill_2[self.date_start_field_name], self.pill_2_start_date, 'Pill 2 should not have been rescheduled.'
)
self.gantt_reschedule_backward(self.pill_3, self.pill_4)
self.assertLess(
self.pill_4[self.date_start_field_name], self.pill_4_start_date,
'Pill 4 should have been rescheduled backward.'
)
self.assertEqual(
self.pill_3[self.date_start_field_name], self.pill_3_start_date, 'Pill 3 should not have been rescheduled.'
)
self.pill_1_master_in_conflict_start_date = self.pill_1_start_date + timedelta(days=1)
self.pill_1_master_in_conflict = self.create_pill(
'Pill in conflict with Pill 1',
self.pill_1_master_in_conflict_start_date, self.pill_1_master_in_conflict_start_date + timedelta(hours=8)
)
self.pill_1.write({self.dependency_field_name: [Command.link(self.pill_1_master_in_conflict.id)]})
self.gantt_reschedule_forward(self.pill_1_master_in_conflict, self.pill_1)
self.assertGreater(
self.pill_1[self.date_start_field_name], self.pill_1_start_date,
'Pill 1 should have been rescheduled.'
)
self.assertEqual(
self.pill_1_master_in_conflict[self.date_start_field_name], self.pill_2_start_date,
'Pill in conflict with Pill 1 should not have been rescheduled.'
)
self.pill_4_slave_in_conflict_start_date = self.pill_4_start_date - timedelta(days=1)
self.pill_4_slave_in_conflict = self.create_pill(
'Pill in conflict with Pill 4',
self.pill_4_slave_in_conflict_start_date, self.pill_4_slave_in_conflict_start_date + timedelta(hours=8),
[self.pill_4.id]
)
self.gantt_reschedule_backward(self.pill_4, self.pill_4_slave_in_conflict)
self.assertLess(
self.pill_4[self.date_start_field_name], self.pill_4_start_date,
'Pill 4 should have been rescheduled.'
)
self.assertEqual(
self.pill_4_slave_in_conflict[self.date_start_field_name], self.pill_4_slave_in_conflict_start_date,
'Pill in conflict with Pill 4 should not have been rescheduled.'
)