diff --git a/stock_picking_batch_custom/models/__init__.py b/stock_picking_batch_custom/models/__init__.py
index c566790..24709d5 100644
--- a/stock_picking_batch_custom/models/__init__.py
+++ b/stock_picking_batch_custom/models/__init__.py
@@ -1,4 +1,2 @@
from . import stock_move_line # noqa: F401
-from . import stock_backorder_confirmation # noqa: F401
-from . import stock_picking # noqa: F401
from . import stock_picking_batch # noqa: F401
diff --git a/stock_picking_batch_custom/models/stock_backorder_confirmation.py b/stock_picking_batch_custom/models/stock_backorder_confirmation.py
deleted file mode 100644
index ab246c5..0000000
--- a/stock_picking_batch_custom/models/stock_backorder_confirmation.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright 2026 Criptomart
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-
-from odoo import models
-
-
-class StockBackorderConfirmation(models.TransientModel):
- _inherit = "stock.backorder.confirmation"
-
- def _check_batch_summary_lines(self):
- pickings = self.env["stock.picking"].browse(
- self.env.context.get("button_validate_picking_ids", [])
- )
- for batch in pickings.batch_id:
- batch._check_all_products_collected()
-
- def process(self):
- self._check_batch_summary_lines()
- return super().process()
-
- def process_cancel_backorder(self):
- self._check_batch_summary_lines()
- return super().process_cancel_backorder()
diff --git a/stock_picking_batch_custom/models/stock_move_line.py b/stock_picking_batch_custom/models/stock_move_line.py
index a388884..82bd6d2 100644
--- a/stock_picking_batch_custom/models/stock_move_line.py
+++ b/stock_picking_batch_custom/models/stock_move_line.py
@@ -1,7 +1,6 @@
# Copyright 2026 Criptomart
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from odoo import api
from odoo import fields
from odoo import models
@@ -22,18 +21,3 @@ class StockMoveLine(models.Model):
default=False,
copy=False,
)
-
- consumer_group_id = fields.Many2one(
- comodel_name="res.partner",
- compute="_compute_consumer_group_id",
- readonly=True,
- )
-
- @api.depends("picking_id")
- def _compute_consumer_group_id(self):
- for line in self:
- picking = line.picking_id
- if picking:
- line.consumer_group_id = picking.batch_consumer_group_id
- else:
- line.consumer_group_id = False
diff --git a/stock_picking_batch_custom/models/stock_picking.py b/stock_picking_batch_custom/models/stock_picking.py
deleted file mode 100644
index f4dc0a2..0000000
--- a/stock_picking_batch_custom/models/stock_picking.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright 2026 Criptomart
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-
-from odoo import fields
-from odoo import models
-
-
-class StockPicking(models.Model):
- _inherit = "stock.picking"
-
- batch_consumer_group_id = fields.Many2one(
- comodel_name="res.partner",
- compute="_compute_batch_consumer_group_id",
- readonly=True,
- string="Batch Consumer Group",
- )
-
- def _compute_batch_consumer_group_id(self):
- for picking in self:
- sale = picking.sale_id if "sale_id" in picking._fields else False
- if sale and "consumer_group_id" in sale._fields:
- picking.batch_consumer_group_id = sale.consumer_group_id
- else:
- picking.batch_consumer_group_id = False
-
- def _pre_action_done_hook(self):
- """Run collected checks only after Odoo resolves backorders.
-
- This keeps the product summary checkbox informative during the initial
- batch validation click, but still enforces it once the user confirms the
- actual picking flow that will be processed.
- """
-
- res = super()._pre_action_done_hook()
- if res is not True:
- return res
-
- self._check_batch_collected_products()
- return res
-
- def _check_batch_collected_products(self):
- for batch in self.batch_id:
- batch_pickings = self.filtered(
- lambda picking, batch=batch: picking.batch_id == batch
- )
- processed_product_ids = (
- batch_pickings.move_line_ids.filtered(
- lambda line: line.move_id.state not in ("cancel", "done")
- and line.product_id
- and line.quantity
- )
- .mapped("product_id")
- .ids
- )
- if not processed_product_ids:
- continue
- batch._check_all_products_collected(processed_product_ids)
diff --git a/stock_picking_batch_custom/models/stock_picking_batch.py b/stock_picking_batch_custom/models/stock_picking_batch.py
index 918c92b..798678c 100644
--- a/stock_picking_batch_custom/models/stock_picking_batch.py
+++ b/stock_picking_batch_custom/models/stock_picking_batch.py
@@ -140,20 +140,11 @@ class StockPickingBatch(models.Model):
else:
batch.summary_line_ids = [fields.Command.clear()]
- def _check_all_products_collected(self, product_ids=None):
- """Ensure collected is checked for processed products only.
-
- The product summary remains informative until Odoo knows which products
- are actually being validated after the backorder decision.
- """
-
+ def _check_all_products_collected(self):
+ """Ensure all product summary lines are marked as collected before done."""
for batch in self:
not_collected_lines = batch.summary_line_ids.filtered(
- lambda line: (
- line.qty_done > 0
- and not line.is_collected
- and (not product_ids or line.product_id.id in product_ids)
- )
+ lambda line: not line.is_collected
)
if not not_collected_lines:
continue
@@ -170,6 +161,10 @@ class StockPickingBatch(models.Model):
)
raise UserError(message)
+ def action_done(self):
+ self._check_all_products_collected()
+ return super().action_done()
+
class StockPickingBatchSummaryLine(models.Model):
_name = "stock.picking.batch.summary.line"
diff --git a/stock_picking_batch_custom/tests/test_batch_summary.py b/stock_picking_batch_custom/tests/test_batch_summary.py
index 2d96b48..07f2518 100644
--- a/stock_picking_batch_custom/tests/test_batch_summary.py
+++ b/stock_picking_batch_custom/tests/test_batch_summary.py
@@ -1,11 +1,9 @@
# Copyright 2026 Criptomart
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-from unittest.mock import patch
-
from odoo.exceptions import UserError
-from odoo.tests import TransactionCase
from odoo.tests import tagged
+from odoo.tests.common import TransactionCase
@tagged("-at_install", "post_install")
@@ -26,13 +24,6 @@ class TestBatchSummary(TransactionCase):
"uom_po_id": cls.uom_unit.id,
}
)
- cls.product_2 = cls.env["product.product"].create(
- {
- "name": "Test Product 2",
- "uom_id": cls.uom_unit.id,
- "uom_po_id": cls.uom_unit.id,
- }
- )
cls.batch = cls.env["stock.picking.batch"].create(
{"name": "Batch Test", "picking_type_id": cls.picking_type.id}
@@ -122,60 +113,6 @@ class TestBatchSummary(TransactionCase):
picking.batch_id = batch.id
return batch
- def _create_partial_batch(self, qty_demanded=2.0, qty_done=1.0, extra_move=False):
- batch = self.env["stock.picking.batch"].create(
- {"name": "Batch Partial", "picking_type_id": self.picking_type.id}
- )
- batch.picking_type_id.create_backorder = "ask"
- picking = self.env["stock.picking"].create(
- {
- "picking_type_id": self.picking_type.id,
- "location_id": self.location_src.id,
- "location_dest_id": self.location_dest.id,
- "batch_id": batch.id,
- }
- )
-
- move = self.env["stock.move"].create(
- {
- "name": self.product.name,
- "product_id": self.product.id,
- "product_uom_qty": qty_demanded,
- "product_uom": self.uom_unit.id,
- "picking_id": picking.id,
- "location_id": self.location_src.id,
- "location_dest_id": self.location_dest.id,
- }
- )
- self.env["stock.move.line"].create(
- {
- "move_id": move.id,
- "picking_id": picking.id,
- "product_id": self.product.id,
- "product_uom_id": self.uom_unit.id,
- "location_id": self.location_src.id,
- "location_dest_id": self.location_dest.id,
- "quantity": qty_done,
- }
- )
-
- if extra_move:
- self.env["stock.move"].create(
- {
- "name": self.product_2.name,
- "product_id": self.product_2.id,
- "product_uom_qty": 1.0,
- "product_uom": self.uom_unit.id,
- "picking_id": picking.id,
- "location_id": self.location_src.id,
- "location_dest_id": self.location_dest.id,
- }
- )
-
- batch.action_confirm()
- batch._compute_summary_line_ids()
- return batch
-
# Tests
def test_totals_and_pending_with_conversion(self):
"""Totals aggregate per product with UoM conversion and pending."""
@@ -253,6 +190,14 @@ class TestBatchSummary(TransactionCase):
self.assertFalse(batch.summary_line_ids)
# 2. Create pickings with moves (without batch assignment)
+ product2 = self.env["product.product"].create(
+ {
+ "name": "Test Product 2",
+ "uom_id": self.uom_unit.id,
+ "uom_po_id": self.uom_unit.id,
+ }
+ )
+
picking_a = self.env["stock.picking"].create(
{
"picking_type_id": self.picking_type.id,
@@ -281,8 +226,8 @@ class TestBatchSummary(TransactionCase):
)
self.env["stock.move"].create(
{
- "name": self.product_2.name,
- "product_id": self.product_2.id,
+ "name": product2.name,
+ "product_id": product2.id,
"product_uom_qty": 10.0,
"product_uom": self.uom_unit.id,
"picking_id": picking_b.id,
@@ -331,7 +276,7 @@ class TestBatchSummary(TransactionCase):
# Two products expected
products_in_summary = batch.summary_line_ids.mapped("product_id")
self.assertIn(self.product, products_in_summary)
- self.assertIn(self.product_2, products_in_summary)
+ self.assertIn(product2, products_in_summary)
# Check aggregated quantities
line_product1 = batch.summary_line_ids.filtered(
@@ -340,12 +285,12 @@ class TestBatchSummary(TransactionCase):
self.assertAlmostEqual(line_product1.qty_demanded, 8.0) # 5 + 3
line_product2 = batch.summary_line_ids.filtered(
- lambda line: line.product_id == self.product_2
+ lambda line: line.product_id == product2
)
self.assertAlmostEqual(line_product2.qty_demanded, 10.0)
- def test_done_requires_all_summary_lines_collected_without_backorder(self):
- """Full validation still blocks if processed products are unchecked."""
+ def test_done_requires_all_summary_lines_collected(self):
+ """Batch validation must fail if there are unchecked collected lines."""
batch = self._create_batch_with_pickings()
batch.action_confirm()
@@ -357,32 +302,6 @@ class TestBatchSummary(TransactionCase):
with self.assertRaises(UserError):
batch.action_done()
- def test_partial_validation_waits_for_backorder_wizard_before_blocking(self):
- """Partial batch validation must open the backorder wizard first."""
-
- batch = self._create_partial_batch()
-
- with patch.object(
- type(self.env["stock.picking"]),
- "_check_backorder",
- autospec=True,
- return_value=batch.picking_ids,
- ):
- action = batch.action_done()
-
- self.assertIsInstance(action, dict)
- self.assertEqual(action.get("res_model"), "stock.backorder.confirmation")
-
- def test_partial_validation_checks_only_processed_products(self):
- """Unchecked lines with no processed quantity must remain informative."""
-
- batch = self._create_partial_batch(extra_move=True)
-
- with self.assertRaisesRegex(UserError, self.product.display_name) as err:
- batch._check_all_products_collected([self.product.id])
-
- self.assertNotIn(self.product_2.display_name, str(err.exception))
-
def test_check_all_products_collected_passes_when_all_checked(self):
"""Collected validation helper must pass when all lines are checked."""
diff --git a/stock_picking_batch_custom/views/stock_move_line_views.xml b/stock_picking_batch_custom/views/stock_move_line_views.xml
index 7fbc218..b857a2c 100644
--- a/stock_picking_batch_custom/views/stock_move_line_views.xml
+++ b/stock_picking_batch_custom/views/stock_move_line_views.xml
@@ -5,27 +5,11 @@
stock.move.line
-
- hide
-
-
- hide
-
-
- hide
-
-
- hide
-
-
- hide
-
-
diff --git a/stock_picking_batch_custom/views/stock_picking_batch_views.xml b/stock_picking_batch_custom/views/stock_picking_batch_views.xml
index 0c6b75e..43b3ae2 100644
--- a/stock_picking_batch_custom/views/stock_picking_batch_views.xml
+++ b/stock_picking_batch_custom/views/stock_picking_batch_views.xml
@@ -28,15 +28,4 @@
-
-
- stock.picking.batch.picking.tree.consumer.group
- stock.picking
-
-
-
-
-
-
-
diff --git a/website_sale_aplicoop/controllers/website_sale.py b/website_sale_aplicoop/controllers/website_sale.py
index ebdf223..e7c1503 100644
--- a/website_sale_aplicoop/controllers/website_sale.py
+++ b/website_sale_aplicoop/controllers/website_sale.py
@@ -768,8 +768,6 @@ class AplicoopWebsiteSale(WebsiteSale):
if not current_user.partner_id:
raise ValueError("User has no associated partner") from None
- self._validate_user_group_access(group_order, current_user)
-
# Validate items
items = data.get("items", [])
if not items:
@@ -822,8 +820,6 @@ class AplicoopWebsiteSale(WebsiteSale):
if not current_user.partner_id:
raise ValueError("User has no associated partner")
- self._validate_user_group_access(group_order, current_user)
-
# Validate items
items = data.get("items", [])
if not items:
@@ -891,8 +887,6 @@ class AplicoopWebsiteSale(WebsiteSale):
if not current_user.partner_id:
raise ValueError("User has no associated partner")
- self._validate_user_group_access(group_order, current_user)
-
# Validate items
items = data.get("items", [])
if not items:
@@ -927,29 +921,6 @@ class AplicoopWebsiteSale(WebsiteSale):
return False
return bool(value)
- def _get_effective_delivery_context(self, group_order, is_delivery):
- """Return effective home delivery flag and commitment date.
-
- Delivery is effective only when requested by the user and enabled
- in the group order configuration.
- """
- delivery_requested = self._to_bool(is_delivery)
- delivery_enabled = bool(group_order and group_order.home_delivery)
- effective_home_delivery = delivery_requested and delivery_enabled
-
- if delivery_requested and not delivery_enabled:
- _logger.info(
- "Delivery requested but disabled in group order %s; using pickup flow",
- group_order.id if group_order else "N/A",
- )
-
- if effective_home_delivery:
- commitment_date = group_order.delivery_date or group_order.pickup_date
- else:
- commitment_date = group_order.pickup_date if group_order else False
-
- return effective_home_delivery, commitment_date
-
def _process_cart_items(self, items, group_order, pricelist=None):
"""Process cart items and build sale.order line data.
@@ -1038,35 +1009,6 @@ class AplicoopWebsiteSale(WebsiteSale):
# No salesperson found
return False
- def _get_consumer_group_for_user(self, group_order, current_user):
- """Return the matching consumer group for the user in this group order."""
- partner = current_user.partner_id
- if not partner or not group_order:
- return False
-
- user_group_ids = set(partner.group_ids.ids)
- for consumer_group in group_order.group_ids:
- if consumer_group.id in user_group_ids:
- return consumer_group.id
-
- _logger.warning(
- "_get_consumer_group_for_user: User %s (%s) is not member of any "
- "consumer group in order %s. user_groups=%s, order_groups=%s",
- current_user.name,
- current_user.id,
- group_order.id,
- sorted(user_group_ids),
- group_order.group_ids.ids,
- )
- return False
-
- def _validate_user_group_access(self, group_order, current_user):
- """Ensure the user belongs to at least one consumer group of the order."""
- consumer_group_id = self._get_consumer_group_for_user(group_order, current_user)
- if not consumer_group_id:
- raise ValueError("User is not a member of any consumer group in this order")
- return consumer_group_id
-
def _create_or_update_sale_order(
self,
group_order,
@@ -1080,7 +1022,11 @@ class AplicoopWebsiteSale(WebsiteSale):
Returns the sale.order record.
"""
- consumer_group_id = self._validate_user_group_access(group_order, current_user)
+ # consumer_group_id comes directly from group_order (first/only group)
+ # No calculations - data propagates directly from the order context
+ consumer_group_id = (
+ group_order.group_ids[0].id if group_order.group_ids else False
+ )
_logger.info(
"[CONSUMER_GROUP DEBUG] _create_or_update_sale_order: "
@@ -1090,25 +1036,22 @@ class AplicoopWebsiteSale(WebsiteSale):
consumer_group_id,
)
- effective_home_delivery, calculated_commitment_date = (
- self._get_effective_delivery_context(group_order, is_delivery)
- )
-
- # Explicit commitment_date wins; otherwise use calculated value
+ # commitment_date: use provided or calculate from group_order
if not commitment_date:
- commitment_date = calculated_commitment_date
+ commitment_date = (
+ group_order.delivery_date if is_delivery else group_order.pickup_date
+ )
if existing_order:
# Update existing order with new lines and propagate fields
# Use sudo() to avoid permission issues with portal users
existing_order_sudo = existing_order.sudo()
- # (5,) clears all existing lines before adding the new ones
- existing_order_sudo.order_line = [(5,)] + sale_order_lines
+ existing_order_sudo.order_line = sale_order_lines
if not existing_order_sudo.group_order_id:
existing_order_sudo.group_order_id = group_order.id
existing_order_sudo.pickup_day = group_order.pickup_day
existing_order_sudo.pickup_date = group_order.pickup_date
- existing_order_sudo.home_delivery = effective_home_delivery
+ existing_order_sudo.home_delivery = is_delivery
existing_order_sudo.consumer_group_id = consumer_group_id
if commitment_date:
existing_order_sudo.commitment_date = commitment_date
@@ -1116,7 +1059,7 @@ class AplicoopWebsiteSale(WebsiteSale):
"Updated existing sale.order %d: commitment_date=%s, home_delivery=%s, consumer_group_id=%s",
existing_order.id,
commitment_date,
- effective_home_delivery,
+ is_delivery,
consumer_group_id,
)
return existing_order
@@ -1128,7 +1071,7 @@ class AplicoopWebsiteSale(WebsiteSale):
"group_order_id": group_order.id,
"pickup_day": group_order.pickup_day,
"pickup_date": group_order.pickup_date,
- "home_delivery": effective_home_delivery,
+ "home_delivery": is_delivery,
"consumer_group_id": consumer_group_id,
}
if commitment_date:
@@ -1151,25 +1094,23 @@ class AplicoopWebsiteSale(WebsiteSale):
sale_order.id,
group_order.id,
group_order.pickup_day,
- effective_home_delivery,
+ group_order.home_delivery,
consumer_group_id,
)
return sale_order
def _create_draft_sale_order(
- self,
- group_order,
- current_user,
- sale_order_lines,
- order_id,
- pickup_date=None,
- is_delivery=False,
+ self, group_order, current_user, sale_order_lines, order_id, pickup_date=None
):
"""Create a draft sale.order from prepared lines and propagate group fields.
Returns created sale.order record.
"""
- consumer_group_id = self._validate_user_group_access(group_order, current_user)
+ # consumer_group_id comes directly from group_order (first/only group)
+ # No calculations - data propagates directly from the order context
+ consumer_group_id = (
+ group_order.group_ids[0].id if group_order.group_ids else False
+ )
_logger.info(
"[CONSUMER_GROUP DEBUG] _create_draft_sale_order: "
@@ -1179,8 +1120,11 @@ class AplicoopWebsiteSale(WebsiteSale):
consumer_group_id,
)
- effective_home_delivery, commitment_date = self._get_effective_delivery_context(
- group_order, is_delivery
+ # commitment_date comes from group_order (delivery_date or pickup_date)
+ commitment_date = (
+ group_order.delivery_date
+ if group_order.home_delivery
+ else group_order.pickup_date
)
order_vals = {
@@ -1190,7 +1134,7 @@ class AplicoopWebsiteSale(WebsiteSale):
"group_order_id": order_id,
"pickup_day": group_order.pickup_day,
"pickup_date": group_order.pickup_date,
- "home_delivery": effective_home_delivery,
+ "home_delivery": group_order.home_delivery,
"consumer_group_id": consumer_group_id,
"commitment_date": commitment_date,
}
@@ -1430,7 +1374,11 @@ class AplicoopWebsiteSale(WebsiteSale):
All fields (commitment_date, consumer_group_id, etc.) come from group_order.
"""
- consumer_group_id = self._validate_user_group_access(group_order, current_user)
+ # consumer_group_id comes directly from group_order (first/only group)
+ # No calculations - data propagates directly from the order context
+ consumer_group_id = (
+ group_order.group_ids[0].id if group_order.group_ids else False
+ )
_logger.info(
"[CONSUMER_GROUP DEBUG] _merge_or_replace_draft: "
@@ -1440,8 +1388,9 @@ class AplicoopWebsiteSale(WebsiteSale):
consumer_group_id,
)
- effective_home_delivery, commitment_date = self._get_effective_delivery_context(
- group_order, is_delivery
+ # commitment_date comes directly from selected flow
+ commitment_date = (
+ group_order.delivery_date if is_delivery else group_order.pickup_date
)
if existing_drafts:
@@ -1457,7 +1406,7 @@ class AplicoopWebsiteSale(WebsiteSale):
"group_order_id": order_id,
"pickup_day": group_order.pickup_day,
"pickup_date": group_order.pickup_date,
- "home_delivery": effective_home_delivery,
+ "home_delivery": is_delivery,
"consumer_group_id": consumer_group_id,
"commitment_date": commitment_date,
}
@@ -1471,7 +1420,7 @@ class AplicoopWebsiteSale(WebsiteSale):
"group_order_id": order_id,
"pickup_day": group_order.pickup_day,
"pickup_date": group_order.pickup_date,
- "home_delivery": effective_home_delivery,
+ "home_delivery": is_delivery,
"consumer_group_id": consumer_group_id,
"commitment_date": commitment_date,
}
@@ -2291,18 +2240,8 @@ class AplicoopWebsiteSale(WebsiteSale):
status=400,
)
- try:
- self._validate_user_group_access(group_order, current_user)
- except ValueError as e:
- return request.make_response(
- json.dumps({"error": str(e)}),
- [("Content-Type", "application/json")],
- status=403,
- )
-
items = data.get("items", [])
pickup_date = data.get("pickup_date")
- is_delivery = self._to_bool(data.get("is_delivery", False))
if not items:
return request.make_response(
json.dumps({"error": "No items in cart"}),
@@ -2323,12 +2262,7 @@ class AplicoopWebsiteSale(WebsiteSale):
)
sale_order = self._create_draft_sale_order(
- group_order,
- current_user,
- sale_order_lines,
- order_id,
- pickup_date,
- is_delivery=is_delivery,
+ group_order, current_user, sale_order_lines, order_id, pickup_date
)
_logger.info(
@@ -2695,15 +2629,6 @@ class AplicoopWebsiteSale(WebsiteSale):
status=400,
)
- try:
- self._validate_user_group_access(group_order, current_user)
- except ValueError as e:
- return request.make_response(
- json.dumps({"error": str(e)}),
- [("Content-Type", "application/json")],
- status=403,
- )
-
# Get cart items
items = data.get("items", [])
is_delivery = self._to_bool(data.get("is_delivery", False))
@@ -2866,23 +2791,27 @@ class AplicoopWebsiteSale(WebsiteSale):
)
existing_order = None
- effective_home_delivery, commitment_date = (
- self._get_effective_delivery_context(group_order, is_delivery)
- )
+ # Get pickup date and delivery info from group order
+ # If delivery, use delivery_date; otherwise use pickup_date
+ commitment_date = None
+ if is_delivery and group_order.delivery_date:
+ commitment_date = group_order.delivery_date
+ elif group_order.pickup_date:
+ commitment_date = group_order.pickup_date
# Create or update sale.order using helper
sale_order = self._create_or_update_sale_order(
group_order,
current_user,
sale_order_lines,
- effective_home_delivery,
+ is_delivery,
commitment_date=commitment_date,
existing_order=existing_order,
)
# Build confirmation message using helper
message_data = self._build_confirmation_message(
- sale_order, group_order, effective_home_delivery
+ sale_order, group_order, is_delivery
)
message = message_data["message"]
pickup_day_name = message_data["pickup_day"]
diff --git a/website_sale_aplicoop/models/group_order.py b/website_sale_aplicoop/models/group_order.py
index e0f4250..20b51bd 100644
--- a/website_sale_aplicoop/models/group_order.py
+++ b/website_sale_aplicoop/models/group_order.py
@@ -799,45 +799,9 @@ class GroupOrder(models.Model):
self.delivery_date,
)
- # Confirm each order in an isolated savepoint so one bad product
- # route configuration doesn't block all remaining orders.
- confirmed_sale_orders = SaleOrder.browse()
- failed_sale_orders = SaleOrder.browse()
-
- for sale_order in sale_orders:
- try:
- with self.env.cr.savepoint():
- # Do not block sales confirmation due to procurement
- # route issues. This cron confirms business orders first
- # and handles stock exceptions operationally.
- sale_order.with_context(from_orderpoint=True).action_confirm()
- confirmed_sale_orders |= sale_order
- except Exception:
- failed_sale_orders |= sale_order
- _logger.exception(
- "Cron: Error confirming sale order %s (%s) for group order %s (%s). "
- "Order skipped; remaining orders will continue.",
- sale_order.id,
- sale_order.name,
- self.id,
- self.name,
- )
-
- if confirmed_sale_orders:
- # Create picking batches only for confirmed sale orders
- self._create_picking_batches_for_sale_orders(confirmed_sale_orders)
- self._log_missing_procurement_warnings(confirmed_sale_orders)
-
- if failed_sale_orders:
- _logger.warning(
- "Cron: %d/%d sale orders failed during confirmation for group order %s (%s). "
- "failed_sale_order_ids=%s",
- len(failed_sale_orders),
- len(sale_orders),
- self.id,
- self.name,
- failed_sale_orders.ids,
- )
+ sale_orders.action_confirm()
+ # Create picking batches after confirmation
+ self._create_picking_batches_for_sale_orders(sale_orders)
except Exception:
_logger.exception(
"Cron: Error confirming sale orders for group order %s (%s)",
@@ -845,42 +809,6 @@ class GroupOrder(models.Model):
self.name,
)
- def _log_missing_procurement_warnings(self, sale_orders):
- """Log warnings for confirmed orders with stockable lines lacking moves.
-
- This helps operations detect products with missing route/procurement
- configuration while still allowing sale order confirmation.
- """
- self.ensure_one()
-
- for sale_order in sale_orders:
- problematic_lines = sale_order.order_line.filtered(
- lambda line: line.product_id
- and getattr(line.product_id, "is_storable", False)
- and line.product_uom_qty > 0
- and not line.move_ids
- )
-
- if not problematic_lines:
- continue
-
- product_labels = []
- for line in problematic_lines:
- code = line.product_id.default_code or "NO-CODE"
- name = line.product_id.display_name or line.product_id.name
- product_labels.append(f"[{code}] {name}")
-
- _logger.warning(
- "Cron: Sale order %s (%s) confirmed but %d stockable lines have no stock moves. "
- "Likely missing replenishment routes/rules. group_order=%s (%s), products=%s",
- sale_order.id,
- sale_order.name,
- len(problematic_lines),
- self.id,
- self.name,
- product_labels,
- )
-
def _create_picking_batches_for_sale_orders(self, sale_orders):
"""Create stock.picking.batch grouped by consumer_group_id.
diff --git a/website_sale_aplicoop/tests/__init__.py b/website_sale_aplicoop/tests/__init__.py
index d423f33..2effc65 100644
--- a/website_sale_aplicoop/tests/__init__.py
+++ b/website_sale_aplicoop/tests/__init__.py
@@ -10,7 +10,6 @@ from . import test_record_rules # noqa: F401
from . import test_multi_company # noqa: F401
from . import test_save_order_endpoints # noqa: F401
from . import test_date_calculations # noqa: F401
-from . import test_phase3_confirm_eskaera # noqa: F401
from . import test_pricing_with_pricelist # noqa: F401
from . import test_portal_sale_order_creation # noqa: F401
from . import test_cron_picking_batch # noqa: F401
diff --git a/website_sale_aplicoop/tests/test_cron_picking_batch.py b/website_sale_aplicoop/tests/test_cron_picking_batch.py
index ae04036..d408f8f 100644
--- a/website_sale_aplicoop/tests/test_cron_picking_batch.py
+++ b/website_sale_aplicoop/tests/test_cron_picking_batch.py
@@ -2,10 +2,8 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from datetime import timedelta
-from unittest.mock import patch
from odoo import fields
-from odoo.exceptions import UserError
from odoo.tests.common import TransactionCase
from odoo.tests.common import tagged
@@ -404,54 +402,3 @@ class TestCronPickingBatch(TransactionCase):
"draft",
"Sale order should remain draft - group order is closed",
)
-
- def test_cron_confirm_ignores_procurement_usererror(self):
- """Procurement UserError must not block confirmation in cron flow."""
- group_order = self._create_group_order(cutoff_in_past=True)
-
- so_ok = self._create_sale_order(
- group_order, self.member_1, self.consumer_group_1
- )
- so_fail = self._create_sale_order(
- group_order,
- self.member_2,
- self.consumer_group_2,
- )
-
- so_ok.write({"name": "SO-OK"})
- so_fail.write({"name": "SO-FAIL"})
-
- SaleOrderClass = type(self.env["sale.order"])
- original_action_confirm = SaleOrderClass.action_confirm
-
- def _patched_action_confirm(recordset):
- should_fail = any(so.name == "SO-FAIL" for so in recordset)
- if should_fail and not recordset.env.context.get("from_orderpoint"):
- raise UserError("Simulated stock route error")
- return original_action_confirm(recordset)
-
- with patch.object(SaleOrderClass, "action_confirm", _patched_action_confirm):
- group_order._confirm_linked_sale_orders()
-
- so_ok.invalidate_recordset()
- so_fail.invalidate_recordset()
-
- self.assertEqual(
- so_ok.state,
- "sale",
- "The valid order must still be confirmed",
- )
- self.assertTrue(
- so_ok.picking_ids,
- "The valid order should have pickings created",
- )
- self.assertTrue(
- so_ok.picking_ids[0].batch_id,
- "Pickings from valid orders should be batched",
- )
-
- self.assertEqual(
- so_fail.state,
- "sale",
- "The order should be confirmed when cron uses non-blocking procurement context",
- )
diff --git a/website_sale_aplicoop/tests/test_phase3_confirm_eskaera.py b/website_sale_aplicoop/tests/test_phase3_confirm_eskaera.py
index f183c0a..3562c90 100644
--- a/website_sale_aplicoop/tests/test_phase3_confirm_eskaera.py
+++ b/website_sale_aplicoop/tests/test_phase3_confirm_eskaera.py
@@ -20,108 +20,37 @@ Includes tests for:
import json
from datetime import date
from datetime import timedelta
-from types import SimpleNamespace
+from unittest.mock import Mock
from unittest.mock import patch
+from odoo import http
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 _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:
- website = SimpleNamespace(
- _get_current_pricelist=lambda: False,
- 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,
- }
- )
+ self.controller = http.request.env["website.sale"].browse([])
+ self.user = self.env.ref("base.user_admin")
+ self.partner = self.env.ref("base.partner_admin")
# 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),
+ "collection_date": date.today() + timedelta(days=3),
"cutoff_day": "3", # Thursday
"pickup_day": "5", # Saturday
}
)
- def test_validate_confirm_json_success(self):
+ @patch("odoo.http.request")
+ def test_validate_confirm_json_success(self, mock_request):
"""Test successful validation of confirm JSON data."""
- request_mock = _build_request_mock(self.env(user=self.user))
+ mock_request.env = self.env.with_user(self.user)
data = {
"order_id": self.group_order.id,
@@ -129,10 +58,9 @@ class TestValidateConfirmJson(TransactionCase):
"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)
- )
+ 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)
@@ -140,51 +68,52 @@ class TestValidateConfirmJson(TransactionCase):
self.assertEqual(len(items), 1)
self.assertFalse(is_delivery)
- def test_validate_confirm_json_missing_order_id(self):
+ @patch("odoo.http.request")
+ def test_validate_confirm_json_missing_order_id(self, mock_request):
"""Test validation fails without order_id."""
- request_mock = _build_request_mock(self.env(user=self.user))
+ mock_request.env = self.env.with_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)
+ with self.assertRaises(ValueError) as context:
+ self.controller._validate_confirm_json(data)
- self.assertIn("order_id is required", str(context.exception))
+ self.assertIn("Missing order_id", str(context.exception))
- def test_validate_confirm_json_order_not_exists(self):
+ @patch("odoo.http.request")
+ def test_validate_confirm_json_order_not_exists(self, mock_request):
"""Test validation fails with non-existent order."""
- request_mock = _build_request_mock(self.env(user=self.user))
+ mock_request.env = self.env.with_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)
+ 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):
+ @patch("odoo.http.request")
+ def test_validate_confirm_json_no_items(self, mock_request):
"""Test validation fails without items in cart."""
- request_mock = _build_request_mock(self.env(user=self.user))
+ mock_request.env = self.env.with_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)
+ 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):
+ @patch("odoo.http.request")
+ def test_validate_confirm_json_with_delivery_flag(self, mock_request):
"""Test validation correctly handles is_delivery flag."""
- request_mock = _build_request_mock(self.env(user=self.user))
+ mock_request.env = self.env.with_user(self.user)
data = {
"order_id": self.group_order.id,
@@ -192,165 +121,17 @@ class TestValidateConfirmJson(TransactionCase):
"is_delivery": True,
}
- with patch(REQUEST_PATCH_TARGET, request_mock):
- _, _, _, _, is_delivery = self.controller._validate_confirm_json(data)
+ _, _, _, _, 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()
+ self.controller = http.request.env["website.sale"].browse([])
# Create test products
self.product1 = self.env["product.product"].create(
@@ -367,28 +148,20 @@ class TestProcessCartItems(TransactionCase):
"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):
+ @patch("odoo.http.request")
+ def test_process_cart_items_success(self, mock_request):
"""Test successful cart item processing."""
- request_mock = _build_request_mock(self.env.with_context(lang="es_ES"))
+ mock_request.env = self.env
+ mock_request.env.lang = "es_ES"
items = [
{
@@ -403,8 +176,7 @@ class TestProcessCartItems(TransactionCase):
},
]
- with patch(REQUEST_PATCH_TARGET, request_mock):
- result = self.controller._process_cart_items(items, self.group_order)
+ 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)
@@ -413,9 +185,11 @@ class TestProcessCartItems(TransactionCase):
self.assertEqual(result[0][2]["product_uom_qty"], 2)
self.assertEqual(result[0][2]["price_unit"], 15.0)
- def test_process_cart_items_uses_list_price_fallback(self):
+ @patch("odoo.http.request")
+ def test_process_cart_items_uses_list_price_fallback(self, mock_request):
"""Test cart processing uses list_price when product_price is 0."""
- request_mock = _build_request_mock(self.env.with_context(lang="es_ES"))
+ mock_request.env = self.env
+ mock_request.env.lang = "es_ES"
items = [
{
@@ -425,16 +199,17 @@ class TestProcessCartItems(TransactionCase):
}
]
- with patch(REQUEST_PATCH_TARGET, request_mock):
- result = self.controller._process_cart_items(items, self.group_order)
+ result = self.controller._process_cart_items(items, self.group_order)
self.assertEqual(len(result), 1)
# Should use product.list_price as fallback
self.assertEqual(result[0][2]["price_unit"], self.product1.list_price)
- def test_process_cart_items_skips_invalid_product(self):
+ @patch("odoo.http.request")
+ def test_process_cart_items_skips_invalid_product(self, mock_request):
"""Test cart processing skips non-existent products."""
- request_mock = _build_request_mock(self.env.with_context(lang="es_ES"))
+ mock_request.env = self.env
+ mock_request.env.lang = "es_ES"
items = [
{
@@ -449,28 +224,30 @@ class TestProcessCartItems(TransactionCase):
},
]
- with patch(REQUEST_PATCH_TARGET, request_mock):
- result = self.controller._process_cart_items(items, self.group_order)
+ 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):
+ @patch("odoo.http.request")
+ def test_process_cart_items_empty_after_filtering(self, mock_request):
"""Test cart processing raises error when no valid items remain."""
- request_mock = _build_request_mock(self.env.with_context(lang="es_ES"))
+ mock_request.env = self.env
+ mock_request.env.lang = "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)
+ 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):
+ @patch("odoo.http.request")
+ def test_process_cart_items_translates_product_name(self, mock_request):
"""Test cart processing uses translated product names."""
- request_mock = _build_request_mock(self.env.with_context(lang="eu_ES"))
+ mock_request.env = self.env
+ mock_request.env.lang = "eu_ES" # Basque
# Add translation for product name
self.env["ir.translation"].create(
@@ -494,8 +271,7 @@ class TestProcessCartItems(TransactionCase):
}
]
- with patch(REQUEST_PATCH_TARGET, request_mock):
- result = self.controller._process_cart_items(items, self.group_order)
+ result = self.controller._process_cart_items(items, self.group_order)
# Product name should be in Basque context
product_name = result[0][2]["name"]
@@ -508,27 +284,21 @@ class TestBuildConfirmationMessage(TransactionCase):
def setUp(self):
super().setUp()
- self.controller = AplicoopWebsiteSale()
+ self.controller = http.request.env["website.sale"].browse([])
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
+ pickup_date = date.today() + timedelta(days=5)
+ delivery_date = pickup_date + timedelta(days=1)
+
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)
+ "pickup_date": pickup_date,
+ "delivery_date": delivery_date,
}
)
@@ -540,7 +310,7 @@ class TestBuildConfirmationMessage(TransactionCase):
}
)
- @patch(REQUEST_PATCH_TARGET)
+ @patch("odoo.http.request")
def test_build_confirmation_message_pickup(self, mock_request):
"""Test confirmation message for pickup (not delivery)."""
mock_request.env = self.env.with_context(lang="es_ES")
@@ -563,7 +333,7 @@ class TestBuildConfirmationMessage(TransactionCase):
# Should have pickup day index
self.assertEqual(result["pickup_day_index"], 5)
- @patch(REQUEST_PATCH_TARGET)
+ @patch("odoo.http.request")
def test_build_confirmation_message_delivery(self, mock_request):
"""Test confirmation message for home delivery."""
mock_request.env = self.env.with_context(lang="es_ES")
@@ -583,7 +353,7 @@ class TestBuildConfirmationMessage(TransactionCase):
# pickup_day_index=5 (Saturday), delivery should be 6 (Sunday)
# Note: _get_day_names would need to be mocked for exact day name
- @patch(REQUEST_PATCH_TARGET)
+ @patch("odoo.http.request")
def test_build_confirmation_message_no_dates(self, mock_request):
"""Test confirmation message when no pickup date is set."""
mock_request.env = self.env.with_context(lang="es_ES")
@@ -593,7 +363,6 @@ class TestBuildConfirmationMessage(TransactionCase):
{
"name": "Order No Dates",
"state": "open",
- "group_ids": [(6, 0, [self.group.id])],
}
)
@@ -615,7 +384,7 @@ class TestBuildConfirmationMessage(TransactionCase):
# Date fields should be empty
self.assertEqual(result["pickup_date"], "")
- @patch(REQUEST_PATCH_TARGET)
+ @patch("odoo.http.request")
def test_build_confirmation_message_formats_date(self, mock_request):
"""Test confirmation message formats dates correctly (DD/MM/YYYY)."""
mock_request.env = self.env.with_context(lang="es_ES")
@@ -633,7 +402,7 @@ class TestBuildConfirmationMessage(TransactionCase):
date_pattern = r"\d{2}/\d{2}/\d{4}"
self.assertRegex(pickup_date_str, date_pattern)
- @patch(REQUEST_PATCH_TARGET)
+ @patch("odoo.http.request")
def test_build_confirmation_message_multilang_es(self, mock_request):
"""Test confirmation message in Spanish (es_ES)."""
mock_request.env = self.env.with_context(lang="es_ES")
@@ -647,7 +416,7 @@ class TestBuildConfirmationMessage(TransactionCase):
self.assertIsNotNone(message)
# In real scenario, would check for "¡Gracias!" or similar
- @patch(REQUEST_PATCH_TARGET)
+ @patch("odoo.http.request")
def test_build_confirmation_message_multilang_eu(self, mock_request):
"""Test confirmation message in Basque (eu_ES)."""
mock_request.env = self.env.with_context(lang="eu_ES")
@@ -660,7 +429,7 @@ class TestBuildConfirmationMessage(TransactionCase):
self.assertIsNotNone(message)
# In real scenario, would check for "Eskerrik asko!" or similar
- @patch(REQUEST_PATCH_TARGET)
+ @patch("odoo.http.request")
def test_build_confirmation_message_multilang_ca(self, mock_request):
"""Test confirmation message in Catalan (ca_ES)."""
mock_request.env = self.env.with_context(lang="ca_ES")
@@ -673,7 +442,7 @@ class TestBuildConfirmationMessage(TransactionCase):
self.assertIsNotNone(message)
# In real scenario, would check for "Gràcies!" or similar
- @patch(REQUEST_PATCH_TARGET)
+ @patch("odoo.http.request")
def test_build_confirmation_message_multilang_gl(self, mock_request):
"""Test confirmation message in Galician (gl_ES)."""
mock_request.env = self.env.with_context(lang="gl_ES")
@@ -686,7 +455,7 @@ class TestBuildConfirmationMessage(TransactionCase):
self.assertIsNotNone(message)
# In real scenario, would check for "Grazas!" or similar
- @patch(REQUEST_PATCH_TARGET)
+ @patch("odoo.http.request")
def test_build_confirmation_message_multilang_pt(self, mock_request):
"""Test confirmation message in Portuguese (pt_PT)."""
mock_request.env = self.env.with_context(lang="pt_PT")
@@ -699,7 +468,7 @@ class TestBuildConfirmationMessage(TransactionCase):
self.assertIsNotNone(message)
# In real scenario, would check for "Obrigado!" or similar
- @patch(REQUEST_PATCH_TARGET)
+ @patch("odoo.http.request")
def test_build_confirmation_message_multilang_fr(self, mock_request):
"""Test confirmation message in French (fr_FR)."""
mock_request.env = self.env.with_context(lang="fr_FR")
@@ -712,7 +481,7 @@ class TestBuildConfirmationMessage(TransactionCase):
self.assertIsNotNone(message)
# In real scenario, would check for "Merci!" or similar
- @patch(REQUEST_PATCH_TARGET)
+ @patch("odoo.http.request")
def test_build_confirmation_message_multilang_it(self, mock_request):
"""Test confirmation message in Italian (it_IT)."""
mock_request.env = self.env.with_context(lang="it_IT")
@@ -731,29 +500,9 @@ class TestConfirmEskaera_Integration(TransactionCase):
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,
- }
- )
+ self.controller = http.request.env["website.sale"].browse([])
+ self.user = self.env.ref("base.user_admin")
+ self.partner = self.env.ref("base.partner_admin")
# Create test product
self.product = self.env["product.product"].create(
@@ -769,20 +518,19 @@ class TestConfirmEskaera_Integration(TransactionCase):
{
"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",
- "home_delivery": True,
+ "pickup_date": date.today() + timedelta(days=5),
}
)
- # 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):
+ @patch("odoo.http.request")
+ def test_confirm_eskaera_full_flow_pickup(self, mock_request):
"""Test full confirm_eskaera flow for pickup order."""
+ mock_request.env = self.env.with_user(self.user)
+ mock_request.env.lang = "es_ES"
+ mock_request.httprequest = Mock()
+
+ # Prepare request data
data = {
"order_id": self.group_order.id,
"items": [
@@ -794,14 +542,11 @@ class TestConfirmEskaera_Integration(TransactionCase):
],
"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)
+ mock_request.httprequest.data = json.dumps(data).encode("utf-8")
+
+ # Call confirm_eskaera
+ response = self.controller.confirm_eskaera()
# Verify response
self.assertIsNotNone(response)
@@ -820,19 +565,20 @@ class TestConfirmEskaera_Integration(TransactionCase):
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):
+ @patch("odoo.http.request")
+ def test_confirm_eskaera_full_flow_delivery(self, mock_request):
"""Test full confirm_eskaera flow for delivery order."""
+ mock_request.env = self.env.with_user(self.user)
+ mock_request.env.lang = "es_ES"
+ mock_request.httprequest = Mock()
+
# Add delivery_date to group order
self.group_order.delivery_date = self.group_order.pickup_date + timedelta(
days=1
)
+ # Prepare request data
data = {
"order_id": self.group_order.id,
"items": [
@@ -844,14 +590,11 @@ class TestConfirmEskaera_Integration(TransactionCase):
],
"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)
+ mock_request.httprequest.data = json.dumps(data).encode("utf-8")
+
+ # Call confirm_eskaera
+ response = self.controller.confirm_eskaera()
# Verify response
response_data = json.loads(response.data.decode("utf-8"))
@@ -868,17 +611,19 @@ class TestConfirmEskaera_Integration(TransactionCase):
sale_order.commitment_date.date(), self.group_order.delivery_date
)
- def test_confirm_eskaera_updates_existing_draft(self):
+ @patch("odoo.http.request")
+ def test_confirm_eskaera_updates_existing_draft(self, mock_request):
"""Test confirm_eskaera updates existing draft order instead of creating new."""
+ mock_request.env = self.env.with_user(self.user)
+ mock_request.env.lang = "es_ES"
+ mock_request.httprequest = Mock()
+
# 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,
@@ -895,6 +640,7 @@ class TestConfirmEskaera_Integration(TransactionCase):
existing_order_id = existing_order.id
+ # Prepare new request data
data = {
"order_id": self.group_order.id,
"items": [
@@ -906,14 +652,11 @@ class TestConfirmEskaera_Integration(TransactionCase):
],
"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)
+ mock_request.httprequest.data = json.dumps(data).encode("utf-8")
+
+ # Call confirm_eskaera
+ response = self.controller.confirm_eskaera()
response_data = json.loads(response.data.decode("utf-8"))
@@ -925,8 +668,13 @@ class TestConfirmEskaera_Integration(TransactionCase):
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):
+ @patch("odoo.http.request")
+ def test_confirm_eskaera_ignores_old_period_draft(self, mock_request):
"""Old draft from previous pickup_date must not be reused."""
+ mock_request.env = self.env.with_user(self.user)
+ mock_request.env.lang = "es_ES"
+ mock_request.httprequest = Mock()
+
old_draft = self.env["sale.order"].create(
{
"partner_id": self.partner.id,
@@ -958,15 +706,10 @@ class TestConfirmEskaera_Integration(TransactionCase):
],
"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)
+ mock_request.httprequest.data = json.dumps(data).encode("utf-8")
+ response = self.controller.confirm_eskaera()
response_data = json.loads(response.data.decode("utf-8"))
self.assertTrue(response_data.get("success"))