Configuring access rights in the CRM Super Admin
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Organization" ADD COLUMN IF NOT EXISTS "enabledModules" TEXT[] DEFAULT ARRAY[]::TEXT[];
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE IF NOT EXISTS "BillingAccount" (
|
||||
"id" TEXT NOT NULL,
|
||||
"ownerUserId" TEXT NOT NULL,
|
||||
"planCode" TEXT NOT NULL DEFAULT 'trial',
|
||||
"status" TEXT NOT NULL DEFAULT 'active',
|
||||
"startsAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"maxOrganizations" INTEGER NOT NULL DEFAULT 1,
|
||||
"externalPaymentRef" TEXT,
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "BillingAccount_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "BillingAccount_ownerUserId_key" ON "BillingAccount"("ownerUserId");
|
||||
CREATE INDEX IF NOT EXISTS "BillingAccount_status_idx" ON "BillingAccount"("status");
|
||||
CREATE INDEX IF NOT EXISTS "BillingAccount_expiresAt_idx" ON "BillingAccount"("expiresAt");
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'BillingAccount_ownerUserId_fkey'
|
||||
) THEN
|
||||
ALTER TABLE "BillingAccount"
|
||||
ADD CONSTRAINT "BillingAccount_ownerUserId_fkey"
|
||||
FOREIGN KEY ("ownerUserId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Backfill: full module catalog for existing orgs (empty → all)
|
||||
UPDATE "Organization"
|
||||
SET "enabledModules" = ARRAY[
|
||||
'customers','pipeline','products','invoices','quotes','commissions','sales_forecast',
|
||||
'activities','tickets','workflow_automations','message_automations','lead_forms',
|
||||
'project_boards','inventory','cheques','attachments','loyalty','reports','notes',
|
||||
'tahesab','custom_fields','currencies','team_settings','faq'
|
||||
]::TEXT[]
|
||||
WHERE cardinality("enabledModules") = 0;
|
||||
|
||||
-- Backfill BillingAccount for every active owner membership (15-day trial)
|
||||
INSERT INTO "BillingAccount" (
|
||||
"id", "ownerUserId", "planCode", "status", "startsAt", "expiresAt",
|
||||
"maxOrganizations", "createdAt", "updatedAt"
|
||||
)
|
||||
SELECT
|
||||
md5(random()::text || clock_timestamp()::text || m."userId"),
|
||||
m."userId",
|
||||
'trial',
|
||||
'active',
|
||||
NOW(),
|
||||
NOW() + INTERVAL '15 days',
|
||||
GREATEST(1, COUNT(*)::int),
|
||||
NOW(),
|
||||
NOW()
|
||||
FROM "Membership" m
|
||||
WHERE m."role" = 'owner' AND m."isActive" = true
|
||||
GROUP BY m."userId"
|
||||
ON CONFLICT ("ownerUserId") DO NOTHING;
|
||||
@@ -147,6 +147,8 @@ model Organization {
|
||||
businessField String?
|
||||
baseCurrency String @default("IRR")
|
||||
enabledCurrencies String[] @default(["IRR", "USD", "EUR"])
|
||||
/// ماژولهای فعال منو برای این محیط
|
||||
enabledModules String[] @default([])
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -230,6 +232,7 @@ model User {
|
||||
ticketMessages TicketMessage[] @relation("TicketMessageAuthor")
|
||||
confirmedMemories MemoryItem[] @relation("MemoryConfirmedBy")
|
||||
memberships Membership[]
|
||||
billingAccount BillingAccount?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -293,6 +296,28 @@ model PlatformAdmin {
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
/// اشتراک SaaS روی مالک حساب (نه روی هر عضویت)
|
||||
model BillingAccount {
|
||||
id String @id @default(cuid())
|
||||
ownerUserId String @unique
|
||||
owner User @relation(fields: [ownerUserId], references: [id], onDelete: Cascade)
|
||||
/// trial | monthly | quarterly | semiannual | yearly | custom
|
||||
planCode String @default("trial")
|
||||
/// active | expired | suspended
|
||||
status String @default("active")
|
||||
startsAt DateTime @default(now())
|
||||
expiresAt DateTime
|
||||
maxOrganizations Int @default(1)
|
||||
/// رزرو برای درگاه/لندینگ بعدی
|
||||
externalPaymentRef String?
|
||||
notes String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([status])
|
||||
@@index([expiresAt])
|
||||
}
|
||||
|
||||
model Company {
|
||||
id String @id @default(cuid())
|
||||
organizationId String
|
||||
|
||||
+38
-4
@@ -44,9 +44,19 @@ async function seedPlatformAdmin() {
|
||||
return;
|
||||
}
|
||||
|
||||
const passwordHash = await bcrypt.hash(password, 10);
|
||||
const existingByMobile = await prisma.platformAdmin.findUnique({ where: { mobile } });
|
||||
if (existingByMobile) {
|
||||
console.log('Platform admin exists:', mobile);
|
||||
await prisma.platformAdmin.update({
|
||||
where: { id: existingByMobile.id },
|
||||
data: {
|
||||
passwordHash,
|
||||
fullName: envName,
|
||||
isActive: true,
|
||||
...(email ? { email } : {}),
|
||||
},
|
||||
});
|
||||
console.log('Platform admin password refreshed:', mobile);
|
||||
return;
|
||||
}
|
||||
if (email) {
|
||||
@@ -54,14 +64,38 @@ async function seedPlatformAdmin() {
|
||||
if (existingByEmail) {
|
||||
await prisma.platformAdmin.update({
|
||||
where: { id: existingByEmail.id },
|
||||
data: { mobile, ...(envName ? { fullName: envName } : {}) },
|
||||
data: {
|
||||
mobile,
|
||||
passwordHash,
|
||||
fullName: envName,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
console.log('Platform admin linked mobile:', mobile, '→', email);
|
||||
console.log('Platform admin linked mobile + password refreshed:', mobile, '→', email);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const passwordHash = await bcrypt.hash(password, 10);
|
||||
const orphans = await prisma.platformAdmin.findMany({
|
||||
where: { mobile: null },
|
||||
take: 2,
|
||||
});
|
||||
if (orphans.length === 1) {
|
||||
const orphan = orphans[0]!;
|
||||
await prisma.platformAdmin.update({
|
||||
where: { id: orphan.id },
|
||||
data: {
|
||||
mobile,
|
||||
passwordHash,
|
||||
fullName: envName,
|
||||
isActive: true,
|
||||
...(email ? { email } : {}),
|
||||
},
|
||||
});
|
||||
console.log('Platform admin orphan linked mobile:', mobile);
|
||||
return;
|
||||
}
|
||||
|
||||
await prisma.platformAdmin.create({
|
||||
data: {
|
||||
mobile,
|
||||
|
||||
Reference in New Issue
Block a user