diff --git a/website_sale_aplicoop/__manifest__.py b/website_sale_aplicoop/__manifest__.py
index 3de62ce..a566e3d 100644
--- a/website_sale_aplicoop/__manifest__.py
+++ b/website_sale_aplicoop/__manifest__.py
@@ -37,6 +37,7 @@
"views/res_partner_views.xml",
"views/res_config_settings_views.xml",
"views/website_templates.xml",
+ "views/website_sale_disable_cart.xml",
"views/product_template_views.xml",
"views/sale_order_views.xml",
"views/stock_picking_views.xml",
diff --git a/website_sale_aplicoop/controllers/website_sale.py b/website_sale_aplicoop/controllers/website_sale.py
index dec32e7..b302a49 100644
--- a/website_sale_aplicoop/controllers/website_sale.py
+++ b/website_sale_aplicoop/controllers/website_sale.py
@@ -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")
diff --git a/website_sale_aplicoop/models/group_order.py b/website_sale_aplicoop/models/group_order.py
index 399e508..3d30765 100644
--- a/website_sale_aplicoop/models/group_order.py
+++ b/website_sale_aplicoop/models/group_order.py
@@ -882,7 +882,7 @@ class GroupOrder(models.Model):
)
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 picking type for this group order.
Args:
sale_orders: Recordset of confirmed sale.order
@@ -902,9 +902,13 @@ class GroupOrder(models.Model):
)
grouped_pickings[picking.picking_type_id.id] |= picking
- scheduled_date = self.pickup_date
- if not scheduled_date and self.delivery_date:
- scheduled_date = self.delivery_date - timedelta(days=1)
+ scheduled_date = None
+ if self.pickup_date:
+ scheduled_date = fields.Datetime.to_datetime(self.pickup_date)
+ elif self.delivery_date:
+ scheduled_date = fields.Datetime.to_datetime(
+ self.delivery_date - timedelta(days=1)
+ )
for picking_type_id, pickings in grouped_pickings.items():
if not pickings:
diff --git a/website_sale_aplicoop/static/src/js/website_sale.js b/website_sale_aplicoop/static/src/js/website_sale.js
index 9e194a6..4c5bdb9 100644
--- a/website_sale_aplicoop/static/src/js/website_sale.js
+++ b/website_sale_aplicoop/static/src/js/website_sale.js
@@ -222,12 +222,14 @@
var pickupDayKey = "load_from_history_pickup_day_" + this.orderId;
var pickupDateKey = "load_from_history_pickup_date_" + this.orderId;
var homeDeliveryKey = "load_from_history_home_delivery_" + this.orderId;
+ var warningKey = "load_from_history_warning_" + this.orderId;
var itemsJson = sessionStorage.getItem(storageKey);
var orderName = sessionStorage.getItem(orderNameKey);
var pickupDay = sessionStorage.getItem(pickupDayKey);
var pickupDate = sessionStorage.getItem(pickupDateKey);
var homeDelivery = sessionStorage.getItem(homeDeliveryKey) === "true";
+ var warningMessage = sessionStorage.getItem(warningKey);
console.log("DEBUG: _loadFromHistory called for orderId:", this.orderId);
console.log("DEBUG: sessionStorageKey:", storageKey);
@@ -240,6 +242,7 @@
homeDelivery,
"(empty means different group order)"
);
+ console.log("DEBUG: warningMessage:", warningMessage);
if (!itemsJson || itemsJson === "[object Object]") {
console.log("No valid items from history found in sessionStorage");
@@ -248,6 +251,7 @@
sessionStorage.removeItem(pickupDayKey);
sessionStorage.removeItem(pickupDateKey);
sessionStorage.removeItem(homeDeliveryKey);
+ sessionStorage.removeItem(warningKey);
return;
}
@@ -269,6 +273,7 @@
sessionStorage.removeItem(pickupDayKey);
sessionStorage.removeItem(pickupDateKey);
sessionStorage.removeItem(homeDeliveryKey);
+ sessionStorage.removeItem(warningKey);
return;
}
@@ -349,12 +354,19 @@
}
this._showNotification(message, "success", 3000);
+ // Show warning if some products were unavailable
+ if (warningMessage) {
+ console.log("Showing warning about unavailable products:", warningMessage);
+ this._showNotification(warningMessage, "warning", 5000);
+ }
+
// Clear sessionStorage
sessionStorage.removeItem(storageKey);
sessionStorage.removeItem(orderNameKey);
sessionStorage.removeItem(pickupDayKey);
sessionStorage.removeItem(pickupDateKey);
sessionStorage.removeItem(homeDeliveryKey);
+ sessionStorage.removeItem(warningKey);
} catch (e) {
console.error("Error loading from history:", e);
console.error("itemsJson was:", itemsJson);
@@ -435,8 +447,32 @@
}
try {
var data = JSON.parse(xhr.responseText || "{}");
- if (data && data.is_open === false) {
+ // Clear cart if order is closed, action requests clearance, or cutoff already passed
+ var shouldClear = false;
+ if (data) {
+ if (data.is_open === false) {
+ shouldClear = true;
+ }
+ if (data.action && data.action === "clear_cart") {
+ shouldClear = true;
+ }
+ if (data.cutoff_passed === true) {
+ shouldClear = true;
+ }
+ }
+ if (shouldClear) {
+ console.log(
+ "[groupOrderShop] check-status: clearing cart (reason:",
+ data,
+ ")"
+ );
self._clearCurrentOrderCartSilently();
+ // Update on-screen cart if visible
+ try {
+ self._updateCartDisplay();
+ } catch (err) {
+ console.warn("_updateCartDisplay failed after clearing cart:", err);
+ }
}
} catch (e) {
console.warn("[groupOrderShop] check-status parse error", e);
diff --git a/website_sale_aplicoop/tests/test_cron_picking_batch.py b/website_sale_aplicoop/tests/test_cron_picking_batch.py
index f2cb3b5..152be12 100644
--- a/website_sale_aplicoop/tests/test_cron_picking_batch.py
+++ b/website_sale_aplicoop/tests/test_cron_picking_batch.py
@@ -361,6 +361,7 @@ class TestCronPickingBatch(TransactionCase):
# Call second time
group_order._confirm_linked_sale_orders()
+ so.invalidate_recordset()
batch_second = so.picking_ids[0].batch_id
batch_count_second = self.env["stock.picking.batch"].search_count(
diff --git a/website_sale_aplicoop/views/load_from_history_templates.xml b/website_sale_aplicoop/views/load_from_history_templates.xml
index 1f254df..5e3fb12 100644
--- a/website_sale_aplicoop/views/load_from_history_templates.xml
+++ b/website_sale_aplicoop/views/load_from_history_templates.xml
@@ -19,12 +19,17 @@
var homeDelivery = ;
var sameGroupOrder = ;
+ // Product availability warning
+ var hasUnavailableItems = ;
+ var warningMessage = '';
+
console.log('load_from_history template: groupOrderId=', groupOrderId);
console.log('load_from_history template: saleOrderName=', saleOrderName);
console.log('load_from_history template: pickupDay=', pickupDay);
console.log('load_from_history template: pickupDate=', pickupDate);
console.log('load_from_history template: homeDelivery=', homeDelivery);
console.log('load_from_history template: sameGroupOrder=', sameGroupOrder);
+ console.log('load_from_history template: hasUnavailableItems=', hasUnavailableItems);
console.log('load_from_history template: itemsJson type=', typeof itemsJson);
console.log('load_from_history template: itemsJson value=', itemsJson);
@@ -47,6 +52,12 @@
console.log('Skipped saving pickup fields (different group order - will use current group order days)');
}
+ // Store warning about unavailable products if they exist
+ if (hasUnavailableItems === 'true') {
+ sessionStorage['load_from_history_warning_' + groupOrderId] = warningMessage;
+ console.log('Unavailable products detected:', warningMessage);
+ }
+
console.log('Saved to sessionStorage[load_from_history_' + groupOrderId + ']:', itemsJsonString);
console.log('Saved order name to sessionStorage[load_from_history_order_name_' + groupOrderId + ']:', saleOrderName);
diff --git a/website_sale_aplicoop/views/website_sale_disable_cart.xml b/website_sale_aplicoop/views/website_sale_disable_cart.xml
new file mode 100644
index 0000000..1733dd8
--- /dev/null
+++ b/website_sale_aplicoop/views/website_sale_disable_cart.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ /
+ get
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+