import { Injectable, NotFoundException, ConflictException } from '@nestjs/common'; import { PrismaService } from '../prisma'; import { CreateSectionDto, UpdateSectionDto, AssignSectionDto, UpdateAssignmentDto } from './dto'; @Injectable() export class ProfileSectionsService { 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 section */ async create(createSectionDto: CreateSectionDto) { const slug = this.generateSlug(createSectionDto.name); // Check if slug already exists const existing = await this.prisma.profileSection.findUnique({ where: { slug }, }); if (existing) { throw new ConflictException('A section with this name already exists'); } return this.prisma.profileSection.create({ data: { ...createSectionDto, slug, }, include: { _count: { select: { fields: true, agentTypeSections: true }, }, }, }); } /** * Get all profile sections */ async findAll(includeInactive = false) { const where = includeInactive ? {} : { isActive: true }; return this.prisma.profileSection.findMany({ where, orderBy: [{ sortOrder: 'asc' }, { name: 'asc' }], include: { _count: { select: { fields: true, agentTypeSections: true }, }, agentTypeSections: { include: { agentType: { select: { id: true, name: true }, }, }, }, }, }); } /** * Get a single section by ID with all fields */ async findOne(id: string) { const section = await this.prisma.profileSection.findUnique({ where: { id }, include: { fields: { orderBy: { sortOrder: 'asc' }, }, agentTypeSections: { include: { agentType: { select: { id: true, name: true }, }, }, orderBy: { sortOrder: 'asc' }, }, _count: { select: { fields: true, agentTypeSections: true }, }, }, }); if (!section) { throw new NotFoundException(`Section with ID ${id} not found`); } return section; } /** * Update a section */ async update(id: string, updateSectionDto: UpdateSectionDto) { // Check if section exists await this.findOne(id); const data: any = { ...updateSectionDto }; // If name is being updated, update the slug too if (updateSectionDto.name) { const newSlug = this.generateSlug(updateSectionDto.name); // Check if new slug conflicts with another section const existing = await this.prisma.profileSection.findFirst({ where: { AND: [{ id: { not: id } }, { slug: newSlug }], }, }); if (existing) { throw new ConflictException('A section with this name already exists'); } data.slug = newSlug; } return this.prisma.profileSection.update({ where: { id }, data, include: { _count: { select: { fields: true, agentTypeSections: true }, }, }, }); } /** * Delete a section */ async remove(id: string) { const section = await this.findOne(id); // Check if section has any fields if (section._count.fields > 0) { throw new ConflictException( `Cannot delete section. It has ${section._count.fields} field(s). Delete the fields first.`, ); } return this.prisma.profileSection.delete({ where: { id }, }); } /** * Assign a section to an agent type */ async assignToAgentType(sectionId: string, assignDto: AssignSectionDto) { // Verify section exists await this.findOne(sectionId); // Verify agent type exists const agentType = await this.prisma.agentType.findUnique({ where: { id: assignDto.agentTypeId }, }); if (!agentType) { throw new NotFoundException(`Agent type with ID ${assignDto.agentTypeId} not found`); } // Check if already assigned const existing = await this.prisma.agentTypeSection.findUnique({ where: { agentTypeId_sectionId: { agentTypeId: assignDto.agentTypeId, sectionId, }, }, }); if (existing) { throw new ConflictException('This section is already assigned to this agent type'); } return this.prisma.agentTypeSection.create({ data: { sectionId, agentTypeId: assignDto.agentTypeId, sortOrder: assignDto.sortOrder ?? 0, isRequired: assignDto.isRequired ?? false, }, include: { section: true, agentType: true, }, }); } /** * Update a section assignment */ async updateAssignment(sectionId: string, agentTypeId: string, updateDto: UpdateAssignmentDto) { const assignment = await this.prisma.agentTypeSection.findUnique({ where: { agentTypeId_sectionId: { agentTypeId, sectionId, }, }, }); if (!assignment) { throw new NotFoundException('Section assignment not found'); } return this.prisma.agentTypeSection.update({ where: { id: assignment.id }, data: updateDto, include: { section: true, agentType: true, }, }); } /** * Remove a section from an agent type */ async removeFromAgentType(sectionId: string, agentTypeId: string) { const assignment = await this.prisma.agentTypeSection.findUnique({ where: { agentTypeId_sectionId: { agentTypeId, sectionId, }, }, }); if (!assignment) { throw new NotFoundException('Section assignment not found'); } return this.prisma.agentTypeSection.delete({ where: { id: assignment.id }, }); } /** * Get all sections for a specific agent type */ async getSectionsForAgentType(agentTypeId: string, includeFields = true) { const agentType = await this.prisma.agentType.findUnique({ where: { id: agentTypeId }, }); if (!agentType) { throw new NotFoundException(`Agent type with ID ${agentTypeId} not found`); } // Get assigned sections const assignments = await this.prisma.agentTypeSection.findMany({ where: { agentTypeId }, orderBy: { sortOrder: 'asc' }, include: { section: { include: includeFields ? { fields: { where: { isActive: true }, orderBy: { sortOrder: 'asc' }, }, } : undefined, }, }, }); // Also get global sections const globalSections = await this.prisma.profileSection.findMany({ where: { isGlobal: true, isActive: true, }, include: includeFields ? { fields: { where: { isActive: true }, orderBy: { sortOrder: 'asc' }, }, } : undefined, orderBy: { sortOrder: 'asc' }, }); return { agentType, sections: [ ...globalSections.map((s) => ({ ...s, isRequired: false, isGlobal: true })), ...assignments.map((a) => ({ ...a.section, isRequired: a.isRequired, isGlobal: false, })), ], }; } }