import { BadRequestException, ConflictException, ForbiddenException, Injectable, NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { BusinessEntity } from '../../entities/business.entity'; import { BusinessServiceClassEntity } from '../../entities/business-service-class.entity'; import { UserRole } from '../../entities/user.entity'; import { AuthUser } from '../auth/auth.types'; import { CreateBusinessServiceClassDto } from './dto/create-business-service-class.dto'; import { UpdateBusinessServiceClassDto } from './dto/update-business-service-class.dto'; type FindAllFilters = { businessId?: string; isActive?: boolean; }; @Injectable() export class BusinessServiceClassesService { constructor( @InjectRepository(BusinessServiceClassEntity) private readonly repo: Repository, @InjectRepository(BusinessEntity) private readonly businessRepo: Repository, ) {} async findAll(filters: FindAllFilters): Promise { const query = this.repo .createQueryBuilder('c') .orderBy('c.sortOrder', 'ASC') .addOrderBy('c.createdAt', 'ASC'); if (filters.businessId) { query.andWhere('c.businessId = :businessId', { businessId: filters.businessId, }); } if (typeof filters.isActive === 'boolean') { query.andWhere('c.isActive = :isActive', { isActive: filters.isActive }); } return query.getMany(); } async findOne(id: string): Promise { const row = await this.repo.findOne({ where: { id } }); if (!row) { throw new NotFoundException('Service class not found'); } return row; } async create( dto: CreateBusinessServiceClassDto, actor?: AuthUser, ): Promise { const business = await this.businessRepo.findOne({ where: { id: dto.businessId }, }); if (!business) { throw new NotFoundException('Business not found'); } this.assertCanManageBusiness(business, actor); const dup = await this.repo.findOne({ where: { businessId: dto.businessId, title: dto.title.trim() }, }); if (dup) { throw new ConflictException('A class with this title already exists'); } let sortOrder = dto.sortOrder; if (sortOrder === undefined) { const max = await this.repo .createQueryBuilder('c') .select('MAX(c.sortOrder)', 'max') .where('c.businessId = :businessId', { businessId: dto.businessId }) .getRawOne<{ max: string | null }>(); sortOrder = (max?.max ? Number.parseInt(max.max, 10) : -1) + 1; } const entity = this.repo.create({ businessId: dto.businessId, title: dto.title.trim(), description: dto.description?.trim() || null, sortOrder, isActive: dto.isActive ?? true, }); return this.repo.save(entity); } async update( id: string, dto: UpdateBusinessServiceClassDto, actor?: AuthUser, ): Promise { const row = await this.findOne(id); const business = await this.businessRepo.findOne({ where: { id: row.businessId }, }); if (!business) { throw new NotFoundException('Business not found'); } this.assertCanManageBusiness(business, actor); if (dto.title !== undefined) { const title = dto.title.trim(); if (!title) { throw new BadRequestException('title cannot be empty'); } const dup = await this.repo.findOne({ where: { businessId: row.businessId, title }, }); if (dup && dup.id !== row.id) { throw new ConflictException('A class with this title already exists'); } row.title = title; } if (dto.description !== undefined) { row.description = dto.description?.trim() || null; } if (dto.sortOrder !== undefined) { row.sortOrder = dto.sortOrder; } if (dto.isActive !== undefined) { row.isActive = dto.isActive; } return this.repo.save(row); } async remove(id: string, actor?: AuthUser): Promise { const row = await this.findOne(id); const business = await this.businessRepo.findOne({ where: { id: row.businessId }, }); if (!business) { throw new NotFoundException('Business not found'); } this.assertCanManageBusiness(business, actor); const result = await this.repo.delete(id); if (!result.affected) { throw new NotFoundException('Service class not found'); } } private assertCanManageBusiness( business: BusinessEntity, actor?: AuthUser, ): void { if (!actor || actor.role === UserRole.ADMIN) { return; } if ( actor.role !== UserRole.BUSINESS_OWNER || business.ownerId !== actor.id ) { throw new ForbiddenException('You can only manage your own business'); } } }