import { Injectable, NotFoundException, ConflictException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { FindOptionsOrder, FindOptionsWhere, ILike, Repository } from 'typeorm'; import { CreateCargoDto } from './dto/create-cargo.dto'; import { UpdateCargoDto } from './dto/update-cargo.dto'; import { LoadCargoDto } from './dto/load-cargo.dto'; import { DeliverCargoDto } from './dto/deliver-cargo.dto'; import { Cargo } from './entities/cargoes.entity'; import { Container } from '../container-management/entities/container.entity'; import { CargoType } from '../rule-engine/entities/cargo-type.entity'; @Injectable() export class CargoesService { constructor( @InjectRepository(Cargo) private readonly cargoRepo: Repository, @InjectRepository(Container) private readonly containerRepo: Repository, @InjectRepository(CargoType) private readonly cargoTypeRepo: Repository, ) {} async create(dto: CreateCargoDto): Promise { const existing = await this.cargoRepo.findOne({ where: { cargoReference: dto.cargoReference }, }); if (existing) { throw new ConflictException(`Cargo reference "${dto.cargoReference}" already exists`); } const container = await this.containerRepo.findOne({ where: { id: dto.containerId } }); if (!container) { throw new NotFoundException(`Container ${dto.containerId} not found`); } if (dto.cargoTypeId) { const cargoType = await this.cargoTypeRepo.findOne({ where: { id: dto.cargoTypeId, isActive: true }, }); if (!cargoType) throw new NotFoundException(`Cargo type ${dto.cargoTypeId} not found`); } const cargo = this.cargoRepo.create(dto); return this.cargoRepo.save(cargo); } async findAll(query: Record = {}): Promise { const where: FindOptionsWhere[] | FindOptionsWhere = []; const search = query.search?.trim(); const status = query.status?.trim(); const containerId = query.containerId?.trim(); if (search) { where.push({ cargoReference: ILike(`%${search}%`), ...(status ? { status } : {}), ...(containerId ? { containerId } : {}), }); where.push({ description: ILike(`%${search}%`), ...(status ? { status } : {}), ...(containerId ? { containerId } : {}), }); } const sortBy = ['cargoReference', 'quantity', 'weight', 'volume', 'status'].includes(query.sortBy ?? '') ? (query.sortBy as keyof Cargo) : 'cargoReference'; const sortOrder = query.sortOrder?.toUpperCase() === 'DESC' ? 'DESC' : 'ASC'; return this.cargoRepo.find({ where: search ? where : { ...(status ? { status } : {}), ...(containerId ? { containerId } : {}) }, order: { [sortBy]: sortOrder } as FindOptionsOrder, skip: query.page && query.limit ? (Number(query.page) - 1) * Number(query.limit) : undefined, take: query.limit ? Number(query.limit) : undefined, }); } async findById(id: string): Promise { const cargo = await this.cargoRepo.findOne({ where: { id } }); if (!cargo) throw new NotFoundException(`Cargo ${id} not found`); return cargo; } async update(id: string, dto: UpdateCargoDto): Promise { const cargo = await this.findById(id); if (dto.cargoReference && dto.cargoReference !== cargo.cargoReference) { const existing = await this.cargoRepo.findOne({ where: { cargoReference: dto.cargoReference }, }); if (existing) { throw new ConflictException(`Cargo reference "${dto.cargoReference}" already exists`); } } if (dto.containerId) { const container = await this.containerRepo.findOne({ where: { id: dto.containerId } }); if (!container) throw new NotFoundException(`Container ${dto.containerId} not found`); } if (dto.cargoTypeId) { const cargoType = await this.cargoTypeRepo.findOne({ where: { id: dto.cargoTypeId, isActive: true }, }); if (!cargoType) throw new NotFoundException(`Cargo type ${dto.cargoTypeId} not found`); } Object.assign(cargo, dto); return this.cargoRepo.save(cargo); } async remove(id: string): Promise { const cargo = await this.findById(id); await this.cargoRepo.remove(cargo); } async loadCargo(id: string, dto: LoadCargoDto): Promise { const cargo = await this.cargoRepo.findOne({ where: { id }, relations: { container: true }, // ✅ fixed }); if (!cargo) throw new NotFoundException('Cargo not found'); if (cargo.status !== 'PENDING') { throw new ConflictException('Cargo already loaded or delivered'); } cargo.status = 'LOADED'; cargo.loadedAt = new Date(); cargo.quantity = dto.quantity; cargo.weight = dto.weight; cargo.volume = dto.volume ?? null; if (dto.description) cargo.description = dto.description; if (cargo.container) { cargo.container.status = 'LOADED'; await this.containerRepo.save(cargo.container); } return this.cargoRepo.save(cargo); } async unloadCargo(id: string): Promise { const cargo = await this.findById(id); if (cargo.status !== 'LOADED') { throw new ConflictException('Cargo is not loaded'); } cargo.status = 'UNLOADED'; cargo.unloadedAt = new Date(); return this.cargoRepo.save(cargo); } async deliverCargo(id: string, dto?: DeliverCargoDto): Promise { const cargo = await this.cargoRepo.findOne({ where: { id }, relations: { container: true }, // ✅ fixed }); if (!cargo) throw new NotFoundException('Cargo not found'); if (cargo.status !== 'LOADED') { throw new ConflictException('Only loaded cargo can be delivered'); } cargo.status = 'DELIVERED'; if (dto?.deliveryRemarks) cargo.description = dto.deliveryRemarks; const remaining = cargo.containerId != null ? await this.cargoRepo.count({ where: { containerId: cargo.containerId, status: 'LOADED' }, }) : 0; if (remaining === 0 && cargo.container) { cargo.container.status = 'AVAILABLE'; await this.containerRepo.save(cargo.container); } return this.cargoRepo.save(cargo); } }