diff --git a/website_sale_aplicoop/controllers/website_sale.py b/website_sale_aplicoop/controllers/website_sale.py index b302a49..2ad7bae 100644 --- a/website_sale_aplicoop/controllers/website_sale.py +++ b/website_sale_aplicoop/controllers/website_sale.py @@ -302,6 +302,7 @@ class AplicoopWebsiteSale(WebsiteSale): category_map[cat.id] = { "id": cat.id, "name": cat.name, + "sequence": cat.sequence, "parent_id": cat.parent_id.id if cat.parent_id else None, "children": [], } @@ -319,7 +320,7 @@ class AplicoopWebsiteSale(WebsiteSale): category_map[parent_id]["children"].append(cat_info) def sort_hierarchy(items): - items.sort(key=lambda x: x["name"]) + items.sort(key=lambda x: (x["sequence"], x["name"])) for item in items: if item["children"]: sort_hierarchy(item["children"]) @@ -1272,20 +1273,78 @@ class AplicoopWebsiteSale(WebsiteSale): "pickup_day_index": pickup_day_index, } - def _format_pickup_info(self, group_order, is_delivery): + def _format_pickup_info(self, group_order, is_delivery): # noqa: C901 """Return (pickup_day_name, pickup_date_str, pickup_day_index) localized. Encapsulates day name detection and date formatting to reduce method complexity. """ - # Get pickup day index + pickup_day_name = "" + pickup_date_str = "" + pickup_day_index = None + + # Prefer configured next pickup slot when available + slot = getattr(group_order, "next_pickup_slot_id", False) or False + if slot: + try: + pickup_day_index = int(slot.weekday) + except Exception: + pickup_day_index = None + + # Day name + if pickup_day_index is not None: + try: + day_names = self._get_day_names(env=request.env) + pickup_day_name = day_names[pickup_day_index % len(day_names)] + except Exception: + pickup_day_name = "" + + # Time label (prefer human label if provided) + time_label = "" + try: + if slot.label: + time_label = slot.label + else: + sh = float(slot.start_hour or 0.0) + eh = float(slot.end_hour or 0.0) + sh_h = int(sh) + sh_m = int(round((sh - sh_h) * 60)) + eh_h = int(eh) + eh_m = int(round((eh - eh_h) * 60)) + time_label = f"{sh_h:02d}:{sh_m:02d}-{eh_h:02d}:{eh_m:02d}" + except Exception: + time_label = "" + + # Date: prefer next_pickup_datetime, fallback to pickup_date + dt_val = getattr(group_order, "next_pickup_datetime", None) or getattr( + group_order, "pickup_date", None + ) + if dt_val: + try: + if isinstance(dt_val, str): + dt = fields.Datetime.to_datetime(dt_val) + pickup_date_str = dt.strftime("%d/%m/%Y") + elif hasattr(dt_val, "strftime"): + pickup_date_str = dt_val.strftime("%d/%m/%Y") + else: + pickup_date_str = str(dt_val) + except Exception: + pickup_date_str = str(dt_val) + + # Compose final name including time label + if time_label: + if pickup_day_name: + pickup_day_name = f"{pickup_day_name} {time_label}" + else: + pickup_day_name = time_label + + return pickup_day_name, pickup_date_str, pickup_day_index + + # Fallback: legacy single-day behavior try: pickup_day_index = int(group_order.pickup_day) except Exception: pickup_day_index = None - pickup_day_name = "" - pickup_date_str = "" - # Get translated day names if pickup_day_index is not None: try: @@ -1386,7 +1445,9 @@ class AplicoopWebsiteSale(WebsiteSale): available_categories = ( request.env["product.category"].sudo().browse(list(all_categories_set)) ) - available_categories = sorted(set(available_categories), key=lambda c: c.name) + available_categories = sorted( + set(available_categories), key=lambda c: (c.sequence, c.name) + ) category_hierarchy = self._build_category_hierarchy(available_categories) return all_products, available_categories, category_hierarchy @@ -2338,7 +2399,7 @@ class AplicoopWebsiteSale(WebsiteSale): methods=["POST"], csrf=False, ) - def save_cart_draft(self, **post): + def save_cart_draft(self, **post): # noqa: C901 """Save cart items as a draft sale.order with pickup date.""" try: _logger.warning("=== SAVE_CART_DRAFT CALLED ===") @@ -2438,12 +2499,29 @@ class AplicoopWebsiteSale(WebsiteSale): current_user.partner_id.id, ) + # Compute a readable pickup slot label for the response. Prefer the + # order's stored computed label, otherwise derive from the group + # order using the same helper the confirmation flow uses. + pickup_slot_label = ( + sale_order.pickup_slot_label + if getattr(sale_order, "pickup_slot_label", False) + else None + ) + if not pickup_slot_label: + try: + pickup_slot_label = self._format_pickup_info( + group_order, is_delivery + )[0] + except Exception: + pickup_slot_label = None + return request.make_response( json.dumps( { "success": True, "message": request.env._("Cart saved as draft"), "sale_order_id": sale_order.id, + "pickup_slot_label": pickup_slot_label, } ), [("Content-Type", "application/json")], @@ -2587,6 +2665,14 @@ class AplicoopWebsiteSale(WebsiteSale): if draft_order.pickup_date else None ), + # Provide a human readable pickup slot label so the frontend + # doesn't need to compute/lookup slots. This may be empty + # but it's the preferred source for displaying pickup info. + "pickup_slot_label": ( + draft_order.pickup_slot_label + if getattr(draft_order, "pickup_slot_label", False) + else None + ), "home_delivery": draft_order.home_delivery, } ), @@ -2863,12 +2949,28 @@ class AplicoopWebsiteSale(WebsiteSale): sale_order.home_delivery, ) + # Provide pickup_slot_label to the frontend so it can display a + # human-readable pickup summary without extra logic client-side. + pickup_slot_label = ( + sale_order.pickup_slot_label + if getattr(sale_order, "pickup_slot_label", False) + else None + ) + if not pickup_slot_label: + try: + pickup_slot_label = self._format_pickup_info( + group_order, is_delivery + )[0] + except Exception: + pickup_slot_label = None + return request.make_response( json.dumps( { "success": True, "message": request.env._("Order saved as draft"), "sale_order_id": sale_order.id, + "pickup_slot_label": pickup_slot_label, } ), [("Content-Type", "application/json")], @@ -3000,6 +3102,14 @@ class AplicoopWebsiteSale(WebsiteSale): "pickup_day_index": pickup_day_index, } + # Also include the human-readable pickup slot label to simplify + # client-side rendering (may be None). + response_data["pickup_slot_label"] = ( + sale_order.pickup_slot_label + if getattr(sale_order, "pickup_slot_label", False) + else pickup_day_name + ) + # Log language and final message to debug translation issues try: _logger.info( @@ -3133,6 +3243,13 @@ class AplicoopWebsiteSale(WebsiteSale): home_delivery_to_restore = ( sale_order.home_delivery if same_group_order else None ) + # Only restore a human-readable label for the pickup slot. Do NOT + # restore or expose internal slot IDs to the frontend. + pickup_slot_label_to_restore = ( + sale_order.pickup_slot_label + if same_group_order and sale_order.pickup_slot_label + else None + ) response = request.make_response( request.render( @@ -3146,6 +3263,9 @@ class AplicoopWebsiteSale(WebsiteSale): "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) + # Do NOT pass slot IDs to the client. Only the readable + # label is useful for the UI and is safe to expose. + "pickup_slot_label": pickup_slot_label_to_restore, "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 diff --git a/website_sale_aplicoop/migrations/18.0.1.0.3/post-migrate.py b/website_sale_aplicoop/migrations/18.0.1.0.3/post-migrate.py new file mode 100644 index 0000000..43b06d2 --- /dev/null +++ b/website_sale_aplicoop/migrations/18.0.1.0.3/post-migrate.py @@ -0,0 +1,43 @@ +"""Remove legacy pickup_slot_id column from sale_order. + +This migration drops the unused pickup_slot_id column which used to store a +snapshot of the assigned slot on each sale.order. We no longer persist that +reference; keep a human readable `pickup_slot_label` instead. +""" + +import logging + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version): + # Raw SQL is used to ensure the column is dropped even if foreign key + # constraints exist. We try to drop the FK constraint first and then the + # column. Use IF EXISTS to avoid errors on already-migrated DBs. + try: + # Try dropping common FK constraint name (Postgres naming) + cr.execute(""" + DO $$ + BEGIN + IF EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conrelid = 'sale_order'::regclass + AND conname = 'sale_order_pickup_slot_id_fkey' + ) THEN + ALTER TABLE sale_order DROP CONSTRAINT sale_order_pickup_slot_id_fkey; + END IF; + END$$; + """) + except Exception as exc: # pragma: no cover - DB-level migration safeguard + # Not critical; constraint may have different name or not exist + _logger.debug( + "Could not drop FK constraint for sale_order.pickup_slot_id: %s", exc + ) + + try: + cr.execute("ALTER TABLE sale_order DROP COLUMN IF EXISTS pickup_slot_id;") + except Exception as exc: # pragma: no cover - DB-level migration safeguard + # If the column cannot be dropped (e.g. referenced elsewhere), log and + # continue; DB admins can drop manually if needed. + _logger.warning("Could not drop column sale_order.pickup_slot_id: %s", exc) diff --git a/website_sale_aplicoop/models/group_order.py b/website_sale_aplicoop/models/group_order.py index 3d30765..0fdcd52 100644 --- a/website_sale_aplicoop/models/group_order.py +++ b/website_sale_aplicoop/models/group_order.py @@ -10,6 +10,9 @@ from odoo import models from odoo.exceptions import ValidationError _logger = logging.getLogger(__name__) +# Pylint: explicit 'string' attributes are intentional for readable labels in views. +# Some linters flag these as redundant; disable that specific check here. +# pylint: disable=attribute-string-redundant class GroupOrder(models.Model): @@ -512,16 +515,149 @@ class GroupOrder(models.Model): return products_page, total_count, has_next - @api.depends("cutoff_date", "pickup_day") - def _compute_pickup_date(self): - """Compute pickup date as the first occurrence of pickup_day AFTER cutoff_date. + # === Pickup slots helpers === + pickup_slot_ids = fields.One2many( + "group.order.slot", + "group_order_id", + string="Pickup slots", + help="Different pickup time slots available for this order (weekday + time)", + tracking=True, + ) - This ensures pickup always comes after cutoff, maintaining logical order. + pickup_slots_count = fields.Integer( + compute="_compute_pickup_slots_count", + store=False, + help="Number of pickup slots configured for this order", + ) + + next_pickup_slot_id = fields.Many2one( + "group.order.slot", + string="Next Pickup Slot", + compute="_compute_next_pickup_slot", + store=True, + help="The pickup slot assigned for the next cycle (computed)", + ) + + next_pickup_datetime = fields.Datetime( + string="Next Pickup Datetime", + compute="_compute_next_pickup_slot", + store=True, + help="Datetime of the next pickup occurrence for the selected slot", + ) + + @api.depends("pickup_slot_ids") + def _compute_pickup_slots_count(self): + """Simple count of configured slots for quick UI badges.""" + for record in self: + record.pickup_slots_count = len(record.pickup_slot_ids or []) + + @api.depends( + "pickup_slot_ids", + "pickup_slot_ids.start_hour", + "pickup_slot_ids.weekday", + "cutoff_date", + "start_date", + ) + def _compute_next_pickup_slot(self): + """Compute the next pickup slot and its concrete datetime. + + Rules: + - If slots are configured, compute for each active slot the next + occurrence (date + start_hour) strictly AFTER the reference date + (cutoff_date if present, otherwise start_date or today). + - Select the slot whose occurrence datetime is the soonest (minimum). + - If no slots are configured, leave fields empty (fallback handled + by existing pickup_day logic). + """ + from datetime import datetime + from datetime import time + + for record in self: + record.next_pickup_slot_id = False + record.next_pickup_datetime = False + + slots = record.pickup_slot_ids.filtered(lambda s: s.active) + if not slots: + continue + + # Determine reference date (use cutoff_date if present) + if record.cutoff_date: + reference_date = record.cutoff_date + else: + today = datetime.now().date() + if record.start_date and record.start_date < today: + reference_date = today + else: + reference_date = record.start_date or today + + candidate_datetimes = [] + for slot in slots: + try: + slot_weekday = int(slot.weekday) + except Exception: + # Skip malformed slot + continue + + current_weekday = reference_date.weekday() + days_ahead = slot_weekday - current_weekday + # Ensure NEXT occurrence AFTER reference (not same-day) + if days_ahead <= 0: + days_ahead += 7 + + target_date = reference_date + timedelta(days=days_ahead) + + # Convert start_hour float to time + sh = float(slot.start_hour or 0.0) + sh_h = int(sh) + sh_m = int(round((sh - sh_h) * 60)) + try: + slot_dt = datetime.combine(target_date, time(sh_h, sh_m)) + except Exception: + # Fallback to date-only + slot_dt = datetime.combine(target_date, time(0, 0)) + + candidate_datetimes.append((slot_dt, slot)) + + if not candidate_datetimes: + continue + + # Choose earliest datetime + candidate_datetimes.sort(key=lambda x: x[0]) + chosen_dt, chosen_slot = candidate_datetimes[0] + + # Assign results (store datetime as timezone-naive; Odoo will convert) + record.next_pickup_slot_id = chosen_slot + record.next_pickup_datetime = chosen_dt + + @api.depends("cutoff_date", "pickup_day", "pickup_slot_ids", "next_pickup_datetime") + def _compute_pickup_date(self): + """Compute pickup date. + + If pickup slots are configured, derive `pickup_date` from the computed + `next_pickup_datetime`. Otherwise, fall back to the previous + single-day `pickup_day` behavior. """ from datetime import datetime _logger.info("_compute_pickup_date called for %d records", len(self)) for record in self: + # If slots exist, prefer the computed next_pickup_datetime + if record.pickup_slot_ids: + if record.next_pickup_datetime: + try: + dt = ( + fields.Datetime.to_datetime(record.next_pickup_datetime) + if isinstance(record.next_pickup_datetime, str) + else record.next_pickup_datetime + ) + record.pickup_date = dt.date() + except Exception: + record.pickup_date = None + else: + record.pickup_date = None + continue + + # Fallback: original single pickup_day logic if not record.pickup_day: record.pickup_date = None continue diff --git a/website_sale_aplicoop/models/group_order_slot.py b/website_sale_aplicoop/models/group_order_slot.py new file mode 100644 index 0000000..b261aaa --- /dev/null +++ b/website_sale_aplicoop/models/group_order_slot.py @@ -0,0 +1,66 @@ +# Copyright 2026 Criptomart +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo import fields +from odoo import models + +# Pylint: explicit 'string' attributes are intentional for readable labels in views. +# Some linters flag these as redundant; disable that specific check here. +# pylint: disable=attribute-string-redundant + + +class GroupOrderSlot(models.Model): + _name = "group.order.slot" + _description = "Pickup slot for a Consumer Group Order" + _order = "sequence, weekday, start_hour" + + group_order_id = fields.Many2one( + "group.order", + string="Group Order", + required=True, + ondelete="cascade", + help="Consumer group order this slot belongs to", + ) + + weekday = fields.Selection( + [(str(i), str(i)) for i in range(7)], + string="Weekday", + required=True, + help="Day of week for this slot (0=Monday)", + ) + + start_hour = fields.Float( + string="Start hour", + help="Start hour in decimal form, e.g. 9.5 = 09:30", + ) + + end_hour = fields.Float( + string="End hour", + help="End hour in decimal form, e.g. 14.25 = 14:15", + ) + + label = fields.Char( + string="Label", + help="Human readable short label for the slot (optional)", + ) + + sequence = fields.Integer(string="Sequence", default=10) + + active = fields.Boolean(default=True) + + def _get_display_label(self): + """Return a fallback display label combining weekday and hours. + + This is a small helper used by views or when a specific `label` is + not provided. + """ + self.ensure_one() + if self.label: + return self.label + # Fallback: simple numeric representation + sh = "%02d:%02d" % ( + int(self.start_hour or 0), + int((self.start_hour or 0) % 1 * 60), + ) + eh = "%02d:%02d" % (int(self.end_hour or 0), int((self.end_hour or 0) % 1 * 60)) + return f"{self.weekday} {sh}-{eh}" diff --git a/website_sale_aplicoop/models/sale_order_extension.py b/website_sale_aplicoop/models/sale_order_extension.py index 93dcbf2..739b300 100644 --- a/website_sale_aplicoop/models/sale_order_extension.py +++ b/website_sale_aplicoop/models/sale_order_extension.py @@ -1,9 +1,19 @@ # Copyright 2025 Criptomart # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +import logging + +from odoo import api from odoo import fields from odoo import models +# Pylint: the explicit 'string' parameter is intentional for clarity in views. +# Some fields may trigger 'attribute-string-redundant' warnings; silence them +# locally where appropriate. +# pylint: disable=attribute-string-redundant + +_logger = logging.getLogger(__name__) + class SaleOrder(models.Model): _inherit = "sale.order" @@ -40,11 +50,108 @@ class SaleOrder(models.Model): help="Pickup/delivery date", ) + pickup_slot_label = fields.Char( + string="Pickup Slot Label", + compute="_compute_pickup_slot_label", + store=True, + readonly=True, + ) + home_delivery = fields.Boolean( default=False, help="Whether this order includes home delivery", ) + @api.depends( + "group_order_id", + "group_order_id.next_pickup_slot_id", + "group_order_id.next_pickup_slot_id.label", + "group_order_id.next_pickup_slot_id.start_hour", + "group_order_id.next_pickup_slot_id.end_hour", + "pickup_date", + "pickup_day", + ) + def _compute_pickup_slot_label(self): + """Compute a human readable label for the pickup information. + + Priority: + 1. Use the group order's current `next_pickup_slot_id` if available + 2. Fallback to legacy `pickup_day` / `pickup_date` fields + Note: we deliberately do NOT store a Many2one reference to the + slot on the sale.order anymore — we compute the label dynamically + from the related group order to avoid persisting slot IDs. + """ + for order in self: + slot = False + if order.group_order_id and order.group_order_id.next_pickup_slot_id: + slot = order.group_order_id.next_pickup_slot_id + + if slot: + if slot.label: + label = slot.label + else: + sh = float(slot.start_hour or 0.0) + eh = float(slot.end_hour or 0.0) + sh_h = int(sh) + sh_m = int(round((sh - sh_h) * 60)) + eh_h = int(eh) + eh_m = int(round((eh - eh_h) * 60)) + label = f"{sh_h:02d}:{sh_m:02d}-{eh_h:02d}:{eh_m:02d}" + + if order.pickup_date: + try: + date_str = ( + order.pickup_date.strftime("%d/%m/%Y") + if hasattr(order.pickup_date, "strftime") + else str(order.pickup_date) + ) + label = f"{label} ({date_str})" + except ( + Exception + ) as exc: # log format errors, but don't break compute + _logger.debug( + "_compute_pickup_slot_label: failed to format pickup_date for order %s: %s", + order.id if order and order.id else None, + exc, + exc_info=True, + ) + order.pickup_slot_label = label + else: + # Fallback to single-day fields + if order.pickup_day: + try: + day_map = dict(order._get_pickup_day_selection()) + day_name = day_map.get(order.pickup_day, order.pickup_day) + except Exception as exc: + _logger.debug( + "_compute_pickup_slot_label: failed to map pickup_day for order %s: %s", + order.id if order and order.id else None, + exc, + exc_info=True, + ) + day_name = order.pickup_day + + if order.pickup_date: + try: + date_str = ( + order.pickup_date.strftime("%d/%m/%Y") + if hasattr(order.pickup_date, "strftime") + else str(order.pickup_date) + ) + order.pickup_slot_label = f"{day_name} ({date_str})" + except Exception as exc: + _logger.debug( + "_compute_pickup_slot_label: failed to format pickup_date (fallback) for order %s: %s", + order.id if order and order.id else None, + exc, + exc_info=True, + ) + order.pickup_slot_label = day_name + else: + order.pickup_slot_label = day_name + else: + order.pickup_slot_label = False + def _get_name_portal_content_view(self): """Override to return custom portal content template with group order info. diff --git a/website_sale_aplicoop/models/stock_picking_extension.py b/website_sale_aplicoop/models/stock_picking_extension.py index 94e7295..735c986 100644 --- a/website_sale_aplicoop/models/stock_picking_extension.py +++ b/website_sale_aplicoop/models/stock_picking_extension.py @@ -33,6 +33,14 @@ class StockPicking(models.Model): help="Pickup/delivery date from sale order", ) + pickup_slot_label = fields.Char( + related="sale_id.pickup_slot_label", + string="Pickup Slot", + store=True, + readonly=True, + help="Human readable pickup slot label from the related sale order", + ) + consumer_group_id = fields.Many2one( "res.partner", related="sale_id.consumer_group_id", diff --git a/website_sale_aplicoop/static/src/js/website_sale.js b/website_sale_aplicoop/static/src/js/website_sale.js index 4c5bdb9..ad4a311 100644 --- a/website_sale_aplicoop/static/src/js/website_sale.js +++ b/website_sale_aplicoop/static/src/js/website_sale.js @@ -180,9 +180,44 @@ } } + // If backend provided a human-readable pickup slot label, + // display it to the user or update a dedicated element if present. + if (data.pickup_slot_label) { + var slotLabel = data.pickup_slot_label; + var slotElement = document.getElementById("pickup-slot-label"); + if (slotElement) { + slotElement.textContent = slotLabel; + console.log( + "Auto-loaded pickup_slot_label into element:", + slotLabel + ); + } else { + // Gentle info notification so user sees the pickup info + self._showNotification("Pickup: " + slotLabel, "info", 3000); + } + } + // Update display self._updateCartDisplay(); + // Show pickup slot label if provided by backend + if (data.pickup_slot_label) { + var slotEl = document.getElementById("pickup-slot-label"); + if (slotEl) { + slotEl.textContent = data.pickup_slot_label; + console.log( + "Restored pickup_slot_label into element:", + data.pickup_slot_label + ); + } else { + self._showNotification( + "Pickup: " + data.pickup_slot_label, + "info", + 3000 + ); + } + } + console.log("Auto-loaded " + items.length + " items from draft"); // Show a subtle notification var labels = self._getLabels(); @@ -222,6 +257,7 @@ 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 pickupSlotLabelKey = "load_from_history_pickup_slot_label_" + this.orderId; var warningKey = "load_from_history_warning_" + this.orderId; var itemsJson = sessionStorage.getItem(storageKey); @@ -229,6 +265,7 @@ var pickupDay = sessionStorage.getItem(pickupDayKey); var pickupDate = sessionStorage.getItem(pickupDateKey); var homeDelivery = sessionStorage.getItem(homeDeliveryKey) === "true"; + var pickupSlotLabel = sessionStorage.getItem(pickupSlotLabelKey); var warningMessage = sessionStorage.getItem(warningKey); console.log("DEBUG: _loadFromHistory called for orderId:", this.orderId); @@ -352,6 +389,14 @@ if (orderName) { message += " - " + orderName; } + if (pickupSlotLabel) { + message += + " — " + + (self.labels && self.labels.pickup_label + ? self.labels.pickup_label + ": " + : "Pickup: ") + + pickupSlotLabel; + } this._showNotification(message, "success", 3000); // Show warning if some products were unavailable @@ -366,6 +411,7 @@ sessionStorage.removeItem(pickupDayKey); sessionStorage.removeItem(pickupDateKey); sessionStorage.removeItem(homeDeliveryKey); + sessionStorage.removeItem(pickupSlotLabelKey); sessionStorage.removeItem(warningKey); } catch (e) { console.error("Error loading from history:", e); diff --git a/website_sale_aplicoop/views/load_from_history_templates.xml b/website_sale_aplicoop/views/load_from_history_templates.xml index 5e3fb12..e17a2dc 100644 --- a/website_sale_aplicoop/views/load_from_history_templates.xml +++ b/website_sale_aplicoop/views/load_from_history_templates.xml @@ -16,6 +16,7 @@ var saleOrderName = ''; var pickupDay = ''; var pickupDate = ''; + var pickupSlotLabel = ''; var homeDelivery = ; var sameGroupOrder = ; @@ -46,6 +47,9 @@ if (sameGroupOrder === 'true') { sessionStorage['load_from_history_pickup_day_' + groupOrderId] = pickupDay; sessionStorage['load_from_history_pickup_date_' + groupOrderId] = pickupDate; + // Only store a human-readable label for pickup slot. + // Do NOT persist or expose internal slot IDs to sessionStorage. + sessionStorage['load_from_history_pickup_slot_label_' + groupOrderId] = pickupSlotLabel; sessionStorage['load_from_history_home_delivery_' + groupOrderId] = homeDelivery; console.log('Saved pickup fields (same group order)'); } else { diff --git a/website_sale_aplicoop/views/portal_templates.xml b/website_sale_aplicoop/views/portal_templates.xml index 10d1f77..cc23fc8 100644 --- a/website_sale_aplicoop/views/portal_templates.xml +++ b/website_sale_aplicoop/views/portal_templates.xml @@ -89,9 +89,14 @@ - - , - + + + + + + , + + diff --git a/website_sale_aplicoop/views/sale_order_views.xml b/website_sale_aplicoop/views/sale_order_views.xml index 5090249..50b5616 100644 --- a/website_sale_aplicoop/views/sale_order_views.xml +++ b/website_sale_aplicoop/views/sale_order_views.xml @@ -13,6 +13,7 @@ + diff --git a/website_sale_aplicoop/views/website_templates.xml b/website_sale_aplicoop/views/website_templates.xml index 9a66ea8..c2e38eb 100644 --- a/website_sale_aplicoop/views/website_templates.xml +++ b/website_sale_aplicoop/views/website_templates.xml @@ -123,7 +123,26 @@ - + + + Pickup slots + + + + + + + + + + + + + + + + + Pickup @@ -191,11 +210,31 @@ () - + + + Store Pickup Slots + + + + + + + + + + + + + + + + + Store Pickup Day - () + () + @@ -402,7 +441,7 @@ - + @@ -425,7 +464,7 @@ () - + Not configured