feat: implement email template system with vendor overrides
Add comprehensive email template management for both admin and vendors:
Admin Features:
- Email templates management page at /admin/email-templates
- Edit platform templates with language support (en, fr, de, lb)
- Preview templates with sample variables
- Send test emails
- View email logs per template
Vendor Features:
- Email templates customization page at /vendor/{code}/email-templates
- Override platform templates with vendor-specific versions
- Preview and test customized templates
- Revert to platform defaults
Technical Changes:
- Migration for vendor_email_templates table
- VendorEmailTemplate model with override management
- Enhanced EmailService with language resolution chain
(customer preferred -> vendor preferred -> platform default)
- Branding resolution (Wizamart default, removed for whitelabel)
- Platform-only template protection (billing templates)
- Admin and vendor API endpoints with full CRUD
- Updated seed script with billing and team templates
Files: 22 changed, ~3,900 lines added
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
114
alembic/versions/u9c0d1e2f3g4_add_vendor_email_templates.py
Normal file
114
alembic/versions/u9c0d1e2f3g4_add_vendor_email_templates.py
Normal file
@@ -0,0 +1,114 @@
|
||||
# alembic/versions/u9c0d1e2f3g4_add_vendor_email_templates.py
|
||||
"""Add vendor email templates and enhance email_templates table.
|
||||
|
||||
Revision ID: u9c0d1e2f3g4
|
||||
Revises: t8b9c0d1e2f3
|
||||
Create Date: 2026-01-03
|
||||
|
||||
Changes:
|
||||
- Add is_platform_only column to email_templates (templates that vendors cannot override)
|
||||
- Add required_variables column to email_templates (JSON list of required variables)
|
||||
- Create vendor_email_templates table for vendor-specific template overrides
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "u9c0d1e2f3g4"
|
||||
down_revision = "t8b9c0d1e2f3"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# Add new columns to email_templates
|
||||
op.add_column(
|
||||
"email_templates",
|
||||
sa.Column("is_platform_only", sa.Boolean(), nullable=False, server_default="0"),
|
||||
)
|
||||
op.add_column(
|
||||
"email_templates",
|
||||
sa.Column("required_variables", sa.Text(), nullable=True),
|
||||
)
|
||||
|
||||
# Create vendor_email_templates table
|
||||
op.create_table(
|
||||
"vendor_email_templates",
|
||||
sa.Column("id", sa.Integer(), nullable=False, autoincrement=True),
|
||||
sa.Column("vendor_id", sa.Integer(), nullable=False),
|
||||
sa.Column("template_code", sa.String(100), nullable=False),
|
||||
sa.Column("language", sa.String(5), nullable=False, server_default="en"),
|
||||
sa.Column("name", sa.String(255), nullable=True),
|
||||
sa.Column("subject", sa.String(500), nullable=False),
|
||||
sa.Column("body_html", sa.Text(), nullable=False),
|
||||
sa.Column("body_text", sa.Text(), nullable=True),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False, server_default="1"),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(),
|
||||
nullable=False,
|
||||
server_default=sa.text("CURRENT_TIMESTAMP"),
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(),
|
||||
nullable=False,
|
||||
server_default=sa.text("CURRENT_TIMESTAMP"),
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.ForeignKeyConstraint(
|
||||
["vendor_id"],
|
||||
["vendors.id"],
|
||||
name="fk_vendor_email_templates_vendor_id",
|
||||
ondelete="CASCADE",
|
||||
),
|
||||
sa.UniqueConstraint(
|
||||
"vendor_id",
|
||||
"template_code",
|
||||
"language",
|
||||
name="uq_vendor_email_template_code_language",
|
||||
),
|
||||
)
|
||||
|
||||
# Create indexes for performance
|
||||
op.create_index(
|
||||
"ix_vendor_email_templates_vendor_id",
|
||||
"vendor_email_templates",
|
||||
["vendor_id"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_vendor_email_templates_template_code",
|
||||
"vendor_email_templates",
|
||||
["template_code"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_vendor_email_templates_lookup",
|
||||
"vendor_email_templates",
|
||||
["vendor_id", "template_code", "language"],
|
||||
)
|
||||
|
||||
# Add unique constraint to email_templates for code+language
|
||||
# This ensures we can reliably look up platform templates
|
||||
op.create_index(
|
||||
"ix_email_templates_code_language",
|
||||
"email_templates",
|
||||
["code", "language"],
|
||||
unique=True,
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
# Drop indexes
|
||||
op.drop_index("ix_email_templates_code_language", table_name="email_templates")
|
||||
op.drop_index("ix_vendor_email_templates_lookup", table_name="vendor_email_templates")
|
||||
op.drop_index("ix_vendor_email_templates_template_code", table_name="vendor_email_templates")
|
||||
op.drop_index("ix_vendor_email_templates_vendor_id", table_name="vendor_email_templates")
|
||||
|
||||
# Drop vendor_email_templates table
|
||||
op.drop_table("vendor_email_templates")
|
||||
|
||||
# Remove new columns from email_templates
|
||||
op.drop_column("email_templates", "required_variables")
|
||||
op.drop_column("email_templates", "is_platform_only")
|
||||
Reference in New Issue
Block a user