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

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

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
# 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.

View file

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