Compare commits
No commits in common. "2237cba034349bf34091f6d1277f63c5e8c5cf5a" and "e9809b90e9c12ec1207d8228b3d5b9640b681501" have entirely different histories.
2237cba034
...
e9809b90e9
13 changed files with 192 additions and 857 deletions
|
|
@ -1,4 +1,2 @@
|
||||||
from . import stock_move_line # noqa: F401
|
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
|
from . import stock_picking_batch # noqa: F401
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright 2026 Criptomart
|
# Copyright 2026 Criptomart
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from odoo import api
|
|
||||||
from odoo import fields
|
from odoo import fields
|
||||||
from odoo import models
|
from odoo import models
|
||||||
|
|
||||||
|
|
@ -22,18 +21,3 @@ class StockMoveLine(models.Model):
|
||||||
default=False,
|
default=False,
|
||||||
copy=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
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -140,20 +140,11 @@ class StockPickingBatch(models.Model):
|
||||||
else:
|
else:
|
||||||
batch.summary_line_ids = [fields.Command.clear()]
|
batch.summary_line_ids = [fields.Command.clear()]
|
||||||
|
|
||||||
def _check_all_products_collected(self, product_ids=None):
|
def _check_all_products_collected(self):
|
||||||
"""Ensure collected is checked for processed products only.
|
"""Ensure all product summary lines are marked as collected before done."""
|
||||||
|
|
||||||
The product summary remains informative until Odoo knows which products
|
|
||||||
are actually being validated after the backorder decision.
|
|
||||||
"""
|
|
||||||
|
|
||||||
for batch in self:
|
for batch in self:
|
||||||
not_collected_lines = batch.summary_line_ids.filtered(
|
not_collected_lines = batch.summary_line_ids.filtered(
|
||||||
lambda line: (
|
lambda line: not line.is_collected
|
||||||
line.qty_done > 0
|
|
||||||
and not line.is_collected
|
|
||||||
and (not product_ids or line.product_id.id in product_ids)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if not not_collected_lines:
|
if not not_collected_lines:
|
||||||
continue
|
continue
|
||||||
|
|
@ -170,6 +161,10 @@ class StockPickingBatch(models.Model):
|
||||||
)
|
)
|
||||||
raise UserError(message)
|
raise UserError(message)
|
||||||
|
|
||||||
|
def action_done(self):
|
||||||
|
self._check_all_products_collected()
|
||||||
|
return super().action_done()
|
||||||
|
|
||||||
|
|
||||||
class StockPickingBatchSummaryLine(models.Model):
|
class StockPickingBatchSummaryLine(models.Model):
|
||||||
_name = "stock.picking.batch.summary.line"
|
_name = "stock.picking.batch.summary.line"
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
# Copyright 2026 Criptomart
|
# Copyright 2026 Criptomart
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
from odoo.tests import TransactionCase
|
|
||||||
from odoo.tests import tagged
|
from odoo.tests import tagged
|
||||||
|
from odoo.tests.common import TransactionCase
|
||||||
|
|
||||||
|
|
||||||
@tagged("-at_install", "post_install")
|
@tagged("-at_install", "post_install")
|
||||||
|
|
@ -26,13 +24,6 @@ class TestBatchSummary(TransactionCase):
|
||||||
"uom_po_id": cls.uom_unit.id,
|
"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(
|
cls.batch = cls.env["stock.picking.batch"].create(
|
||||||
{"name": "Batch Test", "picking_type_id": cls.picking_type.id}
|
{"name": "Batch Test", "picking_type_id": cls.picking_type.id}
|
||||||
|
|
@ -122,60 +113,6 @@ class TestBatchSummary(TransactionCase):
|
||||||
picking.batch_id = batch.id
|
picking.batch_id = batch.id
|
||||||
return batch
|
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
|
# Tests
|
||||||
def test_totals_and_pending_with_conversion(self):
|
def test_totals_and_pending_with_conversion(self):
|
||||||
"""Totals aggregate per product with UoM conversion and pending."""
|
"""Totals aggregate per product with UoM conversion and pending."""
|
||||||
|
|
@ -253,6 +190,14 @@ class TestBatchSummary(TransactionCase):
|
||||||
self.assertFalse(batch.summary_line_ids)
|
self.assertFalse(batch.summary_line_ids)
|
||||||
|
|
||||||
# 2. Create pickings with moves (without batch assignment)
|
# 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_a = self.env["stock.picking"].create(
|
||||||
{
|
{
|
||||||
"picking_type_id": self.picking_type.id,
|
"picking_type_id": self.picking_type.id,
|
||||||
|
|
@ -281,8 +226,8 @@ class TestBatchSummary(TransactionCase):
|
||||||
)
|
)
|
||||||
self.env["stock.move"].create(
|
self.env["stock.move"].create(
|
||||||
{
|
{
|
||||||
"name": self.product_2.name,
|
"name": product2.name,
|
||||||
"product_id": self.product_2.id,
|
"product_id": product2.id,
|
||||||
"product_uom_qty": 10.0,
|
"product_uom_qty": 10.0,
|
||||||
"product_uom": self.uom_unit.id,
|
"product_uom": self.uom_unit.id,
|
||||||
"picking_id": picking_b.id,
|
"picking_id": picking_b.id,
|
||||||
|
|
@ -331,7 +276,7 @@ class TestBatchSummary(TransactionCase):
|
||||||
# Two products expected
|
# Two products expected
|
||||||
products_in_summary = batch.summary_line_ids.mapped("product_id")
|
products_in_summary = batch.summary_line_ids.mapped("product_id")
|
||||||
self.assertIn(self.product, products_in_summary)
|
self.assertIn(self.product, products_in_summary)
|
||||||
self.assertIn(self.product_2, products_in_summary)
|
self.assertIn(product2, products_in_summary)
|
||||||
|
|
||||||
# Check aggregated quantities
|
# Check aggregated quantities
|
||||||
line_product1 = batch.summary_line_ids.filtered(
|
line_product1 = batch.summary_line_ids.filtered(
|
||||||
|
|
@ -340,12 +285,12 @@ class TestBatchSummary(TransactionCase):
|
||||||
self.assertAlmostEqual(line_product1.qty_demanded, 8.0) # 5 + 3
|
self.assertAlmostEqual(line_product1.qty_demanded, 8.0) # 5 + 3
|
||||||
|
|
||||||
line_product2 = batch.summary_line_ids.filtered(
|
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)
|
self.assertAlmostEqual(line_product2.qty_demanded, 10.0)
|
||||||
|
|
||||||
def test_done_requires_all_summary_lines_collected_without_backorder(self):
|
def test_done_requires_all_summary_lines_collected(self):
|
||||||
"""Full validation still blocks if processed products are unchecked."""
|
"""Batch validation must fail if there are unchecked collected lines."""
|
||||||
|
|
||||||
batch = self._create_batch_with_pickings()
|
batch = self._create_batch_with_pickings()
|
||||||
batch.action_confirm()
|
batch.action_confirm()
|
||||||
|
|
@ -357,32 +302,6 @@ class TestBatchSummary(TransactionCase):
|
||||||
with self.assertRaises(UserError):
|
with self.assertRaises(UserError):
|
||||||
batch.action_done()
|
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):
|
def test_check_all_products_collected_passes_when_all_checked(self):
|
||||||
"""Collected validation helper must pass when all lines are checked."""
|
"""Collected validation helper must pass when all lines are checked."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,27 +5,11 @@
|
||||||
<field name="model">stock.move.line</field>
|
<field name="model">stock.move.line</field>
|
||||||
<field name="inherit_id" ref="stock_picking_batch.view_move_line_tree"/>
|
<field name="inherit_id" ref="stock_picking_batch.view_move_line_tree"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//field[@name='picking_id']" position="attributes">
|
|
||||||
<attribute name="optional">hide</attribute>
|
|
||||||
</xpath>
|
|
||||||
<xpath expr="//field[@name='lot_id']" position="attributes">
|
|
||||||
<attribute name="optional">hide</attribute>
|
|
||||||
</xpath>
|
|
||||||
<xpath expr="//field[@name='lot_name']" position="attributes">
|
|
||||||
<attribute name="optional">hide</attribute>
|
|
||||||
</xpath>
|
|
||||||
<xpath expr="//field[@name='location_id']" position="attributes">
|
|
||||||
<attribute name="optional">hide</attribute>
|
|
||||||
</xpath>
|
|
||||||
<xpath expr="//field[@name='location_dest_id']" position="attributes">
|
|
||||||
<attribute name="optional">hide</attribute>
|
|
||||||
</xpath>
|
|
||||||
<xpath expr="//field[@name='product_id']" position="after">
|
<xpath expr="//field[@name='product_id']" position="after">
|
||||||
<field name="product_categ_id" optional="hide"/>
|
<field name="product_categ_id" optional="hide"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='picking_id']" position="after">
|
<xpath expr="//field[@name='picking_id']" position="after">
|
||||||
<field name="picking_partner_id" optional="hide"/>
|
<field name="picking_partner_id" optional="hide"/>
|
||||||
<field name="consumer_group_id" optional="show"/>
|
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='quantity']" position="after">
|
<xpath expr="//field[@name='quantity']" position="after">
|
||||||
<field name="is_collected" optional="show" widget="boolean_toggle"/>
|
<field name="is_collected" optional="show" widget="boolean_toggle"/>
|
||||||
|
|
|
||||||
|
|
@ -28,15 +28,4 @@
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="view_stock_picking_batch_picking_tree_consumer_group" model="ir.ui.view">
|
|
||||||
<field name="name">stock.picking.batch.picking.tree.consumer.group</field>
|
|
||||||
<field name="model">stock.picking</field>
|
|
||||||
<field name="inherit_id" ref="stock_picking_batch.stock_picking_view_batch_tree_ref"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<xpath expr="//field[@name='partner_id']" position="after">
|
|
||||||
<field name="batch_consumer_group_id" optional="show"/>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
|
|
@ -768,8 +768,6 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
if not current_user.partner_id:
|
if not current_user.partner_id:
|
||||||
raise ValueError("User has no associated partner") from None
|
raise ValueError("User has no associated partner") from None
|
||||||
|
|
||||||
self._validate_user_group_access(group_order, current_user)
|
|
||||||
|
|
||||||
# Validate items
|
# Validate items
|
||||||
items = data.get("items", [])
|
items = data.get("items", [])
|
||||||
if not items:
|
if not items:
|
||||||
|
|
@ -822,8 +820,6 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
if not current_user.partner_id:
|
if not current_user.partner_id:
|
||||||
raise ValueError("User has no associated partner")
|
raise ValueError("User has no associated partner")
|
||||||
|
|
||||||
self._validate_user_group_access(group_order, current_user)
|
|
||||||
|
|
||||||
# Validate items
|
# Validate items
|
||||||
items = data.get("items", [])
|
items = data.get("items", [])
|
||||||
if not items:
|
if not items:
|
||||||
|
|
@ -891,8 +887,6 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
if not current_user.partner_id:
|
if not current_user.partner_id:
|
||||||
raise ValueError("User has no associated partner")
|
raise ValueError("User has no associated partner")
|
||||||
|
|
||||||
self._validate_user_group_access(group_order, current_user)
|
|
||||||
|
|
||||||
# Validate items
|
# Validate items
|
||||||
items = data.get("items", [])
|
items = data.get("items", [])
|
||||||
if not items:
|
if not items:
|
||||||
|
|
@ -927,29 +921,6 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
return False
|
return False
|
||||||
return bool(value)
|
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):
|
def _process_cart_items(self, items, group_order, pricelist=None):
|
||||||
"""Process cart items and build sale.order line data.
|
"""Process cart items and build sale.order line data.
|
||||||
|
|
||||||
|
|
@ -1038,35 +1009,6 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
# No salesperson found
|
# No salesperson found
|
||||||
return False
|
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(
|
def _create_or_update_sale_order(
|
||||||
self,
|
self,
|
||||||
group_order,
|
group_order,
|
||||||
|
|
@ -1080,7 +1022,11 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
|
|
||||||
Returns the sale.order record.
|
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(
|
_logger.info(
|
||||||
"[CONSUMER_GROUP DEBUG] _create_or_update_sale_order: "
|
"[CONSUMER_GROUP DEBUG] _create_or_update_sale_order: "
|
||||||
|
|
@ -1090,25 +1036,22 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
consumer_group_id,
|
consumer_group_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
effective_home_delivery, calculated_commitment_date = (
|
# commitment_date: use provided or calculate from group_order
|
||||||
self._get_effective_delivery_context(group_order, is_delivery)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Explicit commitment_date wins; otherwise use calculated value
|
|
||||||
if not commitment_date:
|
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:
|
if existing_order:
|
||||||
# Update existing order with new lines and propagate fields
|
# Update existing order with new lines and propagate fields
|
||||||
# Use sudo() to avoid permission issues with portal users
|
# Use sudo() to avoid permission issues with portal users
|
||||||
existing_order_sudo = existing_order.sudo()
|
existing_order_sudo = existing_order.sudo()
|
||||||
# (5,) clears all existing lines before adding the new ones
|
existing_order_sudo.order_line = sale_order_lines
|
||||||
existing_order_sudo.order_line = [(5,)] + sale_order_lines
|
|
||||||
if not existing_order_sudo.group_order_id:
|
if not existing_order_sudo.group_order_id:
|
||||||
existing_order_sudo.group_order_id = 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_day = group_order.pickup_day
|
||||||
existing_order_sudo.pickup_date = group_order.pickup_date
|
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
|
existing_order_sudo.consumer_group_id = consumer_group_id
|
||||||
if commitment_date:
|
if commitment_date:
|
||||||
existing_order_sudo.commitment_date = 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",
|
"Updated existing sale.order %d: commitment_date=%s, home_delivery=%s, consumer_group_id=%s",
|
||||||
existing_order.id,
|
existing_order.id,
|
||||||
commitment_date,
|
commitment_date,
|
||||||
effective_home_delivery,
|
is_delivery,
|
||||||
consumer_group_id,
|
consumer_group_id,
|
||||||
)
|
)
|
||||||
return existing_order
|
return existing_order
|
||||||
|
|
@ -1128,7 +1071,7 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
"group_order_id": group_order.id,
|
"group_order_id": group_order.id,
|
||||||
"pickup_day": group_order.pickup_day,
|
"pickup_day": group_order.pickup_day,
|
||||||
"pickup_date": group_order.pickup_date,
|
"pickup_date": group_order.pickup_date,
|
||||||
"home_delivery": effective_home_delivery,
|
"home_delivery": is_delivery,
|
||||||
"consumer_group_id": consumer_group_id,
|
"consumer_group_id": consumer_group_id,
|
||||||
}
|
}
|
||||||
if commitment_date:
|
if commitment_date:
|
||||||
|
|
@ -1151,25 +1094,23 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
sale_order.id,
|
sale_order.id,
|
||||||
group_order.id,
|
group_order.id,
|
||||||
group_order.pickup_day,
|
group_order.pickup_day,
|
||||||
effective_home_delivery,
|
group_order.home_delivery,
|
||||||
consumer_group_id,
|
consumer_group_id,
|
||||||
)
|
)
|
||||||
return sale_order
|
return sale_order
|
||||||
|
|
||||||
def _create_draft_sale_order(
|
def _create_draft_sale_order(
|
||||||
self,
|
self, group_order, current_user, sale_order_lines, order_id, pickup_date=None
|
||||||
group_order,
|
|
||||||
current_user,
|
|
||||||
sale_order_lines,
|
|
||||||
order_id,
|
|
||||||
pickup_date=None,
|
|
||||||
is_delivery=False,
|
|
||||||
):
|
):
|
||||||
"""Create a draft sale.order from prepared lines and propagate group fields.
|
"""Create a draft sale.order from prepared lines and propagate group fields.
|
||||||
|
|
||||||
Returns created sale.order record.
|
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(
|
_logger.info(
|
||||||
"[CONSUMER_GROUP DEBUG] _create_draft_sale_order: "
|
"[CONSUMER_GROUP DEBUG] _create_draft_sale_order: "
|
||||||
|
|
@ -1179,8 +1120,11 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
consumer_group_id,
|
consumer_group_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
effective_home_delivery, commitment_date = self._get_effective_delivery_context(
|
# commitment_date comes from group_order (delivery_date or pickup_date)
|
||||||
group_order, is_delivery
|
commitment_date = (
|
||||||
|
group_order.delivery_date
|
||||||
|
if group_order.home_delivery
|
||||||
|
else group_order.pickup_date
|
||||||
)
|
)
|
||||||
|
|
||||||
order_vals = {
|
order_vals = {
|
||||||
|
|
@ -1190,7 +1134,7 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
"group_order_id": order_id,
|
"group_order_id": order_id,
|
||||||
"pickup_day": group_order.pickup_day,
|
"pickup_day": group_order.pickup_day,
|
||||||
"pickup_date": group_order.pickup_date,
|
"pickup_date": group_order.pickup_date,
|
||||||
"home_delivery": effective_home_delivery,
|
"home_delivery": group_order.home_delivery,
|
||||||
"consumer_group_id": consumer_group_id,
|
"consumer_group_id": consumer_group_id,
|
||||||
"commitment_date": commitment_date,
|
"commitment_date": commitment_date,
|
||||||
}
|
}
|
||||||
|
|
@ -1430,7 +1374,11 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
|
|
||||||
All fields (commitment_date, consumer_group_id, etc.) come from group_order.
|
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(
|
_logger.info(
|
||||||
"[CONSUMER_GROUP DEBUG] _merge_or_replace_draft: "
|
"[CONSUMER_GROUP DEBUG] _merge_or_replace_draft: "
|
||||||
|
|
@ -1440,8 +1388,9 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
consumer_group_id,
|
consumer_group_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
effective_home_delivery, commitment_date = self._get_effective_delivery_context(
|
# commitment_date comes directly from selected flow
|
||||||
group_order, is_delivery
|
commitment_date = (
|
||||||
|
group_order.delivery_date if is_delivery else group_order.pickup_date
|
||||||
)
|
)
|
||||||
|
|
||||||
if existing_drafts:
|
if existing_drafts:
|
||||||
|
|
@ -1457,7 +1406,7 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
"group_order_id": order_id,
|
"group_order_id": order_id,
|
||||||
"pickup_day": group_order.pickup_day,
|
"pickup_day": group_order.pickup_day,
|
||||||
"pickup_date": group_order.pickup_date,
|
"pickup_date": group_order.pickup_date,
|
||||||
"home_delivery": effective_home_delivery,
|
"home_delivery": is_delivery,
|
||||||
"consumer_group_id": consumer_group_id,
|
"consumer_group_id": consumer_group_id,
|
||||||
"commitment_date": commitment_date,
|
"commitment_date": commitment_date,
|
||||||
}
|
}
|
||||||
|
|
@ -1471,7 +1420,7 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
"group_order_id": order_id,
|
"group_order_id": order_id,
|
||||||
"pickup_day": group_order.pickup_day,
|
"pickup_day": group_order.pickup_day,
|
||||||
"pickup_date": group_order.pickup_date,
|
"pickup_date": group_order.pickup_date,
|
||||||
"home_delivery": effective_home_delivery,
|
"home_delivery": is_delivery,
|
||||||
"consumer_group_id": consumer_group_id,
|
"consumer_group_id": consumer_group_id,
|
||||||
"commitment_date": commitment_date,
|
"commitment_date": commitment_date,
|
||||||
}
|
}
|
||||||
|
|
@ -2291,18 +2240,8 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
status=400,
|
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", [])
|
items = data.get("items", [])
|
||||||
pickup_date = data.get("pickup_date")
|
pickup_date = data.get("pickup_date")
|
||||||
is_delivery = self._to_bool(data.get("is_delivery", False))
|
|
||||||
if not items:
|
if not items:
|
||||||
return request.make_response(
|
return request.make_response(
|
||||||
json.dumps({"error": "No items in cart"}),
|
json.dumps({"error": "No items in cart"}),
|
||||||
|
|
@ -2323,12 +2262,7 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
)
|
)
|
||||||
|
|
||||||
sale_order = self._create_draft_sale_order(
|
sale_order = self._create_draft_sale_order(
|
||||||
group_order,
|
group_order, current_user, sale_order_lines, order_id, pickup_date
|
||||||
current_user,
|
|
||||||
sale_order_lines,
|
|
||||||
order_id,
|
|
||||||
pickup_date,
|
|
||||||
is_delivery=is_delivery,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_logger.info(
|
_logger.info(
|
||||||
|
|
@ -2695,15 +2629,6 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
status=400,
|
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
|
# Get cart items
|
||||||
items = data.get("items", [])
|
items = data.get("items", [])
|
||||||
is_delivery = self._to_bool(data.get("is_delivery", False))
|
is_delivery = self._to_bool(data.get("is_delivery", False))
|
||||||
|
|
@ -2866,23 +2791,27 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
)
|
)
|
||||||
existing_order = None
|
existing_order = None
|
||||||
|
|
||||||
effective_home_delivery, commitment_date = (
|
# Get pickup date and delivery info from group order
|
||||||
self._get_effective_delivery_context(group_order, is_delivery)
|
# 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
|
# Create or update sale.order using helper
|
||||||
sale_order = self._create_or_update_sale_order(
|
sale_order = self._create_or_update_sale_order(
|
||||||
group_order,
|
group_order,
|
||||||
current_user,
|
current_user,
|
||||||
sale_order_lines,
|
sale_order_lines,
|
||||||
effective_home_delivery,
|
is_delivery,
|
||||||
commitment_date=commitment_date,
|
commitment_date=commitment_date,
|
||||||
existing_order=existing_order,
|
existing_order=existing_order,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Build confirmation message using helper
|
# Build confirmation message using helper
|
||||||
message_data = self._build_confirmation_message(
|
message_data = self._build_confirmation_message(
|
||||||
sale_order, group_order, effective_home_delivery
|
sale_order, group_order, is_delivery
|
||||||
)
|
)
|
||||||
message = message_data["message"]
|
message = message_data["message"]
|
||||||
pickup_day_name = message_data["pickup_day"]
|
pickup_day_name = message_data["pickup_day"]
|
||||||
|
|
|
||||||
|
|
@ -799,45 +799,9 @@ class GroupOrder(models.Model):
|
||||||
self.delivery_date,
|
self.delivery_date,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Confirm each order in an isolated savepoint so one bad product
|
sale_orders.action_confirm()
|
||||||
# route configuration doesn't block all remaining orders.
|
# Create picking batches after confirmation
|
||||||
confirmed_sale_orders = SaleOrder.browse()
|
self._create_picking_batches_for_sale_orders(sale_orders)
|
||||||
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,
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
_logger.exception(
|
_logger.exception(
|
||||||
"Cron: Error confirming sale orders for group order %s (%s)",
|
"Cron: Error confirming sale orders for group order %s (%s)",
|
||||||
|
|
@ -845,42 +809,6 @@ class GroupOrder(models.Model):
|
||||||
self.name,
|
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):
|
def _create_picking_batches_for_sale_orders(self, sale_orders):
|
||||||
"""Create stock.picking.batch grouped by consumer_group_id.
|
"""Create stock.picking.batch grouped by consumer_group_id.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ from . import test_record_rules # noqa: F401
|
||||||
from . import test_multi_company # noqa: F401
|
from . import test_multi_company # noqa: F401
|
||||||
from . import test_save_order_endpoints # noqa: F401
|
from . import test_save_order_endpoints # noqa: F401
|
||||||
from . import test_date_calculations # 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_pricing_with_pricelist # noqa: F401
|
||||||
from . import test_portal_sale_order_creation # noqa: F401
|
from . import test_portal_sale_order_creation # noqa: F401
|
||||||
from . import test_cron_picking_batch # noqa: F401
|
from . import test_cron_picking_batch # noqa: F401
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,8 @@
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from odoo import fields
|
from odoo import fields
|
||||||
from odoo.exceptions import UserError
|
|
||||||
from odoo.tests.common import TransactionCase
|
from odoo.tests.common import TransactionCase
|
||||||
from odoo.tests.common import tagged
|
from odoo.tests.common import tagged
|
||||||
|
|
||||||
|
|
@ -404,54 +402,3 @@ class TestCronPickingBatch(TransactionCase):
|
||||||
"draft",
|
"draft",
|
||||||
"Sale order should remain draft - group order is closed",
|
"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",
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -20,108 +20,37 @@ Includes tests for:
|
||||||
import json
|
import json
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from types import SimpleNamespace
|
from unittest.mock import Mock
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from odoo import http
|
||||||
from odoo.tests.common import TransactionCase
|
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):
|
class TestValidateConfirmJson(TransactionCase):
|
||||||
"""Test _validate_confirm_json() helper method."""
|
"""Test _validate_confirm_json() helper method."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.controller = AplicoopWebsiteSale()
|
self.controller = http.request.env["website.sale"].browse([])
|
||||||
self.group_1 = self.env["res.partner"].create(
|
self.user = self.env.ref("base.user_admin")
|
||||||
{
|
self.partner = self.env.ref("base.partner_admin")
|
||||||
"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
|
# Create test group order
|
||||||
self.group_order = self.env["group.order"].create(
|
self.group_order = self.env["group.order"].create(
|
||||||
{
|
{
|
||||||
"name": "Test Order Phase 3",
|
"name": "Test Order Phase 3",
|
||||||
"state": "open",
|
"state": "open",
|
||||||
"group_ids": [(6, 0, [self.group_1.id, self.group_2.id])],
|
"collection_date": date.today() + timedelta(days=3),
|
||||||
"start_date": date.today(),
|
|
||||||
"end_date": date.today() + timedelta(days=7),
|
|
||||||
"cutoff_day": "3", # Thursday
|
"cutoff_day": "3", # Thursday
|
||||||
"pickup_day": "5", # Saturday
|
"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."""
|
"""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 = {
|
data = {
|
||||||
"order_id": self.group_order.id,
|
"order_id": self.group_order.id,
|
||||||
|
|
@ -129,7 +58,6 @@ class TestValidateConfirmJson(TransactionCase):
|
||||||
"is_delivery": False,
|
"is_delivery": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
with patch(REQUEST_PATCH_TARGET, request_mock):
|
|
||||||
order_id, group_order, current_user, items, is_delivery = (
|
order_id, group_order, current_user, items, is_delivery = (
|
||||||
self.controller._validate_confirm_json(data)
|
self.controller._validate_confirm_json(data)
|
||||||
)
|
)
|
||||||
|
|
@ -140,51 +68,52 @@ class TestValidateConfirmJson(TransactionCase):
|
||||||
self.assertEqual(len(items), 1)
|
self.assertEqual(len(items), 1)
|
||||||
self.assertFalse(is_delivery)
|
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."""
|
"""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}]}
|
data = {"items": [{"product_id": 1, "quantity": 2}]}
|
||||||
|
|
||||||
with patch(REQUEST_PATCH_TARGET, request_mock):
|
|
||||||
with self.assertRaises(ValueError) as context:
|
with self.assertRaises(ValueError) as context:
|
||||||
self.controller._validate_confirm_json(data)
|
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."""
|
"""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 = {
|
data = {
|
||||||
"order_id": 99999, # Non-existent ID
|
"order_id": 99999, # Non-existent ID
|
||||||
"items": [{"product_id": 1, "quantity": 2}],
|
"items": [{"product_id": 1, "quantity": 2}],
|
||||||
}
|
}
|
||||||
|
|
||||||
with patch(REQUEST_PATCH_TARGET, request_mock):
|
|
||||||
with self.assertRaises(ValueError) as context:
|
with self.assertRaises(ValueError) as context:
|
||||||
self.controller._validate_confirm_json(data)
|
self.controller._validate_confirm_json(data)
|
||||||
|
|
||||||
self.assertIn("Order", str(context.exception))
|
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."""
|
"""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 = {
|
data = {
|
||||||
"order_id": self.group_order.id,
|
"order_id": self.group_order.id,
|
||||||
"items": [],
|
"items": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
with patch(REQUEST_PATCH_TARGET, request_mock):
|
|
||||||
with self.assertRaises(ValueError) as context:
|
with self.assertRaises(ValueError) as context:
|
||||||
self.controller._validate_confirm_json(data)
|
self.controller._validate_confirm_json(data)
|
||||||
|
|
||||||
self.assertIn("No items in cart", str(context.exception))
|
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."""
|
"""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 = {
|
data = {
|
||||||
"order_id": self.group_order.id,
|
"order_id": self.group_order.id,
|
||||||
|
|
@ -192,165 +121,17 @@ class TestValidateConfirmJson(TransactionCase):
|
||||||
"is_delivery": True,
|
"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)
|
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):
|
class TestProcessCartItems(TransactionCase):
|
||||||
"""Test _process_cart_items() helper method."""
|
"""Test _process_cart_items() helper method."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.controller = AplicoopWebsiteSale()
|
self.controller = http.request.env["website.sale"].browse([])
|
||||||
|
|
||||||
# Create test products
|
# Create test products
|
||||||
self.product1 = self.env["product.product"].create(
|
self.product1 = self.env["product.product"].create(
|
||||||
|
|
@ -367,28 +148,20 @@ class TestProcessCartItems(TransactionCase):
|
||||||
"type": "consu",
|
"type": "consu",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.group = self.env["res.partner"].create(
|
|
||||||
{
|
|
||||||
"name": "Cart Test Group",
|
|
||||||
"is_company": True,
|
|
||||||
"is_group": True,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create test group order
|
# Create test group order
|
||||||
self.group_order = self.env["group.order"].create(
|
self.group_order = self.env["group.order"].create(
|
||||||
{
|
{
|
||||||
"name": "Test Order for Cart",
|
"name": "Test Order for Cart",
|
||||||
"state": "open",
|
"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."""
|
"""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 = [
|
items = [
|
||||||
{
|
{
|
||||||
|
|
@ -403,7 +176,6 @@ 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(len(result), 2)
|
||||||
|
|
@ -413,9 +185,11 @@ class TestProcessCartItems(TransactionCase):
|
||||||
self.assertEqual(result[0][2]["product_uom_qty"], 2)
|
self.assertEqual(result[0][2]["product_uom_qty"], 2)
|
||||||
self.assertEqual(result[0][2]["price_unit"], 15.0)
|
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."""
|
"""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 = [
|
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)
|
self.assertEqual(len(result), 1)
|
||||||
# Should use product.list_price as fallback
|
# Should use product.list_price as fallback
|
||||||
self.assertEqual(result[0][2]["price_unit"], self.product1.list_price)
|
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."""
|
"""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 = [
|
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
|
# Should only process the valid product
|
||||||
self.assertEqual(len(result), 1)
|
self.assertEqual(len(result), 1)
|
||||||
self.assertEqual(result[0][2]["product_id"], self.product1.id)
|
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."""
|
"""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}]
|
items = [{"product_id": 99999, "quantity": 1, "product_price": 10.0}]
|
||||||
|
|
||||||
with patch(REQUEST_PATCH_TARGET, request_mock):
|
|
||||||
with self.assertRaises(ValueError) as context:
|
with self.assertRaises(ValueError) as context:
|
||||||
self.controller._process_cart_items(items, self.group_order)
|
self.controller._process_cart_items(items, self.group_order)
|
||||||
|
|
||||||
self.assertIn("No valid items", str(context.exception))
|
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."""
|
"""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
|
# Add translation for product name
|
||||||
self.env["ir.translation"].create(
|
self.env["ir.translation"].create(
|
||||||
|
|
@ -494,7 +271,6 @@ 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 should be in Basque context
|
||||||
|
|
@ -508,27 +284,21 @@ class TestBuildConfirmationMessage(TransactionCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.controller = AplicoopWebsiteSale()
|
self.controller = http.request.env["website.sale"].browse([])
|
||||||
self.user = self.env.ref("base.user_admin")
|
self.user = self.env.ref("base.user_admin")
|
||||||
self.partner = self.env.ref("base.partner_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
|
# 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(
|
self.group_order = self.env["group.order"].create(
|
||||||
{
|
{
|
||||||
"name": "Test Order Messages",
|
"name": "Test Order Messages",
|
||||||
"state": "open",
|
"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_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):
|
def test_build_confirmation_message_pickup(self, mock_request):
|
||||||
"""Test confirmation message for pickup (not delivery)."""
|
"""Test confirmation message for pickup (not delivery)."""
|
||||||
mock_request.env = self.env.with_context(lang="es_ES")
|
mock_request.env = self.env.with_context(lang="es_ES")
|
||||||
|
|
@ -563,7 +333,7 @@ class TestBuildConfirmationMessage(TransactionCase):
|
||||||
# Should have pickup day index
|
# Should have pickup day index
|
||||||
self.assertEqual(result["pickup_day_index"], 5)
|
self.assertEqual(result["pickup_day_index"], 5)
|
||||||
|
|
||||||
@patch(REQUEST_PATCH_TARGET)
|
@patch("odoo.http.request")
|
||||||
def test_build_confirmation_message_delivery(self, mock_request):
|
def test_build_confirmation_message_delivery(self, mock_request):
|
||||||
"""Test confirmation message for home delivery."""
|
"""Test confirmation message for home delivery."""
|
||||||
mock_request.env = self.env.with_context(lang="es_ES")
|
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)
|
# pickup_day_index=5 (Saturday), delivery should be 6 (Sunday)
|
||||||
# Note: _get_day_names would need to be mocked for exact day name
|
# 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):
|
def test_build_confirmation_message_no_dates(self, mock_request):
|
||||||
"""Test confirmation message when no pickup date is set."""
|
"""Test confirmation message when no pickup date is set."""
|
||||||
mock_request.env = self.env.with_context(lang="es_ES")
|
mock_request.env = self.env.with_context(lang="es_ES")
|
||||||
|
|
@ -593,7 +363,6 @@ class TestBuildConfirmationMessage(TransactionCase):
|
||||||
{
|
{
|
||||||
"name": "Order No Dates",
|
"name": "Order No Dates",
|
||||||
"state": "open",
|
"state": "open",
|
||||||
"group_ids": [(6, 0, [self.group.id])],
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -615,7 +384,7 @@ class TestBuildConfirmationMessage(TransactionCase):
|
||||||
# Date fields should be empty
|
# Date fields should be empty
|
||||||
self.assertEqual(result["pickup_date"], "")
|
self.assertEqual(result["pickup_date"], "")
|
||||||
|
|
||||||
@patch(REQUEST_PATCH_TARGET)
|
@patch("odoo.http.request")
|
||||||
def test_build_confirmation_message_formats_date(self, mock_request):
|
def test_build_confirmation_message_formats_date(self, mock_request):
|
||||||
"""Test confirmation message formats dates correctly (DD/MM/YYYY)."""
|
"""Test confirmation message formats dates correctly (DD/MM/YYYY)."""
|
||||||
mock_request.env = self.env.with_context(lang="es_ES")
|
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}"
|
date_pattern = r"\d{2}/\d{2}/\d{4}"
|
||||||
self.assertRegex(pickup_date_str, date_pattern)
|
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):
|
def test_build_confirmation_message_multilang_es(self, mock_request):
|
||||||
"""Test confirmation message in Spanish (es_ES)."""
|
"""Test confirmation message in Spanish (es_ES)."""
|
||||||
mock_request.env = self.env.with_context(lang="es_ES")
|
mock_request.env = self.env.with_context(lang="es_ES")
|
||||||
|
|
@ -647,7 +416,7 @@ class TestBuildConfirmationMessage(TransactionCase):
|
||||||
self.assertIsNotNone(message)
|
self.assertIsNotNone(message)
|
||||||
# In real scenario, would check for "¡Gracias!" or similar
|
# 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):
|
def test_build_confirmation_message_multilang_eu(self, mock_request):
|
||||||
"""Test confirmation message in Basque (eu_ES)."""
|
"""Test confirmation message in Basque (eu_ES)."""
|
||||||
mock_request.env = self.env.with_context(lang="eu_ES")
|
mock_request.env = self.env.with_context(lang="eu_ES")
|
||||||
|
|
@ -660,7 +429,7 @@ class TestBuildConfirmationMessage(TransactionCase):
|
||||||
self.assertIsNotNone(message)
|
self.assertIsNotNone(message)
|
||||||
# In real scenario, would check for "Eskerrik asko!" or similar
|
# 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):
|
def test_build_confirmation_message_multilang_ca(self, mock_request):
|
||||||
"""Test confirmation message in Catalan (ca_ES)."""
|
"""Test confirmation message in Catalan (ca_ES)."""
|
||||||
mock_request.env = self.env.with_context(lang="ca_ES")
|
mock_request.env = self.env.with_context(lang="ca_ES")
|
||||||
|
|
@ -673,7 +442,7 @@ class TestBuildConfirmationMessage(TransactionCase):
|
||||||
self.assertIsNotNone(message)
|
self.assertIsNotNone(message)
|
||||||
# In real scenario, would check for "Gràcies!" or similar
|
# 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):
|
def test_build_confirmation_message_multilang_gl(self, mock_request):
|
||||||
"""Test confirmation message in Galician (gl_ES)."""
|
"""Test confirmation message in Galician (gl_ES)."""
|
||||||
mock_request.env = self.env.with_context(lang="gl_ES")
|
mock_request.env = self.env.with_context(lang="gl_ES")
|
||||||
|
|
@ -686,7 +455,7 @@ class TestBuildConfirmationMessage(TransactionCase):
|
||||||
self.assertIsNotNone(message)
|
self.assertIsNotNone(message)
|
||||||
# In real scenario, would check for "Grazas!" or similar
|
# 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):
|
def test_build_confirmation_message_multilang_pt(self, mock_request):
|
||||||
"""Test confirmation message in Portuguese (pt_PT)."""
|
"""Test confirmation message in Portuguese (pt_PT)."""
|
||||||
mock_request.env = self.env.with_context(lang="pt_PT")
|
mock_request.env = self.env.with_context(lang="pt_PT")
|
||||||
|
|
@ -699,7 +468,7 @@ class TestBuildConfirmationMessage(TransactionCase):
|
||||||
self.assertIsNotNone(message)
|
self.assertIsNotNone(message)
|
||||||
# In real scenario, would check for "Obrigado!" or similar
|
# 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):
|
def test_build_confirmation_message_multilang_fr(self, mock_request):
|
||||||
"""Test confirmation message in French (fr_FR)."""
|
"""Test confirmation message in French (fr_FR)."""
|
||||||
mock_request.env = self.env.with_context(lang="fr_FR")
|
mock_request.env = self.env.with_context(lang="fr_FR")
|
||||||
|
|
@ -712,7 +481,7 @@ class TestBuildConfirmationMessage(TransactionCase):
|
||||||
self.assertIsNotNone(message)
|
self.assertIsNotNone(message)
|
||||||
# In real scenario, would check for "Merci!" or similar
|
# 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):
|
def test_build_confirmation_message_multilang_it(self, mock_request):
|
||||||
"""Test confirmation message in Italian (it_IT)."""
|
"""Test confirmation message in Italian (it_IT)."""
|
||||||
mock_request.env = self.env.with_context(lang="it_IT")
|
mock_request.env = self.env.with_context(lang="it_IT")
|
||||||
|
|
@ -731,29 +500,9 @@ class TestConfirmEskaera_Integration(TransactionCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.controller = AplicoopWebsiteSale()
|
self.controller = http.request.env["website.sale"].browse([])
|
||||||
self.consumer_group = self.env["res.partner"].create(
|
self.user = self.env.ref("base.user_admin")
|
||||||
{
|
self.partner = self.env.ref("base.partner_admin")
|
||||||
"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
|
# Create test product
|
||||||
self.product = self.env["product.product"].create(
|
self.product = self.env["product.product"].create(
|
||||||
|
|
@ -769,20 +518,19 @@ class TestConfirmEskaera_Integration(TransactionCase):
|
||||||
{
|
{
|
||||||
"name": "Integration Test Order",
|
"name": "Integration Test Order",
|
||||||
"state": "open",
|
"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",
|
"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
|
@patch("odoo.http.request")
|
||||||
self.website = self.env["website"].search([], limit=1)
|
def test_confirm_eskaera_full_flow_pickup(self, mock_request):
|
||||||
|
|
||||||
def test_confirm_eskaera_full_flow_pickup(self):
|
|
||||||
"""Test full confirm_eskaera flow for pickup order."""
|
"""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 = {
|
data = {
|
||||||
"order_id": self.group_order.id,
|
"order_id": self.group_order.id,
|
||||||
"items": [
|
"items": [
|
||||||
|
|
@ -794,14 +542,11 @@ class TestConfirmEskaera_Integration(TransactionCase):
|
||||||
],
|
],
|
||||||
"is_delivery": False,
|
"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):
|
mock_request.httprequest.data = json.dumps(data).encode("utf-8")
|
||||||
response = self.controller.confirm_eskaera.__wrapped__(self.controller)
|
|
||||||
|
# Call confirm_eskaera
|
||||||
|
response = self.controller.confirm_eskaera()
|
||||||
|
|
||||||
# Verify response
|
# Verify response
|
||||||
self.assertIsNotNone(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(sale_order.group_order_id.id, self.group_order.id)
|
||||||
self.assertEqual(len(sale_order.order_line), 1)
|
self.assertEqual(len(sale_order.order_line), 1)
|
||||||
self.assertEqual(sale_order.order_line[0].product_uom_qty, 3)
|
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."""
|
"""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
|
# Add delivery_date to group order
|
||||||
self.group_order.delivery_date = self.group_order.pickup_date + timedelta(
|
self.group_order.delivery_date = self.group_order.pickup_date + timedelta(
|
||||||
days=1
|
days=1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Prepare request data
|
||||||
data = {
|
data = {
|
||||||
"order_id": self.group_order.id,
|
"order_id": self.group_order.id,
|
||||||
"items": [
|
"items": [
|
||||||
|
|
@ -844,14 +590,11 @@ class TestConfirmEskaera_Integration(TransactionCase):
|
||||||
],
|
],
|
||||||
"is_delivery": True,
|
"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):
|
mock_request.httprequest.data = json.dumps(data).encode("utf-8")
|
||||||
response = self.controller.confirm_eskaera.__wrapped__(self.controller)
|
|
||||||
|
# Call confirm_eskaera
|
||||||
|
response = self.controller.confirm_eskaera()
|
||||||
|
|
||||||
# Verify response
|
# Verify response
|
||||||
response_data = json.loads(response.data.decode("utf-8"))
|
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
|
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."""
|
"""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
|
# Create existing draft order
|
||||||
existing_order = self.env["sale.order"].create(
|
existing_order = self.env["sale.order"].create(
|
||||||
{
|
{
|
||||||
"partner_id": self.partner.id,
|
"partner_id": self.partner.id,
|
||||||
"group_order_id": self.group_order.id,
|
"group_order_id": self.group_order.id,
|
||||||
"state": "draft",
|
"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": [
|
"order_line": [
|
||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
|
|
@ -895,6 +640,7 @@ class TestConfirmEskaera_Integration(TransactionCase):
|
||||||
|
|
||||||
existing_order_id = existing_order.id
|
existing_order_id = existing_order.id
|
||||||
|
|
||||||
|
# Prepare new request data
|
||||||
data = {
|
data = {
|
||||||
"order_id": self.group_order.id,
|
"order_id": self.group_order.id,
|
||||||
"items": [
|
"items": [
|
||||||
|
|
@ -906,14 +652,11 @@ class TestConfirmEskaera_Integration(TransactionCase):
|
||||||
],
|
],
|
||||||
"is_delivery": False,
|
"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):
|
mock_request.httprequest.data = json.dumps(data).encode("utf-8")
|
||||||
response = self.controller.confirm_eskaera.__wrapped__(self.controller)
|
|
||||||
|
# Call confirm_eskaera
|
||||||
|
response = self.controller.confirm_eskaera()
|
||||||
|
|
||||||
response_data = json.loads(response.data.decode("utf-8"))
|
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(len(existing_order.order_line), 1)
|
||||||
self.assertEqual(existing_order.order_line[0].product_uom_qty, 5)
|
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."""
|
"""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(
|
old_draft = self.env["sale.order"].create(
|
||||||
{
|
{
|
||||||
"partner_id": self.partner.id,
|
"partner_id": self.partner.id,
|
||||||
|
|
@ -958,15 +706,10 @@ class TestConfirmEskaera_Integration(TransactionCase):
|
||||||
],
|
],
|
||||||
"is_delivery": False,
|
"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):
|
mock_request.httprequest.data = json.dumps(data).encode("utf-8")
|
||||||
response = self.controller.confirm_eskaera.__wrapped__(self.controller)
|
|
||||||
|
|
||||||
|
response = self.controller.confirm_eskaera()
|
||||||
response_data = json.loads(response.data.decode("utf-8"))
|
response_data = json.loads(response.data.decode("utf-8"))
|
||||||
|
|
||||||
self.assertTrue(response_data.get("success"))
|
self.assertTrue(response_data.get("success"))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue