- FastAPI skeleton, SQLAlchemy models (§13), Alembic initial migration - SchedulingProvider interface with google_calendar (free/busy read-only), partner_api (Appendix B client) and mock implementations - Proposal engine: create → provider-routed delivery → owner actions (resolve/confirm+SMS/reject) → expiry + reminders (§9) - Signed single-use action links, .ics METHOD:REQUEST attachment - Partner outcome webhook with HMAC verification + polling fallback - SmsProvider (console) with Bosnian templates (§5.5), EmailProvider (console/SMTP) - Fake partner API server in tests/ — Appendix B reference implementation - 43 tests: slot math, proposal lifecycle, action links, partner contract Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
38 lines
1.3 KiB
Python
38 lines
1.3 KiB
Python
"""Per-tenant SMS templates with safe Bosnian defaults (§5.5).
|
|
|
|
Tenant overrides live in tenants.sms_templates (only overridden keys stored).
|
|
Placeholders are .format()-style; unknown placeholders are left intact.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from gogo.models import Tenant
|
|
|
|
DEFAULT_TEMPLATES: dict[str, str] = {
|
|
"missed_call": (
|
|
"Poštovani, dobili ste {salon}. Možete zakazati i putem poruke ili chata: {link}. "
|
|
"Nazvaćemo vas ili nas pozovite ponovo."
|
|
),
|
|
"request_received": (
|
|
"Primili smo vaš zahtjev za termin. Javićemo vam potvrdu u najkraćem roku. — {salon}"
|
|
),
|
|
"confirmation": "Potvrđen termin: {usluga}, {dan} {datum} u {vrijeme}h — {salon}.",
|
|
"rejection": "{salon}: nažalost traženi termin nije moguć. Molimo pozovite nas da dogovorimo drugi.",
|
|
"expiry": (
|
|
"{salon}: nismo uspjeli potvrditi vaš zahtjev za termin. "
|
|
"Molimo pozovite nas ponovo da dogovorimo termin."
|
|
),
|
|
}
|
|
|
|
|
|
class _SafeDict(dict):
|
|
def __missing__(self, key: str) -> str:
|
|
return "{" + key + "}"
|
|
|
|
|
|
def render_sms(tenant: Tenant, kind: str, **params: str) -> str:
|
|
templates = {**DEFAULT_TEMPLATES, **(tenant.sms_templates or {})}
|
|
template = templates[kind]
|
|
params.setdefault("salon", tenant.name)
|
|
return template.format_map(_SafeDict(params))
|