19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:32:12 +01:00
parent 79f83631d5
commit 73afc09215
6267 changed files with 1534193 additions and 1130106 deletions

View file

@ -1,8 +1,15 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_website_sale_stock_abandoned_cart_email
from . import test_website_sale_stock_configurators
from . import test_website_sale_stock_multilang
from . import test_website_sale_stock_product_combo
from . import test_website_sale_stock_product_product
from . import test_website_sale_stock_product_template
from . import test_website_sale_stock_product_warehouse
from . import test_website_sale_stock_delivery
from . import test_website_sale_stock_gmc
from . import test_website_sale_stock_stock_notification
from . import test_website_sale_stock_reorder_from_portal
from . import test_website_sale_stock_sale_order_line
from . import test_website_sale_stock_stock_message

View file

@ -0,0 +1,40 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.website_sale.tests.common import WebsiteSaleCommon
class WebsiteSaleStockCommon(WebsiteSaleCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.warehouse = cls.env['stock.warehouse'].search([('company_id', '=', cls.company.id)])
@classmethod
def _add_product_qty_to_wh(cls, product_id, qty, loc_id):
cls.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': product_id,
'inventory_quantity': qty,
'location_id': loc_id,
}).action_apply_inventory()
@classmethod
def _create_product(cls, **create_values):
""" Override of `website_sale` to create storable products by default and restrict them from
selling when out of stock.
"""
if create_values.get('type', 'consu') == 'consu': # Only for goods.
if 'is_storable' not in create_values:
create_values['is_storable'] = True
if 'allow_out_of_stock_order' not in create_values:
create_values['allow_out_of_stock_order'] = False
return super()._create_product(**create_values)
@classmethod
def _create_warehouse(cls, **kwargs):
return cls.env['stock.warehouse'].create({
'name': 'Test Warehouse',
'code': 'TWH',
**kwargs,
})

View file

