import { ConflictException, Injectable, NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { BusinessCategoryEntity } from '../../entities/business-category.entity'; import { ServiceTemplateEntity } from '../../entities/service-template.entity'; import { resolveUniqueSlug, normalizeSlugInput } from '../../common/utils/slug.util'; import { CreateServiceTemplateDto } from './dto/create-service-template.dto'; import { UpdateServiceTemplateDto } from './dto/update-service-template.dto'; type FindAllFilters = { categoryId?: string; isActive?: boolean; }; @Injectable() export class ServiceTemplatesService { constructor( @InjectRepository(ServiceTemplateEntity) private readonly repo: Repository, @InjectRepository(BusinessCategoryEntity) private readonly categoryRepo: Repository, ) {} async findAll(filters: FindAllFilters): Promise { const query = this.repo .createQueryBuilder('template') .leftJoinAndSelect('template.category', 'category') .orderBy('template.createdAt', 'DESC'); if (filters.categoryId) { query.andWhere('template.categoryId = :categoryId', { categoryId: filters.categoryId, }); } if (typeof filters.isActive === 'boolean') { query.andWhere('template.isActive = :isActive', { isActive: filters.isActive, }); } return query.getMany(); } async findOne(id: string): Promise { const template = await this.repo.findOne({ where: { id }, relations: { category: true }, }); if (!template) { throw new NotFoundException('Service template not found'); } return template; } async create(p: CreateServiceTemplateDto): Promise { const explicitSlug = normalizeSlugInput(p.slug); if (explicitSlug) { const taken = await this.repo.findOne({ where: { slug: explicitSlug } }); if (taken) { throw new ConflictException('Slug already exists'); } } const slug = await resolveUniqueSlug( p.title, async (candidate) => Boolean(await this.repo.findOne({ where: { slug: candidate } })), p.slug, 'service', ); await this.ensureCategoryExists(p.categoryId); const entity = this.repo.create({ title: p.title, slug, categoryId: p.categoryId, description: p.description ?? null, defaultDurationMinutes: p.defaultDurationMinutes ?? 30, isActive: p.isActive ?? true, }); return this.repo.save(entity); } async update( id: string, payload: UpdateServiceTemplateDto, ): Promise { const template = await this.findOne(id); if (payload.slug && payload.slug !== template.slug) { const slugTaken = await this.repo.findOne({ where: { slug: payload.slug }, }); if (slugTaken) { throw new ConflictException('Slug already exists'); } } if (payload.categoryId && payload.categoryId !== template.categoryId) { await this.ensureCategoryExists(payload.categoryId); } Object.assign(template, payload); return this.repo.save(template); } async remove(id: string): Promise { const result = await this.repo.delete(id); if (!result.affected) { throw new NotFoundException('Service template not found'); } } private async ensureCategoryExists(categoryId: string): Promise { const category = await this.categoryRepo.findOne({ where: { id: categoryId }, }); if (!category) { throw new NotFoundException('Business category not found'); } } }