169 lines
4.9 KiB
TypeScript
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');
|
|
}
|
|
}
|
|
}
|