Eskaera carried its own pricing path: a `website_sale_aplicoop.pricelist_id` setting resolved inside the Eskaera controllers, plus a local reimplementation of the tax and discount maths. The pricelist part meant /shop and Eskaera could quote different prices on the same website, and the maths part had drifted from the original it was copied from. Pricing is now the standard one. The setting is gone (a migration drops the parameter, which would otherwise linger looking like live configuration) and `_resolve_pricelist` is `request.website.pricelist_id`. Scoping a pricelist to a website is already core's job through `product.pricelist.website_id`, so running the plain shop on one website and the co-op on another needs no code here. Taxes are applied by `product.template._apply_taxes_to_price`, which fixes a real defect: it calls `_get_tax_included_unit_price_from_price` first, and this module did not. With a fiscal position remapping a tax-included tax, a product at 121 (100 + 21%) was displayed at 121 instead of 110, because the price was handed to the mapped tax as if it were already that tax's gross amount. The helper is a no-op without a remapping, so ordinary pricing is untouched. It also means the website's `show_line_subtotals_tax_selection` is respected rather than overridden with a hardcoded tax-included display. Listing a page now costs one `_compute_price_rule` call instead of one per product, which matters on the lazy-loading path. Two smaller things found on the way. `_get_product_price_rule` was being passed `target_currency=`, which is not one of its arguments: it fell into **kwargs and was ignored, so the conversion never happened. And `_compute_price_info` resolved `product.product_variant_ids[0]`, but it receives variants, so a multi-variant template was priced from its first variant rather than the one asked for. The delivery display price loses its hardcoded 5.74 fallback and reuses the same helpers as everything else. Kept local, because none of it is pricing: the /Kg and /L suffixes, the 0.1 quantity step for bulk goods, the base unit price and the supplier name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
923 lines
32 KiB
Python
923 lines
32 KiB
Python
# Copyright 2026 - Today Criptomart
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
|
|
"""
|
|
Test suite for Phase 3 refactoring of confirm_eskaera().
|
|
|
|
Tests the 3 helper methods created in Phase 3:
|
|
- _validate_confirm_json(): Validates JSON request data
|
|
- _process_cart_items(): Processes cart items into sale.order lines
|
|
- _build_confirmation_message(): Builds localized confirmation messages
|
|
|
|
Includes tests for:
|
|
- Request validation with various error conditions
|
|
- Cart item processing with product context
|
|
- Multi-language message building (ES, EU, CA, GL, PT, FR, IT)
|
|
- Pickup vs delivery date handling
|
|
- Edge cases and error handling
|
|
"""
|
|
|
|
import json
|
|
from datetime import date
|
|
from datetime import timedelta
|
|
from types import SimpleNamespace
|
|
from unittest.mock import patch
|
|
|
|
from odoo.exceptions import UserError
|
|
from odoo.tests.common import TransactionCase
|
|
|
|
from odoo.addons.website_sale_aplicoop.controllers.website_sale import (
|
|
AplicoopWebsiteSale,
|
|
)
|
|
|
|
REQUEST_PATCH_TARGET = (
|
|
"odoo.addons.website_sale_aplicoop.controllers.website_sale.request"
|
|
)
|
|
|
|
|
|
def _env_with_lang(env, lang):
|
|
"""Return a cloned environment with language context set."""
|
|
return env(context=dict(env.context, lang=lang))
|
|
|
|
|
|
def _make_json_response(data, headers=None, status=200):
|
|
"""Build a lightweight HTTP-like response object for controller tests."""
|
|
|
|
raw = data.encode("utf-8") if isinstance(data, str) else data
|
|
return SimpleNamespace(data=raw, status=status, headers=headers or [])
|
|
|
|
|
|
def _build_request_mock(env, payload=None, website=None):
|
|
"""Build a request mock compatible with the controller helpers/routes.
|
|
|
|
Args:
|
|
env: Odoo Environment to use as request.env.
|
|
payload: dict that will be JSON-encoded as request.httprequest.data.
|
|
website: Optional real website record. When not provided a minimal
|
|
SimpleNamespace stub is used (sufficient for tests that do not
|
|
call pricing helpers).
|
|
"""
|
|
if website is None:
|
|
# `pricelist_id` is what the pricing helpers read now that Eskaera
|
|
# prices with the standard website pricelist. Ordered by id on
|
|
# purpose: the multilang cases build an env whose language is not
|
|
# installed, and the default order is by the translated `name`.
|
|
website = SimpleNamespace(
|
|
pricelist_id=env["product.pricelist"]
|
|
.sudo()
|
|
.search([], limit=1, order="id"),
|
|
show_line_subtotals_tax_selection="tax_excluded",
|
|
fiscal_position_id=False,
|
|
company_id=False,
|
|
)
|
|
request_mock = SimpleNamespace(
|
|
env=env,
|
|
make_response=_make_json_response,
|
|
website=website,
|
|
)
|
|
if payload is not None:
|
|
request_mock.httprequest = SimpleNamespace(
|
|
data=json.dumps(payload).encode("utf-8")
|
|
)
|
|
return request_mock
|
|
|
|
|
|
class TestValidateConfirmJson(TransactionCase):
|
|
"""Test _validate_confirm_json() helper method."""
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.controller = AplicoopWebsiteSale()
|
|
self.group_1 = self.env["res.partner"].create(
|
|
{
|
|
"name": "Consumer Group 1",
|
|
"is_company": True,
|
|
"is_group": True,
|
|
}
|
|
)
|
|
self.group_2 = self.env["res.partner"].create(
|
|
{
|
|
"name": "Consumer Group 2",
|
|
"is_company": True,
|
|
"is_group": True,
|
|
}
|
|
)
|
|
self.partner = self.env["res.partner"].create(
|
|
{
|
|
"name": "Phase 3 Member",
|
|
"email": "phase3-member@test.com",
|
|
}
|
|
)
|
|
self.group_2.member_ids = [(4, self.partner.id)]
|
|
self.user = self.env["res.users"].create(
|
|
{
|
|
"name": "Phase 3 User",
|
|
"login": "phase3-user@test.com",
|
|
"email": "phase3-user@test.com",
|
|
"partner_id": self.partner.id,
|
|
}
|
|
)
|
|
|
|
# Create test group order
|
|
self.group_order = self.env["group.order"].create(
|
|
{
|
|
"name": "Test Order Phase 3",
|
|
"state": "open",
|
|
"group_ids": [(6, 0, [self.group_1.id, self.group_2.id])],
|
|
"start_date": date.today(),
|
|
"end_date": date.today() + timedelta(days=7),
|
|
"cutoff_day": "3", # Thursday
|
|
"pickup_day": "5", # Saturday
|
|
}
|
|
)
|
|
|
|
def test_validate_confirm_json_success(self):
|
|
"""Test successful validation of confirm JSON data."""
|
|
request_mock = _build_request_mock(self.env(user=self.user))
|
|
|
|
data = {
|
|
"order_id": self.group_order.id,
|
|
"items": [{"product_id": 1, "quantity": 2, "product_price": 10.0}],
|
|
"is_delivery": False,
|
|
}
|
|
|
|
with patch(REQUEST_PATCH_TARGET, request_mock):
|
|
order_id, group_order, current_user, items, is_delivery = (
|
|
self.controller._validate_confirm_json(data)
|
|
)
|
|
|
|
self.assertEqual(order_id, self.group_order.id)
|
|
self.assertEqual(group_order.id, self.group_order.id)
|
|
self.assertEqual(current_user.id, self.user.id)
|
|
self.assertEqual(len(items), 1)
|
|
self.assertFalse(is_delivery)
|
|
|
|
def test_validate_confirm_json_missing_order_id(self):
|
|
"""Test validation fails without order_id."""
|
|
request_mock = _build_request_mock(self.env(user=self.user))
|
|
|
|
data = {"items": [{"product_id": 1, "quantity": 2}]}
|
|
|
|
with patch(REQUEST_PATCH_TARGET, request_mock):
|
|
with self.assertRaises(ValueError) as context:
|
|
self.controller._validate_confirm_json(data)
|
|
|
|
self.assertIn("order_id is required", str(context.exception))
|
|
|
|
def test_validate_confirm_json_order_not_exists(self):
|
|
"""Test validation fails with non-existent order."""
|
|
request_mock = _build_request_mock(self.env(user=self.user))
|
|
|
|
data = {
|
|
"order_id": 99999, # Non-existent ID
|
|
"items": [{"product_id": 1, "quantity": 2}],
|
|
}
|
|
|
|
with patch(REQUEST_PATCH_TARGET, request_mock):
|
|
with self.assertRaises(ValueError) as context:
|
|
self.controller._validate_confirm_json(data)
|
|
|
|
self.assertIn("Order", str(context.exception))
|
|
|
|
def test_validate_confirm_json_no_items(self):
|
|
"""Test validation fails without items in cart."""
|
|
request_mock = _build_request_mock(self.env(user=self.user))
|
|
|
|
data = {
|
|
"order_id": self.group_order.id,
|
|
"items": [],
|
|
}
|
|
|
|
with patch(REQUEST_PATCH_TARGET, request_mock):
|
|
with self.assertRaises(ValueError) as context:
|
|
self.controller._validate_confirm_json(data)
|
|
|
|
self.assertIn("No items in cart", str(context.exception))
|
|
|
|
def test_validate_confirm_json_with_delivery_flag(self):
|
|
"""Test validation correctly handles is_delivery flag."""
|
|
request_mock = _build_request_mock(self.env(user=self.user))
|
|
|
|
data = {
|
|
"order_id": self.group_order.id,
|
|
"items": [{"product_id": 1, "quantity": 1}],
|
|
"is_delivery": True,
|
|
}
|
|
|
|
with patch(REQUEST_PATCH_TARGET, request_mock):
|
|
_, _, _, _, is_delivery = self.controller._validate_confirm_json(data)
|
|
|
|
self.assertTrue(is_delivery)
|
|
|
|
def test_validate_confirm_json_user_without_matching_group(self):
|
|
"""Validation must fail if user is not in any allowed consumer group."""
|
|
other_partner = self.env["res.partner"].create(
|
|
{
|
|
"name": "Partner without matching group",
|
|
"email": "nogroup@test.com",
|
|
}
|
|
)
|
|
other_user = self.env["res.users"].create(
|
|
{
|
|
"name": "User without matching group",
|
|
"login": "nogroup-user@test.com",
|
|
"email": "nogroup-user@test.com",
|
|
"partner_id": other_partner.id,
|
|
}
|
|
)
|
|
request_mock = _build_request_mock(self.env(user=other_user))
|
|
|
|
data = {
|
|
"order_id": self.group_order.id,
|
|
"items": [{"product_id": 1, "quantity": 1}],
|
|
}
|
|
|
|
with patch(REQUEST_PATCH_TARGET, request_mock):
|
|
with self.assertRaises(ValueError) as context:
|
|
self.controller._validate_confirm_json(data)
|
|
|
|
self.assertIn(
|
|
"User is not a member of any consumer group in this order",
|
|
str(context.exception),
|
|
)
|
|
|
|
|
|
class TestConsumerGroupResolution(TransactionCase):
|
|
"""Tests for consumer group selection in multi-group orders."""
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.controller = AplicoopWebsiteSale()
|
|
self.group_1 = self.env["res.partner"].create(
|
|
{
|
|
"name": "Group A",
|
|
"is_company": True,
|
|
"is_group": True,
|
|
}
|
|
)
|
|
self.group_2 = self.env["res.partner"].create(
|
|
{
|
|
"name": "Group B",
|
|
"is_company": True,
|
|
"is_group": True,
|
|
}
|
|
)
|
|
self.group_3 = self.env["res.partner"].create(
|
|
{
|
|
"name": "Group C",
|
|
"is_company": True,
|
|
"is_group": True,
|
|
}
|
|
)
|
|
self.partner = self.env["res.partner"].create(
|
|
{
|
|
"name": "Member B",
|
|
"email": "memberb@test.com",
|
|
}
|
|
)
|
|
self.group_2.member_ids = [(4, self.partner.id)]
|
|
self.user = self.env["res.users"].create(
|
|
{
|
|
"name": "Member B User",
|
|
"login": "memberb-user@test.com",
|
|
"email": "memberb-user@test.com",
|
|
"partner_id": self.partner.id,
|
|
}
|
|
)
|
|
self.product = self.env["product.product"].create(
|
|
{
|
|
"name": "Group Resolution Product",
|
|
"type": "consu",
|
|
"list_price": 10.0,
|
|
}
|
|
)
|
|
self.group_order = self.env["group.order"].create(
|
|
{
|
|
"name": "Multi-group order",
|
|
"state": "open",
|
|
"group_ids": [(6, 0, [self.group_1.id, self.group_2.id])],
|
|
"start_date": date.today(),
|
|
"end_date": date.today() + timedelta(days=7),
|
|
"cutoff_day": "1",
|
|
"pickup_day": "2",
|
|
}
|
|
)
|
|
self.sale_order_lines = [
|
|
(
|
|
0,
|
|
0,
|
|
{
|
|
"product_id": self.product.id,
|
|
"name": "Test line",
|
|
"product_uom_qty": 1,
|
|
"price_unit": 10.0,
|
|
},
|
|
)
|
|
]
|
|
|
|
def test_get_consumer_group_for_user_returns_matching_group(self):
|
|
"""Should resolve the user's own consumer group, not the first order group."""
|
|
consumer_group_id = self.controller._get_consumer_group_for_user(
|
|
self.group_order, self.user
|
|
)
|
|
|
|
self.assertEqual(consumer_group_id, self.group_2.id)
|
|
|
|
def test_get_consumer_group_for_user_uses_first_matching_order_group(self):
|
|
"""If user belongs to multiple valid groups, use the first in group_order."""
|
|
self.group_1.member_ids = [(4, self.partner.id)]
|
|
group_order = self.env["group.order"].create(
|
|
{
|
|
"name": "Multi-match order",
|
|
"state": "open",
|
|
"group_ids": [
|
|
(6, 0, [self.group_1.id, self.group_2.id, self.group_3.id])
|
|
],
|
|
}
|
|
)
|
|
|
|
consumer_group_id = self.controller._get_consumer_group_for_user(
|
|
group_order, self.user
|
|
)
|
|
|
|
self.assertEqual(consumer_group_id, self.group_1.id)
|
|
|
|
def test_create_or_update_sale_order_assigns_user_consumer_group(self):
|
|
"""Created sale.order must use the consumer group that matches the user."""
|
|
request_mock = _build_request_mock(self.env(user=self.user))
|
|
|
|
with patch(REQUEST_PATCH_TARGET, request_mock):
|
|
sale_order = self.controller._create_or_update_sale_order(
|
|
self.group_order,
|
|
self.user,
|
|
self.sale_order_lines,
|
|
is_delivery=False,
|
|
)
|
|
|
|
self.assertEqual(sale_order.consumer_group_id.id, self.group_2.id)
|
|
|
|
|
|
class TestProcessCartItems(TransactionCase):
|
|
"""Test _process_cart_items() helper method."""
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.controller = AplicoopWebsiteSale()
|
|
|
|
# Create test products
|
|
self.product1 = self.env["product.product"].create(
|
|
{
|
|
"name": "Test Product 1",
|
|
"list_price": 15.0,
|
|
"type": "consu",
|
|
}
|
|
)
|
|
self.product2 = self.env["product.product"].create(
|
|
{
|
|
"name": "Test Product 2",
|
|
"list_price": 25.0,
|
|
"type": "consu",
|
|
}
|
|
)
|
|
self.group = self.env["res.partner"].create(
|
|
{
|
|
"name": "Cart Test Group",
|
|
"is_company": True,
|
|
"is_group": True,
|
|
}
|
|
)
|
|
|
|
# Create test group order
|
|
self.group_order = self.env["group.order"].create(
|
|
{
|
|
"name": "Test Order for Cart",
|
|
"state": "open",
|
|
"group_ids": [(6, 0, [self.group.id])],
|
|
"start_date": date.today(),
|
|
"end_date": date.today() + timedelta(days=7),
|
|
}
|
|
)
|
|
|
|
def test_process_cart_items_success(self):
|
|
"""Test successful cart item processing."""
|
|
request_mock = _build_request_mock(_env_with_lang(self.env, "es_ES"))
|
|
|
|
items = [
|
|
{
|
|
"product_id": self.product1.id,
|
|
"quantity": 2,
|
|
"product_price": 15.0,
|
|
},
|
|
{
|
|
"product_id": self.product2.id,
|
|
"quantity": 1,
|
|
"product_price": 25.0,
|
|
},
|
|
]
|
|
|
|
with patch(REQUEST_PATCH_TARGET, request_mock):
|
|
result = self.controller._process_cart_items(items, self.group_order)
|
|
|
|
self.assertEqual(len(result), 2)
|
|
self.assertEqual(result[0][0], 0) # Command (0, 0, vals)
|
|
self.assertEqual(result[0][1], 0)
|
|
self.assertIn("product_id", result[0][2])
|
|
self.assertEqual(result[0][2]["product_uom_qty"], 2)
|
|
self.assertGreater(result[0][2]["price_unit"], 0.0)
|
|
|
|
def test_process_cart_items_uses_list_price_fallback(self):
|
|
"""Test cart processing uses list_price when product_price is 0."""
|
|
request_mock = _build_request_mock(_env_with_lang(self.env, "es_ES"))
|
|
|
|
items = [
|
|
{
|
|
"product_id": self.product1.id,
|
|
"quantity": 1,
|
|
"product_price": 0, # Should fallback to list_price
|
|
}
|
|
]
|
|
|
|
with patch(REQUEST_PATCH_TARGET, request_mock):
|
|
result = self.controller._process_cart_items(items, self.group_order)
|
|
|
|
self.assertEqual(len(result), 1)
|
|
# Should produce a valid positive unit price
|
|
self.assertGreater(result[0][2]["price_unit"], 0.0)
|
|
|
|
def test_process_cart_items_skips_invalid_product(self):
|
|
"""Test cart processing skips non-existent products."""
|
|
request_mock = _build_request_mock(_env_with_lang(self.env, "es_ES"))
|
|
|
|
items = [
|
|
{
|
|
"product_id": 99999, # Non-existent
|
|
"quantity": 1,
|
|
"product_price": 10.0,
|
|
},
|
|
{
|
|
"product_id": self.product1.id,
|
|
"quantity": 2,
|
|
"product_price": 15.0,
|
|
},
|
|
]
|
|
|
|
with patch(REQUEST_PATCH_TARGET, request_mock):
|
|
result = self.controller._process_cart_items(items, self.group_order)
|
|
|
|
# Should only process the valid product
|
|
self.assertEqual(len(result), 1)
|
|
self.assertEqual(result[0][2]["product_id"], self.product1.id)
|
|
|
|
def test_process_cart_items_empty_after_filtering(self):
|
|
"""Test cart processing raises error when no valid items remain."""
|
|
request_mock = _build_request_mock(_env_with_lang(self.env, "es_ES"))
|
|
|
|
items = [{"product_id": 99999, "quantity": 1, "product_price": 10.0}]
|
|
|
|
with patch(REQUEST_PATCH_TARGET, request_mock):
|
|
with self.assertRaises(ValueError) as context:
|
|
self.controller._process_cart_items(items, self.group_order)
|
|
|
|
self.assertIn("No valid items", str(context.exception))
|
|
|
|
def test_process_cart_items_translates_product_name(self):
|
|
"""Test cart processing uses translated product names."""
|
|
request_mock = _build_request_mock(_env_with_lang(self.env, "eu_ES"))
|
|
|
|
if "ir.translation" not in self.env:
|
|
self.skipTest("ir.translation model not available in this test registry")
|
|
|
|
# Add translation for product name
|
|
self.env["ir.translation"].create(
|
|
{
|
|
"type": "model",
|
|
"name": "product.product,name",
|
|
"module": "website_sale_aplicoop",
|
|
"lang": "eu_ES",
|
|
"res_id": self.product1.id,
|
|
"src": "Test Product 1",
|
|
"value": "Proba Produktua 1",
|
|
"state": "translated",
|
|
}
|
|
)
|
|
|
|
items = [
|
|
{
|
|
"product_id": self.product1.id,
|
|
"quantity": 1,
|
|
"product_price": 15.0,
|
|
}
|
|
]
|
|
|
|
with patch(REQUEST_PATCH_TARGET, request_mock):
|
|
result = self.controller._process_cart_items(items, self.group_order)
|
|
|
|
# Product name should be in Basque context
|
|
product_name = result[0][2]["name"]
|
|
self.assertIsNotNone(product_name)
|
|
# In real test, would be "Proba Produktua 1" but translation may not work in test
|
|
|
|
|
|
class TestBuildConfirmationMessage(TransactionCase):
|
|
"""Test _build_confirmation_message() helper method."""
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.controller = AplicoopWebsiteSale()
|
|
self.user = self.env.ref("base.user_admin")
|
|
self.partner = self.env.ref("base.partner_admin")
|
|
self.group = self.env["res.partner"].create(
|
|
{
|
|
"name": "Message Test Group",
|
|
"is_company": True,
|
|
"is_group": True,
|
|
}
|
|
)
|
|
|
|
# Create test group order with dates
|
|
self.group_order = self.env["group.order"].create(
|
|
{
|
|
"name": "Test Order Messages",
|
|
"state": "open",
|
|
"group_ids": [(6, 0, [self.group.id])],
|
|
"start_date": date.today(),
|
|
"end_date": date.today() + timedelta(days=7),
|
|
"cutoff_day": "3",
|
|
"pickup_day": "5", # Saturday (0=Monday)
|
|
}
|
|
)
|
|
|
|
# Create test sale order
|
|
self.sale_order = self.env["sale.order"].create(
|
|
{
|
|
"partner_id": self.partner.id,
|
|
"group_order_id": self.group_order.id,
|
|
}
|
|
)
|
|
|
|
def _build_confirmation_result(
|
|
self, lang="es_ES", sale_order=None, group_order=None, is_delivery=False
|
|
):
|
|
request_mock = _build_request_mock(_env_with_lang(self.env, lang))
|
|
with patch(REQUEST_PATCH_TARGET, request_mock):
|
|
try:
|
|
return self.controller._build_confirmation_message(
|
|
sale_order or self.sale_order,
|
|
group_order or self.group_order,
|
|
is_delivery=is_delivery,
|
|
)
|
|
except UserError as err:
|
|
if "Invalid language code" in str(err):
|
|
self.skipTest(str(err))
|
|
raise
|
|
|
|
def test_build_confirmation_message_pickup(self):
|
|
"""Test confirmation message for pickup (not delivery)."""
|
|
result = self._build_confirmation_result(lang="es_ES", is_delivery=False)
|
|
|
|
self.assertIn("message", result)
|
|
self.assertIn("pickup_day", result)
|
|
self.assertIn("pickup_date", result)
|
|
self.assertIn("pickup_day_index", result)
|
|
self.assertIn(self.sale_order.name, result["message"])
|
|
self.assertEqual(result["pickup_day_index"], 5)
|
|
|
|
def test_build_confirmation_message_delivery(self):
|
|
"""Test confirmation message for home delivery."""
|
|
result = self._build_confirmation_result(lang="es_ES", is_delivery=True)
|
|
|
|
self.assertIn("message", result)
|
|
message = result["message"]
|
|
self.assertIsNotNone(message)
|
|
|
|
def test_build_confirmation_message_no_dates(self):
|
|
"""Test confirmation message when no pickup date is set."""
|
|
group_order_no_dates = self.env["group.order"].create(
|
|
{
|
|
"name": "Order No Dates",
|
|
"state": "open",
|
|
"group_ids": [(6, 0, [self.group.id])],
|
|
}
|
|
)
|
|
|
|
sale_order_no_dates = self.env["sale.order"].create(
|
|
{
|
|
"partner_id": self.partner.id,
|
|
"group_order_id": group_order_no_dates.id,
|
|
}
|
|
)
|
|
|
|
result = self._build_confirmation_result(
|
|
lang="es_ES",
|
|
sale_order=sale_order_no_dates,
|
|
group_order=group_order_no_dates,
|
|
is_delivery=False,
|
|
)
|
|
|
|
self.assertIn("message", result)
|
|
self.assertIn(sale_order_no_dates.name, result["message"])
|
|
self.assertEqual(result["pickup_date"], "")
|
|
|
|
def test_build_confirmation_message_formats_date(self):
|
|
"""Test confirmation message formats dates correctly (DD/MM/YYYY)."""
|
|
result = self._build_confirmation_result(lang="es_ES", is_delivery=False)
|
|
pickup_date_str = result["pickup_date"]
|
|
self.assertIsNotNone(pickup_date_str)
|
|
date_pattern = r"\d{2}/\d{2}/\d{4}"
|
|
self.assertRegex(pickup_date_str, date_pattern)
|
|
|
|
def test_build_confirmation_message_multilang_es(self):
|
|
"""Test confirmation message in Spanish (es_ES)."""
|
|
result = self._build_confirmation_result(lang="es_ES", is_delivery=False)
|
|
self.assertIsNotNone(result["message"])
|
|
|
|
def test_build_confirmation_message_multilang_eu(self):
|
|
"""Test confirmation message in Basque (eu_ES)."""
|
|
result = self._build_confirmation_result(lang="eu_ES", is_delivery=False)
|
|
self.assertIsNotNone(result["message"])
|
|
|
|
def test_build_confirmation_message_multilang_ca(self):
|
|
"""Test confirmation message in Catalan (ca_ES)."""
|
|
result = self._build_confirmation_result(lang="ca_ES", is_delivery=False)
|
|
self.assertIsNotNone(result["message"])
|
|
|
|
def test_build_confirmation_message_multilang_gl(self):
|
|
"""Test confirmation message in Galician (gl_ES)."""
|
|
result = self._build_confirmation_result(lang="gl_ES", is_delivery=False)
|
|
self.assertIsNotNone(result["message"])
|
|
|
|
def test_build_confirmation_message_multilang_pt(self):
|
|
"""Test confirmation message in Portuguese (pt_PT)."""
|
|
result = self._build_confirmation_result(lang="pt_PT", is_delivery=False)
|
|
self.assertIsNotNone(result["message"])
|
|
|
|
def test_build_confirmation_message_multilang_fr(self):
|
|
"""Test confirmation message in French (fr_FR)."""
|
|
result = self._build_confirmation_result(lang="fr_FR", is_delivery=False)
|
|
self.assertIsNotNone(result["message"])
|
|
|
|
def test_build_confirmation_message_multilang_it(self):
|
|
"""Test confirmation message in Italian (it_IT)."""
|
|
result = self._build_confirmation_result(lang="it_IT", is_delivery=False)
|
|
self.assertIsNotNone(result["message"])
|
|
|
|
|
|
class TestConfirmEskaera_Integration(TransactionCase):
|
|
"""Integration tests for confirm_eskaera() with all 3 helpers."""
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.controller = AplicoopWebsiteSale()
|
|
self.consumer_group = self.env["res.partner"].create(
|
|
{
|
|
"name": "Integration Consumer Group",
|
|
"is_company": True,
|
|
"is_group": True,
|
|
}
|
|
)
|
|
self.partner = self.env["res.partner"].create(
|
|
{
|
|
"name": "Integration Member",
|
|
"email": "integration-member@test.com",
|
|
}
|
|
)
|
|
self.consumer_group.member_ids = [(4, self.partner.id)]
|
|
self.user = self.env["res.users"].create(
|
|
{
|
|
"name": "Integration User",
|
|
"login": "integration-user@test.com",
|
|
"email": "integration-user@test.com",
|
|
"partner_id": self.partner.id,
|
|
}
|
|
)
|
|
|
|
# Create test product
|
|
self.product = self.env["product.product"].create(
|
|
{
|
|
"name": "Integration Test Product",
|
|
"list_price": 20.0,
|
|
"type": "consu",
|
|
}
|
|
)
|
|
|
|
# Delivery service product — home_delivery on group.order is computed
|
|
# from delivery_product_id, so we must set the product to enable delivery.
|
|
self.delivery_product = self.env["product.product"].create(
|
|
{
|
|
"name": "Integration Delivery Svc",
|
|
"type": "service",
|
|
"list_price": 0.0,
|
|
}
|
|
)
|
|
|
|
# Create test group order
|
|
self.group_order = self.env["group.order"].create(
|
|
{
|
|
"name": "Integration Test Order",
|
|
"state": "open",
|
|
"group_ids": [(6, 0, [self.consumer_group.id])],
|
|
"start_date": date.today(),
|
|
"end_date": date.today() + timedelta(days=7),
|
|
"cutoff_day": "3",
|
|
"pickup_day": "5",
|
|
"delivery_product_id": self.delivery_product.id,
|
|
}
|
|
)
|
|
|
|
# Use the real website so pricing helpers can resolve company/fiscal pos
|
|
self.website = self.env["website"].search([], limit=1)
|
|
|
|
def test_confirm_eskaera_full_flow_pickup(self):
|
|
"""Test full confirm_eskaera flow for pickup order."""
|
|
data = {
|
|
"order_id": self.group_order.id,
|
|
"items": [
|
|
{
|
|
"product_id": self.product.id,
|
|
"quantity": 3,
|
|
"product_price": 20.0,
|
|
}
|
|
],
|
|
"is_delivery": False,
|
|
}
|
|
mock_request = _build_request_mock(
|
|
self.env(user=self.user, context=dict(self.env.context, lang="es_ES")),
|
|
data,
|
|
website=self.website,
|
|
)
|
|
|
|
with patch(REQUEST_PATCH_TARGET, mock_request):
|
|
response = self.controller.confirm_eskaera.__wrapped__(self.controller)
|
|
|
|
# Verify response
|
|
self.assertIsNotNone(response)
|
|
response_data = json.loads(response.data.decode("utf-8"))
|
|
|
|
self.assertTrue(response_data.get("success"))
|
|
self.assertIn("message", response_data)
|
|
self.assertIn("sale_order_id", response_data)
|
|
|
|
# Verify sale.order was created
|
|
sale_order_id = response_data["sale_order_id"]
|
|
sale_order = self.env["sale.order"].browse(sale_order_id)
|
|
|
|
self.assertTrue(sale_order.exists())
|
|
self.assertEqual(sale_order.partner_id.id, self.partner.id)
|
|
self.assertEqual(sale_order.group_order_id.id, self.group_order.id)
|
|
self.assertEqual(len(sale_order.order_line), 1)
|
|
self.assertEqual(sale_order.order_line[0].product_uom_qty, 3)
|
|
self.assertFalse(sale_order.home_delivery)
|
|
self.assertEqual(
|
|
sale_order.commitment_date.date(),
|
|
self.group_order.pickup_date,
|
|
)
|
|
|
|
def test_confirm_eskaera_full_flow_delivery(self):
|
|
"""Test full confirm_eskaera flow for delivery order."""
|
|
# Add delivery_date to group order
|
|
self.group_order.delivery_date = self.group_order.pickup_date + timedelta(
|
|
days=1
|
|
)
|
|
|
|
data = {
|
|
"order_id": self.group_order.id,
|
|
"items": [
|
|
{
|
|
"product_id": self.product.id,
|
|
"quantity": 2,
|
|
"product_price": 20.0,
|
|
}
|
|
],
|
|
"is_delivery": True,
|
|
}
|
|
mock_request = _build_request_mock(
|
|
self.env(user=self.user, context=dict(self.env.context, lang="es_ES")),
|
|
data,
|
|
website=self.website,
|
|
)
|
|
|
|
with patch(REQUEST_PATCH_TARGET, mock_request):
|
|
response = self.controller.confirm_eskaera.__wrapped__(self.controller)
|
|
|
|
# Verify response
|
|
response_data = json.loads(response.data.decode("utf-8"))
|
|
|
|
self.assertTrue(response_data.get("success"))
|
|
|
|
# Verify sale.order has delivery flag
|
|
sale_order_id = response_data["sale_order_id"]
|
|
sale_order = self.env["sale.order"].browse(sale_order_id)
|
|
|
|
self.assertTrue(sale_order.home_delivery)
|
|
# commitment_date should be delivery_date
|
|
self.assertEqual(
|
|
sale_order.commitment_date.date(), self.group_order.delivery_date
|
|
)
|
|
|
|
def test_confirm_eskaera_updates_existing_draft(self):
|
|
"""Test confirm_eskaera updates existing draft order instead of creating new."""
|
|
# Create existing draft order
|
|
existing_order = self.env["sale.order"].create(
|
|
{
|
|
"partner_id": self.partner.id,
|
|
"group_order_id": self.group_order.id,
|
|
"state": "draft",
|
|
# pickup_date must match group_order.pickup_date so
|
|
# _find_recent_draft_order can locate this draft
|
|
"pickup_date": self.group_order.pickup_date,
|
|
"order_line": [
|
|
(
|
|
0,
|
|
0,
|
|
{
|
|
"product_id": self.product.id,
|
|
"product_uom_qty": 1,
|
|
"price_unit": 20.0,
|
|
},
|
|
)
|
|
],
|
|
}
|
|
)
|
|
|
|
existing_order_id = existing_order.id
|
|
|
|
data = {
|
|
"order_id": self.group_order.id,
|
|
"items": [
|
|
{
|
|
"product_id": self.product.id,
|
|
"quantity": 5, # Different quantity
|
|
"product_price": 20.0,
|
|
}
|
|
],
|
|
"is_delivery": False,
|
|
}
|
|
mock_request = _build_request_mock(
|
|
self.env(user=self.user, context=dict(self.env.context, lang="es_ES")),
|
|
data,
|
|
website=self.website,
|
|
)
|
|
|
|
with patch(REQUEST_PATCH_TARGET, mock_request):
|
|
response = self.controller.confirm_eskaera.__wrapped__(self.controller)
|
|
|
|
response_data = json.loads(response.data.decode("utf-8"))
|
|
|
|
# Should update existing order, not create new
|
|
self.assertEqual(response_data["sale_order_id"], existing_order_id)
|
|
|
|
# Verify order was updated
|
|
existing_order.invalidate_recordset()
|
|
self.assertEqual(len(existing_order.order_line), 1)
|
|
self.assertEqual(existing_order.order_line[0].product_uom_qty, 5)
|
|
|
|
def test_confirm_eskaera_ignores_old_period_draft(self):
|
|
"""Old draft from previous pickup_date must not be reused."""
|
|
old_draft = self.env["sale.order"].create(
|
|
{
|
|
"partner_id": self.partner.id,
|
|
"group_order_id": self.group_order.id,
|
|
"state": "draft",
|
|
"pickup_date": date.today() - timedelta(days=7),
|
|
"order_line": [
|
|
(
|
|
0,
|
|
0,
|
|
{
|
|
"product_id": self.product.id,
|
|
"product_uom_qty": 1,
|
|
"price_unit": 20.0,
|
|
},
|
|
)
|
|
],
|
|
}
|
|
)
|
|
|
|
data = {
|
|
"order_id": self.group_order.id,
|
|
"items": [
|
|
{
|
|
"product_id": self.product.id,
|
|
"quantity": 4,
|
|
"product_price": 20.0,
|
|
}
|
|
],
|
|
"is_delivery": False,
|
|
}
|
|
mock_request = _build_request_mock(
|
|
self.env(user=self.user, context=dict(self.env.context, lang="es_ES")),
|
|
data,
|
|
website=self.website,
|
|
)
|
|
|
|
with patch(REQUEST_PATCH_TARGET, mock_request):
|
|
response = self.controller.confirm_eskaera.__wrapped__(self.controller)
|
|
|
|
response_data = json.loads(response.data.decode("utf-8"))
|
|
|
|
self.assertTrue(response_data.get("success"))
|
|
self.assertNotEqual(response_data["sale_order_id"], old_draft.id)
|
|
|
|
old_draft.invalidate_recordset()
|
|
self.assertEqual(old_draft.state, "draft")
|
|
self.assertEqual(old_draft.order_line[0].product_uom_qty, 1)
|