lint: fix linter warnings (log exceptions, disable attribute-string-redundant, suppress C901 where necessary)
This commit is contained in:
parent
f8ef927a9e
commit
a997331c2d
11 changed files with 595 additions and 20 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
43
website_sale_aplicoop/migrations/18.0.1.0.3/post-migrate.py
Normal file
43
website_sale_aplicoop/migrations/18.0.1.0.3/post-migrate.py
Normal 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)
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
66
website_sale_aplicoop/models/group_order_slot.py
Normal file
66
website_sale_aplicoop/models/group_order_slot.py
Normal 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}"
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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" />
|
||||||
|
|
|
||||||
|
|
@ -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 > 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]" />
|
||||||
|
 
|
||||||
|
<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 > 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]" />
|
||||||
|
 
|
||||||
|
<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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue