mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 09:58:12 +00:00
Yard create/update now rejects capacities that overflow the parent warehouse, and zone create/update rejects capacities that overflow the parent yard, via BadRequestException. Dispatcher permission preset expanded to full CRUD on warehouse and fleet management; allocation and fee rules remain view-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
145 lines
5.4 KiB
TypeScript
145 lines
5.4 KiB
TypeScript
import { BadRequestException, ConflictException, Injectable, NotFoundException } from '@nestjs/common';
|
|
|
|
import { CreateWarehouseYardDto } from './dto/create-warehouse-yard.dto';
|
|
import { UpdateWarehouseYardDto } from './dto/update-warehouse-yard.dto';
|
|
import { WarehouseYard } from './entities/warehouse-yard.entity';
|
|
import { WarehouseYardsRepository } from './warehouse-yards.repository';
|
|
import { WarehousesService } from './warehouses.service';
|
|
|
|
@Injectable()
|
|
export class WarehouseYardsService {
|
|
constructor(
|
|
private readonly yardsRepository: WarehouseYardsRepository,
|
|
private readonly warehousesService: WarehousesService,
|
|
) {}
|
|
|
|
findAll(): Promise<WarehouseYard[]> {
|
|
return this.yardsRepository.findAll({
|
|
relations: { warehouse: true, zones: true },
|
|
order: { code: 'ASC' },
|
|
});
|
|
}
|
|
|
|
findByWarehouse(warehouseId: string): Promise<WarehouseYard[]> {
|
|
return this.yardsRepository.findAll({
|
|
where: { warehouseId },
|
|
relations: { zones: true },
|
|
order: { code: 'ASC' },
|
|
});
|
|
}
|
|
|
|
async findById(id: string): Promise<WarehouseYard> {
|
|
const yard = await this.yardsRepository.findById(id, {
|
|
relations: { warehouse: true, zones: true },
|
|
});
|
|
|
|
if (!yard) {
|
|
throw new NotFoundException(`Warehouse yard ${id} not found`);
|
|
}
|
|
|
|
return yard;
|
|
}
|
|
|
|
async create(warehouseId: string, dto: CreateWarehouseYardDto): Promise<WarehouseYard> {
|
|
// Ensure the parent warehouse exists.
|
|
await this.warehousesService.findById(warehouseId);
|
|
await this.assertCodeUnique(warehouseId, dto.code.trim());
|
|
await this.assertCapacityWithinWarehouse(warehouseId, dto.capacityWeight ?? null, dto.capacityContainers ?? null);
|
|
|
|
return this.yardsRepository.create({
|
|
warehouseId,
|
|
name: dto.name.trim(),
|
|
code: dto.code.trim(),
|
|
type: dto.type,
|
|
capacityWeight: dto.capacityWeight ?? null,
|
|
capacityContainers: dto.capacityContainers ?? null,
|
|
maxWeight: dto.maxWeight ?? dto.capacityWeight ?? null,
|
|
maxVolume: dto.maxVolume ?? null,
|
|
currentWeight: 0,
|
|
currentContainers: 0,
|
|
currentVolume: 0,
|
|
status: 'ACTIVE',
|
|
isActive: true,
|
|
});
|
|
}
|
|
|
|
async update(id: string, dto: UpdateWarehouseYardDto): Promise<WarehouseYard> {
|
|
const existing = await this.findById(id);
|
|
|
|
if (dto.code && dto.code.trim() !== existing.code) {
|
|
await this.assertCodeUnique(existing.warehouseId, dto.code.trim(), id);
|
|
}
|
|
|
|
const newCapacityWeight = dto.capacityWeight ?? existing.capacityWeight ?? null;
|
|
const newCapacityContainers = dto.capacityContainers ?? existing.capacityContainers ?? null;
|
|
|
|
// Validate updated capacity doesn't exceed warehouse limits
|
|
if (newCapacityWeight !== (existing.capacityWeight ?? null) || newCapacityContainers !== (existing.capacityContainers ?? null)) {
|
|
await this.assertCapacityWithinWarehouse(existing.warehouseId, newCapacityWeight, newCapacityContainers, id);
|
|
}
|
|
|
|
const status = dto.status ?? existing.status;
|
|
|
|
const updated = await this.yardsRepository.update(id, {
|
|
name: dto.name?.trim() ?? existing.name,
|
|
code: dto.code?.trim() ?? existing.code,
|
|
type: dto.type ?? existing.type,
|
|
capacityWeight: newCapacityWeight,
|
|
capacityContainers: newCapacityContainers,
|
|
maxWeight: dto.maxWeight ?? existing.maxWeight,
|
|
maxVolume: dto.maxVolume ?? existing.maxVolume,
|
|
status,
|
|
isActive: status === 'ACTIVE',
|
|
});
|
|
|
|
if (!updated) {
|
|
throw new NotFoundException(`Warehouse yard ${id} not found`);
|
|
}
|
|
|
|
return this.findById(id);
|
|
}
|
|
|
|
private async assertCodeUnique(warehouseId: string, code: string, ignoreId?: string): Promise<void> {
|
|
const [existing] = await this.yardsRepository.findAll({ where: { warehouseId, code } });
|
|
|
|
if (existing && existing.id !== ignoreId) {
|
|
throw new ConflictException(`Yard code ${code} already exists in this warehouse`);
|
|
}
|
|
}
|
|
|
|
private async assertCapacityWithinWarehouse(
|
|
warehouseId: string,
|
|
newCapacityWeight: number | null,
|
|
newCapacityContainers: number | null,
|
|
excludeYardId?: string,
|
|
): Promise<void> {
|
|
const warehouse = await this.warehousesService.findById(warehouseId);
|
|
const yards = await this.findByWarehouse(warehouseId);
|
|
|
|
// Sum existing yard capacities, excluding the yard being updated if provided
|
|
const otherYards = excludeYardId ? yards.filter((y) => y.id !== excludeYardId) : yards;
|
|
const totalExistingWeight = otherYards.reduce((sum, y) => sum + (y.capacityWeight ?? 0), 0);
|
|
const totalExistingContainers = otherYards.reduce((sum, y) => sum + (y.capacityContainers ?? 0), 0);
|
|
|
|
// Check weight capacity
|
|
if (newCapacityWeight !== null && warehouse.capacityWeight != null) {
|
|
const totalWeight = totalExistingWeight + newCapacityWeight;
|
|
if (totalWeight > warehouse.capacityWeight) {
|
|
throw new BadRequestException(
|
|
`Total yard weight capacity (${totalWeight}t) exceeds warehouse limit (${warehouse.capacityWeight}t)`,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Check container capacity
|
|
if (newCapacityContainers !== null && warehouse.capacityContainers != null) {
|
|
const totalContainers = totalExistingContainers + newCapacityContainers;
|
|
if (totalContainers > warehouse.capacityContainers) {
|
|
throw new BadRequestException(
|
|
`Total yard container capacity (${totalContainers}) exceeds warehouse limit (${warehouse.capacityContainers})`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|