#!/usr/bin/env python3
"""
Seed loyalty email templates.
Idempotent: safe to run repeatedly — upserts by (code, language).
Run: python scripts/seed/seed_email_templates_loyalty.py
"""
import contextlib
import json
import sys
from pathlib import Path
# Add project root to path
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
for _mod in [
"app.modules.billing.models",
"app.modules.inventory.models",
"app.modules.cart.models",
"app.modules.messaging.models",
"app.modules.loyalty.models",
"app.modules.catalog.models",
"app.modules.customers.models",
"app.modules.orders.models",
"app.modules.marketplace.models",
"app.modules.cms.models",
]:
with contextlib.suppress(ImportError):
__import__(_mod)
from app.core.database import get_db
from app.modules.messaging.models import EmailCategory, EmailTemplate
# =============================================================================
# SHARED HTML STRUCTURE
# =============================================================================
_HEAD = """
"""
def _wrap(gradient_from, gradient_to, title, body_html):
"""Wrap email body in the standard template chrome."""
return f"""{_HEAD}
{title}
{body_html}
"""
# =============================================================================
# LOYALTY TEMPLATES
# =============================================================================
# ── Enrollment ──────────────────────────────────────────────────────────────
_ENROLLMENT_BODY_EN = """
Hi {{ customer_name }},
Welcome to {{ program_name }}! Your loyalty card is ready.
Card Number: {{ card_number }}
Store: {{ store_name }}
Start earning rewards on every visit!
Best regards,
{{ store_name }}
"""
_ENROLLMENT_BODY_FR = """
Bonjour {{ customer_name }},
Bienvenue dans {{ program_name }} ! Votre carte de fidélité est prête.
Numéro de carte : {{ card_number }}
Point de vente : {{ store_name }}
Commencez à gagner des récompenses à chaque visite !
Cordialement,
{{ store_name }}
"""
_ENROLLMENT_BODY_DE = """
Hallo {{ customer_name }},
Willkommen bei {{ program_name }}! Ihre Treuekarte ist bereit.
Kartennummer: {{ card_number }}
Filiale: {{ store_name }}
Sammeln Sie ab sofort Prämien bei jedem Besuch!
Mit freundlichen Grüßen,
{{ store_name }}
"""
_ENROLLMENT_BODY_LB = """
Moien {{ customer_name }},
Wëllkomm bei {{ program_name }}! Är Treiekaart ass prett.
Kaartenummer: {{ card_number }}
Geschäft: {{ store_name }}
Fänkt un Belounungen ze sammelen bei all Besuch!
Léif Gréiss,
{{ store_name }}
"""
# ── Welcome Bonus ───────────────────────────────────────────────────────────
_WELCOME_BONUS_BODY_EN = """
Hi {{ customer_name }},
Great news! You've received {{ points }} bonus points as a welcome gift from {{ program_name }}.
+{{ points }}
bonus points
These points are already in your balance and ready to use toward rewards.
Best regards,
{{ store_name }}
"""
_WELCOME_BONUS_BODY_FR = """
Bonjour {{ customer_name }},
Bonne nouvelle ! Vous avez reçu {{ points }} points bonus en cadeau de bienvenue de {{ program_name }}.
+{{ points }}
points bonus
Ces points sont déjà sur votre solde et utilisables pour des récompenses.
Cordialement,
{{ store_name }}
"""
_WELCOME_BONUS_BODY_DE = """
Hallo {{ customer_name }},
Tolle Neuigkeiten! Sie haben {{ points }} Bonuspunkte als Willkommensgeschenk von {{ program_name }} erhalten.
+{{ points }}
Bonuspunkte
Diese Punkte sind bereits auf Ihrem Konto und können für Prämien eingesetzt werden.
Mit freundlichen Grüßen,
{{ store_name }}
"""
_WELCOME_BONUS_BODY_LB = """
Moien {{ customer_name }},
Gutt Noriichten! Dir hutt {{ points }} Bonuspunkten als Wëllkommsgeschenk vu {{ program_name }} kritt.
+{{ points }}
Bonuspunkten
Dës Punkten sinn schonn op Ärem Konto a kënne fir Beloununge benotzt ginn.
Léif Gréiss,
{{ store_name }}
"""
# ── Points Expiring ─────────────────────────────────────────────────────────
_POINTS_EXPIRING_BODY_EN = """
Hi {{ customer_name }},
This is a friendly reminder that {{ points }} points in your {{ program_name }} account will expire in {{ days_remaining }} days (on {{ expiration_date }}).
⏳ {{ points }} points expiring on {{ expiration_date }}
Visit us before then to use your points toward a reward!
Best regards,
{{ store_name }}
"""
_POINTS_EXPIRING_BODY_FR = """
Bonjour {{ customer_name }},
Un petit rappel : {{ points }} points de votre compte {{ program_name }} expireront dans {{ days_remaining }} jours (le {{ expiration_date }}).
⏳ {{ points }} points expirent le {{ expiration_date }}
Rendez-nous visite avant cette date pour utiliser vos points !
Cordialement,
{{ store_name }}
"""
_POINTS_EXPIRING_BODY_DE = """
Hallo {{ customer_name }},
Eine freundliche Erinnerung: {{ points }} Punkte auf Ihrem {{ program_name }}-Konto verfallen in {{ days_remaining }} Tagen (am {{ expiration_date }}).
⏳ {{ points }} Punkte verfallen am {{ expiration_date }}
Besuchen Sie uns vorher, um Ihre Punkte einzulösen!
Mit freundlichen Grüßen,
{{ store_name }}
"""
_POINTS_EXPIRING_BODY_LB = """
Moien {{ customer_name }},
Eng kleng Erënnerung: {{ points }} Punkten op Ärem {{ program_name }}-Konto verfalen an {{ days_remaining }} Deeg (den {{ expiration_date }}).
⏳ {{ points }} Punkten verfalen den {{ expiration_date }}
Besicht eis virun deem Datum fir Är Punkten anzeléisen!
Léif Gréiss,
{{ store_name }}
"""
# ── Points Expired ──────────────────────────────────────────────────────────
_POINTS_EXPIRED_BODY_EN = """
Hi {{ customer_name }},
Unfortunately, {{ expired_points }} points in your {{ program_name }} account have expired.
{{ expired_points }} points expired
Don't worry — you can keep earning points on your next visit!
Best regards,
{{ store_name }}
"""
_POINTS_EXPIRED_BODY_FR = """
Bonjour {{ customer_name }},
Malheureusement, {{ expired_points }} points de votre compte {{ program_name }} ont expiré.
{{ expired_points }} points expirés
Pas d'inquiétude — vous pouvez continuer à gagner des points lors de votre prochaine visite !
Cordialement,
{{ store_name }}
"""
_POINTS_EXPIRED_BODY_DE = """
Hallo {{ customer_name }},
Leider sind {{ expired_points }} Punkte auf Ihrem {{ program_name }}-Konto verfallen.
{{ expired_points }} Punkte verfallen
Keine Sorge — Sie können bei Ihrem nächsten Besuch weiter Punkte sammeln!
Mit freundlichen Grüßen,
{{ store_name }}
"""
_POINTS_EXPIRED_BODY_LB = """
Moien {{ customer_name }},
Leider sinn {{ expired_points }} Punkten op Ärem {{ program_name }}-Konto ofgelaf.
{{ expired_points }} Punkten ofgelaf
Keng Suergen — Dir kënnt bei Ärem nächste Besuch weider Punkten sammelen!
Léif Gréiss,
{{ store_name }}
"""
# ── Reward Ready ────────────────────────────────────────────────────────────
_REWARD_READY_BODY_EN = """
Hi {{ customer_name }},
Congratulations! You've earned a reward at {{ program_name }}! 🎉
Visit {{ store_name }} to redeem your reward. Just show your loyalty card!
Best regards,
{{ store_name }}
"""
_REWARD_READY_BODY_FR = """
Bonjour {{ customer_name }},
Félicitations ! Vous avez gagné une récompense chez {{ program_name }} ! 🎉
Rendez-vous chez {{ store_name }} pour récupérer votre récompense. Montrez simplement votre carte de fidélité !
Cordialement,
{{ store_name }}
"""
_REWARD_READY_BODY_DE = """
Hallo {{ customer_name }},
Herzlichen Glückwunsch! Sie haben eine Prämie bei {{ program_name }} verdient! 🎉
Besuchen Sie {{ store_name }} um Ihre Prämie einzulösen. Zeigen Sie einfach Ihre Treuekarte!
Mit freundlichen Grüßen,
{{ store_name }}
"""
_REWARD_READY_BODY_LB = """
Moien {{ customer_name }},
Felicitatiounen! Dir hutt eng Belounung bei {{ program_name }} verdéngt! 🎉
Besicht {{ store_name }} fir Är Belounung ofzehuelen. Weist einfach Är Treiekaart!
Léif Gréiss,
{{ store_name }}
"""
# =============================================================================
# BUILD TEMPLATE LIST
# =============================================================================
def _make_templates():
"""Build the full list of loyalty email templates."""
templates = []
_defs = [
{
"code": "loyalty_enrollment",
"name": {
"en": "Loyalty Enrollment",
"fr": "Inscription fidélité",
"de": "Treue-Anmeldung",
"lb": "Treie-Umeldung",
},
"description": "Sent when a customer enrolls in a loyalty program",
"category": EmailCategory.SYSTEM.value,
"variables": ["customer_name", "program_name", "card_number", "store_name"],
"subject": {
"en": "Welcome to {{ program_name }}!",
"fr": "Bienvenue chez {{ program_name }} !",
"de": "Willkommen bei {{ program_name }}!",
"lb": "Wëllkomm bei {{ program_name }}!",
},
"body_html": {
"en": _wrap("#6366f1", "#8b5cf6", "Welcome!", _ENROLLMENT_BODY_EN),
"fr": _wrap("#6366f1", "#8b5cf6", "Bienvenue !", _ENROLLMENT_BODY_FR),
"de": _wrap("#6366f1", "#8b5cf6", "Willkommen!", _ENROLLMENT_BODY_DE),
"lb": _wrap("#6366f1", "#8b5cf6", "Wëllkomm!", _ENROLLMENT_BODY_LB),
},
"body_text": {
"en": "Hi {{ customer_name }},\n\nWelcome to {{ program_name }}! Your loyalty card number is {{ card_number }}.\n\nStart earning rewards on every visit!\n\nBest regards,\n{{ store_name }}",
"fr": "Bonjour {{ customer_name }},\n\nBienvenue chez {{ program_name }} ! Votre numéro de carte est {{ card_number }}.\n\nCommencez à gagner des récompenses !\n\nCordialement,\n{{ store_name }}",
"de": "Hallo {{ customer_name }},\n\nWillkommen bei {{ program_name }}! Ihre Kartennummer ist {{ card_number }}.\n\nSammeln Sie ab sofort Prämien!\n\nMit freundlichen Grüßen,\n{{ store_name }}",
"lb": "Moien {{ customer_name }},\n\nWëllkomm bei {{ program_name }}! Är Kaartenummer ass {{ card_number }}.\n\nFänkt un Belounungen ze sammelen!\n\nLéif Gréiss,\n{{ store_name }}",
},
},
{
"code": "loyalty_welcome_bonus",
"name": {
"en": "Loyalty Welcome Bonus",
"fr": "Bonus de bienvenue fidélité",
"de": "Treue-Willkommensbonus",
"lb": "Treie-Wëllkommsbonus",
},
"description": "Sent when a customer receives welcome bonus points",
"category": EmailCategory.SYSTEM.value,
"variables": ["customer_name", "program_name", "points", "store_name"],
"subject": {
"en": "You earned {{ points }} bonus points!",
"fr": "Vous avez gagné {{ points }} points bonus !",
"de": "Sie haben {{ points }} Bonuspunkte erhalten!",
"lb": "Dir hutt {{ points }} Bonuspunkten kritt!",
},
"body_html": {
"en": _wrap("#6366f1", "#8b5cf6", "Bonus Points!", _WELCOME_BONUS_BODY_EN),
"fr": _wrap("#6366f1", "#8b5cf6", "Points Bonus !", _WELCOME_BONUS_BODY_FR),
"de": _wrap("#6366f1", "#8b5cf6", "Bonuspunkte!", _WELCOME_BONUS_BODY_DE),
"lb": _wrap("#6366f1", "#8b5cf6", "Bonuspunkten!", _WELCOME_BONUS_BODY_LB),
},
"body_text": {
"en": "Hi {{ customer_name }},\n\nYou've received {{ points }} bonus points as a welcome gift from {{ program_name }}.\n\nThese points are already in your balance.\n\nBest regards,\n{{ store_name }}",
"fr": "Bonjour {{ customer_name }},\n\nVous avez reçu {{ points }} points bonus de {{ program_name }}.\n\nCes points sont déjà sur votre solde.\n\nCordialement,\n{{ store_name }}",
"de": "Hallo {{ customer_name }},\n\nSie haben {{ points }} Bonuspunkte von {{ program_name }} erhalten.\n\nDiese Punkte sind bereits auf Ihrem Konto.\n\nMit freundlichen Grüßen,\n{{ store_name }}",
"lb": "Moien {{ customer_name }},\n\nDir hutt {{ points }} Bonuspunkten vu {{ program_name }} kritt.\n\nDës Punkten sinn schonn op Ärem Konto.\n\nLéif Gréiss,\n{{ store_name }}",
},
},
{
"code": "loyalty_points_expiring",
"name": {
"en": "Points Expiring Warning",
"fr": "Avertissement expiration points",
"de": "Punkteverfall-Warnung",
"lb": "Punkten-Oflaaf-Warnung",
},
"description": "Sent 14 days before loyalty points expire",
"category": EmailCategory.SYSTEM.value,
"variables": ["customer_name", "program_name", "points", "days_remaining", "expiration_date", "store_name"],
"subject": {
"en": "Your {{ points }} points expire in {{ days_remaining }} days",
"fr": "Vos {{ points }} points expirent dans {{ days_remaining }} jours",
"de": "Ihre {{ points }} Punkte verfallen in {{ days_remaining }} Tagen",
"lb": "Är {{ points }} Punkten verfalen an {{ days_remaining }} Deeg",
},
"body_html": {
"en": _wrap("#f59e0b", "#d97706", "Points Expiring Soon", _POINTS_EXPIRING_BODY_EN),
"fr": _wrap("#f59e0b", "#d97706", "Points bientôt expirés", _POINTS_EXPIRING_BODY_FR),
"de": _wrap("#f59e0b", "#d97706", "Punkte verfallen bald", _POINTS_EXPIRING_BODY_DE),
"lb": _wrap("#f59e0b", "#d97706", "Punkten verfalen geschwënn", _POINTS_EXPIRING_BODY_LB),
},
"body_text": {
"en": "Hi {{ customer_name }},\n\n{{ points }} points in your {{ program_name }} account will expire in {{ days_remaining }} days (on {{ expiration_date }}).\n\nVisit us to use your points!\n\nBest regards,\n{{ store_name }}",
"fr": "Bonjour {{ customer_name }},\n\n{{ points }} points de votre compte {{ program_name }} expireront dans {{ days_remaining }} jours (le {{ expiration_date }}).\n\nRendez-nous visite !\n\nCordialement,\n{{ store_name }}",
"de": "Hallo {{ customer_name }},\n\n{{ points }} Punkte auf Ihrem {{ program_name }}-Konto verfallen in {{ days_remaining }} Tagen (am {{ expiration_date }}).\n\nBesuchen Sie uns!\n\nMit freundlichen Grüßen,\n{{ store_name }}",
"lb": "Moien {{ customer_name }},\n\n{{ points }} Punkten op Ärem {{ program_name }}-Konto verfalen an {{ days_remaining }} Deeg (den {{ expiration_date }}).\n\nBesicht eis!\n\nLéif Gréiss,\n{{ store_name }}",
},
},
{
"code": "loyalty_points_expired",
"name": {
"en": "Points Expired",
"fr": "Points expirés",
"de": "Punkte verfallen",
"lb": "Punkten ofgelaf",
},
"description": "Sent when loyalty points have expired",
"category": EmailCategory.SYSTEM.value,
"variables": ["customer_name", "program_name", "expired_points", "store_name"],
"subject": {
"en": "{{ expired_points }} points have expired",
"fr": "{{ expired_points }} points ont expiré",
"de": "{{ expired_points }} Punkte sind verfallen",
"lb": "{{ expired_points }} Punkten sinn ofgelaf",
},
"body_html": {
"en": _wrap("#ef4444", "#dc2626", "Points Expired", _POINTS_EXPIRED_BODY_EN),
"fr": _wrap("#ef4444", "#dc2626", "Points expirés", _POINTS_EXPIRED_BODY_FR),
"de": _wrap("#ef4444", "#dc2626", "Punkte verfallen", _POINTS_EXPIRED_BODY_DE),
"lb": _wrap("#ef4444", "#dc2626", "Punkten ofgelaf", _POINTS_EXPIRED_BODY_LB),
},
"body_text": {
"en": "Hi {{ customer_name }},\n\n{{ expired_points }} points in your {{ program_name }} account have expired.\n\nKeep earning on your next visit!\n\nBest regards,\n{{ store_name }}",
"fr": "Bonjour {{ customer_name }},\n\n{{ expired_points }} points de votre compte {{ program_name }} ont expiré.\n\nContinuez à gagner des points !\n\nCordialement,\n{{ store_name }}",
"de": "Hallo {{ customer_name }},\n\n{{ expired_points }} Punkte auf Ihrem {{ program_name }}-Konto sind verfallen.\n\nSammeln Sie weiter!\n\nMit freundlichen Grüßen,\n{{ store_name }}",
"lb": "Moien {{ customer_name }},\n\n{{ expired_points }} Punkten op Ärem {{ program_name }}-Konto sinn ofgelaf.\n\nSammelt weider!\n\nLéif Gréiss,\n{{ store_name }}",
},
},
{
"code": "loyalty_reward_ready",
"name": {
"en": "Reward Ready",
"fr": "Récompense disponible",
"de": "Prämie bereit",
"lb": "Belounung prett",
},
"description": "Sent when a customer earns enough stamps for a reward",
"category": EmailCategory.MARKETING.value,
"variables": ["customer_name", "program_name", "reward_name", "store_name"],
"subject": {
"en": "You've earned a reward at {{ program_name }}! 🎉",
"fr": "Vous avez gagné une récompense chez {{ program_name }} ! 🎉",
"de": "Sie haben eine Prämie bei {{ program_name }} verdient! 🎉",
"lb": "Dir hutt eng Belounung bei {{ program_name }} verdéngt! 🎉",
},
"body_html": {
"en": _wrap("#10b981", "#059669", "Reward Earned! 🎉", _REWARD_READY_BODY_EN),
"fr": _wrap("#10b981", "#059669", "Récompense gagnée ! 🎉", _REWARD_READY_BODY_FR),
"de": _wrap("#10b981", "#059669", "Prämie verdient! 🎉", _REWARD_READY_BODY_DE),
"lb": _wrap("#10b981", "#059669", "Belounung verdéngt! 🎉", _REWARD_READY_BODY_LB),
},
"body_text": {
"en": "Hi {{ customer_name }},\n\nCongratulations! You've earned a reward: {{ reward_name }}\n\nVisit {{ store_name }} to redeem it!\n\nBest regards,\n{{ store_name }}",
"fr": "Bonjour {{ customer_name }},\n\nFélicitations ! Vous avez gagné : {{ reward_name }}\n\nRendez-vous chez {{ store_name }} !\n\nCordialement,\n{{ store_name }}",
"de": "Hallo {{ customer_name }},\n\nHerzlichen Glückwunsch! Ihre Prämie: {{ reward_name }}\n\nBesuchen Sie {{ store_name }}!\n\nMit freundlichen Grüßen,\n{{ store_name }}",
"lb": "Moien {{ customer_name }},\n\nFelicitatiounen! Är Belounung: {{ reward_name }}\n\nBesicht {{ store_name }}!\n\nLéif Gréiss,\n{{ store_name }}",
},
},
]
for defn in _defs:
for lang in ("en", "fr", "de", "lb"):
templates.append({
"code": defn["code"],
"language": lang,
"name": defn["name"][lang],
"description": defn["description"],
"category": defn["category"],
"variables": json.dumps(defn["variables"]),
"required_variables": json.dumps(defn["variables"]),
"subject": defn["subject"][lang],
"body_html": defn["body_html"][lang],
"body_text": defn["body_text"][lang],
"is_platform_only": False,
})
return templates
TEMPLATES = _make_templates()
# =============================================================================
# SEED FUNCTION
# =============================================================================
def seed_templates():
"""Seed loyalty email templates into database (idempotent)."""
db = next(get_db())
try:
created = 0
updated = 0
for template_data in TEMPLATES:
existing = (
db.query(EmailTemplate)
.filter(
EmailTemplate.code == template_data["code"],
EmailTemplate.language == template_data["language"],
)
.first()
)
if existing:
for key, value in template_data.items():
setattr(existing, key, value)
updated += 1
print(f" Updated: {template_data['code']} ({template_data['language']})")
else:
template = EmailTemplate(**template_data)
db.add(template)
created += 1
print(f" Created: {template_data['code']} ({template_data['language']})")
db.commit()
print(f"\nLoyalty templates — Created: {created}, Updated: {updated}")
except Exception as e:
db.rollback()
print(f"Error: {e}")
raise
finally:
db.close()
if __name__ == "__main__":
seed_templates()