Initial commit: Sale packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:49 +02:00
commit 14e3d26998
6469 changed files with 2479670 additions and 0 deletions

View file

@ -0,0 +1,8 @@
# -*- 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_multilang
from . import test_website_sale_stock_product_warehouse
from . import test_website_sale_stock_stock_notification
from . import test_website_sale_stock_reorder_from_portal

View file

@ -0,0 +1,88 @@
# -*- 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.addons.website_sale.tests.test_website_sale_cart_abandoned import TestWebsiteSaleCartAbandonedCommon
from odoo.tests.common import tagged
@tagged('post_install', '-at_install')
class TestWebsiteSaleStockAbandonedCartEmail(TestWebsiteSaleCartAbandonedCommon):
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
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
order_line = [[0, 0, {
'name': 'The Product',
'product_id': storable_product_product.id,
'product_uom_qty': 1,
}]]
customer = self.env['res.partner'].create({
'name': 'a',
'email': 'a@example.com',
})
sale_order = self.env['sale.order'].create({
'partner_id': customer.id,
'website_id': website.id,
'state': 'draft',
'date_order': (datetime.utcnow() - relativedelta(hours=website.cart_abandoned_delay)) - relativedelta(
minutes=1),
'order_line': order_line
})
self.assertFalse(self.send_mail_patched(sale_order.id))
# Reset cart_recovery sent state
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.assertTrue(self.send_mail_patched(sale_order.id))

View file

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# 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
@tagged('post_install', '-at_install')
class TestWebsiteSaleStockMultilang(HttpCase):
def test_website_sale_stock_multilang(self):
# Install French
website = self.env.ref('website.default_website')
lang_fr = self.env['res.lang']._activate_lang('fr_FR')
website.language_ids = [Command.link(lang_fr.id)]
# Configure product: out-of-stock message in EN and FR
unavailable_product = self.env['product.product'].create({
'name': 'unavailable_product',
'type': 'product',
'allow_out_of_stock_order': False,
'sale_ok': True,
'website_published': True,
'list_price': 123.45,
'out_of_stock_message': 'Out of stock',
})
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,106 @@
# -*- 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
@tagged('post_install', '-at_install')
class TestWebsiteSaleStockProductWarehouse(TestSaleProductAttributeValueCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Run the tests in another company, so the tests do not rely on the
# database state (eg the default company's warehouse)
cls.company = cls.env['res.company'].create({'name': 'Company C'})
cls.env.user.company_id = cls.company
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',
})
# 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)]])
# 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()
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.
"""
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_02_update_cart_with_multi_warehouses(self):
""" When the user updates his cart and increases a product quantity, if
this quantity is not available in the SO's warehouse, a warning should
be returned and the quantity updated to its maximum. """
so = self.env['sale.order'].create({
'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")
values = so._cart_update(product_id=self.product_A.id, line_id=so.order_line.id, set_qty=20)
self.assertTrue(values.get('warning', False))
self.assertEqual(values.get('quantity'), 10)

View file

@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import tagged
from odoo.tests.common import HttpCase
@tagged('post_install', '-at_install')
class TestWebsiteSaleStockReorderFromPortal(HttpCase):
@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,
})
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, {
'product_id': cls.available_product.id,
'product_uom_qty': 1,
}),
(0, 0, {
'product_id': cls.unavailable_product.id,
'product_uom_qty': 1,
}),
(0, 0, {
'product_id': cls.partially_available_product.id,
'product_uom_qty': 2,
})
]
})
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()
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,60 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import tagged
from odoo.tests.common import HttpCase
@tagged('post_install', '-at_install')
class TestStockNotificationProduct(HttpCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
warehouse = cls.env['stock.warehouse'].create({
'name': 'Wishlist Warehouse',
'code': 'W_WH'
})
current_website = cls.env['website'].get_current_website()
current_website.warehouse_id = warehouse
cls.warehouse = warehouse
cls.current_website = current_website
cls.product = cls.env['product.product'].create({
'name': 'Macbook Pro',
'website_published': True,
'type': 'product',
'allow_out_of_stock_order': False,
})
cls.pricelist = cls.env['product.pricelist'].create({
'name': 'Public Pricelist',
})
cls.currency = cls.env.ref("base.USD")
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]
ProductProduct = self.env['product.product']
product = ProductProduct.browse(self.product.id)
self.assertTrue(product._has_stock_notification(partner))
# No email should be sent
ProductProduct._send_availability_email()
emails = self.env['mail.mail'].search([('email_to', '=', partner.email_formatted)])
self.assertEqual(len(emails), 0)
# Replenish Product
quants = self.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': self.product.id,
'inventory_quantity': 10.0,
'location_id': self.warehouse.lot_stock_id.id,
})
quants.action_apply_inventory()
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.assertFalse(product._has_stock_notification(partner))