[IMP] website_sale_aplicoop: Validar disponibilidad de productos al cargar órdenes históricas

- Backend: Agregar método _validate_items_for_group_order() para validar que los productos históricos sigan siendo disponibles en la orden de grupo actual
- Backend: Modificar load_order_from_history() para filtrar solo items disponibles antes de pasar al template
- Backend: Generar mensaje de aviso traducido cuando hay productos no disponibles
- Template: Pasar información de productos no disponibles y warnings al JavaScript
- Frontend: Mostrar notificación de advertencia si hubo productos excluidos durante la carga histórica
- Notas: Esto evita cargar productos que ya no existen en la orden actual debido a cambios en categorías, proveedores o listas negras
This commit is contained in:
snt 2026-05-19 16:45:42 +02:00 committed by GitHub Copilot
parent 4a928e92dd
commit 3ca90578ae
7 changed files with 271 additions and 8 deletions

View file

@ -1511,6 +1511,87 @@ class AplicoopWebsiteSale(WebsiteSale):
status=status,
)
def _validate_items_for_group_order(self, items, group_order):
"""Validate items from historical order against current group order availability.
Args:
items: list of dicts with keys: product_id, product_name, quantity, price
group_order: group.order record
Returns:
dict with keys:
- available_items: list of items that are available in current group order
- unavailable_items: list of items that are NOT available
- unavailable_products: set of product IDs that are unavailable
- warning_message: str, message about unavailable items (or empty)
"""
if not items:
return {
"available_items": [],
"unavailable_items": [],
"unavailable_products": set(),
"warning_message": "",
}
# Get available products for the current group order
try:
available_products = request.env[
"group.order"
]._get_products_for_group_order(group_order.id)
available_product_ids = set(available_products.ids)
except Exception as e:
_logger.error(
"Error getting available products for group_order %d: %s",
group_order.id,
e,
)
# If something fails, return all items as available (failsafe)
return {
"available_items": items,
"unavailable_items": [],
"unavailable_products": set(),
"warning_message": "",
}
# Separate items into available and unavailable
available_items = []
unavailable_items = []
unavailable_product_ids = set()
for item in items:
product_id = item.get("product_id")
if product_id in available_product_ids:
available_items.append(item)
else:
unavailable_items.append(item)
unavailable_product_ids.add(product_id)
# Build warning message if there are unavailable items
warning_message = ""
if unavailable_items:
unavailable_names = [
item.get("product_name", "Unknown") for item in unavailable_items
]
warning_message = request.env._(
"%(count)d product(s) from your saved order are no longer available in this group order: %(names)s. "
"Only available products will be loaded.",
count=len(unavailable_items),
names=", ".join(unavailable_names),
)
_logger.warning(
"load_order_from_history: %d unavailable items in group_order %d (products: %s)",
len(unavailable_items),
group_order.id,
unavailable_product_ids,
)
return {
"available_items": available_items,
"unavailable_items": unavailable_items,
"unavailable_products": unavailable_product_ids,
"warning_message": warning_message,
}
def _find_recent_draft_order(self, partner_id, group_order):
"""Find most recent draft sale.order for partner in the active order period.
@ -2218,12 +2299,31 @@ class AplicoopWebsiteSale(WebsiteSale):
status=404,
)
# Determine if cutoff date for the current cycle has already passed
try:
today = fields.Date.today()
cutoff_passed = False
cutoff_date_str = None
if group_order.cutoff_date:
cutoff_passed = group_order.cutoff_date < today
# Convert to ISO-like string for frontend (YYYY-MM-DD)
cutoff_date_str = str(group_order.cutoff_date)
except Exception:
cutoff_passed = False
cutoff_date_str = None
response_data = {
"success": True,
"order_id": group_order.id,
"state": group_order.state,
"is_open": group_order.state == "open",
"action": "clear_cart" if group_order.state != "open" else "none",
"action": (
"clear_cart"
if (group_order.state != "open" or cutoff_passed)
else "none"
),
"cutoff_passed": cutoff_passed,
"cutoff_date": cutoff_date_str,
}
return request.make_response(
json.dumps(response_data),
@ -2975,9 +3075,14 @@ class AplicoopWebsiteSale(WebsiteSale):
if sale_order.group_order_id.id != group_order_id:
return request.redirect("/eskaera/%d" % sale_order.group_order_id.id)
# Get the current group_order (the one being viewed, not necessarily the one from the history)
group_order = request.env["group.order"].sudo().browse(group_order_id)
if not group_order.exists():
return request.redirect("/shop")
# Extract items from the order (skip delivery product)
# Use the delivery_product_id from the group_order
delivery_product = sale_order.group_order_id.delivery_product_id
delivery_product = group_order.delivery_product_id
delivery_product_id = delivery_product.id if delivery_product else None
items = []
@ -2995,6 +3100,21 @@ class AplicoopWebsiteSale(WebsiteSale):
}
)
# Validate items against current group order availability
validation_result = self._validate_items_for_group_order(items, group_order)
available_items = validation_result["available_items"]
unavailable_items = validation_result["unavailable_items"]
warning_message = validation_result["warning_message"]
_logger.info(
"load_order_from_history: Loaded %d items, %d available, %d unavailable from sale_order %d into group_order %d",
len(items),
len(available_items),
len(unavailable_items),
sale_order_id,
group_order_id,
)
# Store items in localStorage by passing via URL parameter or session
# We'll use sessionStorage in JavaScript to avoid URL length limits
@ -3019,13 +3139,19 @@ class AplicoopWebsiteSale(WebsiteSale):
"website_sale_aplicoop.eskaera_load_from_history",
{
"group_order_id": group_order_id,
"items_json": json.dumps(items), # Pass serialized JSON
"items_json": json.dumps(
available_items
), # Pass ONLY available items
"sale_order": sale_order,
"sale_order_name": sale_order.name, # Pass order reference
"pickup_day": pickup_day_to_restore, # Pass pickup day (or None if different group)
"pickup_date": pickup_date_to_restore, # Pass pickup date (or None if different group)
"home_delivery": home_delivery_to_restore, # Pass home delivery flag (or None if different group)
"same_group_order": same_group_order, # Indicate if from same group order
"unavailable_items": unavailable_items, # List of unavailable items
"warning_message": warning_message, # Warning about unavailable products
"has_unavailable_items": len(unavailable_items)
> 0, # Boolean flag for template
},
),
)
@ -3269,3 +3395,50 @@ class AplicoopWebsiteSale(WebsiteSale):
"empty_cart": "Your cart is empty",
"added_to_cart": "added to cart",
}
# ================================================================
# CART REDIRECT METHODS - Redirect /shop/cart routes to /eskaera
# ================================================================
@http.route(["/shop/cart"], type="http", auth="public", website=True)
def cart_redirect(self, access_token=None, revive="", **post):
"""Redirect /shop/cart to /eskaera (no standard cart)."""
_logger.info("🛒 Redirecting /shop/cart → /eskaera")
return http.redirect_with_hash("/eskaera")
@http.route(
["/shop/cart/update"],
type="http",
auth="public",
website=True,
methods=["POST"],
)
def cart_update_redirect(self, **post):
"""Redirect /shop/cart/update to /eskaera (no standard cart)."""
_logger.info("🛒 Redirecting /shop/cart/update → /eskaera")
return http.redirect_with_hash("/eskaera")
@http.route(
["/shop/cart/update_json"],
type="http",
auth="public",
website=True,
methods=["POST"],
csrf=False,
)
def cart_update_json_redirect(self, **post):
"""Redirect /shop/cart/update_json to /eskaera (no standard cart)."""
_logger.info("🛒 Redirecting /shop/cart/update_json → /eskaera")
return http.redirect_with_hash("/eskaera")
@http.route(
["/shop/cart_quantity"],
type="http",
auth="public",
website=True,
methods=["GET"],
)
def cart_quantity_redirect(self):
"""Redirect /shop/cart_quantity to /eskaera (no standard cart)."""
_logger.info("🛒 Redirecting /shop/cart_quantity → /eskaera")
return http.redirect_with_hash("/eskaera")