Replace the entity slugs on organizations, workspaces, resource pools, and plan ladders with nullable `key` columns and add keys to products, prices, and entitlement sets. Rename `providers.slug` to `provider` and add partial unique indexes for system and org role names. Assign invoice numbers per billing account from a gapless transactional counter; Stripe's number moves to the invoice mapping as an external reference. Seeds, fixtures, and the operator lookup address rows by key, and the returning-login resync no longer blanks a display name when the IdP sends no `name` claim.
440 lines
22 KiB
PL/PgSQL
440 lines
22 KiB
PL/PgSQL
-- mockshot-seed.sql — reversible sample data at deployment scale, for
|
|
-- screenshots and demos (first used for the README operator-overview shot).
|
|
--
|
|
-- Identity: the database generates every ID (Decision 30), and this seed
|
|
-- addresses the rows it owns by `key` (grammar `^[a-z][a-z0-9_]*$`).
|
|
-- Organizations are keyed `mockshot_0001` .. `mockshot_2842` (one per mock
|
|
-- member) and `mockshot_t0001` .. `mockshot_t0038` (the teams). Each mock
|
|
-- person's user row carries the external reference `mockshot:<org key>`, so a
|
|
-- person is found by its OIDC subject and never by its email address, its
|
|
-- display name, or a marker column. The catalog rows are keyed
|
|
-- `mockshot_hosting` (entitlement set), `mockshot_starter_hosting` and its
|
|
-- four siblings (products), and `monthly` / `yearly` within each product
|
|
-- (prices). Everything else — pools, workspaces, assignments, accounts,
|
|
-- subscriptions, items, invoices, payments, grants, provisions — is reached
|
|
-- through its organization or its catalog parent, so it needs no key of its
|
|
-- own. mockshot-nuke.sql reverses the seed from exactly those three
|
|
-- predicates: the `mockshot_` organization key prefix, the `mockshot:`
|
|
-- subject prefix, and the catalog keys.
|
|
--
|
|
-- Re-running the seed inserts nothing: each keyed insert conflicts on its
|
|
-- key, and each keyless insert is guarded by a NOT EXISTS on the parent that
|
|
-- already carries the child.
|
|
--
|
|
-- Run against a test stack (port/creds from bootstrap-stack.sh output):
|
|
-- psql -h localhost -p $POSTGRES_PORT -U member_console -d member_console \
|
|
-- -v ON_ERROR_STOP=1 -f test/mockshot-seed.sql
|
|
-- Reverse with mockshot-nuke.sql the same way. Nuke BEFORE restarting the
|
|
-- app: the boot-time entitlement materializer must never reconcile the mock
|
|
-- provisions into real pool entitlements.
|
|
--
|
|
-- Shape follows the app's own mechanics: signup mints a personal org per
|
|
-- member (backdated over ~19 months of joins) with its default pool,
|
|
-- workspace, and default-plan grant; team orgs are created by long-standing
|
|
-- members; upgrading to a paid subscription ends the free default's delivery
|
|
-- (ladder exclusion); subscriptions carry priced items so monthly recurring
|
|
-- money is real.
|
|
--
|
|
-- Targets (existing → after): People 5→2,847 (~148 joined in 30 days),
|
|
-- Team orgs 0→38, Subscriptions 0→214 (205 active + 9 trialing, MRR
|
|
-- $2,160.00), Open invoices 0→9 ($372.00 outstanding), Delivering operator
|
|
-- grants →19 with ≈2,669 signup defaults in the caption, Catalog
|
|
-- products 10→15.
|
|
BEGIN;
|
|
|
|
-- 2,842 users, joined over ~19 months (i*5h + jitter). The subject carries
|
|
-- the organization key the member's personal org gets below, which is how
|
|
-- every later statement recovers the member's index without an ID of its own.
|
|
INSERT INTO core.users (oidc_subject, created_at)
|
|
SELECT 'mockshot:mockshot_' || lpad(i::text, 4, '0'),
|
|
now() - (i * 5 || ' hours')::interval - (random() * interval '55 minutes')
|
|
FROM generate_series(1, 2842) AS i
|
|
ON CONFLICT (oidc_subject) DO NOTHING;
|
|
|
|
-- The member index, its organization key, and its join date, read back from
|
|
-- the rows themselves so a second run sees exactly what the first created.
|
|
-- `i` orders members by join date: member 1 is the newest, 2842 the oldest.
|
|
CREATE TEMP TABLE mock_members ON COMMIT DROP AS
|
|
SELECT split_part(u.oidc_subject, '_', 2)::int AS i,
|
|
'mockshot_' || split_part(u.oidc_subject, '_', 2) AS org_key,
|
|
u.user_id,
|
|
u.created_at AS joined_at
|
|
FROM core.users u
|
|
WHERE u.oidc_subject LIKE 'mockshot:%';
|
|
|
|
INSERT INTO core.persons (user_id, display_name, primary_email, primary_email_verified, created_at)
|
|
SELECT
|
|
m.user_id,
|
|
-- Stride 7 is coprime to 20, so consecutive members vary in both names
|
|
-- and (first, last) pairs stay unique across lcm(21,20)=420 indices.
|
|
(ARRAY['Ada','Basil','Corin','Dara','Emil','Freya','Gus','Hana','Iris','Jonas','Kira',
|
|
'Lior','Mara','Nils','Odile','Piotr','Quinn','Rosa','Sana','Theo','Uma'])[1 + (m.i % 21)]
|
|
|| ' ' ||
|
|
(ARRAY['Aldana','Brook','Calder','Dove','Ellis','Fen','Grange','Hale','Ingram','Jarvis',
|
|
'Keel','Lark','Marsh','North','Orr','Penn','Quill','Reyes','Sorrel','Vale'])[1 + ((m.i * 7) % 20)],
|
|
-- Realistic data, never a predicate: nothing in this file or in the nuke
|
|
-- selects on an address.
|
|
'mock-p' || m.i || '@example.test',
|
|
true,
|
|
m.joined_at + interval '1 minute'
|
|
FROM mock_members m
|
|
WHERE NOT EXISTS (SELECT 1 FROM core.persons p WHERE p.user_id = m.user_id);
|
|
|
|
-- One personal org per mock person, keyed with the member's own key and timed
|
|
-- the way signup mints them (two minutes after the person row).
|
|
INSERT INTO core.organizations (name, key, org_type, owner_person_id, created_at)
|
|
SELECT p.display_name || '''s Organization',
|
|
m.org_key,
|
|
'personal',
|
|
p.person_id,
|
|
m.joined_at + interval '2 minutes'
|
|
FROM mock_members m
|
|
JOIN core.persons p ON p.user_id = m.user_id
|
|
ON CONFLICT (key) DO NOTHING;
|
|
|
|
-- The team org type (design taxonomy: personal / team / enterprise —
|
|
-- design/organization/architecture.md §2). Org types are vocabulary whose
|
|
-- primary key is the type itself, so there is nothing to key; the nuke
|
|
-- removes it only if no organization is left using it.
|
|
INSERT INTO core.org_types (org_type, display_name, description, is_active)
|
|
VALUES ('team', 'Team', 'An organization more than one person works in.', true)
|
|
ON CONFLICT (org_type) DO NOTHING;
|
|
|
|
-- 38 team orgs created by long-standing members (the oldest signups), each
|
|
-- 10-70 days after its owner joined.
|
|
INSERT INTO core.organizations (name, key, org_type, owner_person_id, created_at)
|
|
SELECT
|
|
CASE WHEN k <= 4 THEN
|
|
(ARRAY['Cedar Print Collective','Prairie Signal Cooperative',
|
|
'Harbor Works Guild','Juniper Commons'])[k]
|
|
ELSE
|
|
(ARRAY['Cedar','Prairie','Harbor','Juniper','Granite','Meadow','Larkspur','Alder'])[1 + ((k - 5) % 8)]
|
|
|| ' ' ||
|
|
(ARRAY['Collective','Cooperative','Commons','Works','Guild','Studio','Assembly','Exchange'])[1 + ((k - 5) / 8)]
|
|
END,
|
|
'mockshot_t' || lpad(k::text, 4, '0'),
|
|
'team',
|
|
p.person_id,
|
|
m.joined_at + interval '10 days' + (random() * interval '60 days')
|
|
FROM generate_series(1, 38) AS k
|
|
JOIN mock_members m ON m.i = 2843 - k
|
|
JOIN core.persons p ON p.user_id = m.user_id
|
|
ON CONFLICT (key) DO NOTHING;
|
|
|
|
-- Every org gets the default pool and workspace signup mints for it
|
|
-- (provisioning.CreateWorkspaceWithPrimaryAssignment), so mock provisions
|
|
-- land in the org's own pool instead of borrowing somebody else's. The pool
|
|
-- is the one row the system keys by design, `default`; the workspace carries
|
|
-- no key, and the assignment is the pair, so neither does it.
|
|
INSERT INTO core.resource_pools (org_id, name, key, pool_type, is_auto_managed, created_at)
|
|
SELECT o.org_id, 'Default', 'default', 'default', TRUE, o.created_at
|
|
FROM core.organizations o
|
|
WHERE o.key LIKE 'mockshot\_%'
|
|
ON CONFLICT (org_id, key) DO NOTHING;
|
|
|
|
INSERT INTO core.workspaces (org_id, name, created_at)
|
|
SELECT o.org_id, 'Default', o.created_at
|
|
FROM core.organizations o
|
|
WHERE o.key LIKE 'mockshot\_%'
|
|
AND NOT EXISTS (SELECT 1 FROM core.workspaces w WHERE w.org_id = o.org_id);
|
|
|
|
INSERT INTO core.pool_assignments (pool_id, workspace_id, is_primary, created_at)
|
|
SELECT rp.pool_id, w.workspace_id, TRUE, o.created_at
|
|
FROM core.organizations o
|
|
JOIN core.resource_pools rp ON rp.org_id = o.org_id AND rp.key = 'default'
|
|
JOIN core.workspaces w ON w.org_id = o.org_id
|
|
WHERE o.key LIKE 'mockshot\_%'
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM core.pool_assignments pa
|
|
WHERE pa.pool_id = rp.pool_id AND pa.workspace_id = w.workspace_id
|
|
);
|
|
|
|
-- Billing accounts: every team + the 176 most recent personal orgs.
|
|
INSERT INTO core.accounts (org_id, name, created_at)
|
|
SELECT o.org_id, o.name || ' billing', o.created_at
|
|
FROM (
|
|
SELECT org_id, name, created_at
|
|
FROM core.organizations
|
|
WHERE key LIKE 'mockshot\_%'
|
|
ORDER BY (key LIKE 'mockshot\_t%') DESC, created_at DESC
|
|
LIMIT 214
|
|
) o
|
|
WHERE NOT EXISTS (SELECT 1 FROM core.accounts a WHERE a.org_id = o.org_id);
|
|
|
|
-- 214 live subscriptions: teams first (all active), then individuals;
|
|
-- the last 9 are trialing.
|
|
INSERT INTO core.subscriptions (billing_account_id, status, current_period_start, current_period_end)
|
|
SELECT billing_account_id,
|
|
CASE WHEN rn <= 205 THEN 'active' ELSE 'trialing' END,
|
|
date_trunc('month', now()),
|
|
date_trunc('month', now()) + interval '1 month'
|
|
FROM (
|
|
SELECT a.billing_account_id,
|
|
row_number() OVER (ORDER BY (o.key LIKE 'mockshot\_t%') DESC, a.billing_account_id) AS rn
|
|
FROM core.accounts a
|
|
JOIN core.organizations o ON o.org_id = a.org_id
|
|
WHERE o.key LIKE 'mockshot\_%'
|
|
AND NOT EXISTS (SELECT 1 FROM core.subscriptions s WHERE s.billing_account_id = a.billing_account_id)
|
|
) accts;
|
|
|
|
-- The entitlement the mock plans confer. One set, one numeric rule, so the
|
|
-- mock provisions carry a real entitlement set instead of borrowing one.
|
|
INSERT INTO core.entitlement_sets (name, key, description, is_active)
|
|
VALUES ('Hosting baseline', 'mockshot_hosting',
|
|
'Entitlements every hosting plan confers.', TRUE)
|
|
ON CONFLICT (key) DO NOTHING;
|
|
|
|
INSERT INTO core.entitlement_set_rules (set_id, rule_type, resource_key,
|
|
resource_value, resource_per_unit, stacking_policy, description)
|
|
SELECT es.set_id, 'limit', 'fedwiki_sites', 5, FALSE, 'additive', 'Up to 5 sites.'
|
|
FROM core.entitlement_sets es
|
|
WHERE es.key = 'mockshot_hosting'
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM core.entitlement_set_rules r
|
|
WHERE r.set_id = es.set_id AND r.resource_key = 'fedwiki_sites'
|
|
);
|
|
|
|
-- Five mock products with deployment-plausible names (before the prices,
|
|
-- items, and grants that reference them). Published so grant events print
|
|
-- them.
|
|
INSERT INTO core.products (name, key, description, display_category,
|
|
entitlement_set_id, lifecycle_status)
|
|
SELECT v.name, v.key, v.description, v.display_category,
|
|
CASE WHEN v.confers THEN es.set_id END,
|
|
'published'
|
|
FROM (VALUES
|
|
('Starter Hosting', 'mockshot_starter_hosting', 'Default plan for new members', NULL, TRUE),
|
|
('Standard Hosting', 'mockshot_standard_hosting', 'Paid hosting tier', NULL, TRUE),
|
|
('Sustainer Hosting', 'mockshot_sustainer_hosting', 'Supporter hosting tier', NULL, TRUE),
|
|
('Custom Domain', 'mockshot_custom_domain', 'Serve a site on your own domain', 'addon', FALSE),
|
|
('Storage Boost', 'mockshot_storage_boost', 'Additional media storage', 'addon', FALSE)
|
|
) AS v(name, key, description, display_category, confers)
|
|
CROSS JOIN (SELECT set_id FROM core.entitlement_sets WHERE key = 'mockshot_hosting') es
|
|
ON CONFLICT (key) DO NOTHING;
|
|
|
|
-- Prices for the paid tiers, keyed within their product: Standard $9/mo
|
|
-- (default), Sustainer $18/mo (default) with an $180/yr alternative — the
|
|
-- yearly price exercises the one-twelfth normalization on the overview's
|
|
-- money headline.
|
|
INSERT INTO core.prices (product_id, key, currency, unit_amount, recurring_interval, is_default)
|
|
SELECT p.product_id, v.key, 'usd', v.unit_amount, v.recurring_interval, v.is_default
|
|
FROM (VALUES
|
|
('mockshot_standard_hosting', 'monthly', 900, 'month', TRUE),
|
|
('mockshot_sustainer_hosting', 'monthly', 1800, 'month', TRUE),
|
|
('mockshot_sustainer_hosting', 'yearly', 18000, 'year', FALSE)
|
|
) AS v(product_key, key, unit_amount, recurring_interval, is_default)
|
|
JOIN core.products p ON p.key = v.product_key
|
|
ON CONFLICT (product_id, key) DO NOTHING;
|
|
|
|
-- Subscription items: teams ride Sustainer (every 4th on the yearly price),
|
|
-- individuals ride Standard monthly. The team's number comes out of its own
|
|
-- key, the only arithmetic the rest of the file needs.
|
|
INSERT INTO core.subscription_items (subscription_id, product_id, price_id, quantity)
|
|
SELECT s.subscription_id, pr.product_id, pr.price_id, 1
|
|
FROM core.subscriptions s
|
|
JOIN core.accounts a ON a.billing_account_id = s.billing_account_id
|
|
JOIN core.organizations o ON o.org_id = a.org_id
|
|
JOIN LATERAL (
|
|
SELECT p.price_id, p.product_id
|
|
FROM core.prices p
|
|
JOIN core.products pd ON pd.product_id = p.product_id
|
|
WHERE CASE
|
|
WHEN o.key LIKE 'mockshot\_t%' THEN
|
|
pd.key = 'mockshot_sustainer_hosting'
|
|
AND p.key = CASE WHEN split_part(o.key, '_t', 2)::int % 4 = 0 THEN 'yearly' ELSE 'monthly' END
|
|
ELSE pd.key = 'mockshot_standard_hosting' AND p.key = 'monthly'
|
|
END
|
|
LIMIT 1
|
|
) pr ON true
|
|
WHERE o.key LIKE 'mockshot\_%'
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM core.subscription_items si WHERE si.subscription_id = s.subscription_id
|
|
);
|
|
|
|
-- The signup default-plan grant for every mock org, stamped minutes after
|
|
-- the org itself (system-authored: reason 'default' ⟺ no granting person,
|
|
-- per chk_grants_default_iff_system_authored).
|
|
INSERT INTO core.grants (product_id, granted_to_org_id, granted_by_person_id,
|
|
grant_reason, description, valid_from, created_at)
|
|
SELECT pd.product_id, o.org_id, NULL, 'default', 'Plan issued at signup',
|
|
o.created_at + interval '2 minutes',
|
|
o.created_at + interval '2 minutes'
|
|
FROM core.organizations o
|
|
CROSS JOIN (SELECT product_id FROM core.products WHERE key = 'mockshot_starter_hosting') pd
|
|
WHERE o.key LIKE 'mockshot\_%'
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM core.grants g
|
|
WHERE g.granted_to_org_id = o.org_id AND g.grant_reason = 'default'
|
|
);
|
|
|
|
-- Each default grant delivers in its own org's pool.
|
|
INSERT INTO core.pool_provisions (pool_id, grant_id, status, entitlement_set_id, product_id, activated_at)
|
|
SELECT rp.pool_id, g.grant_id, 'active', es.set_id, g.product_id, g.created_at
|
|
FROM core.grants g
|
|
JOIN core.organizations o ON o.org_id = g.granted_to_org_id AND o.key LIKE 'mockshot\_%'
|
|
JOIN core.resource_pools rp ON rp.org_id = o.org_id AND rp.key = 'default'
|
|
CROSS JOIN (SELECT set_id FROM core.entitlement_sets WHERE key = 'mockshot_hosting') es
|
|
WHERE g.grant_reason = 'default'
|
|
AND NOT EXISTS (SELECT 1 FROM core.pool_provisions pp WHERE pp.grant_id = g.grant_id);
|
|
|
|
-- Auto-provisioning: every live mock subscription delivers its plan, so a
|
|
-- paying org is never provision-less after its free default ends below.
|
|
INSERT INTO core.pool_provisions (pool_id, subscription_id, status, entitlement_set_id, product_id)
|
|
SELECT rp.pool_id, s.subscription_id, 'active', es.set_id, si.product_id
|
|
FROM core.subscriptions s
|
|
JOIN core.accounts a ON a.billing_account_id = s.billing_account_id
|
|
JOIN core.organizations o ON o.org_id = a.org_id AND o.key LIKE 'mockshot\_%'
|
|
JOIN core.resource_pools rp ON rp.org_id = o.org_id AND rp.key = 'default'
|
|
JOIN core.subscription_items si ON si.subscription_id = s.subscription_id
|
|
CROSS JOIN (SELECT set_id FROM core.entitlement_sets WHERE key = 'mockshot_hosting') es
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM core.pool_provisions pp
|
|
WHERE pp.subscription_id = s.subscription_id AND pp.product_id = si.product_id
|
|
);
|
|
|
|
-- Eighteen operator-issued extras (upgrades, promos) over the last ~3 days,
|
|
-- on the 18 newest members' orgs. The operator is a real console person,
|
|
-- never one of the mock members: mock members are exactly the users whose
|
|
-- subject carries the mockshot prefix.
|
|
INSERT INTO core.grants (product_id, granted_to_org_id, granted_by_person_id,
|
|
grant_reason, description, valid_from, created_at)
|
|
SELECT pd.product_id,
|
|
o.org_id,
|
|
op.person_id,
|
|
CASE WHEN m.i % 2 = 0 THEN 'manual' ELSE 'promotional' END,
|
|
'Issued by an operator',
|
|
now() - (m.i * interval '4 hours') - interval '20 minutes',
|
|
now() - (m.i * interval '4 hours') - interval '20 minutes'
|
|
FROM mock_members m
|
|
JOIN core.organizations o ON o.key = m.org_key
|
|
JOIN core.products pd ON pd.key = CASE
|
|
WHEN m.i % 4 = 1 THEN 'mockshot_standard_hosting'
|
|
WHEN m.i % 4 = 2 THEN 'mockshot_sustainer_hosting'
|
|
WHEN m.i % 4 = 3 THEN 'mockshot_custom_domain'
|
|
ELSE 'mockshot_storage_boost' END
|
|
CROSS JOIN LATERAL (
|
|
SELECT p.person_id
|
|
FROM core.persons p
|
|
JOIN core.users u ON u.user_id = p.user_id
|
|
WHERE u.oidc_subject NOT LIKE 'mockshot:%'
|
|
ORDER BY (p.display_name ILIKE 'alice%') DESC, p.created_at
|
|
LIMIT 1
|
|
) op
|
|
WHERE m.i <= 18
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM core.grants g
|
|
WHERE g.granted_to_org_id = o.org_id AND g.grant_reason <> 'default'
|
|
);
|
|
|
|
INSERT INTO core.pool_provisions (pool_id, grant_id, status, entitlement_set_id, product_id, activated_at)
|
|
SELECT rp.pool_id, g.grant_id, 'active', es.set_id, g.product_id, g.created_at
|
|
FROM core.grants g
|
|
JOIN core.organizations o ON o.org_id = g.granted_to_org_id AND o.key LIKE 'mockshot\_%'
|
|
JOIN core.resource_pools rp ON rp.org_id = o.org_id AND rp.key = 'default'
|
|
CROSS JOIN (SELECT set_id FROM core.entitlement_sets WHERE key = 'mockshot_hosting') es
|
|
WHERE g.grant_reason <> 'default'
|
|
AND NOT EXISTS (SELECT 1 FROM core.pool_provisions pp WHERE pp.grant_id = g.grant_id);
|
|
|
|
-- Invoices, one per billing account across the first fifteen teams
|
|
-- (invoice-numbers change, data model v16.2): the number is the platform's
|
|
-- own, per billing account, a zero-padded four-digit decimal starting at
|
|
-- 0001 — and since every account here holds exactly one seeded invoice,
|
|
-- every one of the fifteen is numbered 0001. Each account's
|
|
-- next_invoice_number moves to 2 below, past the number this seed assigned.
|
|
--
|
|
-- 9 open invoices, on teams t0001..t0009, staggered over the last two days
|
|
-- for the timeline.
|
|
INSERT INTO core.invoices (billing_account_id, status, amount_due, amount_paid, currency,
|
|
period_start, period_end, due_date, created_at, invoice_number)
|
|
SELECT a.billing_account_id, 'open',
|
|
(ARRAY[2400, 4800, 1200, 9600, 2400, 7200, 4800, 1200, 3600])[n], 0, 'usd',
|
|
now() - interval '1 month', now(), now() + interval '14 days',
|
|
now() - (n * interval '5 hours'),
|
|
'0001'
|
|
FROM generate_series(1, 9) AS n
|
|
JOIN core.organizations o ON o.key = 'mockshot_t' || lpad(n::text, 4, '0')
|
|
JOIN core.accounts a ON a.org_id = o.org_id
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM core.invoices i WHERE i.billing_account_id = a.billing_account_id
|
|
);
|
|
|
|
-- 6 recently paid invoices, on teams t0010..t0015, each settled by a
|
|
-- succeeded payment two minutes after issue — the timeline's
|
|
-- payment_received rows.
|
|
INSERT INTO core.invoices (billing_account_id, status, amount_due, amount_paid, currency,
|
|
period_start, period_end, due_date, paid_at, created_at, invoice_number)
|
|
SELECT a.billing_account_id, 'paid',
|
|
(ARRAY[2400, 4800, 2400, 9600, 4800, 2400])[n],
|
|
(ARRAY[2400, 4800, 2400, 9600, 4800, 2400])[n], 'usd',
|
|
now() - interval '1 month', now(), now() + interval '14 days',
|
|
now() - (n * interval '11 hours') + interval '2 minutes',
|
|
now() - (n * interval '11 hours'),
|
|
'0001'
|
|
FROM generate_series(1, 6) AS n
|
|
JOIN core.organizations o ON o.key = 'mockshot_t' || lpad((n + 9)::text, 4, '0')
|
|
JOIN core.accounts a ON a.org_id = o.org_id
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM core.invoices i WHERE i.billing_account_id = a.billing_account_id
|
|
);
|
|
|
|
INSERT INTO core.payments (invoice_id, billing_account_id, amount, currency, status, created_at)
|
|
SELECT i.invoice_id, i.billing_account_id, i.amount_paid, 'usd', 'succeeded',
|
|
i.created_at + interval '2 minutes'
|
|
FROM core.invoices i
|
|
JOIN core.accounts a ON a.billing_account_id = i.billing_account_id
|
|
JOIN core.organizations o ON o.org_id = a.org_id AND o.key LIKE 'mockshot\_t%'
|
|
WHERE i.status = 'paid'
|
|
AND NOT EXISTS (SELECT 1 FROM core.payments pm WHERE pm.invoice_id = i.invoice_id);
|
|
|
|
-- Each of the fifteen accounts just numbered one invoice 0001; its counter
|
|
-- moves to 2. The `<> 2` guard makes a second run of this seed a no-op here
|
|
-- as well as everywhere above.
|
|
UPDATE core.accounts a
|
|
SET next_invoice_number = 2
|
|
FROM core.organizations o
|
|
WHERE o.org_id = a.org_id
|
|
AND o.key LIKE 'mockshot\_t%'
|
|
AND split_part(o.key, '_t', 2)::int <= 15
|
|
AND a.next_invoice_number <> 2;
|
|
|
|
-- Upgrading ends the free default's delivery: the ladder table's exclusion
|
|
-- admits one active provision per pool per ladder, so each subscribed org's
|
|
-- default-grant provision ended when its paid subscription provisioned. The
|
|
-- grant itself stays 'active' in the ledger — exactly the ledger-vs-delivery
|
|
-- distinction the overview tile is built around.
|
|
UPDATE core.pool_provisions pp
|
|
SET status = 'ended',
|
|
ended_at = pp.activated_at + interval '40 minutes'
|
|
FROM core.grants g
|
|
JOIN core.organizations o ON o.org_id = g.granted_to_org_id
|
|
JOIN core.accounts a ON a.org_id = g.granted_to_org_id
|
|
JOIN core.subscriptions s ON s.billing_account_id = a.billing_account_id
|
|
WHERE pp.grant_id = g.grant_id
|
|
AND g.grant_reason = 'default'
|
|
AND o.key LIKE 'mockshot\_%'
|
|
AND pp.status <> 'ended';
|
|
|
|
COMMIT;
|
|
|
|
-- Post-seed tallies
|
|
SELECT (SELECT count(*) FROM core.persons WHERE status = 'active') AS people,
|
|
(SELECT count(*) FROM core.persons WHERE status = 'active'
|
|
AND created_at > now() - interval '30 days') AS joined_30d,
|
|
(SELECT count(*) FROM core.organizations o JOIN core.org_types t ON t.org_type = o.org_type
|
|
WHERE o.status = 'active' AND o.org_type <> 'personal' AND NOT t.is_reserved) AS team_orgs,
|
|
(SELECT count(*) FROM core.subscriptions WHERE status = 'active') AS active_subs,
|
|
(SELECT count(*) FROM core.subscriptions WHERE status = 'trialing') AS trialing_subs,
|
|
(SELECT sum(CASE p.recurring_interval WHEN 'month' THEN p.unit_amount * si.quantity
|
|
WHEN 'year' THEN (p.unit_amount * si.quantity) / 12 ELSE 0 END)
|
|
FROM core.subscriptions s
|
|
JOIN core.subscription_items si ON si.subscription_id = s.subscription_id
|
|
JOIN core.prices p ON p.price_id = si.price_id
|
|
WHERE s.status = 'active') AS mrr_cents,
|
|
(SELECT count(*) FROM core.invoices WHERE status = 'open') AS open_invoices,
|
|
(SELECT count(DISTINCT g.grant_id) FROM core.grants g
|
|
JOIN core.pool_provisions pp ON pp.grant_id = g.grant_id
|
|
WHERE pp.status = 'active' AND g.grant_reason <> 'default') AS operator_grants,
|
|
(SELECT count(DISTINCT g.grant_id) FROM core.grants g
|
|
JOIN core.pool_provisions pp ON pp.grant_id = g.grant_id
|
|
WHERE pp.status = 'active' AND g.grant_reason = 'default') AS default_grants;
|