278 files
This commit is contained in:
@@ -486,6 +486,21 @@ class Chore:
|
||||
}
|
||||
|
||||
|
||||
def _clean_weekdays(raw: Any) -> list[int]:
|
||||
"""Coerce stored weekday numbers to a sorted, de-duplicated 0-6 list."""
|
||||
if not isinstance(raw, (list, tuple, set)):
|
||||
return []
|
||||
out: set[int] = set()
|
||||
for item in raw:
|
||||
try:
|
||||
day = int(item)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if 0 <= day <= 6:
|
||||
out.add(day)
|
||||
return sorted(out)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Reward:
|
||||
"""Represents a reward."""
|
||||
@@ -509,6 +524,15 @@ class Reward:
|
||||
# checked both at save time and again when it fires.
|
||||
unlock_entity: str = ""
|
||||
unlock_minutes: int = 0
|
||||
# Time lock (#857): restrict claiming to certain weekdays and/or a
|
||||
# time-of-day window. `available_days` holds Python weekday numbers
|
||||
# (0=Monday .. 6=Sunday); empty means every day. The times are "HH:MM";
|
||||
# a start later than the end is an overnight window. All of it is inert
|
||||
# unless `time_lock_enabled` is set.
|
||||
time_lock_enabled: bool = False
|
||||
available_days: list[int] = field(default_factory=list)
|
||||
available_from: str = ""
|
||||
available_until: str = ""
|
||||
id: str = field(default_factory=generate_id)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
@@ -542,6 +566,10 @@ class Reward:
|
||||
unlock_minutes=int(data.get("unlock_minutes", 0) or 0),
|
||||
restock_period=data.get("restock_period", "weekly"),
|
||||
restock_last=data.get("restock_last", ""),
|
||||
time_lock_enabled=bool(data.get("time_lock_enabled", False)),
|
||||
available_days=_clean_weekdays(data.get("available_days")),
|
||||
available_from=str(data.get("available_from", "") or ""),
|
||||
available_until=str(data.get("available_until", "") or ""),
|
||||
id=data.get("id") or generate_id(),
|
||||
)
|
||||
|
||||
@@ -563,6 +591,10 @@ class Reward:
|
||||
"restock_last": self.restock_last,
|
||||
"unlock_entity": self.unlock_entity,
|
||||
"unlock_minutes": self.unlock_minutes,
|
||||
"time_lock_enabled": self.time_lock_enabled,
|
||||
"available_days": list(self.available_days),
|
||||
"available_from": self.available_from,
|
||||
"available_until": self.available_until,
|
||||
"id": self.id,
|
||||
}
|
||||
|
||||
@@ -1283,3 +1315,34 @@ class CustomNotification:
|
||||
"recipient_ids": list(self.recipient_ids),
|
||||
"enabled": self.enabled,
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class CustomSound:
|
||||
"""A user-uploaded completion sound (#856).
|
||||
|
||||
``file`` is the generated ``<32 hex>.<ext>`` name on disk under
|
||||
``<config>/taskmate_sounds``; it doubles as the sound's identity, so a
|
||||
chore references this row as ``custom:<file>``. There is no separate id:
|
||||
a second id would let the row and the file drift apart, and the filename
|
||||
is already unique and immutable.
|
||||
"""
|
||||
|
||||
name: str
|
||||
file: str
|
||||
created_at: str = field(default_factory=dt_util_now_iso)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any]) -> CustomSound:
|
||||
return cls(
|
||||
name=data.get("name", ""),
|
||||
file=data.get("file", ""),
|
||||
created_at=data.get("created_at", "") or dt_util_now_iso(),
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"name": self.name,
|
||||
"file": self.file,
|
||||
"created_at": self.created_at,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user