@ -1,26 +1,33 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime
from dateutil.relativedelta import relativedelta
from odoo.tests import tagged
from odoo.addons.website_sale.tests.test_website_sale_cart_abandoned import TestWebsiteSaleCartAbandonedCommon
from odoo.tests.common import tagged
from odoo.addons.website_sale_stock.tests.common import WebsiteSaleStockCommon
@tagged('post_install', '-at_install')
class TestWebsiteSaleStockAbandonedCartEmail(TestWebsiteSaleCartAbandonedCommon):
class TestWebsiteSaleStockAbandonedCartEmail(
TestWebsiteSaleCartAbandonedCommon, WebsiteSaleStockCommon
):
def test_website_sale_stock_abandoned_cart_email(self):
"""Make sure the send_abandoned_cart_email method sends the correct emails."""
website = self.env['website'].get_current_website()
website.send_abandoned_cart_email = True
website.write(
{
"send_abandoned_cart_email_activation_time": (
datetime.utcnow() - relativedelta(hours=website.cart_abandoned_delay)
)
- relativedelta(minutes=10)
}
)
storable_product_template = self.env['product.template'].create({
'name': 'storable_product_template',
'type': 'product',
'allow_out_of_stock_order': False
})
storable_product_product = storable_product_template.product_variant_id
storable_product_product = self._create_product()
order_line = [[0, 0, {
'name': 'The Product',
'product_id': storable_product_product.id,
@ -44,45 +51,10 @@ class TestWebsiteSaleStockAbandonedCartEmail(TestWebsiteSaleCartAbandonedCommon)
sale_order.cart_recovery_email_sent = False
# Replenish the stock of the product
self.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': storable_product_product.id,
'inventory_quantity': 10.0,
'location_id': self.env.user._get_default_warehouse_id().lot_stock_id.id,
}).action_apply_inventory()
self.assertTrue(self.send_mail_patched(sale_order.id))
company = self.env['res.company'].create({'name': 'Company C'})
self.env.user.company_id = company
website_1 = self.env['website'].create({
'name': 'Website Company C',
'company_id': company.id,
'send_abandoned_cart_email': True,
})
warehouse_1 = self.env['stock.warehouse'].search([('company_id', '=', company.id)])
product = self.env['product.product'].create({
'name': 'Product',
'allow_out_of_stock_order': False,
'type': 'product',
'default_code': 'E-COM1',
})
self.env['stock.quant'].with_context(inventory_mode=True).create([{
'product_id': product.id,
'inventory_quantity': 25.0,
'location_id': warehouse_1.lot_stock_id.id,
}]).action_apply_inventory()
sale_order = self.env['sale.order'].create({
'partner_id': self.customer.id,
'website_id': website_1.id,
'date_order': (datetime.utcnow() - relativedelta(hours=website.cart_abandoned_delay)) - relativedelta(
minutes=1),
'order_line': [
(0, 0, {
'product_id': product.id,
'product_uom_qty': 5,
}),
],
})
self._add_product_qty_to_wh(
storable_product_product.id,
10,
self.env.user._get_default_warehouse_id().lot_stock_id.id,
)
self.assertTrue(self.send_mail_patched(sale_order.id))

View file

@ -0,0 +1,73 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.fields import Command
from odoo.tests import HttpCase, tagged
from odoo.addons.website_sale_stock.tests.common import WebsiteSaleStockCommon
@tagged('post_install', '-at_install')
class TestWebsiteSaleStockConfigurators(HttpCase, WebsiteSaleStockCommon):
def test_website_sale_stock_product_configurator(self):
stock_attribute = self.env['product.attribute'].create({
'name': "Stock",
'value_ids': [
Command.create({'name': "Out of stock"}),
Command.create({'name': "In stock"}),
]
})
optional_product = self.env['product.template'].create({
'name': "Optional product",
'website_published': True,
'is_storable': True,
'allow_out_of_stock_order': False,
'attribute_line_ids': [
Command.create({
'attribute_id': stock_attribute.id,
'value_ids': [Command.set(stock_attribute.value_ids.ids)],
})
],
})
main_product = self.env['product.product'].create({
'name': "Main product",
'website_published': True,
'is_storable': True,
'allow_out_of_stock_order': False,
'optional_product_ids': [Command.set(optional_product.ids)],
})
self.env['stock.quant'].create([
{
'product_id': optional_product.product_variant_ids[1].id,
'location_id': self.warehouse.lot_stock_id.id,
'quantity': 10,
}, {
'product_id': main_product.id,
'location_id': self.warehouse.lot_stock_id.id,
'quantity': 10,
},
])
self.start_tour('/', 'website_sale_stock_product_configurator')
def test_website_sale_stock_combo_configurator(self):
product = self._create_product(name="Test product")
self.env['stock.quant'].create({
'product_id': product.id,
'location_id': self.warehouse.lot_stock_id.id,
'quantity': 2,
})
combo = self.env['product.combo'].create({
'name': "Test combo",
'combo_item_ids': [
Command.create({'product_id': product.id}),
Command.create({'product_id': self._create_product(
allow_out_of_stock_order=True, is_storable=False
).id}),
],
})
self._create_product(
name="Combo product",
type='combo',
combo_ids=[Command.link(combo.id)]
)
self.start_tour('/', 'website_sale_stock_combo_configurator')

View file

@ -0,0 +1,75 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import Command
from odoo.exceptions import ValidationError
from odoo.tests import tagged
from odoo.http import request
from odoo.addons.payment.tests.common import PaymentCommon
from odoo.addons.website_sale.controllers.cart import Cart
from odoo.addons.website_sale.controllers.main import WebsiteSale
from odoo.addons.website_sale.tests.common import MockRequest, WebsiteSaleCommon
from odoo.addons.delivery.tests.common import DeliveryCommon
@tagged('post_install', '-at_install')
class TestWebsiteSaleStockDeliveryController(PaymentCommon, WebsiteSaleCommon, DeliveryCommon):
def test_validate_payment_with_no_available_delivery_method(self):
"""
An error should be raised if you try to validate an order with a storable
product without any delivery method available
"""
storable_product = self.env['product.product'].create([{
'name': 'Storable Product',
'sale_ok': True,
'is_storable': True,
'website_published': True,
}])
carriers = self.env['delivery.carrier'].search([])
carriers.write({'website_published': False})
WebsiteSaleCartController = Cart()
WebsiteSaleController = WebsiteSale()
with MockRequest(self.env, website=self.website):
WebsiteSaleCartController.add_to_cart(
product_template_id=storable_product.product_tmpl_id,
product_id=storable_product.id,
quantity=1,
)
with self.assertRaises(ValidationError):
WebsiteSaleController.shop_payment_validate()
def test_validate_order_out_of_stock_zero_price(self):
"""
An error should be raised if you try to validate an order for
an out of stock product with 0 price
"""
WebsiteSaleController = WebsiteSale()
storable_product = self.env['product.product'].create({
'name': 'Storable Product',
'sale_ok': True,
'is_storable': True,
'website_published': True,
'allow_out_of_stock_order': False,
'lst_price': 0,
})
sale_order = self.env['sale.order'].create({
'partner_id': self.partner.id,
'order_line': [Command.create({
'product_id': storable_product.id,
'product_uom_qty': 12.0,
})],
'carrier_id': self.free_delivery.id,
})
self.free_delivery.write({'website_published': True})
self.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': storable_product.id,
'inventory_quantity': 10.0,
'location_id': self.env.user._get_default_warehouse_id().lot_stock_id.id,
}).action_apply_inventory()
with MockRequest(self.env, website=self.website):
request.cart = sale_order
with self.assertRaises(ValidationError):
WebsiteSaleController.shop_payment_validate()

