fix
This commit is contained in:
233
src/profile-fields/profile-fields.service.ts
Normal file
233
src/profile-fields/profile-fields.service.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
import { Injectable, NotFoundException, ConflictException, BadRequestException } from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma';
|
||||
import { CreateFieldDto, UpdateFieldDto } from './dto';
|
||||
|
||||
@Injectable()
|
||||
export class ProfileFieldsService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
/**
|
||||
* Generate a URL-friendly slug from the name
|
||||
*/
|
||||
private generateSlug(name: string): string {
|
||||
return name
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '_')
|
||||
.replace(/(^_|_$)/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new profile field
|
||||
*/
|
||||
async create(createFieldDto: CreateFieldDto) {
|
||||
const { sectionId, options, rangeConfig, validation, uiConfig, ...rest } = createFieldDto;
|
||||
|
||||
// Verify section exists
|
||||
const section = await this.prisma.profileSection.findUnique({
|
||||
where: { id: sectionId },
|
||||
});
|
||||
|
||||
if (!section) {
|
||||
throw new NotFoundException(`Section with ID ${sectionId} not found`);
|
||||
}
|
||||
|
||||
// Generate slug
|
||||
const slug = this.generateSlug(createFieldDto.name);
|
||||
|
||||
// Check if slug is unique within section
|
||||
const existing = await this.prisma.profileField.findUnique({
|
||||
where: {
|
||||
sectionId_slug: {
|
||||
sectionId,
|
||||
slug,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
throw new ConflictException('A field with this name already exists in this section');
|
||||
}
|
||||
|
||||
// Validate options for certain field types
|
||||
const requiresOptions = ['SELECT', 'RADIO', 'MULTI_SELECT', 'CHECKBOX'];
|
||||
if (requiresOptions.includes(createFieldDto.fieldType) && (!options || options.length === 0)) {
|
||||
// CHECKBOX can be a single toggle without options
|
||||
if (createFieldDto.fieldType !== 'CHECKBOX') {
|
||||
throw new BadRequestException(`Field type ${createFieldDto.fieldType} requires options`);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate range config for RANGE type
|
||||
if (createFieldDto.fieldType === 'RANGE' && !rangeConfig) {
|
||||
throw new BadRequestException('RANGE field type requires rangeConfig');
|
||||
}
|
||||
|
||||
return this.prisma.profileField.create({
|
||||
data: {
|
||||
...rest,
|
||||
sectionId,
|
||||
slug,
|
||||
options: options ? (options as unknown as Prisma.InputJsonValue) : undefined,
|
||||
rangeConfig: rangeConfig ? (rangeConfig as unknown as Prisma.InputJsonValue) : undefined,
|
||||
validation: validation ? (validation as unknown as Prisma.InputJsonValue) : undefined,
|
||||
uiConfig: uiConfig ? (uiConfig as unknown as Prisma.InputJsonValue) : undefined,
|
||||
},
|
||||
include: {
|
||||
section: {
|
||||
select: { id: true, name: true, slug: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all fields, optionally filtered by section
|
||||
*/
|
||||
async findAll(sectionId?: string, includeInactive = false) {
|
||||
const where: any = {};
|
||||
|
||||
if (sectionId) {
|
||||
where.sectionId = sectionId;
|
||||
}
|
||||
|
||||
if (!includeInactive) {
|
||||
where.isActive = true;
|
||||
}
|
||||
|
||||
return this.prisma.profileField.findMany({
|
||||
where,
|
||||
orderBy: [{ sortOrder: 'asc' }, { name: 'asc' }],
|
||||
include: {
|
||||
section: {
|
||||
select: { id: true, name: true, slug: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single field by ID
|
||||
*/
|
||||
async findOne(id: string) {
|
||||
const field = await this.prisma.profileField.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
section: {
|
||||
select: { id: true, name: true, slug: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!field) {
|
||||
throw new NotFoundException(`Field with ID ${id} not found`);
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a field
|
||||
*/
|
||||
async update(id: string, updateFieldDto: UpdateFieldDto) {
|
||||
const field = await this.findOne(id);
|
||||
|
||||
const data: any = { ...updateFieldDto };
|
||||
|
||||
// If name is being updated, update the slug too
|
||||
if (updateFieldDto.name) {
|
||||
const newSlug = this.generateSlug(updateFieldDto.name);
|
||||
|
||||
// Check if new slug conflicts within the section
|
||||
const existing = await this.prisma.profileField.findFirst({
|
||||
where: {
|
||||
AND: [
|
||||
{ id: { not: id } },
|
||||
{ sectionId: field.sectionId },
|
||||
{ slug: newSlug },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
throw new ConflictException('A field with this name already exists in this section');
|
||||
}
|
||||
|
||||
data.slug = newSlug;
|
||||
}
|
||||
|
||||
return this.prisma.profileField.update({
|
||||
where: { id },
|
||||
data,
|
||||
include: {
|
||||
section: {
|
||||
select: { id: true, name: true, slug: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a field
|
||||
*/
|
||||
async remove(id: string) {
|
||||
await this.findOne(id);
|
||||
|
||||
// Check if any agents have values for this field
|
||||
const valuesCount = await this.prisma.agentProfileFieldValue.count({
|
||||
where: { fieldId: id },
|
||||
});
|
||||
|
||||
if (valuesCount > 0) {
|
||||
throw new ConflictException(
|
||||
`Cannot delete field. ${valuesCount} agent(s) have values for this field. Deactivate the field instead.`,
|
||||
);
|
||||
}
|
||||
|
||||
return this.prisma.profileField.delete({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder fields within a section
|
||||
*/
|
||||
async reorderFields(sectionId: string, fieldIds: string[]) {
|
||||
// Verify section exists
|
||||
const section = await this.prisma.profileSection.findUnique({
|
||||
where: { id: sectionId },
|
||||
});
|
||||
|
||||
if (!section) {
|
||||
throw new NotFoundException(`Section with ID ${sectionId} not found`);
|
||||
}
|
||||
|
||||
// Update sort order for each field
|
||||
const updates = fieldIds.map((fieldId, index) =>
|
||||
this.prisma.profileField.update({
|
||||
where: { id: fieldId },
|
||||
data: { sortOrder: index },
|
||||
}),
|
||||
);
|
||||
|
||||
await this.prisma.$transaction(updates);
|
||||
|
||||
return this.findAll(sectionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk update fields (for drag-drop reordering)
|
||||
*/
|
||||
async bulkUpdateOrder(updates: { id: string; sortOrder: number }[]) {
|
||||
const operations = updates.map(({ id, sortOrder }) =>
|
||||
this.prisma.profileField.update({
|
||||
where: { id },
|
||||
data: { sortOrder },
|
||||
}),
|
||||
);
|
||||
|
||||
await this.prisma.$transaction(operations);
|
||||
|
||||
return { success: true, updated: updates.length };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user