update account creation fileds

This commit is contained in:
root
2026-05-25 14:05:37 -04:00
parent 813eb5404c
commit 914ae839a7
10 changed files with 448 additions and 50 deletions
+25
View File
@@ -322,6 +322,31 @@ export function updateAdminRole(id: string, role: string) {
return prisma.adminUser.update({ where: { id }, data: { role: role as any } })
}
export function updateAdmin(
id: string,
data: {
email?: string
firstName?: string
lastName?: string
role?: string
passwordHash?: string
isActive?: boolean
},
) {
return prisma.adminUser.update({
where: { id },
data: {
...(data.email !== undefined ? { email: data.email } : {}),
...(data.firstName !== undefined ? { firstName: data.firstName } : {}),
...(data.lastName !== undefined ? { lastName: data.lastName } : {}),
...(data.role !== undefined ? { role: data.role as any } : {}),
...(data.passwordHash !== undefined ? { passwordHash: data.passwordHash } : {}),
...(data.isActive !== undefined ? { isActive: data.isActive } : {}),
},
include: { permissions: true },
})
}
export async function replaceAdminPermissions(id: string, permissions: any[]) {
await prisma.adminUser.findUniqueOrThrow({ where: { id } })
await prisma.$transaction([
@@ -10,6 +10,7 @@ import {
companiesQuerySchema, rentersQuerySchema, auditLogQuerySchema, billingQuerySchema, notificationsQuerySchema,
invoicesQuerySchema, adminCompanyUpdateSchema, companyStatusSchema,
createAdminSchema, adminRoleSchema, adminPermissionsSchema,
updateAdminSchema,
homepageUpdateSchema, idParamSchema, companyIdParamSchema, billingAccountIdParamSchema, invoiceIdParamSchema,
pricingUpdateSchema, planFeatureCreateSchema, planFeatureUpdateSchema, planFeatureIdParamSchema,
promotionCreateSchema, promotionUpdateSchema, promotionIdParamSchema,
@@ -188,6 +189,13 @@ router.post('/admins', requireAdminAuth, requireAdminRole('SUPER_ADMIN'), async
} catch (err) { next(err) }
})
router.patch('/admins/:id', requireAdminAuth, requireAdminRole('SUPER_ADMIN'), async (req, res, next) => {
try {
const { id } = parseParams(idParamSchema, req)
ok(res, await service.updateAdmin(id, parseBody(updateAdminSchema, req)))
} catch (err) { next(err) }
})
router.patch('/admins/:id/role', requireAdminAuth, requireAdminRole('SUPER_ADMIN'), async (req, res, next) => {
try {
const { id } = parseParams(idParamSchema, req)
+12 -3
View File
@@ -71,14 +71,23 @@ export const permissionSchema = z.object({
})
export const createAdminSchema = z.object({
email: z.string().email(),
firstName: z.string(),
lastName: z.string(),
email: z.string().email().trim().toLowerCase(),
firstName: z.string().min(1).max(100).trim(),
lastName: z.string().min(1).max(100).trim(),
role: z.enum(['SUPER_ADMIN', 'ADMIN', 'SUPPORT', 'FINANCE', 'VIEWER']),
password: z.string().min(8),
permissions: z.array(permissionSchema).optional(),
})
export const updateAdminSchema = z.object({
email: z.string().email().trim().toLowerCase().optional(),
firstName: z.string().min(1).max(100).trim().optional(),
lastName: z.string().min(1).max(100).trim().optional(),
role: z.enum(['SUPER_ADMIN', 'ADMIN', 'SUPPORT', 'FINANCE', 'VIEWER']).optional(),
password: z.string().min(8).optional(),
isActive: z.boolean().optional(),
})
export const adminRoleSchema = z.object({
role: z.enum(['SUPER_ADMIN', 'ADMIN', 'SUPPORT', 'FINANCE', 'VIEWER']),
})
@@ -216,6 +216,29 @@ export async function createAdmin(body: { email: string; firstName: string; last
return presenter.presentAdminUser(admin)
}
export async function updateAdmin(
id: string,
body: {
email?: string
firstName?: string
lastName?: string
role?: string
password?: string
isActive?: boolean
},
) {
const admin = await repo.updateAdmin(id, {
...(body.email !== undefined ? { email: body.email } : {}),
...(body.firstName !== undefined ? { firstName: body.firstName } : {}),
...(body.lastName !== undefined ? { lastName: body.lastName } : {}),
...(body.role !== undefined ? { role: body.role } : {}),
...(body.isActive !== undefined ? { isActive: body.isActive } : {}),
...(body.password ? { passwordHash: await bcrypt.hash(body.password, 12) } : {}),
})
return presenter.presentAdminUser(admin)
}
export function updateAdminRole(id: string, role: string) {
return repo.updateAdminRole(id, role)
}
+35 -5
View File
@@ -4,8 +4,10 @@ type DbClient = Pick<typeof prisma, 'company' | 'brandSettings' | 'contractSetti
type CompanySignupInput = {
companyName: string
legalName: string
slug: string
email: string
ownerEmail: string
companyEmail: string
companyPhone: string
streetAddress: string
city: string
@@ -13,8 +15,21 @@ type CompanySignupInput = {
zipCode: string
legalForm: string
managerName: string
iceNumber: string
taxId: string
operatingLicenseNumber: string
operatingLicenseIssuedAt: string
operatingLicenseIssuedBy: string
fax?: string
yearsActive: string
representativeName?: string
representativeTitle?: string
responsibleName: string
responsibleRole: string
responsibleIdentityNumber: string
responsibleQualification?: string
responsiblePhone: string
responsibleEmail: string
currency: 'MAD'
registrationNumber: string
plan: 'STARTER' | 'GROWTH' | 'PRO'
@@ -44,17 +59,31 @@ export async function createCompanySignup(db: DbClient, input: CompanySignupInpu
data: {
name: input.companyName,
slug: input.slug,
email: input.email,
email: input.companyEmail,
phone: input.companyPhone,
address: {
streetAddress: input.streetAddress,
city: input.city,
country: input.country,
zipCode: input.zipCode,
legalName: input.legalName,
legalForm: input.legalForm,
companyEmail: input.companyEmail,
managerName: input.managerName,
iceNumber: input.iceNumber,
operatingLicenseNumber: input.operatingLicenseNumber,
operatingLicenseIssuedAt: input.operatingLicenseIssuedAt,
operatingLicenseIssuedBy: input.operatingLicenseIssuedBy,
fax: input.fax || null,
yearsActive: input.yearsActive,
representativeName: input.representativeName || null,
representativeTitle: input.representativeTitle || null,
responsibleName: input.responsibleName,
responsibleRole: input.responsibleRole,
responsibleIdentityNumber: input.responsibleIdentityNumber,
responsibleQualification: input.responsibleQualification || null,
responsiblePhone: input.responsiblePhone,
responsibleEmail: input.responsibleEmail,
},
status: 'TRIALING',
},
@@ -65,7 +94,7 @@ export async function createCompanySignup(db: DbClient, input: CompanySignupInpu
companyId: company.id,
displayName: input.companyName,
subdomain: input.slug,
publicEmail: input.email,
publicEmail: input.companyEmail,
publicPhone: input.companyPhone,
publicAddress: input.streetAddress,
publicCity: input.city,
@@ -78,8 +107,9 @@ export async function createCompanySignup(db: DbClient, input: CompanySignupInpu
await db.contractSettings.create({
data: {
companyId: company.id,
legalName: input.companyName,
legalName: input.legalName,
registrationNumber: input.registrationNumber,
taxId: input.taxId,
},
})
@@ -103,7 +133,7 @@ export async function createCompanySignup(db: DbClient, input: CompanySignupInpu
clerkUserId: `local_owner_${company.id}`,
firstName: input.firstName,
lastName: input.lastName,
email: input.email,
email: input.ownerEmail,
phone: input.companyPhone,
passwordHash: input.passwordHash,
role: 'OWNER',
@@ -6,15 +6,30 @@ export const companySignupSchema = z.object({
email: z.string().email(),
password: z.string().min(8).max(128),
companyName: z.string().min(2).max(120),
legalName: z.string().min(2).max(160),
legalForm: z.string().min(1).max(80),
registrationNumber: z.string().min(1).max(120),
iceNumber: z.string().min(1).max(120),
taxId: z.string().min(1).max(120),
operatingLicenseNumber: z.string().min(1).max(120),
operatingLicenseIssuedAt: z.string().min(1).max(40),
operatingLicenseIssuedBy: z.string().min(1).max(160),
streetAddress: z.string().min(1).max(200),
city: z.string().min(1).max(120),
country: z.string().min(1).max(120),
zipCode: z.string().min(1).max(40),
companyPhone: z.string().min(1).max(80),
companyEmail: z.string().email(),
fax: z.string().max(80).optional(),
yearsActive: z.string().min(1).max(80),
representativeName: z.string().max(160).optional(),
representativeTitle: z.string().max(120).optional(),
responsibleName: z.string().min(1).max(160),
responsibleRole: z.string().min(1).max(120),
responsibleIdentityNumber: z.string().min(1).max(120),
responsibleQualification: z.string().max(200).optional(),
responsiblePhone: z.string().min(1).max(80),
responsibleEmail: z.string().email(),
preferredLanguage: z.enum(['en', 'fr', 'ar']).default('en'),
plan: z.enum(['STARTER', 'GROWTH', 'PRO']),
billingPeriod: z.enum(['MONTHLY', 'ANNUAL']),
@@ -32,12 +32,12 @@ async function generateUniqueSlug(baseName: string) {
}
export async function signup(body: CompanySignupInput) {
if (await repo.findCompanyByEmail(body.email)) {
throw new AppError('A company account with this email already exists', 409, 'email_taken')
if (await repo.findCompanyByEmail(body.companyEmail)) {
throw new AppError('A company account with this contact email already exists', 409, 'company_email_taken')
}
if (await repo.findEmployeeByEmail(body.email)) {
throw new AppError('An employee account with this email already exists', 409, 'email_taken')
throw new AppError('An employee account with this owner email already exists', 409, 'owner_email_taken')
}
const slug = await generateUniqueSlug(body.companyName)
@@ -47,8 +47,10 @@ export async function signup(body: CompanySignupInput) {
const result = await prisma.$transaction((tx) => repo.createCompanySignup(tx, {
companyName: body.companyName,
legalName: body.legalName,
slug,
email: body.email,
ownerEmail: body.email,
companyEmail: body.companyEmail,
companyPhone: body.companyPhone,
streetAddress: body.streetAddress,
city: body.city,
@@ -56,8 +58,21 @@ export async function signup(body: CompanySignupInput) {
zipCode: body.zipCode,
legalForm: body.legalForm,
managerName: `${body.firstName} ${body.lastName}`.trim(),
iceNumber: body.iceNumber,
taxId: body.taxId,
operatingLicenseNumber: body.operatingLicenseNumber,
operatingLicenseIssuedAt: body.operatingLicenseIssuedAt,
operatingLicenseIssuedBy: body.operatingLicenseIssuedBy,
fax: body.fax,
yearsActive: body.yearsActive,
representativeName: body.representativeName,
representativeTitle: body.representativeTitle,
responsibleName: body.responsibleName,
responsibleRole: body.responsibleRole,
responsibleIdentityNumber: body.responsibleIdentityNumber,
responsibleQualification: body.responsibleQualification,
responsiblePhone: body.responsiblePhone,
responsibleEmail: body.responsibleEmail,
currency: body.currency,
registrationNumber: body.registrationNumber,
plan: body.plan,