# -*- 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 odoo.addons.web_gantt.models.models import Base from odoo.tests.common import TransactionCase class TestWebGantt(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() """ pills structure ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Pill 1 │ --> │ Pill 2 │ --> │ Pill 3 │ --> │ Pill 4 │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ """ cls.TestWebGanttPill = cls.env['test.web.gantt.pill'] cls.dependency_field_name = 'dependency_field' cls.dependency_inverted_field_name = 'dependency_inverted_field' cls.date_start_field_name = 'date_start' cls.date_stop_field_name = 'date_stop' cls.pill_1_start_date = datetime(2021, 3, 1, 8) cls.pill_1_stop_date = cls.pill_1_start_date + timedelta(hours=8) cls.pill_1 = cls.create_pill('Pill 1', cls.pill_1_start_date, cls.pill_1_stop_date) cls.pill_2_start_date = cls.pill_1_start_date + timedelta(days=1) cls.pill_2_stop_date = cls.pill_2_start_date + timedelta(hours=8) cls.pill_2 = cls.create_pill('Pill 2', cls.pill_2_start_date, cls.pill_2_stop_date, [cls.pill_1.id]) cls.pill_3_start_date = cls.pill_2_start_date + timedelta(days=1) cls.pill_3_stop_date = cls.pill_3_start_date + timedelta(hours=8) cls.pill_3 = cls.create_pill('Pill 3', cls.pill_3_start_date, cls.pill_3_stop_date, [cls.pill_2.id]) cls.pill_4_start_date = cls.pill_3_start_date + timedelta(days=1) cls.pill_4_stop_date = cls.pill_4_start_date + timedelta(hours=8) cls.pill_4 = cls.create_pill('Pill 4', cls.pill_4_start_date, cls.pill_4_stop_date, [cls.pill_3.id]) cls.pills_topo_order = [cls.pill_1, cls.pill_2, cls.pill_3, cls.pill_4] cls.buffer12 = (cls.pill_2[cls.date_start_field_name] - cls.pill_1[cls.date_stop_field_name]).seconds cls.buffer23 = (cls.pill_3[cls.date_start_field_name] - cls.pill_2[cls.date_stop_field_name]).seconds cls.buffer34 = (cls.pill_4[cls.date_start_field_name] - cls.pill_3[cls.date_stop_field_name]).seconds """ Second pills 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 <: left arrow to move task 8 backward task 0 >: right arrow to move task 0 forward task 8 x: delete the dependence """ 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 = [] for name, dates in cls.initial_dates.items(): vals.append({ 'name': name, cls.date_start_field_name: dates[0], cls.date_stop_field_name: dates[1], }) cls.pills2_0, \ cls.pills2_1, \ cls.pills2_2, \ cls.pills2_3, \ cls.pills2_4, \ cls.pills2_5, \ cls.pills2_6, \ cls.pills2_7, \ cls.pills2_8, \ cls.pills2_9, \ cls.pills2_10, \ cls.pills2_11, \ cls.pills2_12, \ cls.pills2_13, \ cls.pills2_14 = cls.TestWebGanttPill.create(vals) cls.initial_dates.update({ cls.pill_1.name: (cls.pill_1.date_start, cls.pill_1.date_stop), cls.pill_2.name: (cls.pill_2.date_start, cls.pill_2.date_stop), cls.pill_3.name: (cls.pill_3.date_start, cls.pill_3.date_stop), cls.pill_4.name: (cls.pill_4.date_start, cls.pill_4.date_stop), }) cls.pills2_0.write({ cls.dependency_field_name: [Command.link(cls.pills2_8.id)], cls.dependency_inverted_field_name: [Command.link(cls.pills2_1.id)], }) cls.pills2_1.write({ cls.dependency_inverted_field_name: [Command.link(cls.pills2_2.id), Command.link(cls.pills2_3.id)] }) cls.pills2_6.write({ cls.dependency_field_name: [Command.link(cls.pills2_4.id)], }) cls.pills2_7.write({ cls.dependency_field_name: [Command.link(cls.pills2_5.id), Command.link(cls.pills2_6.id)], }) cls.pills2_8.write({ cls.dependency_field_name: [Command.link(cls.pills2_7.id)], }) cls.pills2_9.write({ cls.dependency_field_name: [Command.link(cls.pills2_5.id)], }) cls.pills2_10.write({ cls.dependency_field_name: [Command.link(cls.pills2_9.id)], }) cls.maxDiff = None @classmethod def create_pill(cls, name, date_start, date_stop, master_pill_ids=None): if master_pill_ids is None: master_pill_ids = [] return cls.TestWebGanttPill.create({ 'name': name, cls.date_start_field_name: date_start, cls.date_stop_field_name: date_stop, cls.dependency_field_name: [Command.set([master_pill_id for master_pill_id in master_pill_ids])], }) @classmethod def gantt_reschedule_maintain_buffer(cls, record, data): return cls.TestWebGanttPill.web_gantt_reschedule( data, Base._WEB_GANTT_RESCHEDULE_MAINTAIN_BUFFER, record.id, cls.dependency_field_name, cls.dependency_inverted_field_name, cls.date_start_field_name, cls.date_stop_field_name ) @classmethod def gantt_reschedule_consume_buffer(cls, record, data): return cls.TestWebGanttPill.web_gantt_reschedule( data, Base._WEB_GANTT_RESCHEDULE_CONSUME_BUFFER, record.id, cls.dependency_field_name, cls.dependency_inverted_field_name, cls.date_start_field_name, cls.date_stop_field_name ) def assert_not_replanned(cls, pills, initial_dates): for pill in pills: cls.assertEqual(pill[cls.date_start_field_name], initial_dates[pill.name][0], f"pill {pill.name} should not be replanned") cls.assertEqual(pill[cls.date_stop_field_name], initial_dates[pill.name][1], f"pill {pill.name} should not be replanned") def assert_old_pills_vals(cls, res, _type, message, moved_pills, initial_dates): cls.assertDictEqual(res, { 'type': _type, 'message': message, 'old_vals_per_pill_id': { pill.id: { 'date_start': initial_dates[pill.name][0], 'date_stop': initial_dates[pill.name][1], } for pill in moved_pills }, })