Files
enterprise-addons/website_appointment_sale_project/tests/test_project_appointment_task.py
T

288 lines
15 KiB
Python

from odoo import Command
from odoo.addons.project.tests.test_project_base import TestProjectCommon
from odoo.addons.appointment.tests.common import AppointmentCommon
from odoo.addons.mail.tests.common import mail_new_test_user
from odoo.tests import users, tagged
from datetime import datetime
from freezegun import freeze_time
import re
@tagged('post_install', '-at_install')
class TestProjectAppointmentTask(TestProjectCommon, AppointmentCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.project = cls.env['project.project'].create({
'name': 'Project Test APT',
'company_id': cls.env.company.id,
})
# create additional users to invite as guests in the appointment
cls.test_user_1 = mail_new_test_user(
cls.env, login='test1',
name='Test User 1', email='[email protected]',
groups='base.group_user',
)
cls.test_user_2 = mail_new_test_user(
cls.env, login='test2',
name='Test User 2', email='[email protected]',
groups='base.group_user',
)
# create a product associated with project defined in common
cls.product = cls.env['product.product'].create({
'name': 'Project Test APT Product',
'company_id': cls.env.company.id,
'project_id': cls.project.id,
'type': 'service',
'list_price': 100,
"service_policy": "ordered_prepaid",
"service_tracking": "task_global_project",
"service_type": "manual",
})
# define 2 types of appointment (one for users and one for resources)
paid_apt_common_values = {
'appointment_tz': 'UTC',
'has_payment_step': True,
'min_schedule_hours': 1.0,
'max_schedule_days': 2,
'product_id': cls.product.id,
'slot_ids': [Command.create({
'weekday': str(cls.reference_monday.isoweekday()),
'start_hour': 14,
'end_hour': 17,
})],
'allow_guests': True,
}
cls.appointment_users, cls.appointment_resources = cls.env['appointment.type'].create([{
'name': 'Test Paid Appointment Type - Users',
'schedule_based_on': 'users',
'staff_user_ids': [(4, cls.staff_user_bxls.id)],
**paid_apt_common_values,
}, {
'name': 'Test Paid Appointment Type - Resource',
'manage_capacity': True,
'schedule_based_on': 'resources',
**paid_apt_common_values,
}])
cls.resource_1, cls.resource_2 = cls.env['appointment.resource'].create([{
'appointment_type_ids': cls.appointment_resources.ids,
'capacity': 3,
'name': 'APT Resource 1',
}, {
'appointment_type_ids': cls.appointment_resources.ids,
'capacity': 2,
'name': 'APT Resource 2',
'shareable': True,
}])
cls.start_slot = cls.reference_monday.replace(hour=14)
cls.stop_slot = cls.reference_monday.replace(hour=15)
# Define questions and answers for the appointment form (only user type appointment)
# Types of questions: char, select, radio, checkbox
cls.appointment_question_single_line_text, cls.appointment_question_dropdown, cls.appointment_question_radio, cls.appointment_question_checkbox = cls.env['appointment.question'].create([
{
'appointment_type_ids': cls.appointment_users.ids,
'name': 'How are you ?',
'question_type': 'char',
},
{
'appointment_type_ids': cls.appointment_users.ids,
'name': 'How do you feel?',
'question_type': 'select',
'answer_ids': [
Command.create({'name': name}) for name in ['Happy', 'Sad', 'Neutral']
]
},
{
'appointment_type_ids': cls.appointment_users.ids,
'name': "Don't answer this",
'question_type': 'radio',
'answer_ids': [
Command.create({'name': name}) for name in ['Ok', 'Not Ok', 'Maybe Ok']
]
},
{
'appointment_type_ids': cls.appointment_users.ids,
'name': 'Select multiple',
'question_type': 'checkbox',
'answer_ids': [
Command.create({'name': name}) for name in ['odoo', 'oqoo', 'opoo', 'oboo']
]
}
])
@freeze_time('2022-02-13 20:00:00')
@users('apt_manager')
def test_project_user_appointment_type_task_population_on_confirmed_so(self):
"""
Verify that when the SO of a appointment booking of USER type is confirmed,
a task is created with its data populated, and a reference to the appointment (calendar_event) is created.
The selected users (named here as staff) should be added as users on the task.
The questions and answers of the appointment form should be displayed in the description of the task.
"""
appointment_type = self.appointment_users
appointment_answer_single_line_text_input_values = {
'appointment_type_id': appointment_type.id,
'question_id': self.appointment_question_single_line_text.id,
'value_text_box': 'I am Good',
}
appointment_answer_dropdown_input_values = {
'appointment_type_id': appointment_type.id,
'question_id': self.appointment_question_dropdown.id,
'value_answer_id': self.appointment_question_dropdown.answer_ids.filtered(lambda a: a.name == 'Happy').id,
}
appointment_answer_checkbox_input_values = [
{
'appointment_type_id': appointment_type.id,
'question_id': self.appointment_question_checkbox.id,
'value_answer_id': answer.id
} for answer in self.appointment_question_checkbox.answer_ids
if answer.name != 'odoo'
]
# dictionary with the values of the answers for the questions of the appointment form (question_id: answer_value)
user_answers = {
self.appointment_question_single_line_text.id: 'I am Good',
self.appointment_question_dropdown.id: 'Happy',
self.appointment_question_radio.id: '',
self.appointment_question_checkbox.id: ['oqoo', 'opoo', 'oboo'],
}
booking_values = {
'appointment_type_id': appointment_type.id,
'appointment_answer_input_ids': [
Command.create(appointment_answer_single_line_text_input_values),
Command.create(appointment_answer_dropdown_input_values),
] + [Command.create(values) for values in appointment_answer_checkbox_input_values],
'booking_line_ids': [Command.create({'appointment_user_id': self.staff_user_bxls.id, 'capacity_reserved': 1, 'capacity_used': 1})],
'partner_id': self.apt_manager.partner_id.id,
'product_id': self.product.id,
'staff_user_id': self.staff_user_bxls.id,
'start': self.start_slot,
'stop': self.stop_slot,
'guest_ids': [self.test_user_1.partner_id.id, self.test_user_2.partner_id.id],
}
calendar_booking = self.env['calendar.booking'].create(booking_values)
# Create SO (quotation) and SOL linked to booking
sale_order = self.env['sale.order'].sudo().create({
'partner_id': calendar_booking.partner_id.id,
'company_id': self.env.company.id,
})
self.assertFalse(calendar_booking._filter_unavailable_bookings(), "No unavailable booking should be found")
cart_values = sale_order._cart_add(
product_id=appointment_type.product_id.id,
quantity=1,
calendar_booking_id=calendar_booking.id,
)
self.assertEqual(cart_values['quantity'], 1, "Cart should successfully add 1 product (the appointment)")
sale_order_line = sale_order.order_line.filtered(lambda line: line.id == cart_values['line_id'])
self.assertTrue(sale_order_line, "Sale order line should be created after adding the product to the cart")
self.assertEqual(sale_order_line.calendar_booking_ids, calendar_booking, "Sale order line should be linked to the calendar booking")
self.assertFalse(sale_order_line.calendar_event_id)
sale_order.action_confirm()
task = sale_order_line.task_id
event = task.calendar_event_id
self.assertTrue(task, "Task should be created after confirming the sale order")
self.assertTrue(event, "Calendar event should be created after confirming the sale order")
self.assertEqual(event, calendar_booking.calendar_event_id, "Calendar event of the task should be linked to the calendar booking")
self.assertEqual(task.user_ids.ids, calendar_booking.staff_user_id.ids, "Staff users of the booking should be added as users on the task")
self.assertEqual(task.allocated_hours, event.duration, "Allocated hours of the task should be equal to the duration of the event")
self.assertEqual(task.planned_date_begin, event.start, "Planned date begin of the task should be equal to the start of the event")
self.assertEqual(task.date_deadline, event.stop, "Date deadline of the task should be equal to the stop of the event")
# Assert that the invited guests are followers of the task (remove the staff user and apt manager)
followers_ids = set(task.message_partner_ids.ids)
followers_ids.difference_update({self.staff_user_bxls.partner_id.id, self.apt_manager.partner_id.id})
self.assertEqual(followers_ids, set(calendar_booking.guest_ids.ids), "Invited guests should be followers of the task")
# Assert the desctiption of the task, which should contain the questions of the appointment form.
questions = calendar_booking.appointment_type_id.question_ids
# Define the regular expression pattern (<dt> for questions, <dd> tags for answers)
reg_pattern = r'<dt><h3>(.*?)<\/h3><\/dt>[\s\S]*?<dd>(.*?)<\/dd>'
# Find all matches in the description of the task (type is MarkUp so cast it to str)
matches = re.findall(reg_pattern, str(task.description))
# Assert that the pair of question and answer are identical in the description of the task
for match in matches:
task_question_name, task_answer_value = match
question = questions.filtered(lambda q: q.name == task_question_name)
self.assertTrue(question, f"Question {task_question_name} not found in description of task")
# compare the answers found in the description with the answers prepared for the test `user_answers`
answer_value = user_answers.get(question.id)
if not answer_value:
self.assertEqual(task_answer_value, "/", "When no answer was provided by user, the value should be '/'")
elif question.question_type == 'checkbox':
self.assertEqual(set(task_answer_value.split(', ')), set(answer_value), "For multiple answers, the values should be comma-separated")
else:
self.assertEqual(task_answer_value, answer_value, "The answer value should be equal to the value provided by the user")
@freeze_time('2022-02-13 20:00:00')
def test_project_resource_appointment_type_task_population_on_confirmed_so(self):
"""
Verify that when the SO of a appointment booking of RESOURCE type is confirmed,
a task is created with its data populated, and a reference to the appointment (calendar_event) is created.
The selected resources should be added as tags on the task.
"""
appointment_type = self.appointment_resources
booking_values = {
'appointment_type_id': appointment_type.id,
'partner_id': self.partner_1.id,
'product_id': self.product.id,
'name': 'Project Appointment Booking Resource test',
'booking_line_ids': [
Command.create({'appointment_resource_id': self.resource_1.id, 'capacity_reserved': 1, 'capacity_used': 2}),
Command.create({'appointment_resource_id': self.resource_2.id, 'capacity_reserved': 1}),
],
'start': self.start_slot,
'stop': self.stop_slot,
}
calendar_booking = self.env['calendar.booking'].create(booking_values)
# Create SO (quotation) and SOL linked to booking
sale_order = self.env['sale.order'].sudo().create({
'partner_id': self.partner_1.id,
'company_id': self.env.company.id,
})
self.assertFalse(calendar_booking._filter_unavailable_bookings(), "No unavailable booking should be found")
cart_values = sale_order._cart_add(
product_id=appointment_type.product_id.id,
quantity=1,
calendar_booking_id=calendar_booking.id,
)
self.assertEqual(cart_values['quantity'], 1, "Cart should successfully add 1 product (the appointment)")
sale_order_line = sale_order.order_line.filtered(lambda line: line.id == cart_values['line_id'])
self.assertTrue(sale_order_line, "Sale order line should be created after adding the product to the cart")
self.assertEqual(sale_order_line.calendar_booking_ids, calendar_booking, "Sale order line should be linked to the calendar booking")
self.assertFalse(sale_order_line.calendar_event_id)
sale_order.action_confirm()
task = sale_order_line.task_id
event = task.calendar_event_id
self.assertTrue(task, "Task should be created after confirming the sale order")
self.assertTrue(event, "Calendar event should be created after confirming the sale order")
self.assertEqual(event, calendar_booking.calendar_event_id, "Calendar event of the task should be linked to the calendar booking")
self.assertEqual(set(task.tag_ids.mapped('name')), set(calendar_booking.booking_line_ids.appointment_resource_id.mapped('name')),
"Selected resources should be added as tags on the task")
self.assertEqual(task.allocated_hours, event.duration, "Allocated hours of the task should be equal to the duration of the event")
self.assertEqual(task.planned_date_begin, event.start, "Planned date begin of the task should be equal to the start of the event")
self.assertEqual(task.date_deadline, event.stop, "Date deadline of the task should be equal to the stop of the event")