Seeding patterns.
apps/backend/prisma/seed.ts.apps/backend/prisma/seed-production.ts.apps/backend/prisma/seeds/*.seed.ts.apps/backend/prisma/seeds/shared/client.ts.apps/backend/prisma/seeds/shared/database.ts and database-scripts/clean.ts.Seeds are flat modules under apps/backend/prisma/seeds/, not seeds/dev and seeds/prod directories.
seed.ts runs modules sequentially, logs errors per module, continues, and exits with code 1 if any module failed.
Current main order includes default templates, permissions/roles, system payment methods, organizations/stores, legal documents, users, products/categories, PUC/account mappings, domains, addresses, inventory locations, test orders, help articles, payroll defaults, AI apps, and subscription plans.
seed-production.ts is a lighter runner for essential production data: templates, permissions/roles, account mappings, payroll rules, AI apps, system payment methods, and default trial subscription plan.
Commented usage is npx tsx prisma/seed-production.ts; there is no current backend package script pointing to it.
npm run db:seed -w apps/backend
npm run db:reset -w apps/backend
npm run db:clean -w apps/backend
npm run db:reset-seed
Do not run reset/clean unless explicitly requested; these are destructive.
seeds/shared/client.ts uses a singleton Prisma 7 client with PrismaPg adapter and pg.Pool. It falls back to a local placeholder URL if DATABASE_URL is missing.
upsert for stable records.findUnique + update/create when composite/business logic needs custom behavior.ON CONFLICT DO NOTHING in migration seed SQL.Examples in current code:
organization_id_code.code to preserve ids.clearDatabase() uses deleteMany({}) in reverse dependency order. It is destructive and intended for local/dev seed utilities only. Do not use it in production flows.
syncRolePermissionsLocation: apps/backend/prisma/seeds/shared/sync-role-permissions.ts.
Canonical helper for synchronizing a role's role_permissions rows against a desired permission set. Replaces the legacy "upsert in a loop + optional deleteMany({ permission_id: { notIn } })" pattern that previously lived inline in permissions-roles.seed.ts.
import { syncRolePermissions } from './shared/sync-role-permissions';
await syncRolePermissions(
client, // PrismaClient | Prisma.TransactionClient
roleId, // number β roles.id
allowedPermissionIds, // number[] β canonical permissions.id list
label, // string β log label, e.g. "STORE_ADMIN (manager)"
);
// β { added: number; revoked: number }
permissions.findMany().allowedPermissionIds as authoritative for that role.createMany({ skipDuplicates: true }) against the
@@unique([role_id, permission_id]) constraint β re-runs never duplicate
rows.deleteMany({ permission_id: { notIn: allowedIds } }),
which is a set-difference operation. The second run sees an empty diff and
is a no-op.allowedIds is handled safely: the helper deletes everything for
that role and skips the insert pass β useful when fully retiring a role's
permissions.permissions-roles.seed.ts)const managerPermissions = allPermissions.filter(/* canonical filter */);
const managerSync = await syncRolePermissions(
client,
managerRole.id,
managerPermissions.map((p) => p.id),
'STORE_ADMIN (manager)',
);
assignmentsCreated += managerSync.added;
// Logs: "π Synced STORE_ADMIN (manager): +N added, -M revoked (canonical=K)"
The helper replaced ~15 lines per role of upsert loops plus the
deleteMany+notIn revocation block, while preserving the existing
assignmentsCreated accounting and console.log traceability. All seven
system roles (super_admin, owner, admin, manager, supervisor,
employee, customer, cashier) now use it.
vendix-prisma-migrationsvendix-prisma-schemavendix-prisma-scopes