View file

@ -0,0 +1,47 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import tagged
from odoo.addons.website_sale.tests.common_gmc import WebsiteSaleGMCCommon
@tagged('post_install', '-at_install')
class TestWebsiteSaleStockGMC(WebsiteSaleGMCCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.website.warehouse_id = cls.env.ref('stock.warehouse0')
cls.stock_loc = cls.website.warehouse_id.lot_stock_id
cls.supplier_loc = cls.env.ref('stock.stock_location_suppliers')
(cls.blue_sofa + cls.red_sofa + cls.blanket).write({
'is_storable': True,
'allow_out_of_stock_order': False,
})
cls.red_sofa.allow_out_of_stock_order = True
cls.env['stock.quant'].create({
'product_id': cls.blue_sofa.id,
'quantity': 10.0,
'location_id': cls.stock_loc.id,
})
def test_gmc_items_availability_check_stock(self):
self.update_items()
self.assertEqual('in_stock', self.blue_sofa_item['availability'])
self.assertEqual('out_of_stock', self.items[self.blanket]['availability'])
self.assertEqual('in_stock', self.red_sofa_item['availability']) # allow_out_of_stock_order
def test_gmc_items_keep_website_stock_separate(self):
self.blue_sofa.allow_out_of_stock_order = False
# setup second website with seperate stock
warehouse2 = self.env['stock.warehouse'].create({'name': 'Stock 2', 'code': 'WH2'})
self.gmc_feed.website_id = self.env['website'].create({
'name': 'Website Test 2',
'domain': 'https://my-website.net',
'warehouse_id': warehouse2.id,
})
self.update_items()
self.assertEqual('out_of_stock', self.red_sofa_item['availability'])

View file

@ -17,7 +17,7 @@ class TestWebsiteSaleStockMultilang(HttpCase):
# Configure product: out-of-stock message in EN and FR
unavailable_product = self.env['product.product'].create({
'name': 'unavailable_product',
'type': 'product',
'is_storable': True,
'allow_out_of_stock_order': False,
'sale_ok': True,
'website_published': True,
@ -27,5 +27,4 @@ class TestWebsiteSaleStockMultilang(HttpCase):
unavailable_product.update_field_translations('out_of_stock_message', {
'fr_FR': {'Out of stock': 'Hors-stock'},
})
self.start_tour("/fr/shop?search=unavailable", 'website_sale_stock_multilang')

View file

@ -0,0 +1,56 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.fields import Command
from odoo.tests import tagged
from odoo.tests.common import HttpCase
from odoo.addons.website_sale.tests.common import MockRequest
from odoo.addons.website_sale_stock.tests.common import WebsiteSaleStockCommon
@tagged('post_install', '-at_install')
class TestWebsiteSaleStockProductCombo(HttpCase, WebsiteSaleStockCommon):
def test_get_max_quantity_with_max(self):
product_a = self._create_product(is_storable=True, allow_out_of_stock_order=False)
product_b = self._create_product(is_storable=True, allow_out_of_stock_order=False)
self.env['stock.quant'].create([
{
'product_id': product_a.id,
'location_id': self.warehouse.lot_stock_id.id,
'quantity': 5,
}, {
'product_id': product_b.id,
'location_id': self.warehouse.lot_stock_id.id,
'quantity': 10,
},
])
combo = self.env['product.combo'].create({
'name': "Test combo",
'combo_item_ids': [
Command.create({'product_id': product_a.id}),
Command.create({'product_id': product_b.id}),
],
})
self.cart.order_line = [Command.create({'product_id': product_b.id, 'product_uom_qty': 3})]
with MockRequest(self.env, website=self.website, sale_order_id=self.cart.id):
self.assertEqual(combo._get_max_quantity(self.website, self.cart), 7)
def test_get_max_quantity_without_max(self):
product_a = self._create_product(is_storable=True, allow_out_of_stock_order=False)
product_b = self._create_product(is_storable=True, allow_out_of_stock_order=True)
self.env['stock.quant'].create({
'product_id': product_a.id,
'location_id': self.warehouse.lot_stock_id.id,
'quantity': 5,
})
combo = self.env['product.combo'].create({
'name': "Test combo",
'combo_item_ids': [
Command.create({'product_id': product_a.id}),
Command.create({'product_id': product_b.id}),
],
})
self.assertIsNone(combo._get_max_quantity(self.website, self.cart))

View file

@ -0,0 +1,27 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.fields import Command
from odoo.tests import tagged
from odoo.tests.common import HttpCase
from odoo.addons.website_sale.tests.common import MockRequest
from odoo.addons.website_sale_stock.tests.common import WebsiteSaleStockCommon
@tagged('post_install', '-at_install')
class TestWebsiteSaleStockProductProduct(HttpCase, WebsiteSaleStockCommon):
def test_get_max_quantity_with_max(self):
product = self._create_product(is_storable=True, allow_out_of_stock_order=False)
self.env['stock.quant'].create({
'product_id': product.id, 'location_id': self.warehouse.lot_stock_id.id, 'quantity': 5
})
self.cart.order_line = [Command.create({'product_id': product.id, 'product_uom_qty': 3})]
with MockRequest(self.env, website=self.website, sale_order_id=self.cart.id):
self.assertEqual(product._get_max_quantity(self.website, self.cart), 2)
def test_get_max_quantity_without_max(self):
product = self._create_product(is_storable=True, allow_out_of_stock_order=True)
self.assertIsNone(product._get_max_quantity(self.website, self.cart))

View file

@ -0,0 +1,123 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime
from odoo.fields import Command
from odoo.tests import HttpCase, tagged
from odoo.addons.website_sale.tests.common import MockRequest
from odoo.addons.website_sale_stock.tests.common import WebsiteSaleStockCommon
@tagged('post_install', '-at_install')
class TestWebsiteSaleStockProductTemplate(HttpCase, WebsiteSaleStockCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.product_oos_order_allowed = cls._create_product(
is_storable=True,
allow_out_of_stock_order=True,
)
cls.product_oos_order_not_allowed = cls._create_product(
is_storable=True,
allow_out_of_stock_order=False,
)
def test_website_sale_stock_get_additional_configurator_data(self):
product = self.product_oos_order_not_allowed
self.env['stock.quant'].create({
'product_id': product.id,
'location_id': self.warehouse.lot_stock_id.id,
'quantity': 10,
})
env = self.env(user=self.public_user)
with MockRequest(env, website=self.website.with_env(env)):
configurator_data = self.env['product.template']._get_additional_configurator_data(
product_or_template=product,
date=datetime(2000, 1, 1),
currency=self.currency,
pricelist=self.pricelist,
)
self.assertEqual(configurator_data['free_qty'], 10)
def test_get_additional_combination_info_max_combo_quantity_with_max(self):
product_a = self.product_oos_order_not_allowed
product_b = self._create_product(is_storable=True, allow_out_of_stock_order=False)
product_c = self.product_oos_order_allowed
self.env['stock.quant'].create([
{
'product_id': product_a.id,
'location_id': self.warehouse.lot_stock_id.id,
'quantity': 5,
}, {
'product_id': product_b.id,
'location_id': self.warehouse.lot_stock_id.id,
'quantity': 10,
},
])
combo_a, combo_b, combo_c = self.env['product.combo'].create([
{'name': "Combo A", 'combo_item_ids': [Command.create({'product_id': product_a.id})]},
{'name': "Combo B", 'combo_item_ids': [Command.create({'product_id': product_b.id})]},
{'name': "Combo C", 'combo_item_ids': [Command.create({'product_id': product_c.id})]},
])
combo_product = self._create_product(
type='combo',
combo_ids=[
Command.link(combo_a.id), Command.link(combo_b.id), Command.link(combo_c.id)
],
)
self.cart.order_line = [Command.create({'product_id': product_a.id, 'product_uom_qty': 3})]
with MockRequest(self.env, website=self.website, sale_order_id=self.cart.id):
combination_info = self.env['product.template'].with_context(
website_sale_stock_get_quantity=True
)._get_additionnal_combination_info(
combo_product,
quantity=3,
uom=combo_product.uom_id,
date=datetime(2000, 1, 1),
website=self.website
)
self.assertEqual(combination_info['max_combo_quantity'], 2)
def test_get_additional_combination_info_max_combo_quantity_without_max(self):
product = self.product_oos_order_allowed
combo = self.env['product.combo'].create({
'name': "Test combo", 'combo_item_ids': [Command.create({'product_id': product.id})]
})
combo_product = self._create_product(type='combo', combo_ids=[Command.link(combo.id)])
with MockRequest(self.env, website=self.website, sale_order_id=self.cart.id):
combination_info = self.env['product.template'].with_context(
website_sale_stock_get_quantity=True
)._get_additionnal_combination_info(
combo_product,
quantity=3,
uom=combo_product.uom_id,
date=datetime(2000, 1, 1),
website=self.website
)
self.assertNotIn('max_combo_quantity', combination_info)
def test_get_additional_combination_info_free_quantity_is_integer(self):
self._add_product_qty_to_wh(
self.product_oos_order_not_allowed.id,
9,
self.warehouse.lot_stock_id.id,
)
with MockRequest(self.env, website=self.website, sale_order_id=self.cart.id):
combination_info = self.env['product.template'].with_context(
website_sale_stock_get_quantity=True,
)._get_additionnal_combination_info(
self.product_oos_order_not_allowed,
quantity=9,
uom=self.env.ref('uom.product_uom_pack_6'),
date=datetime(2000, 1, 1),
website=self.website,
)
self.assertEqual(combination_info['free_qty'], 1)

View file

@ -1,13 +1,19 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.website.tools import MockRequest
from odoo.addons.sale.tests.test_sale_product_attribute_value_config import TestSaleProductAttributeValueCommon
from odoo.tests import tagged
from odoo.addons.product.tests.test_product_attribute_value_config import (
TestProductAttributeValueCommon,
)
from odoo.addons.website_sale.tests.common import MockRequest
from odoo.addons.website_sale_stock.tests.common import WebsiteSaleStockCommon
@tagged('post_install', '-at_install')
class TestWebsiteSaleStockProductWarehouse(TestSaleProductAttributeValueCommon):
class TestWebsiteSaleStockProductWarehouse(
TestProductAttributeValueCommon, WebsiteSaleStockCommon
):
@classmethod
def setUpClass(cls):
@ -19,67 +25,44 @@ class TestWebsiteSaleStockProductWarehouse(TestSaleProductAttributeValueCommon):
cls.website = cls.env['website'].create({'name': 'Website Company C'})
cls.website.company_id = cls.company
# Set two warehouses (one was created on company creation)
cls.warehouse_1 = cls.env['stock.warehouse'].search([('company_id', '=', cls.company.id)])
cls.warehouse_2 = cls.env['stock.warehouse'].create({
'name': 'Warehouse 2',
'code': 'WH2'
})
# Create two stockable products
cls.product_A = cls.env['product.product'].create({
'name': 'Product A',
'allow_out_of_stock_order': False,
'type': 'product',
'default_code': 'E-COM1',
})
cls.product_B = cls.env['product.product'].create({
'name': 'Product B',
'allow_out_of_stock_order': False,
'type': 'product',
'default_code': 'E-COM2',
})
# Set two warehouses (one was created on company creation)
cls.warehouse_1 = cls.env['stock.warehouse'].search([
('company_id', '=', cls.company.id)
])
cls.warehouse_2 = cls._create_warehouse()
cls.product_A = cls._create_product()
cls.product_B = cls._create_product()
cls.test_env = cls.env['base'].with_context(
website_id=cls.website.id,
website_sale_stock_get_quantity=True,
).env
# Add 10 Product A in WH1 and 15 Product 1 in WH2
quants = cls.env['stock.quant'].with_context(inventory_mode=True).create([{
'product_id': cls.product_A.id,
'inventory_quantity': qty,
'location_id': wh.lot_stock_id.id,
} for wh, qty in [(cls.warehouse_1, 10.0), (cls.warehouse_2, 15.0)]])
cls._add_product_qty_to_wh(cls.product_A.id, 10, cls.warehouse_1.lot_stock_id.id)
cls._add_product_qty_to_wh(cls.product_A.id, 15, cls.warehouse_2.lot_stock_id.id)
# Add 10 Product 2 in WH2
quants |= cls.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': cls.product_B.id,
'inventory_quantity': 10.0,
'location_id': cls.warehouse_2.lot_stock_id.id,
})
quants.action_apply_inventory()
cls._add_product_qty_to_wh(cls.product_B.id, 10, cls.warehouse_2.lot_stock_id.id)
def test_01_get_combination_info(self):
""" Checked that correct product quantity is shown in website according
to the warehouse which is set in current website.
- Set Warehouse 1, Warehouse 2 or none in website and:
- Check available quantity of Product A and Product B in website
When the user doesn't set any warehouse, the module should still select
a default one.
"""
def test_get_combination_info_free_qty_when_warehouse_is_set(self):
self.website.warehouse_id = self.warehouse_2
test_env = self.test_env
with MockRequest(test_env, website=self.website.with_env(test_env)):
combination_info = self.product_A.with_env(test_env)._get_combination_info_variant()
self.assertEqual(combination_info['free_qty'], 15)
with MockRequest(test_env, website=self.website.with_env(test_env)):
combination_info = self.product_B.with_env(test_env)._get_combination_info_variant()
self.assertEqual(combination_info['free_qty'], 10)
for wh, qty_a, qty_b in [(self.warehouse_1, 10, 0), (self.warehouse_2, 15, 10), (False, 10, 0)]:
# set warehouse_id
self.website.warehouse_id = wh
product = self.product_A.with_context(website_id=self.website.id)
combination_info = product.product_tmpl_id.with_context(website_sale_stock_get_quantity=True)._get_combination_info()
# Check available quantity of product is according to warehouse
self.assertEqual(combination_info['free_qty'], qty_a, "%s units of Product A should be available in warehouse %s" % (qty_a, wh))
product = self.product_B.with_context(website_id=self.website.id)
combination_info = product.product_tmpl_id.with_context(website_sale_stock_get_quantity=True)._get_combination_info()
# Check available quantity of product is according to warehouse
self.assertEqual(combination_info['free_qty'], qty_b, "%s units of Product B should be available in warehouse %s" % (qty_b, wh))
def test_get_combination_info_free_qty_when_no_warehouse_is_set(self):
self.website.warehouse_id = False
test_env = self.test_env
with MockRequest(test_env, website=self.website.with_env(test_env)):
combination_info = self.product_A.with_env(test_env)._get_combination_info_variant()
self.assertEqual(combination_info['free_qty'], 25)
with MockRequest(test_env, website=self.website.with_env(test_env)):
combination_info = self.product_B.with_env(test_env)._get_combination_info_variant()
self.assertEqual(combination_info['free_qty'], 10)
def test_02_update_cart_with_multi_warehouses(self):
""" When the user updates his cart and increases a product quantity, if
@ -87,20 +70,25 @@ class TestWebsiteSaleStockProductWarehouse(TestSaleProductAttributeValueCommon):
be returned and the quantity updated to its maximum. """
so = self.env['sale.order'].create({
'website_id': self.website.id,
'partner_id': self.env.user.partner_id.id,
'order_line': [(0, 0, {
'name': self.product_A.name,
'product_id': self.product_A.id,
'product_uom_qty': 5,
'product_uom': self.product_A.uom_id.id,
'price_unit': self.product_A.list_price,
})]
})
with MockRequest(self.env, website=self.website, sale_order_id=so.id):
website_so = self.website.sale_get_order()
self.assertEqual(website_so.order_line.product_id.virtual_available, 10, "This quantity should be based on SO's warehouse")
with MockRequest(self.env, website=self.website, sale_order_id=so.id) as req:
website_so = req.cart
self.assertEqual(website_so, so)
self.assertEqual(
website_so.order_line.product_id.virtual_available,
25,
"This quantity should be based on all warehouses.",
)
values = so._cart_update(product_id=self.product_A.id, line_id=so.order_line.id, set_qty=20)
values = so._cart_update_line_quantity(line_id=so.order_line.id, quantity=30)
self.assertTrue(values.get('warning', False))
self.assertEqual(values.get('quantity'), 10)
self.assertEqual(values.get('quantity'), 25)

View file

@ -1,52 +1,38 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import Command
from odoo.tests import tagged
from odoo.tests.common import HttpCase
from odoo.addons.website_sale_stock.tests.common import WebsiteSaleStockCommon
@tagged('post_install', '-at_install')
class TestWebsiteSaleStockReorderFromPortal(HttpCase):
class TestWebsiteSaleStockReorderFromPortal(HttpCase, WebsiteSaleStockCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env['website'].get_current_website().enabled_portal_reorder_button = True
cls.available_product = cls.env['product.product'].create({
'name': 'available_product',
'type': 'product',
'allow_out_of_stock_order': False,
'sale_ok': True,
'website_published': True,
})
cls.unavailable_product = cls.env['product.product'].create({
'name': 'unavailable_product',
'type': 'product',
'allow_out_of_stock_order': False,
'sale_ok': True,
'website_published': True,
})
cls.partially_available_product = cls.env['product.product'].create({
'name': 'partially_available_product',
'type': 'product',
'allow_out_of_stock_order': False,
'sale_ok': True,
'website_published': True,
})
cls.available_product = cls._create_product(name='available_product')
cls.unavailable_product = cls._create_product(name='unavailable_product')
cls.partially_available_product = cls._create_product(
name='partially_available_product'
)
user_admin = cls.env.ref('base.user_admin')
order = cls.env['sale.order'].create({
'partner_id': user_admin.partner_id.id,
'state': 'sale',
'order_line': [
(0, 0, {
Command.create({
'product_id': cls.available_product.id,
'product_uom_qty': 1,
}),
(0, 0, {
Command.create({
'product_id': cls.unavailable_product.id,
'product_uom_qty': 1,
}),
(0, 0, {
Command.create({
'product_id': cls.partially_available_product.id,
'product_uom_qty': 2,
})
@ -54,16 +40,9 @@ class TestWebsiteSaleStockReorderFromPortal(HttpCase):
})
order.message_subscribe(user_admin.partner_id.ids)
cls.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': cls.available_product.id,
'inventory_quantity': 10.0,
'location_id': 8,
}).action_apply_inventory()
cls.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': cls.partially_available_product.id,
'inventory_quantity': 1.0,
'location_id': 8,
}).action_apply_inventory()
stock_loc_id = cls.env.ref('stock.stock_location_stock').id
cls._add_product_qty_to_wh(cls.available_product.id, 10, stock_loc_id)
cls._add_product_qty_to_wh(cls.partially_available_product.id, 3, stock_loc_id)
def test_website_sale_stock_reorder_from_portal_stock(self):
self.start_tour("/", 'website_sale_stock_reorder_from_portal', login='admin')

View file

@ -0,0 +1,79 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.fields import Command
from odoo.tests import tagged
from odoo.tests.common import HttpCase
from odoo.addons.website_sale.tests.common import MockRequest
from odoo.addons.website_sale_stock.tests.common import WebsiteSaleStockCommon
@tagged('post_install', '-at_install')
class TestWebsiteSaleStockSaleOrderLine(HttpCase, WebsiteSaleStockCommon):
def test_get_max_line_qty_with_max(self):
product_a = self._create_product(is_storable=True, allow_out_of_stock_order=False)
product_b = self._create_product(is_storable=True, allow_out_of_stock_order=False)
self.env['stock.quant'].create([
{
'product_id': product_a.id,
'location_id': self.warehouse.lot_stock_id.id,
'quantity': 5,
}, {
'product_id': product_b.id,
'location_id': self.warehouse.lot_stock_id.id,
'quantity': 10,
},
])
combo_a, combo_b = self.env['product.combo'].create([
{'name': "Combo A", 'combo_item_ids': [Command.create({'product_id': product_a.id})]},
{'name': "Combo B", 'combo_item_ids': [Command.create({'product_id': product_b.id})]},
])
combo_product = self._create_product(
type='combo', combo_ids=[Command.link(combo_a.id), Command.link(combo_b.id)]
)
combo_product_line = self.env['sale.order.line'].create({
'order_id': self.cart.id, 'product_id': combo_product.id, 'product_uom_qty': 3
})
combo_item_line_a, _combo_item_line_b = self.env['sale.order.line'].create([
{
'order_id': self.cart.id,
'product_id': product_a.id,
'product_uom_qty': 3,
'linked_line_id': combo_product_line.id,
'combo_item_id': combo_a.combo_item_ids[0].id,
}, {
'order_id': self.cart.id,
'product_id': product_b.id,
'product_uom_qty': 3,
'linked_line_id': combo_product_line.id,
'combo_item_id': combo_b.combo_item_ids[0].id,
},
])
with MockRequest(self.env, website=self.website, sale_order_id=self.cart.id):
self.assertEqual(combo_product_line._get_max_available_qty(), 2)
self.assertEqual(combo_product_line._get_max_line_qty(), 5)
self.assertEqual(combo_item_line_a._get_max_available_qty(), 2)
self.assertEqual(combo_item_line_a._get_max_line_qty(), 5)
def test_get_max_line_qty_without_max(self):
product = self._create_product(is_storable=True, allow_out_of_stock_order=True)
combo = self.env['product.combo'].create({
'name': "Test combo", 'combo_item_ids': [Command.create({'product_id': product.id})]
})
combo_product = self._create_product(type='combo', combo_ids=[Command.link(combo.id)])
combo_product_line = self.env['sale.order.line'].create({
'order_id': self.cart.id, 'product_id': combo_product.id, 'product_uom_qty': 3,
})
combo_item_line = self.env['sale.order.line'].create({
'order_id': self.cart.id,
'product_id': product.id,
'product_uom_qty': 3,
'linked_line_id': combo_product_line.id,
})
self.assertIsNone(combo_product_line._get_max_available_qty())
self.assertIsNone(combo_product_line._get_max_line_qty())
self.assertIsNone(combo_item_line._get_max_available_qty())
self.assertIsNone(combo_item_line._get_max_line_qty())

View file

@ -0,0 +1,56 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.fields import Command
from odoo.tests import HttpCase, tagged
from odoo.addons.product.tests.common import ProductCommon
@tagged('post_install', '-at_install')
class TestWebsiteSaleStockProductConfigurator(ProductCommon, HttpCase):
def test_01_stock_message_update_after_close_with_optional_products(self):
self.env['delivery.carrier'].search([]).is_published = False
product_product_with_options = self.env['product.product'].create({
'name': 'Product With Optional (TEST)',
'standard_price': 500.0,
'list_price': 750.0,
'optional_product_ids': [Command.link(self.product.product_tmpl_id.id)],
'website_published': True,
'show_availability': True,
'available_threshold': 5000,
'allow_out_of_stock_order': False,
'is_storable': True,
})
self.product.website_published = True
self.env['stock.quant'].create({
'product_id': product_product_with_options.id,
'location_id': self.quick_ref('stock.stock_location_stock').id,
'quantity': 30.0,
})
self.start_tour(
"/",
'website_sale_stock_message_after_close_onfigurator_modal_with_optional_products',
)
def test_02_stock_message_update_after_close_without_optional_products(self):
self.env['delivery.carrier'].search([]).is_published = False
product_product_without_options = self.env['product.product'].create({
'name': 'Product Without Optional (TEST)',
'standard_price': 500.0,
'list_price': 750.0,
'website_published': True,
'show_availability': True,
'available_threshold': 5000,
'allow_out_of_stock_order': False,
'is_storable': True,
})
self.env['stock.quant'].create({
'product_id': product_product_without_options.id,
'location_id': self.quick_ref('stock.stock_location_stock').id,
'quantity': 30.0,
})
self.start_tour(
"/",
'website_sale_stock_message_after_close_onfigurator_modal_without_optional_products',
)

View file

@ -23,7 +23,7 @@ class TestStockNotificationProduct(HttpCase):
cls.product = cls.env['product.product'].create({
'name': 'Macbook Pro',
'website_published': True,
'type': 'product',
'is_storable': True,
'allow_out_of_stock_order': False,
})
@ -35,8 +35,7 @@ class TestStockNotificationProduct(HttpCase):
def test_back_in_stock_notification_product(self):
self.start_tour("/", 'back_in_stock_notification_product')
partner_ids = self.env['res.partner']._mail_find_partner_from_emails(['test@test.test'])
partner = partner_ids[0]
partner = self.env['mail.thread']._partner_find_from_emails_single(['test@test.test'], no_create=True)
ProductProduct = self.env['product.product']
product = ProductProduct.browse(self.product.id)
self.assertTrue(product._has_stock_notification(partner))
@ -54,7 +53,10 @@ class TestStockNotificationProduct(HttpCase):
})
quants.action_apply_inventory()
website = self.env['website'].get_current_website()
ProductProduct._send_availability_email()
emails = self.env['mail.mail'].search([('email_to', '=', partner.email_formatted)])
self.assertEqual(emails[0].subject, "The product 'Macbook Pro' is now available")
self.assertEqual(emails[0].email_from, website.company_id.partner_id.email_formatted)
self.assertFalse(product._has_stock_notification(partner))