odoo18/addons/pos_restaurant/tests/test_frontend.py

557 lines
24 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import odoo.tests
from odoo.addons.point_of_sale.tests.common_setup_methods import setup_product_combo_items
from odoo.addons.point_of_sale.tests.common import archive_products
from odoo.addons.point_of_sale.tests.test_frontend import TestPointOfSaleHttpCommon
from odoo import Command
from unittest.mock import patch
@odoo.tests.tagged('post_install', '-at_install')
class TestFrontendCommon(TestPointOfSaleHttpCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
archive_products(cls.env)
food_category = cls.env['pos.category'].create({'name': 'Food', 'sequence': 1})
drinks_category = cls.env['pos.category'].create({'name': 'Drinks', 'sequence': 2})
printer = cls.env['pos.printer'].create({
'name': 'Preparation Printer',
'epson_printer_ip': '127.0.0.1',
'printer_type': 'epson_epos',
'product_categories_ids': [drinks_category.id]
})
main_company = cls.env.company
test_sale_journal_2 = cls.env['account.journal'].create({
'name': 'Sales Journal - Test2',
'code': 'TSJ2',
'type': 'sale',
'company_id': main_company.id
})
cash_journal_2 = cls.env['account.journal'].create({
'name': 'Cash 2',
'type': 'cash',
'company_id': main_company.id,
})
cls.pos_config = cls.env['pos.config'].create({
'name': 'Bar Prout',
'module_pos_restaurant': True,
'iface_splitbill': True,
'iface_printbill': True,
'is_order_printer': True,
'printer_ids': [(4, printer.id)],
'iface_tipproduct': False,
'company_id': cls.env.company.id,
'journal_id': test_sale_journal_2.id,
'invoice_journal_id': test_sale_journal_2.id,
'payment_method_ids': [
(4, cls.bank_payment_method.id),
(0, 0, {
'name': 'Cash',
'split_transactions': False,
'receivable_account_id': cls.account_receivable.id,
'journal_id': cash_journal_2.id,
})
],
})
cls.main_pos_config = cls.pos_config
cls.pos_config.floor_ids.unlink()
main_floor = cls.env['restaurant.floor'].create({
'name': 'Main Floor',
'pos_config_ids': [(4, cls.pos_config.id)],
})
second_floor = cls.env['restaurant.floor'].create({
'name': 'Second Floor',
'pos_config_ids': [(4, cls.pos_config.id)],
})
cls.main_floor_table_5 = cls.env['restaurant.table'].create([{
'table_number': 5,
'floor_id': main_floor.id,
'seats': 4,
'position_h': 100,
'position_v': 100,
}])
cls.env['restaurant.table'].create([{
'table_number': 4,
'floor_id': main_floor.id,
'seats': 4,
'shape': 'square',
'position_h': 350,
'position_v': 100,
},
{
'table_number': 2,
'floor_id': main_floor.id,
'seats': 4,
'position_h': 250,
'position_v': 100,
},
{
'table_number': 1,
'floor_id': second_floor.id,
'seats': 4,
'shape': 'square',
'position_h': 100,
'position_v': 150,
},
{
'table_number': 3,
'floor_id': second_floor.id,
'seats': 4,
'position_h': 100,
'position_v': 250,
}])
cls.env['ir.default'].set(
'res.partner',
'property_account_receivable_id',
cls.account_receivable.id,
company_id=main_company.id,
)
cls.env['product.product'].create({
'available_in_pos': True,
'list_price': 2.20,
'name': 'Coca-Cola',
'weight': 0.01,
'pos_categ_ids': [(4, drinks_category.id)],
'categ_id': cls.env.ref('point_of_sale.product_category_pos').id,
'taxes_id': [(6, 0, [])],
})
cls.env['product.product'].create({
'available_in_pos': True,
'list_price': 2.20,
'name': 'Water',
'weight': 0.01,
'pos_categ_ids': [(4, drinks_category.id)],
'categ_id': cls.env.ref('point_of_sale.product_category_pos').id,
'taxes_id': [(6, 0, [])],
})
cls.env['product.product'].create({
'available_in_pos': True,
'list_price': 2.20,
'name': 'Minute Maid',
'weight': 0.01,
'pos_categ_ids': [(4, drinks_category.id)],
'categ_id': cls.env.ref('point_of_sale.product_category_pos').id,
'taxes_id': [(6, 0, [])],
})
# multiple categories product
cls.env['product.product'].create({
'available_in_pos': True,
'list_price': 2.20,
'name': 'Test Multi Category Product',
'weight': 0.01,
'pos_categ_ids': [(4, drinks_category.id), (4, food_category.id)],
'categ_id': cls.env.ref('point_of_sale.product_category_pos').id,
'taxes_id': [(6, 0, [])],
})
# desk organizer (variant product)
cls.desk_organizer = cls.env['product.product'].create({
'name': 'Desk Organizer',
'available_in_pos': True,
'list_price': 5.10,
'pos_categ_ids': [(4, drinks_category.id)], # will put it as a drink for convenience
})
desk_size_attribute = cls.env['product.attribute'].create({
'name': 'Size',
'display_type': 'radio',
'create_variant': 'no_variant',
})
desk_size_s = cls.env['product.attribute.value'].create({
'name': 'S',
'attribute_id': desk_size_attribute.id,
})
desk_size_m = cls.env['product.attribute.value'].create({
'name': 'M',
'attribute_id': desk_size_attribute.id,
})
desk_size_l = cls.env['product.attribute.value'].create({
'name': 'L',
'attribute_id': desk_size_attribute.id,
})
cls.env['product.template.attribute.line'].create({
'product_tmpl_id': cls.desk_organizer.product_tmpl_id.id,
'attribute_id': desk_size_attribute.id,
'value_ids': [(6, 0, [desk_size_s.id, desk_size_m.id, desk_size_l.id])]
})
desk_fabrics_attribute = cls.env['product.attribute'].create({
'name': 'Fabric',
'display_type': 'select',
'create_variant': 'no_variant',
})
desk_fabrics_leather = cls.env['product.attribute.value'].create({
'name': 'Leather',
'attribute_id': desk_fabrics_attribute.id,
})
desk_fabrics_other = cls.env['product.attribute.value'].create({
'name': 'Custom',
'attribute_id': desk_fabrics_attribute.id,
'is_custom': True,
})
cls.env['product.template.attribute.line'].create({
'product_tmpl_id': cls.desk_organizer.product_tmpl_id.id,
'attribute_id': desk_fabrics_attribute.id,
'value_ids': [(6, 0, [desk_fabrics_leather.id, desk_fabrics_other.id])]
})
pricelist = cls.env['product.pricelist'].create({'name': 'Restaurant Pricelist'})
cls.pos_config.write({'pricelist_id': pricelist.id})
class TestFrontend(TestFrontendCommon):
def test_01_pos_restaurant(self):
self.pos_user.write({
'groups_id': [
(4, self.env.ref('account.group_account_invoice').id),
]
})
dummy_fiscal_position = self.env['account.fiscal.position'].create({
'name': 'No Tax',
})
self.pos_config.write({
'takeaway': True,
'takeaway_fp_id': dummy_fiscal_position.id,
})
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('pos_restaurant_sync')
self.assertEqual(1, self.env['pos.order'].search_count([('amount_total', '=', 4.4), ('state', '=', 'draft')]))
self.assertEqual(1, self.env['pos.order'].search_count([('amount_total', '=', 4.4), ('state', '=', 'paid')]))
self.start_pos_tour('pos_restaurant_sync_second_login')
self.assertEqual(0, self.env['pos.order'].search_count([('amount_total', '=', 4.4), ('state', '=', 'draft')]))
self.assertEqual(1, self.env['pos.order'].search_count([('amount_total', '=', 2.2), ('state', '=', 'draft')]))
self.assertEqual(2, self.env['pos.order'].search_count([('amount_total', '=', 4.4), ('state', '=', 'paid')]))
def test_02_others(self):
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('SplitBillScreenTour')
self.start_pos_tour('FloorScreenTour', login="pos_admin")
def test_02_others_bis(self):
self.pos_config.with_user(self.pos_admin).open_ui()
self.start_pos_tour('ControlButtonsTour', login="pos_admin")
def test_04_ticket_screen(self):
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('PosResTicketScreenTour')
def test_05_tip_screen(self):
self.pos_config.write({'set_tip_after_payment': True, 'iface_tipproduct': True, 'tip_product_id': self.env.ref('point_of_sale.product_product_tip')})
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('PosResTipScreenTour')
orders = self.env['pos.order'].search([], limit=5, order="id desc")
order_tips = [o.tip_amount for o in orders]
# orders order can be different depending on which module is install so we sort the tips
order_tips.sort()
self.assertEqual(order_tips, [0.0, 0.4, 1.0, 1.0, 1.5])
order4 = self.env['pos.order'].search([('pos_reference', 'ilike', '%-0004')], limit=1, order='id desc')
self.assertEqual(order4.customer_count, 2)
def test_06_split_bill_screen(self):
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('SplitBillScreenTour2')
def test_07_split_bill_screen(self):
# disable kitchen printer to avoid printing errors
self.pos_config.is_order_printer = False
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('SplitBillScreenTour3')
def test_08_refund_stay_current_table(self):
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('RefundStayCurrentTableTour')
def test_09_combo_split_bill(self):
setup_product_combo_items(self)
self.office_combo.write({'lst_price': 40})
self.pos_config.is_order_printer = False
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('SplitBillScreenTour4ProductCombo')
def test_10_save_last_preparation_changes(self):
self.pos_config.write({'printer_ids': False})
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('SaveLastPreparationChangesTour')
self.assertTrue(self.pos_config.current_session_id.order_ids.last_order_preparation_change, "There should be a last order preparation change")
self.assertTrue("Coca" in self.pos_config.current_session_id.order_ids.last_order_preparation_change, "The last order preparation change should contain 'Coca'")
def test_11_bill_screen_qrcode_data(self):
self.pos_config.write({'printer_ids': False})
self.pos_config.company_id.point_of_sale_use_ticket_qr_code = True
self.pos_config.company_id.point_of_sale_ticket_portal_url_display_mode = 'qr_code_and_url'
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('BillScreenTour')
def test_12_order_tracking(self):
self.pos_config.write({'order_edit_tracking': True})
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('OrderTrackingTour')
order1 = self.env['pos.order'].search([('pos_reference', 'ilike', '%-0001')], limit=1, order='id desc')
self.assertTrue(order1.is_edited)
def test_13_category_check(self):
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('CategLabelCheck')
def test_14_change_synced_order(self):
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('OrderChange')
def test_13_crm_team(self):
if self.env['ir.module.module']._get('pos_sale').state != 'installed':
self.skipTest("'pos_sale' module is required")
sale_team = self.env['crm.team'].search([], limit=1)
self.pos_config.crm_team_id = sale_team
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('CrmTeamTour')
order = self.env['pos.order'].search([], limit=1)
self.assertEqual(order.crm_team_id.id, sale_team.id)
def test_14_pos_payment_sync(self):
self.pos_config.write({'printer_ids': False})
self.pos_config.with_user(self.pos_user).open_ui()
def assert_payment(lines_count, amount):
self.assertEqual(len(order.payment_ids), lines_count)
self.assertEqual(round(sum(payment.amount for payment in order.payment_ids), 2), amount)
self.start_pos_tour('PoSPaymentSyncTour1')
order = self.pos_config.current_session_id.order_ids
self.assertEqual(len(order), 1)
assert_payment(1, 2.2)
self.start_pos_tour('PoSPaymentSyncTour2')
assert_payment(1, 4.4)
self.start_pos_tour('PoSPaymentSyncTour3')
assert_payment(2, 6.6)
def test_preparation_printer_content(self):
self.env['pos.printer'].create({
'name': 'Printer',
'printer_type': 'epson_epos',
'epson_printer_ip': '0.0.0.0',
'product_categories_ids': [Command.set(self.env['pos.category'].search([]).ids)],
})
self.main_pos_config.write({
'is_order_printer' : True,
'printer_ids': [Command.set(self.env['pos.printer'].search([]).ids)],
})
self.product_test = self.env['product.product'].create({
'name': 'Product Test',
'available_in_pos': True,
'list_price': 10,
'pos_categ_ids': [(6, 0, [self.env['pos.category'].search([], limit=1).id])],
'taxes_id': False,
})
attribute = self.env['product.attribute'].create({
'name': 'Attribute 1',
'create_variant': 'no_variant',
})
attribute_value = self.env['product.attribute.value'].create({
'name': 'Value 1',
'attribute_id': attribute.id,
})
attribute_value_2 = self.env['product.attribute.value'].create({
'name': 'Value 2',
'attribute_id': attribute.id,
})
self.env['product.template.attribute.line'].create({
'product_tmpl_id': self.product_test.product_tmpl_id.id,
'attribute_id': attribute.id,
'value_ids': [(6, 0, [attribute_value.id, attribute_value_2.id])],
})
attribute_2 = self.env['product.attribute'].create({
'name': 'Attribute 1',
'create_variant': 'always',
})
attribute_2_value = self.env['product.attribute.value'].create({
'name': 'Value 1',
'attribute_id': attribute_2.id,
})
attribute_2_value_2 = self.env['product.attribute.value'].create({
'name': 'Value 2',
'attribute_id': attribute_2.id,
})
self.env['product.template.attribute.line'].create({
'product_tmpl_id': self.product_test.product_tmpl_id.id,
'attribute_id': attribute_2.id,
'value_ids': [(6, 0, [attribute_2_value.id, attribute_2_value_2.id])],
})
self.main_pos_config.with_user(self.pos_user).open_ui()
self.start_tour(f"/pos/ui?config_id={self.main_pos_config.id}", 'PreparationPrinterContent', login="pos_user")
def test_transfer_table_last_preparation_change(self):
"""This test will check if the last preparation change is correctly transferred to the new table with 6 possible cases:
- Transfer sent product on table with same product sent
- Transfer sent product on table with same product not sent
- Transfer sent product on table without the same product
- Transfer not sent product on table with same product not sent
- Transfer not sent product on table with same product sent
- Transfer not sent product on table without the same product"""
self.env['pos.printer'].create({
'name': 'Printer',
'printer_type': 'epson_epos',
'epson_printer_ip': '0.0.0.0',
'product_categories_ids': [Command.set(self.env['pos.category'].search([]).ids)],
})
self.main_pos_config.write({
'is_order_printer' : True,
'printer_ids': [Command.set(self.env['pos.printer'].search([]).ids)],
})
self.product_test = self.env['product.product'].create({
'name': 'Product Test',
'available_in_pos': True,
'list_price': 10,
'pos_categ_ids': [(6, 0, [self.env['pos.category'].search([], limit=1).id])],
'taxes_id': False,
})
self.main_pos_config.with_user(self.pos_user).open_ui()
self.start_tour(f"/pos/ui?config_id={self.main_pos_config.id}", 'TableTransferPreparationChange1', login="pos_user")
self.start_tour(f"/pos/ui?config_id={self.main_pos_config.id}", 'TableTransferPreparationChange2', login="pos_user")
self.start_tour(f"/pos/ui?config_id={self.main_pos_config.id}", 'TableTransferPreparationChange3', login="pos_user")
self.start_tour(f"/pos/ui?config_id={self.main_pos_config.id}", 'TableTransferPreparationChange4', login="pos_user")
self.start_tour(f"/pos/ui?config_id={self.main_pos_config.id}", 'TableTransferPreparationChange5', login="pos_user")
self.start_tour(f"/pos/ui?config_id={self.main_pos_config.id}", 'TableTransferPreparationChange6', login="pos_user")
def test_combo_preparation_receipt(self):
setup_product_combo_items(self)
pos_printer = self.env['pos.printer'].create({
'name': 'Printer',
'printer_type': 'epson_epos',
'epson_printer_ip': '0.0.0.0',
'product_categories_ids': [Command.set(self.env['pos.category'].search([]).ids)],
})
self.pos_config.write({
'is_order_printer' : True,
'printer_ids': [Command.set(pos_printer.ids)],
})
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('ComboSortedPreparationReceiptTour')
def test_multiple_preparation_printer(self):
"""This test make sure that no empty receipt are sent when using multiple printer with different categories
The tour will check that we tried did not try to print two receipt. We can achieve that by checking the content
of the error message. Because we do not have real printer an error message will be displayed, this will contain
all the receipt that failed to print. If it contains more than 1 it means that we tried to print a second receipt
and it should not be the case here. The only one we should see is 'Detailed Receipt'
"""
pos_category_1 = self.env['pos.category'].create({'name': 'Category 1'})
pos_category_2 = self.env['pos.category'].create({'name': 'Category 2'})
printer_1 = self.env['pos.printer'].create({
'name': 'Printer',
'printer_type': 'epson_epos',
'epson_printer_ip': '0.0.0.0',
'product_categories_ids': [Command.set(pos_category_2.ids)],
})
printer_2 = self.env['pos.printer'].create({
'name': 'Printer',
'printer_type': 'epson_epos',
'epson_printer_ip': '0.0.0.0',
'product_categories_ids': [Command.set(pos_category_1.ids)],
})
self.main_pos_config.write({
'is_order_printer' : True,
'printer_ids': [Command.set([printer_1.id, printer_2.id])],
})
self.product_1 = self.env['product.product'].create({
'name': 'Product 1',
'available_in_pos': True,
'list_price': 10,
'pos_categ_ids': [(6, 0, [pos_category_1.id])],
'taxes_id': False,
})
self.main_pos_config.with_user(self.pos_user).open_ui()
self.start_tour(f"/pos/ui?config_id={self.main_pos_config.id}", 'MultiPreparationPrinter', login="pos_user")
def test_user_on_residual_order(self):
self.pos_config.write({'printer_ids': False})
self.pos_config.with_user(self.pos_admin).open_ui()
self.start_pos_tour('LeaveResidualOrder', login="pos_admin")
self.start_pos_tour('FinishResidualOrder', login="pos_user")
orders = self.env['pos.order'].search([])
self.assertEqual(orders[0].user_id.id, self.pos_user.id, "Pos user not registered on order")
self.assertEqual(orders[1].user_id.id, self.pos_admin.id, "Pos admin not registered on order")
def test_15_pos_refund_qty(self):
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('RefundQtyTour')
def test_combo_preparation_receipt_layout(self):
setup_product_combo_items(self)
self.env['product.product'].search([('name', 'ilike', 'Combo')]).write({'pos_categ_ids': [(Command.set(self.env['pos.category'].search([], limit=1).ids))]})
self.env['pos.printer'].create({
'name': 'Printer',
'printer_type': 'epson_epos',
'epson_printer_ip': '0.0.0.0',
'product_categories_ids': [Command.set(self.env['pos.category'].search([]).ids)],
})
self.pos_config.write({
'is_order_printer': True,
'printer_ids': [Command.set(self.env['pos.printer'].search([]).ids)],
})
self.pos_config.with_user(self.pos_admin).open_ui()
self.start_tour(f"/pos/ui?config_id={self.pos_config.id}", 'test_combo_preparation_receipt_layout', login="pos_admin")
def test_synchronisation_of_orders(self):
""" Test order synchronization with order data using the notify_synchronisation method.
First, an ongoing order is created on the server, and verify its presence in the POS UI.
Then, the order is paid from the server, and confirm if the order state is updated correctly.
"""
self.start_pos_tour("OrderSynchronisationTour")
def test_cancel_order_from_ui(self):
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('test_cancel_order_from_ui')
order = self.pos_config.current_session_id.order_ids[0]
self.assertEqual(order.state, "cancel", "The order should be in cancel state")
def test_book_and_release_table(self):
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('test_book_and_release_table', login="pos_user")
order = self.env['pos.order'].search([], limit=1, order='id desc')
self.assertEqual(order.state, "cancel", "The order should be in cancel state after releasing the table")
def test_combo_synchronisation(self):
"""This test checks that when a combo line is set as dirty, the parent combo line is also set as dirty.
if this is not the case, the combo lines would lose their link to the parent combo line and appear as
normal line"""
setup_product_combo_items(self)
self.pos_config.is_order_printer = False
self.pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('test_combo_synchronisation')
def test_reload_order_line_removed(self):
""" This test checks that when a saved order line is removed but not yet synced to the backend,
if PoS gets reloaded, the order line gets back
"""
self.start_pos_tour('test_reload_order_line_removed')