import { BadRequestException, ConflictException, Inject, Injectable, NotFoundException, } from '@nestjs/common'; import { CreateWeightLimitRuleDto } from '../dto/create-weight-limit-rule.dto'; import { UpdateWeightLimitRuleDto } from '../dto/update-weight-limit-rule.dto'; import { WeightLimitRule } from '../entities/weight-limit-rule.entity'; import { IWeightLimitRulesRepository, WEIGHT_LIMIT_RULES_REPOSITORY, } from '../interfaces/weight-limit-rules.repository.interface'; @Injectable() export class WeightLimitRulesService { constructor( @Inject(WEIGHT_LIMIT_RULES_REPOSITORY) private readonly repository: IWeightLimitRulesRepository, ) {} /** List weight limit rules with pagination. */ async findAll(filter: { containerTypeId?: string; tradeDirection?: string; page?: number; pageSize?: number; }): Promise<{ data: WeightLimitRule[]; meta: { total: number; page: number; pageSize: number; totalPages: number } }> { const page = filter.page ?? 1; const pageSize = filter.pageSize ?? 20; const where: Record = {}; if (filter.containerTypeId) where.containerTypeId = filter.containerTypeId; if (filter.tradeDirection) where.tradeDirection = filter.tradeDirection; const [data, total] = await this.repository.findAndCount({ where, relations: { containerType: true }, order: { createdAt: 'DESC' }, skip: (page - 1) * pageSize, take: pageSize, }); return { data, meta: { total, page, pageSize, totalPages: Math.ceil(total / pageSize) } }; } /** Get a single weight limit rule by ID. */ async findById(id: string): Promise { const entity = await this.repository.findById(id); if (!entity) throw new NotFoundException(`Weight limit rule ${id} not found`); return entity; } /** * Reject a second rule for the same container + direction. One VGM limit per * (container, direction) — otherwise the booking engine can't tell which * applies. */ private async assertNoDuplicate( containerTypeId: string, tradeDirection: string, ignoreId?: string, ): Promise { const existing = await this.repository.findByPattern(containerTypeId, tradeDirection, ignoreId); if (existing) { throw new ConflictException( 'A weight limit rule for this container type and trade direction already exists. Edit the existing rule instead.', ); } } /** * Capacity is the hard ceiling; the VGM limit is the soft overweight * threshold. A ceiling below the threshold would make every overweight * booking impossible to create, which is never what the operator means. */ private assertCapacityAboveVgmLimit( maxVgmTons: number, maxCapacityTons: number | null | undefined, ): void { if (maxCapacityTons != null && Number(maxCapacityTons) < Number(maxVgmTons)) { throw new BadRequestException( 'Max capacity must be greater than or equal to the max VGM limit.', ); } } /** Create a new weight limit rule. */ async create(dto: CreateWeightLimitRuleDto): Promise { await this.assertNoDuplicate(dto.containerTypeId, dto.tradeDirection); this.assertCapacityAboveVgmLimit(dto.maxVgmTons, dto.maxCapacityTons); return this.repository.create({ containerTypeId: dto.containerTypeId, tradeDirection: dto.tradeDirection, maxVgmTons: dto.maxVgmTons, maxCapacityTons: dto.maxCapacityTons ?? null, }); } /** Update an existing weight limit rule. */ async update(id: string, dto: UpdateWeightLimitRuleDto): Promise { const existing = await this.findById(id); const patch: Partial = {}; if (dto.containerTypeId !== undefined) patch.containerTypeId = dto.containerTypeId; if (dto.tradeDirection !== undefined) patch.tradeDirection = dto.tradeDirection; if (dto.maxVgmTons !== undefined) patch.maxVgmTons = dto.maxVgmTons; if (dto.maxCapacityTons !== undefined) patch.maxCapacityTons = dto.maxCapacityTons; this.assertCapacityAboveVgmLimit( patch.maxVgmTons ?? Number(existing.maxVgmTons), patch.maxCapacityTons !== undefined ? patch.maxCapacityTons : existing.maxCapacityTons, ); // Re-check uniqueness when the identity (container/direction) changes. if (dto.containerTypeId !== undefined || dto.tradeDirection !== undefined) { await this.assertNoDuplicate( patch.containerTypeId ?? existing.containerTypeId, patch.tradeDirection ?? existing.tradeDirection, id, ); } const updated = await this.repository.update(id, patch); if (!updated) throw new NotFoundException(`Weight limit rule ${id} not found`); return updated; } /** Soft-delete a weight limit rule. */ async remove(id: string): Promise { await this.findById(id); await this.repository.softDelete(id); } }