[ADD] website_sale_aplicoop: serve Eskaera per website

A co-op can run the plain shop on one website and the group orders on another,
but routes are registered process-wide: every /eskaera page answered on every
website of the database, and Odoo had already copied the Eskaera menu to all of
them.

`website.eskaera_enabled` decides which websites serve it, on by default so
installing changes nothing. It reaches the settings screen through `website_id`,
so it follows the website selector there. Where it is off the routes raise
NotFound and the menu is hidden.

The menu is hidden rather than deleted, by extending `_compute_visible`. That
keeps the record and any manual rename or reordering, so switching the feature
back on restores it as it was.

Note that a JSON route reports the 404 inside the JSON-RPC payload and still
answers HTTP 200; that is the transport, not a hole in the guard, and a test
pins it so the next reader does not take it for one.

The three remaining settings (lazy loading, products per page, low stock
threshold) are still `config_parameter`, so they stay global to the database.
Two websites share their values.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
GitHub Copilot 2026-08-17 17:31:03 +02:00
parent 817ff31d39
commit 5a2f5d120f
9 changed files with 313 additions and 0 deletions

View file

@ -1,10 +1,13 @@
# Copyright 2025 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
import functools
import json
import logging
from urllib.parse import urlencode
from werkzeug.exceptions import NotFound
from odoo import fields
from odoo import http
from odoo.http import request
@ -25,6 +28,22 @@ from .exceptions import GroupOrderUnavailable
_logger = logging.getLogger(__name__)
def eskaera_route(route_method):
"""Serve an Eskaera page only on websites that run Eskaera.
A co-op can keep the plain shop on one website and the group orders on
another. Routes are registered process-wide, so without this guard the
/eskaera pages would answer on every website of the database.
"""
@functools.wraps(route_method)
def wrapper(self, *args, **kwargs):
if not request.website.eskaera_enabled:
raise NotFound()
return route_method(self, *args, **kwargs)
return wrapper
class AplicoopWebsiteSale(WebsiteSale):
"""Controlador personalizado para website_sale de Aplicoop.
@ -435,6 +454,7 @@ class AplicoopWebsiteSale(WebsiteSale):
)
@http.route(["/eskaera"], type="http", auth="user", website=True)
@eskaera_route
def eskaera_list(self, **post):
"""Página de pedidos de grupo abiertos esta semana.
@ -711,6 +731,7 @@ class AplicoopWebsiteSale(WebsiteSale):
return request.redirect(url)
@http.route(["/eskaera/<int:order_id>"], type="http", auth="user", website=True)
@eskaera_route
def eskaera_shop_legacy(self, order_id, **post):
"""Keep the old numeric shop URL working, pointing at the slug one."""
return self._redirect_to_slug_url(order_id, post)
@ -721,6 +742,7 @@ class AplicoopWebsiteSale(WebsiteSale):
auth="user",
website=True,
)
@eskaera_route
def eskaera_shop(self, group_order_slug, **post):
"""Página de tienda para un pedido específico (eskaera).
@ -913,6 +935,7 @@ class AplicoopWebsiteSale(WebsiteSale):
website=True,
methods=["GET"],
)
@eskaera_route
def load_eskaera_page(self, order_id, **post):
"""Load next page of products for lazy loading.
@ -1030,6 +1053,7 @@ class AplicoopWebsiteSale(WebsiteSale):
methods=["POST"],
csrf=False,
)
@eskaera_route
def load_products_ajax(self, order_id, **post):
"""Load products via AJAX for infinite scroll.
@ -1166,6 +1190,7 @@ class AplicoopWebsiteSale(WebsiteSale):
@http.route(
["/eskaera/<int:order_id>/checkout"], type="http", auth="user", website=True
)
@eskaera_route
def eskaera_checkout_legacy(self, order_id, **post):
"""Keep the old numeric checkout URL working, pointing at the slug one."""
return self._redirect_to_slug_url(order_id, post, suffix="/checkout")
@ -1176,6 +1201,7 @@ class AplicoopWebsiteSale(WebsiteSale):
auth="user",
website=True,
)
@eskaera_route
def eskaera_checkout(self, group_order_slug, **post):
"""Checkout page to close the cart for the order (eskaera)."""
group_order = self._get_group_order_by_slug(group_order_slug)
@ -1381,6 +1407,7 @@ class AplicoopWebsiteSale(WebsiteSale):
auth="user",
website=True,
)
@eskaera_route
def eskaera_payment(self, group_order_slug, **post):
"""Payment step: pick a method and pay the order placed at checkout."""
group_order = self._get_group_order_by_slug(group_order_slug)
@ -1451,6 +1478,7 @@ class AplicoopWebsiteSale(WebsiteSale):
auth="user",
website=True,
)
@eskaera_route
def eskaera_payment_confirmation(self, group_order_slug, order_id, **post):
"""Landing page after paying: show the outcome and free the cart.
@ -1488,6 +1516,7 @@ class AplicoopWebsiteSale(WebsiteSale):
methods=["POST"],
csrf=False,
)
@eskaera_route
def check_group_order_status(self, **post):
"""Return status information for a group.order.
@ -1568,6 +1597,7 @@ class AplicoopWebsiteSale(WebsiteSale):
methods=["POST"],
csrf=False,
)
@eskaera_route
def load_draft_cart(self, **post):
"""Load items from the most recent draft sale.order for current period."""
import json
@ -1727,6 +1757,7 @@ class AplicoopWebsiteSale(WebsiteSale):
methods=["POST"],
csrf=False,
)
@eskaera_route
def eskaera_clear_cart(self, **post):
"""Clear the user's cart and cancel any existing draft sale.order.
@ -1828,6 +1859,7 @@ class AplicoopWebsiteSale(WebsiteSale):
methods=["POST"],
csrf=False,
)
@eskaera_route
def save_eskaera_draft(self, **post):
"""Save order as draft (without confirming).
@ -1968,6 +2000,7 @@ class AplicoopWebsiteSale(WebsiteSale):
methods=["POST"],
csrf=False,
)
@eskaera_route
def confirm_eskaera(self, **post):
"""Confirm order and create sale.order from cart (localStorage).
@ -2130,6 +2163,7 @@ class AplicoopWebsiteSale(WebsiteSale):
auth="user",
website=True,
)
@eskaera_route
def load_order_from_history(self, group_order_id=None, sale_order_id=None, **post):
"""Load a historical order (draft/confirmed) back into the cart.
@ -2276,6 +2310,7 @@ class AplicoopWebsiteSale(WebsiteSale):
website=True,
methods=["POST"],
)
@eskaera_route
def confirm_order_from_portal(
self, group_order_id=None, sale_order_id=None, **post
):
@ -2367,6 +2402,7 @@ class AplicoopWebsiteSale(WebsiteSale):
website=True,
csrf=False,
)
@eskaera_route
def get_checkout_labels(self, **post):
"""Return ALL translated UI labels and messages unified.