feat(loyalty): translatable categories + mandatory on earn points
Some checks failed
CI / pytest (push) Failing after 2h47m45s
CI / validate (push) Successful in 39s
CI / dependency-scanning (push) Successful in 47s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped
CI / ruff (push) Successful in 21s

- Add name_translations JSON column to StoreTransactionCategory
  (migration loyalty_008). Stores {"en": "Men", "fr": "Hommes", ...}.
  Model has get_translated_name(lang) helper.
- Admin CRUD form now has FR/DE/LB translation inputs alongside the
  default name.
- Points earn: category_id is now mandatory when the store has
  active categories configured. Returns CATEGORY_REQUIRED error.
- Stamps: category remains optional (quick tap workflow).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-19 14:12:55 +02:00
parent ab2daf99bd
commit eafa086c73
6 changed files with 101 additions and 9 deletions

View File

@@ -15,6 +15,7 @@ from sqlalchemy import (
String,
UniqueConstraint,
)
from sqlalchemy.dialects.sqlite import JSON
from sqlalchemy.orm import relationship
from app.core.database import Base
@@ -29,6 +30,11 @@ class StoreTransactionCategory(Base, TimestampMixin):
id = Column(Integer, primary_key=True, index=True)
store_id = Column(Integer, ForeignKey("stores.id"), nullable=False)
name = Column(String(100), nullable=False)
name_translations = Column(
JSON,
nullable=True,
comment='Language-keyed name dict, e.g. {"en": "Men", "fr": "Hommes"}',
)
display_order = Column(Integer, nullable=False, default=0)
is_active = Column(Boolean, nullable=False, default=True)
@@ -42,3 +48,9 @@ class StoreTransactionCategory(Base, TimestampMixin):
def __repr__(self):
return f"<StoreTransactionCategory(id={self.id}, store={self.store_id}, name='{self.name}')>"
def get_translated_name(self, lang: str) -> str:
"""Get name in the given language, falling back to self.name."""
if self.name_translations and isinstance(self.name_translations, dict):
return self.name_translations.get(lang) or self.name
return self.name