[FIX] website_sale_aplicoop: Add portal user support for sale.order creation
Portal users don't have write/create permissions on sale.order by default. This causes errors when trying to create orders during checkout or draft save. Changes: - Add _get_salesperson_for_order() helper to retrieve partner's salesperson - Use sudo() for all sale.order create() operations - Automatically assign user_id (salesperson) when creating orders - Use sudo() for order updates and line modifications - Add fallback to commercial_partner_id.user_id for salesperson This ensures orders are created with proper permissions while maintaining traceability through the assigned salesperson. Test coverage: - Add test_portal_sale_order_creation.py with 3 tests - Test portal user creates sale.order - Test salesperson fallback logic - Test portal user updates order lines
This commit is contained in:
parent
cf9ea887c1
commit
ed8c6acd92
3 changed files with 243 additions and 28 deletions
|
|
@ -831,6 +831,30 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
)
|
)
|
||||||
return sale_order_lines
|
return sale_order_lines
|
||||||
|
|
||||||
|
def _get_salesperson_for_order(self, partner):
|
||||||
|
"""Get the salesperson (user_id) for creating sale orders.
|
||||||
|
|
||||||
|
For portal users without write access to sale.order, we need to create
|
||||||
|
the order as the assigned salesperson or with sudo().
|
||||||
|
|
||||||
|
Args:
|
||||||
|
partner: res.partner record
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
res.users record (salesperson) or False
|
||||||
|
"""
|
||||||
|
# First check if partner has an assigned salesperson
|
||||||
|
if partner.user_id and not partner.user_id._is_public():
|
||||||
|
return partner.user_id
|
||||||
|
|
||||||
|
# Fallback to commercial partner's salesperson
|
||||||
|
commercial_partner = partner.commercial_partner_id
|
||||||
|
if commercial_partner.user_id and not commercial_partner.user_id._is_public():
|
||||||
|
return commercial_partner.user_id
|
||||||
|
|
||||||
|
# No salesperson found
|
||||||
|
return False
|
||||||
|
|
||||||
def _create_or_update_sale_order(
|
def _create_or_update_sale_order(
|
||||||
self,
|
self,
|
||||||
group_order,
|
group_order,
|
||||||
|
|
@ -846,14 +870,16 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
"""
|
"""
|
||||||
if existing_order:
|
if existing_order:
|
||||||
# Update existing order with new lines and propagate fields
|
# Update existing order with new lines and propagate fields
|
||||||
existing_order.order_line = sale_order_lines
|
# Use sudo() to avoid permission issues with portal users
|
||||||
if not existing_order.group_order_id:
|
existing_order_sudo = existing_order.sudo()
|
||||||
existing_order.group_order_id = group_order.id
|
existing_order_sudo.order_line = sale_order_lines
|
||||||
existing_order.pickup_day = group_order.pickup_day
|
if not existing_order_sudo.group_order_id:
|
||||||
existing_order.pickup_date = group_order.pickup_date
|
existing_order_sudo.group_order_id = group_order.id
|
||||||
existing_order.home_delivery = is_delivery
|
existing_order_sudo.pickup_day = group_order.pickup_day
|
||||||
|
existing_order_sudo.pickup_date = group_order.pickup_date
|
||||||
|
existing_order_sudo.home_delivery = is_delivery
|
||||||
if commitment_date:
|
if commitment_date:
|
||||||
existing_order.commitment_date = commitment_date
|
existing_order_sudo.commitment_date = commitment_date
|
||||||
_logger.info(
|
_logger.info(
|
||||||
"Updated existing sale.order %d: commitment_date=%s, home_delivery=%s",
|
"Updated existing sale.order %d: commitment_date=%s, home_delivery=%s",
|
||||||
existing_order.id,
|
existing_order.id,
|
||||||
|
|
@ -874,7 +900,18 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
if commitment_date:
|
if commitment_date:
|
||||||
order_vals["commitment_date"] = commitment_date
|
order_vals["commitment_date"] = commitment_date
|
||||||
|
|
||||||
sale_order = request.env["sale.order"].create(order_vals)
|
# Get salesperson for order creation (portal users need this)
|
||||||
|
salesperson = self._get_salesperson_for_order(current_user.partner_id)
|
||||||
|
if salesperson:
|
||||||
|
order_vals["user_id"] = salesperson.id
|
||||||
|
_logger.info(
|
||||||
|
"Creating sale.order with salesperson %s (%d)",
|
||||||
|
salesperson.name,
|
||||||
|
salesperson.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create order with sudo to avoid permission issues with portal users
|
||||||
|
sale_order = request.env["sale.order"].sudo().create(order_vals)
|
||||||
_logger.info(
|
_logger.info(
|
||||||
"sale.order created successfully: %d with group_order_id=%d, pickup_day=%s, home_delivery=%s",
|
"sale.order created successfully: %d with group_order_id=%d, pickup_day=%s, home_delivery=%s",
|
||||||
sale_order.id,
|
sale_order.id,
|
||||||
|
|
@ -912,7 +949,18 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
elif group_order.pickup_date:
|
elif group_order.pickup_date:
|
||||||
order_vals["commitment_date"] = group_order.pickup_date
|
order_vals["commitment_date"] = group_order.pickup_date
|
||||||
|
|
||||||
sale_order = request.env["sale.order"].create(order_vals)
|
# Get salesperson for order creation (portal users need this)
|
||||||
|
salesperson = self._get_salesperson_for_order(current_user.partner_id)
|
||||||
|
if salesperson:
|
||||||
|
order_vals["user_id"] = salesperson.id
|
||||||
|
_logger.info(
|
||||||
|
"Creating draft sale.order with salesperson %s (%d)",
|
||||||
|
salesperson.name,
|
||||||
|
salesperson.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create order with sudo to avoid permission issues with portal users
|
||||||
|
sale_order = request.env["sale.order"].sudo().create(order_vals)
|
||||||
|
|
||||||
# Ensure the order has a name (sequence)
|
# Ensure the order has a name (sequence)
|
||||||
try:
|
try:
|
||||||
|
|
@ -1158,7 +1206,8 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
lambda line, pid=product_id: line.product_id.id == pid
|
lambda line, pid=product_id: line.product_id.id == pid
|
||||||
)
|
)
|
||||||
if existing_line:
|
if existing_line:
|
||||||
existing_line.write(
|
# Use sudo() to avoid permission issues with portal users
|
||||||
|
existing_line.sudo().write(
|
||||||
{
|
{
|
||||||
"product_uom_qty": existing_line.product_uom_qty
|
"product_uom_qty": existing_line.product_uom_qty
|
||||||
+ new_quantity
|
+ new_quantity
|
||||||
|
|
@ -1170,7 +1219,8 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
existing_line.product_uom_qty,
|
existing_line.product_uom_qty,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
existing_draft.order_line.create(
|
# Use sudo() to avoid permission issues with portal users
|
||||||
|
existing_draft.order_line.sudo().create(
|
||||||
{
|
{
|
||||||
"order_id": existing_draft.id,
|
"order_id": existing_draft.id,
|
||||||
"product_id": product_id,
|
"product_id": product_id,
|
||||||
|
|
@ -1193,8 +1243,7 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
"Deleted existing draft(s) for replace: %s",
|
"Deleted existing draft(s) for replace: %s",
|
||||||
existing_drafts.mapped("id"),
|
existing_drafts.mapped("id"),
|
||||||
)
|
)
|
||||||
sale_order = request.env["sale.order"].create(
|
order_vals = {
|
||||||
{
|
|
||||||
"partner_id": current_user.partner_id.id,
|
"partner_id": current_user.partner_id.id,
|
||||||
"order_line": sale_order_lines,
|
"order_line": sale_order_lines,
|
||||||
"state": "draft",
|
"state": "draft",
|
||||||
|
|
@ -1203,12 +1252,16 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
"pickup_date": group_order.pickup_date,
|
"pickup_date": group_order.pickup_date,
|
||||||
"home_delivery": group_order.home_delivery,
|
"home_delivery": group_order.home_delivery,
|
||||||
}
|
}
|
||||||
)
|
# Get salesperson for order creation (portal users need this)
|
||||||
|
salesperson = self._get_salesperson_for_order(current_user.partner_id)
|
||||||
|
if salesperson:
|
||||||
|
order_vals["user_id"] = salesperson.id
|
||||||
|
|
||||||
|
sale_order = request.env["sale.order"].sudo().create(order_vals)
|
||||||
return sale_order, False
|
return sale_order, False
|
||||||
|
|
||||||
# Default: create new draft
|
# Default: create new draft
|
||||||
sale_order = request.env["sale.order"].create(
|
order_vals = {
|
||||||
{
|
|
||||||
"partner_id": current_user.partner_id.id,
|
"partner_id": current_user.partner_id.id,
|
||||||
"order_line": sale_order_lines,
|
"order_line": sale_order_lines,
|
||||||
"state": "draft",
|
"state": "draft",
|
||||||
|
|
@ -1217,7 +1270,12 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
"pickup_date": group_order.pickup_date,
|
"pickup_date": group_order.pickup_date,
|
||||||
"home_delivery": group_order.home_delivery,
|
"home_delivery": group_order.home_delivery,
|
||||||
}
|
}
|
||||||
)
|
# Get salesperson for order creation (portal users need this)
|
||||||
|
salesperson = self._get_salesperson_for_order(current_user.partner_id)
|
||||||
|
if salesperson:
|
||||||
|
order_vals["user_id"] = salesperson.id
|
||||||
|
|
||||||
|
sale_order = request.env["sale.order"].sudo().create(order_vals)
|
||||||
return sale_order, False
|
return sale_order, False
|
||||||
|
|
||||||
def _decode_json_body(self):
|
def _decode_json_body(self):
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,4 @@ from . import test_multi_company
|
||||||
from . import test_save_order_endpoints
|
from . import test_save_order_endpoints
|
||||||
from . import test_date_calculations
|
from . import test_date_calculations
|
||||||
from . import test_pricing_with_pricelist
|
from . import test_pricing_with_pricelist
|
||||||
|
from . import test_portal_sale_order_creation
|
||||||
|
|
|
||||||
156
website_sale_aplicoop/tests/test_portal_sale_order_creation.py
Normal file
156
website_sale_aplicoop/tests/test_portal_sale_order_creation.py
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
# Copyright 2026 Criptomart
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
"""Test portal users can create sale orders with proper permissions."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from odoo.tests import tagged
|
||||||
|
from odoo.tests.common import TransactionCase
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@tagged("post_install", "-at_install")
|
||||||
|
class TestPortalSaleOrderCreation(TransactionCase):
|
||||||
|
"""Test that portal users can create sale orders through the controller."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
|
||||||
|
# Create a portal user
|
||||||
|
cls.portal_user = cls.env["res.users"].create(
|
||||||
|
{
|
||||||
|
"name": "Portal Test User",
|
||||||
|
"login": "portal_test_user",
|
||||||
|
"email": "portal@test.com",
|
||||||
|
"groups_id": [(6, 0, [cls.env.ref("base.group_portal").id])],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a salesperson
|
||||||
|
cls.salesperson = cls.env["res.users"].create(
|
||||||
|
{
|
||||||
|
"name": "Salesperson Test",
|
||||||
|
"login": "salesperson_test",
|
||||||
|
"email": "sales@test.com",
|
||||||
|
"groups_id": [
|
||||||
|
(6, 0, [cls.env.ref("sales_team.group_sale_salesman").id])
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assign salesperson to portal user's partner
|
||||||
|
cls.portal_user.partner_id.user_id = cls.salesperson
|
||||||
|
|
||||||
|
# Create a group order for testing
|
||||||
|
cls.group_order = cls.env["group.order"].create(
|
||||||
|
{
|
||||||
|
"name": "Test Group Order",
|
||||||
|
"state": "confirmed",
|
||||||
|
"pickup_day": "0", # Monday
|
||||||
|
"pickup_date": datetime.now().date() + timedelta(days=7),
|
||||||
|
"cutoff_date": datetime.now().date() + timedelta(days=3),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a test product
|
||||||
|
cls.product = cls.env["product.product"].create(
|
||||||
|
{
|
||||||
|
"name": "Test Product",
|
||||||
|
"list_price": 100.0,
|
||||||
|
"type": "product",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_portal_user_can_create_sale_order(self):
|
||||||
|
"""Test that portal users can create sale orders with sudo()."""
|
||||||
|
# Create sale order as portal user
|
||||||
|
order_vals = {
|
||||||
|
"partner_id": self.portal_user.partner_id.id,
|
||||||
|
"order_line": [
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
"product_id": self.product.id,
|
||||||
|
"product_uom_qty": 2,
|
||||||
|
"price_unit": 100.0,
|
||||||
|
"name": self.product.name,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
"state": "draft",
|
||||||
|
"group_order_id": self.group_order.id,
|
||||||
|
"user_id": self.salesperson.id, # Assign salesperson
|
||||||
|
}
|
||||||
|
|
||||||
|
# This should work with sudo()
|
||||||
|
sale_order = self.env["sale.order"].sudo().create(order_vals)
|
||||||
|
|
||||||
|
self.assertTrue(sale_order.exists())
|
||||||
|
self.assertEqual(sale_order.partner_id, self.portal_user.partner_id)
|
||||||
|
self.assertEqual(sale_order.user_id, self.salesperson)
|
||||||
|
self.assertEqual(len(sale_order.order_line), 1)
|
||||||
|
self.assertEqual(sale_order.group_order_id, self.group_order)
|
||||||
|
|
||||||
|
def test_get_salesperson_fallback(self):
|
||||||
|
"""Test salesperson fallback to commercial partner."""
|
||||||
|
# Create commercial partner with salesperson
|
||||||
|
commercial_partner = self.env["res.partner"].create(
|
||||||
|
{
|
||||||
|
"name": "Commercial Partner",
|
||||||
|
"is_company": True,
|
||||||
|
"user_id": self.salesperson.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create child contact without salesperson
|
||||||
|
child_partner = self.env["res.partner"].create(
|
||||||
|
{
|
||||||
|
"name": "Child Contact",
|
||||||
|
"parent_id": commercial_partner.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Child should fallback to commercial partner's salesperson
|
||||||
|
self.assertEqual(
|
||||||
|
child_partner.commercial_partner_id.user_id, self.salesperson
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_portal_user_can_update_order_lines(self):
|
||||||
|
"""Test that portal users can update existing order lines with sudo()."""
|
||||||
|
# Create initial order
|
||||||
|
sale_order = (
|
||||||
|
self.env["sale.order"]
|
||||||
|
.sudo()
|
||||||
|
.create(
|
||||||
|
{
|
||||||
|
"partner_id": self.portal_user.partner_id.id,
|
||||||
|
"state": "draft",
|
||||||
|
"group_order_id": self.group_order.id,
|
||||||
|
"user_id": self.salesperson.id,
|
||||||
|
"order_line": [
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
"product_id": self.product.id,
|
||||||
|
"product_uom_qty": 1,
|
||||||
|
"price_unit": 100.0,
|
||||||
|
"name": self.product.name,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update order line as portal user (with sudo)
|
||||||
|
existing_line = sale_order.order_line[0]
|
||||||
|
existing_line.sudo().write({"product_uom_qty": 5})
|
||||||
|
|
||||||
|
self.assertEqual(existing_line.product_uom_qty, 5)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue