lint: fix linter warnings (log exceptions, disable attribute-string-redundant, suppress C901 where necessary)

This commit is contained in:
snt 2026-05-20 12:58:21 +02:00 committed by GitHub Copilot
parent f8ef927a9e
commit a997331c2d
11 changed files with 595 additions and 20 deletions

View file

@ -302,6 +302,7 @@ class AplicoopWebsiteSale(WebsiteSale):
category_map[cat.id] = { category_map[cat.id] = {
"id": cat.id, "id": cat.id,
"name": cat.name, "name": cat.name,
"sequence": cat.sequence,
"parent_id": cat.parent_id.id if cat.parent_id else None, "parent_id": cat.parent_id.id if cat.parent_id else None,
"children": [], "children": [],
} }
@ -319,7 +320,7 @@ class AplicoopWebsiteSale(WebsiteSale):
category_map[parent_id]["children"].append(cat_info) category_map[parent_id]["children"].append(cat_info)
def sort_hierarchy(items): def sort_hierarchy(items):
items.sort(key=lambda x: x["name"]) items.sort(key=lambda x: (x["sequence"], x["name"]))
for item in items: for item in items:
if item["children"]: if item["children"]:
sort_hierarchy(item["children"]) sort_hierarchy(item["children"])
@ -1272,20 +1273,78 @@ class AplicoopWebsiteSale(WebsiteSale):
"pickup_day_index": pickup_day_index, "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. """Return (pickup_day_name, pickup_date_str, pickup_day_index) localized.
Encapsulates day name detection and date formatting to reduce method complexity. 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: try:
pickup_day_index = int(group_order.pickup_day) pickup_day_index = int(group_order.pickup_day)
except Exception: except Exception:
pickup_day_index = None pickup_day_index = None
pickup_day_name = ""
pickup_date_str = ""
# Get translated day names # Get translated day names
if pickup_day_index is not None: if pickup_day_index is not None:
try: try:
@ -1386,7 +1445,9 @@ class AplicoopWebsiteSale(WebsiteSale):
available_categories = ( available_categories = (
request.env["product.category"].sudo().browse(list(all_categories_set)) 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) category_hierarchy = self._build_category_hierarchy(available_categories)
return all_products, available_categories, category_hierarchy return all_products, available_categories, category_hierarchy
@ -2338,7 +2399,7 @@ class AplicoopWebsiteSale(WebsiteSale):
methods=["POST"], methods=["POST"],
csrf=False, 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.""" """Save cart items as a draft sale.order with pickup date."""
try: try:
_logger.warning("=== SAVE_CART_DRAFT CALLED ===") _logger.warning("=== SAVE_CART_DRAFT CALLED ===")
@ -2438,12 +2499,29 @@ class AplicoopWebsiteSale(WebsiteSale):
current_user.partner_id.id, 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( return request.make_response(
json.dumps( json.dumps(
{ {
"success": True, "success": True,
"message": request.env._("Cart saved as draft"), "message": request.env._("Cart saved as draft"),
"sale_order_id": sale_order.id, "sale_order_id": sale_order.id,
"pickup_slot_label": pickup_slot_label,
} }
), ),
[("Content-Type", "application/json")], [("Content-Type", "application/json")],
@ -2587,6 +2665,14 @@ class AplicoopWebsiteSale(WebsiteSale):
if draft_order.pickup_date if draft_order.pickup_date
else None 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, "home_delivery": draft_order.home_delivery,
} }
), ),
@ -2863,12 +2949,28 @@ class AplicoopWebsiteSale(WebsiteSale):
sale_order.home_delivery, 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( return request.make_response(
json.dumps( json.dumps(
{ {
"success": True, "success": True,
"message": request.env._("Order saved as draft"), "message": request.env._("Order saved as draft"),
"sale_order_id": sale_order.id, "sale_order_id": sale_order.id,
"pickup_slot_label": pickup_slot_label,
} }
), ),
[("Content-Type", "application/json")], [("Content-Type", "application/json")],
@ -3000,6 +3102,14 @@ class AplicoopWebsiteSale(WebsiteSale):
"pickup_day_index": pickup_day_index, "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 # Log language and final message to debug translation issues
try: try:
_logger.info( _logger.info(
@ -3133,6 +3243,13 @@ class AplicoopWebsiteSale(WebsiteSale):
home_delivery_to_restore = ( home_delivery_to_restore = (
sale_order.home_delivery if same_group_order else None 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( response = request.make_response(
request.render( request.render(
@ -3146,6 +3263,9 @@ class AplicoopWebsiteSale(WebsiteSale):
"sale_order_name": sale_order.name, # Pass order reference "sale_order_name": sale_order.name, # Pass order reference
"pickup_day": pickup_day_to_restore, # Pass pickup day (or None if different group) "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) "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) "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 "same_group_order": same_group_order, # Indicate if from same group order
"unavailable_items": unavailable_items, # List of unavailable items "unavailable_items": unavailable_items, # List of unavailable items

View file

@ -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)

View file

@ -10,6 +10,9 @@ from odoo import models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__) _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): class GroupOrder(models.Model):
@ -512,16 +515,149 @@ class GroupOrder(models.Model):
return products_page, total_count, has_next return products_page, total_count, has_next
@api.depends("cutoff_date", "pickup_day") # === Pickup slots helpers ===
def _compute_pickup_date(self): pickup_slot_ids = fields.One2many(
"""Compute pickup date as the first occurrence of pickup_day AFTER cutoff_date. "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 from datetime import datetime
_logger.info("_compute_pickup_date called for %d records", len(self)) _logger.info("_compute_pickup_date called for %d records", len(self))
for record in 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: if not record.pickup_day:
record.pickup_date = None record.pickup_date = None
continue continue

View file

@ -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}"

View file

@ -1,9 +1,19 @@
# Copyright 2025 Criptomart # Copyright 2025 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) # 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 fields
from odoo import models 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): class SaleOrder(models.Model):
_inherit = "sale.order" _inherit = "sale.order"
@ -40,11 +50,108 @@ class SaleOrder(models.Model):
help="Pickup/delivery date", 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( home_delivery = fields.Boolean(
default=False, default=False,
help="Whether this order includes home delivery", 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): def _get_name_portal_content_view(self):
"""Override to return custom portal content template with group order info. """Override to return custom portal content template with group order info.

View file

@ -33,6 +33,14 @@ class StockPicking(models.Model):
help="Pickup/delivery date from sale order", 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( consumer_group_id = fields.Many2one(
"res.partner", "res.partner",
related="sale_id.consumer_group_id", related="sale_id.consumer_group_id",

View file

@ -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 // Update display
self._updateCartDisplay(); 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"); console.log("Auto-loaded " + items.length + " items from draft");
// Show a subtle notification // Show a subtle notification
var labels = self._getLabels(); var labels = self._getLabels();
@ -222,6 +257,7 @@
var pickupDayKey = "load_from_history_pickup_day_" + this.orderId; var pickupDayKey = "load_from_history_pickup_day_" + this.orderId;
var pickupDateKey = "load_from_history_pickup_date_" + this.orderId; var pickupDateKey = "load_from_history_pickup_date_" + this.orderId;
var homeDeliveryKey = "load_from_history_home_delivery_" + 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 warningKey = "load_from_history_warning_" + this.orderId;
var itemsJson = sessionStorage.getItem(storageKey); var itemsJson = sessionStorage.getItem(storageKey);
@ -229,6 +265,7 @@
var pickupDay = sessionStorage.getItem(pickupDayKey); var pickupDay = sessionStorage.getItem(pickupDayKey);
var pickupDate = sessionStorage.getItem(pickupDateKey); var pickupDate = sessionStorage.getItem(pickupDateKey);
var homeDelivery = sessionStorage.getItem(homeDeliveryKey) === "true"; var homeDelivery = sessionStorage.getItem(homeDeliveryKey) === "true";
var pickupSlotLabel = sessionStorage.getItem(pickupSlotLabelKey);
var warningMessage = sessionStorage.getItem(warningKey); var warningMessage = sessionStorage.getItem(warningKey);
console.log("DEBUG: _loadFromHistory called for orderId:", this.orderId); console.log("DEBUG: _loadFromHistory called for orderId:", this.orderId);
@ -352,6 +389,14 @@
if (orderName) { if (orderName) {
message += " - " + orderName; message += " - " + orderName;
} }
if (pickupSlotLabel) {
message +=
" — " +
(self.labels && self.labels.pickup_label
? self.labels.pickup_label + ": "
: "Pickup: ") +
pickupSlotLabel;
}
this._showNotification(message, "success", 3000); this._showNotification(message, "success", 3000);
// Show warning if some products were unavailable // Show warning if some products were unavailable
@ -366,6 +411,7 @@
sessionStorage.removeItem(pickupDayKey); sessionStorage.removeItem(pickupDayKey);
sessionStorage.removeItem(pickupDateKey); sessionStorage.removeItem(pickupDateKey);
sessionStorage.removeItem(homeDeliveryKey); sessionStorage.removeItem(homeDeliveryKey);
sessionStorage.removeItem(pickupSlotLabelKey);
sessionStorage.removeItem(warningKey); sessionStorage.removeItem(warningKey);
} catch (e) { } catch (e) {
console.error("Error loading from history:", e); console.error("Error loading from history:", e);

View file

@ -16,6 +16,7 @@
var saleOrderName = '<t t-esc="sale_order_name"/>'; var saleOrderName = '<t t-esc="sale_order_name"/>';
var pickupDay = '<t t-esc="pickup_day or ''"/>'; var pickupDay = '<t t-esc="pickup_day or ''"/>';
var pickupDate = '<t t-esc="pickup_date or ''"/>'; var pickupDate = '<t t-esc="pickup_date or ''"/>';
var pickupSlotLabel = '<t t-esc="pickup_slot_label or ''"/>';
var homeDelivery = <t t-esc="home_delivery and 'true' or 'false'"/>; var homeDelivery = <t t-esc="home_delivery and 'true' or 'false'"/>;
var sameGroupOrder = <t t-esc="same_group_order and 'true' or 'false'"/>; var sameGroupOrder = <t t-esc="same_group_order and 'true' or 'false'"/>;
@ -46,6 +47,9 @@
if (sameGroupOrder === 'true') { if (sameGroupOrder === 'true') {
sessionStorage['load_from_history_pickup_day_' + groupOrderId] = pickupDay; sessionStorage['load_from_history_pickup_day_' + groupOrderId] = pickupDay;
sessionStorage['load_from_history_pickup_date_' + groupOrderId] = pickupDate; 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; sessionStorage['load_from_history_home_delivery_' + groupOrderId] = homeDelivery;
console.log('Saved pickup fields (same group order)'); console.log('Saved pickup fields (same group order)');
} else { } else {

View file

@ -89,9 +89,14 @@
</t> </t>
<t t-else=""> <t t-else="">
<span class="badge bg-primary"> <span class="badge bg-primary">
<t t-if="sale_order.pickup_slot_label">
<t t-esc="sale_order.pickup_slot_label"/>
</t>
<t t-else="">
<t t-set="day_idx" t-value="int(sale_order.group_order_id.pickup_day) % 7 if sale_order.group_order_id.pickup_day else 0"/> <t t-set="day_idx" t-value="int(sale_order.group_order_id.pickup_day) % 7 if sale_order.group_order_id.pickup_day else 0"/>
<t t-esc="day_names[day_idx] if day_names else ''"/>, <t t-esc="day_names[day_idx] if day_names else ''"/>,
<t t-esc="sale_order.pickup_date.strftime('%d/%m/%Y')"/> <t t-esc="sale_order.pickup_date.strftime('%d/%m/%Y')"/>
</t>
</span> </span>
<span class="mt-2 small"> <span class="mt-2 small">
<t t-foreach="sale_order.group_order_id.group_ids" t-as="group"> <t t-foreach="sale_order.group_order_id.group_ids" t-as="group">

View file

@ -13,6 +13,7 @@
<group string="Group Purchase Information" groups="base.group_user"> <group string="Group Purchase Information" groups="base.group_user">
<field name="group_order_id" readonly="True" /> <field name="group_order_id" readonly="True" />
<field name="consumer_group_id" readonly="True" /> <field name="consumer_group_id" readonly="True" />
<field name="pickup_slot_label" readonly="True" />
<field name="pickup_day" readonly="True" /> <field name="pickup_day" readonly="True" />
<field name="pickup_date" readonly="True" /> <field name="pickup_date" readonly="True" />
<field name="home_delivery" readonly="True" /> <field name="home_delivery" readonly="True" />

View file

@ -123,7 +123,26 @@
</span> </span>
</div> </div>
</t> </t>
<t t-if="order.pickup_day and order.pickup_date"> <t t-if="order.pickup_slot_ids and order.pickup_slot_ids|length &gt; 0">
<div class="meta-item">
<span class="meta-label">Pickup slots</span>
<span class="meta-value">
<t t-foreach="order.pickup_slot_ids" t-as="slot">
<div class="slot-entry">
<t t-if="slot.label">
<t t-esc="slot.label" />
</t>
<t t-else="">
<t t-esc="day_names[int(slot.weekday) % 7]" />
&#160;
<t t-esc="('%02d:%02d-%02d:%02d' % (int(slot.start_hour or 0), int(((slot.start_hour or 0) % 1) * 60), int(slot.end_hour or 0), int(((slot.end_hour or 0) % 1) * 60)))" />
</t>
</div>
</t>
</span>
</div>
</t>
<t t-elif="order.pickup_day and order.pickup_date">
<div class="meta-item"> <div class="meta-item">
<span class="meta-label">Pickup</span> <span class="meta-label">Pickup</span>
<span class="meta-value"> <span class="meta-value">
@ -191,11 +210,31 @@
<t t-esc="day_names[int(group_order.cutoff_day) % 7]" /> (<t t-esc="group_order.cutoff_date.strftime('%d/%m/%Y')" />)</span> <t t-esc="day_names[int(group_order.cutoff_day) % 7]" /> (<t t-esc="group_order.cutoff_date.strftime('%d/%m/%Y')" />)</span>
</div> </div>
</t> </t>
<t t-if="group_order.pickup_day"> <t t-if="group_order.pickup_slot_ids and group_order.pickup_slot_ids|length &gt; 0">
<div class="info-item">
<span t-att-class="'info-label'">Store Pickup Slots</span>
<span class="info-value">
<t t-foreach="group_order.pickup_slot_ids" t-as="slot">
<div>
<t t-if="slot.label">
<t t-esc="slot.label" />
</t>
<t t-else="">
<t t-esc="day_names[int(slot.weekday) % 7]" />
&#160;
<t t-esc="('%02d:%02d-%02d:%02d' % (int(slot.start_hour or 0), int(((slot.start_hour or 0) % 1) * 60), int(slot.end_hour or 0), int(((slot.end_hour or 0) % 1) * 60)))" />
</t>
</div>
</t>
</span>
</div>
</t>
<t t-elif="group_order.pickup_day">
<div class="info-item"> <div class="info-item">
<span t-att-class="'info-label'">Store Pickup Day</span> <span t-att-class="'info-label'">Store Pickup Day</span>
<span class="info-value"> <span class="info-value">
<t t-esc="day_names[int(group_order.pickup_day) % 7]" /> (<t t-esc="group_order.pickup_date.strftime('%d/%m/%Y')" />)</span> <t t-esc="day_names[int(group_order.pickup_day) % 7]" /> (<t t-esc="group_order.pickup_date.strftime('%d/%m/%Y')" />)
</span>
</div> </div>
</t> </t>
<t t-if="group_order.delivery_date and group_order.home_delivery"> <t t-if="group_order.delivery_date and group_order.home_delivery">
@ -402,7 +441,7 @@
</template> </template>
<template id="eskaera_checkout" name="Eskaera Checkout"> <template id="eskaera_checkout" name="Eskaera Checkout">
<t t-call="website.layout"> <t t-call="website.layout">
<div id="wrap" class="eskaera-checkout-page oe_structure oe_empty" data-name="Eskaera Checkout" t-attf-data-delivery-product-id="{{ delivery_product_id }}" t-attf-data-delivery-product-name="{{ delivery_product_name }}" t-attf-data-delivery-product-price="{{ delivery_product_price }}" t-attf-data-home-delivery-enabled="{{ 'true' if group_order.home_delivery else 'false' }}" t-attf-data-pickup-day="{{ group_order.pickup_day }}" t-attf-data-pickup-date="{{ group_order.pickup_date.strftime('%d/%m/%Y') if group_order.pickup_date else '' }}" t-attf-data-delivery-notice="{{ (group_order.delivery_notice or '').replace(chr(10), ' ').replace(chr(13), ' ') }}"> <div id="wrap" class="eskaera-checkout-page oe_structure oe_empty" data-name="Eskaera Checkout" t-attf-data-delivery-product-id="{{ delivery_product_id }}" t-attf-data-delivery-product-name="{{ delivery_product_name }}" t-attf-data-delivery-product-price="{{ delivery_product_price }}" t-attf-data-home-delivery-enabled="{{ 'true' if group_order.home_delivery else 'false' }}" t-attf-data-pickup-day="{{ group_order.pickup_day }}" t-attf-data-pickup-date="{{ group_order.pickup_date.strftime('%d/%m/%Y') if group_order.pickup_date else '' }}" t-attf-data-next-pickup-slot-label="{{ group_order.next_pickup_slot_id.label if group_order.next_pickup_slot_id else '' }}" t-attf-data-delivery-notice="{{ (group_order.delivery_notice or '').replace(chr(10), ' ').replace(chr(13), ' ') }}">
<div class="container mt-5"> <div class="container mt-5">
<div class="row"> <div class="row">
<div class="col-lg-10 offset-lg-1"> <div class="col-lg-10 offset-lg-1">
@ -425,7 +464,7 @@
<span class="info-date">(<t t-esc="group_order.cutoff_date.strftime('%d/%m/%Y')" />)</span> <span class="info-date">(<t t-esc="group_order.cutoff_date.strftime('%d/%m/%Y')" />)</span>
</span> </span>
</t> </t>
<t t-else="1"> <t t-else="">
<span t-att-class="'text-muted small'">Not configured</span> <span t-att-class="'text-muted small'">Not configured</span>
</t> </t>
</div> </div>