Files
serverMontime/src/modules/business-service-classes/business-service-classes.service.ts
T
2026-05-31 21:36:19 +03:30

169 lines
4.9 KiB
TypeScript

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<BusinessServiceClassEntity>,
@InjectRepository(BusinessEntity)
private readonly businessRepo: Repository<BusinessEntity>,
) {}
async findAll(filters: FindAllFilters): Promise<BusinessServiceClassEntity[]> {
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<BusinessServiceClassEntity> {
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<BusinessServiceClassEntity> {
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<BusinessServiceClassEntity> {
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<void> {
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');
}
}
}