diff --git a/deploy/position-type-grant-mapping.sql b/deploy/position-type-grant-mapping.sql new file mode 100644 index 000000000..8efe7baba --- /dev/null +++ b/deploy/position-type-grant-mapping.sql @@ -0,0 +1,105 @@ +-- Position-type grant mapping for the granular permission system rollout. +-- Grants the NEW granular keys to every hand-curated position type that holds +-- the old broad key whose routes the new keys took over. Idempotent (unique +-- constraint on (position_type_id, permission_id) + ON CONFLICT DO NOTHING). +-- +-- PREREQUISITE: run the seeded API once first (SEED_EDR_ORG=true) so +-- EdrOrgSeeder has created the new permission rows this script references. +-- Running it too early is not destructive but silently under-applies: keys that +-- do not exist yet simply match nothing (measured: 11 of 42 rows land pre-seed, +-- because invoices:view/export and payments:view already exist on dev). Re-run +-- after seeding — it is safe to run any number of times. +-- +-- Verified 2026-08-07 on a virgin restore of the live dev DB: seeded boot, then +-- this script → 42 rows inserted, second run → 0 rows, final per-key grant +-- counts identical to the reference environment. Per-user API probes across 20 +-- departmental test accounts confirm the keys resolve through /me and gate +-- routes correctly. + +BEGIN; + +-- Helper shape used throughout: +-- holders of => also grant + +-- 1. bookings:view holders => dashboard/read keys that replaced blanket access +INSERT INTO iam.position_type_permissions (position_type_id, permission_id) +SELECT DISTINCT ptp.position_type_id, pnew.id +FROM iam.position_type_permissions ptp +JOIN iam.permissions pold ON pold.id = ptp.permission_id + AND pold.key = 'edr_freight_app:bookings:view' +JOIN iam.permissions pnew ON pnew.key IN ( + 'edr_freight_app:overview:view', + 'edr_freight_app:reports:view', + 'edr_freight_app:invoices:view', + 'edr_freight_app:invoices:export', + 'edr_freight_app:payments:view' +) +ON CONFLICT (position_type_id, permission_id) DO NOTHING; + +-- 2. train_scheduling:update holders => the write actions split out of it +INSERT INTO iam.position_type_permissions (position_type_id, permission_id) +SELECT DISTINCT ptp.position_type_id, pnew.id +FROM iam.position_type_permissions ptp +JOIN iam.permissions pold ON pold.id = ptp.permission_id + AND pold.key = 'edr_freight_app:train_scheduling:update' +JOIN iam.permissions pnew ON pnew.key IN ( + 'edr_freight_app:train_scheduling:dispatch', + 'edr_freight_app:train_scheduling:mark_paid', + 'edr_freight_app:train_scheduling:expire_booking' +) +ON CONFLICT (position_type_id, permission_id) DO NOTHING; + +-- 3. GL Djibouti clearance holders => final-invoice raise + confirm +INSERT INTO iam.position_type_permissions (position_type_id, permission_id) +SELECT DISTINCT ptp.position_type_id, pnew.id +FROM iam.position_type_permissions ptp +JOIN iam.permissions pold ON pold.id = ptp.permission_id + AND pold.key = 'edr_freight_app:contracts:clearance_dj_actions' +JOIN iam.permissions pnew ON pnew.key IN ( + 'edr_freight_app:contracts:final_invoice_raise', + 'edr_freight_app:contracts:final_invoice_confirm' +) +ON CONFLICT (position_type_id, permission_id) DO NOTHING; + +-- 4. GL Ethiopia clearance holders => final-invoice confirm +INSERT INTO iam.position_type_permissions (position_type_id, permission_id) +SELECT DISTINCT ptp.position_type_id, pnew.id +FROM iam.position_type_permissions ptp +JOIN iam.permissions pold ON pold.id = ptp.permission_id + AND pold.key = 'edr_freight_app:contracts:clearance_et_actions' +JOIN iam.permissions pnew ON pnew.key = 'edr_freight_app:contracts:final_invoice_confirm' +ON CONFLICT (position_type_id, permission_id) DO NOTHING; + +-- 5. Contract-intake holders (any staff_accept flavour) => edit_document +INSERT INTO iam.position_type_permissions (position_type_id, permission_id) +SELECT DISTINCT ptp.position_type_id, pnew.id +FROM iam.position_type_permissions ptp +JOIN iam.permissions pold ON pold.id = ptp.permission_id + AND pold.key IN ( + 'edr_freight_app:bookings:staff_accept', + 'edr_freight_app:contracts:staff_accept:bulk', + 'edr_freight_app:contracts:staff_accept:container' + ) +JOIN iam.permissions pnew ON pnew.key = 'edr_freight_app:contracts:edit_document' +ON CONFLICT (position_type_id, permission_id) DO NOTHING; + +-- 6. Support inbox ownership (decision 2026-08-07): marketing department types +INSERT INTO iam.position_type_permissions (position_type_id, permission_id) +SELECT pt.id, p.id +FROM iam.position_types pt +CROSS JOIN iam.permissions p +WHERE (pt.name::text ILIKE '%marketing%' OR pt.key ILIKE '%marketing%') + AND p.key IN ( + 'edr_freight_app:support:agent_view', + 'edr_freight_app:support:agent_send' + ) +ON CONFLICT (position_type_id, permission_id) DO NOTHING; + +COMMIT; + +-- Verification: expected non-zero counts per new key after running. +-- SELECT p.key, count(*) FROM iam.position_type_permissions ptp +-- JOIN iam.permissions p ON p.id = ptp.permission_id +-- WHERE p.key IN ('edr_freight_app:overview:view','edr_freight_app:support:agent_view', +-- 'edr_freight_app:train_scheduling:dispatch','edr_freight_app:contracts:final_invoice_raise') +-- GROUP BY p.key;