Files
2026-06-28 18:00:14 -04:00

96 lines
3.6 KiB
Python

"""
control/seed.py
---------------
Idempotent seeding of the baseline plans (upsert by `code`). Mirrors the
plan-tier matrix in MULTI_TENANT_PLAN.md §6. Safe to re-run — re-running
updates existing plan rows in place rather than creating duplicates.
stripe_price_id values are read from the environment at seed time so this
file never contains live keys. Set before running:
export STRIPE_PRICE_STARTER=price_xxx
export STRIPE_PRICE_PRO=price_yyy
export STRIPE_PRICE_ENTERPRISE=price_zzz
Rows where the env var is absent keep their current stripe_price_id value
(existing rows) or get NULL (new rows) — no data is lost on a partial run.
"""
import os
from dotenv import load_dotenv
load_dotenv()
from control.base import control_session
from control.models import Plan
# Read Stripe price IDs from the environment; fall back to None so the
# seeder is safe to run even when Stripe is not yet configured.
_STRIPE_PRICES = {
'free': None, # Free plan is never sold via Stripe
'starter': os.environ.get('STRIPE_PRICE_STARTER'),
'pro': os.environ.get('STRIPE_PRICE_PRO'),
'enterprise': os.environ.get('STRIPE_PRICE_ENTERPRISE'),
}
PLAN_DEFS = [
dict(code='free', name='Free',
max_users=3, max_facilities=2,
max_inspections_month=50, max_issues_month=50,
allow_custom_domain=False, allow_mobile_api=False,
allow_scheduled_reports=False, allow_branding=False,
price_cents=0, billing_period='month'),
dict(code='starter', name='Starter',
max_users=15, max_facilities=10,
max_inspections_month=500, max_issues_month=500,
allow_custom_domain=False, allow_mobile_api=True,
allow_scheduled_reports=False, allow_branding=False,
price_cents=None, billing_period='month'),
dict(code='pro', name='Pro',
max_users=50, max_facilities=50,
max_inspections_month=5000, max_issues_month=5000,
allow_custom_domain=True, allow_mobile_api=True,
allow_scheduled_reports=True, allow_branding=True,
price_cents=None, billing_period='month'),
dict(code='enterprise', name='Enterprise',
max_users=None, max_facilities=None,
max_inspections_month=None, max_issues_month=None,
allow_custom_domain=True, allow_mobile_api=True,
allow_scheduled_reports=True, allow_branding=True,
price_cents=None, billing_period='month'),
]
def seed_plans():
"""Upsert baseline plans. Returns (created_count, updated_count).
stripe_price_id is only written when the corresponding env var is set —
existing values are never overwritten with None on a partial run.
"""
created, updated = 0, 0
with control_session() as s:
for d in PLAN_DEFS:
plan = s.query(Plan).filter_by(code=d['code']).first()
price_id = _STRIPE_PRICES.get(d['code'])
if plan is None:
plan = Plan(**d)
plan.stripe_price_id = price_id # None is fine for new rows
s.add(plan)
created += 1
else:
for k, v in d.items():
setattr(plan, k, v)
# Only overwrite stripe_price_id when the env var is present.
if price_id is not None:
plan.stripe_price_id = price_id
updated += 1
return created, updated
if __name__ == '__main__':
c, u = seed_plans()
print(f'Plans seeded — created={c} updated={u}')
for code, pid in _STRIPE_PRICES.items():
label = pid or '(not set — env var absent)'
print(f' {code}: stripe_price_id = {label}')