mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 05:18:11 +00:00
Move within warehouses
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsOptional, IsString, IsUUID } from 'class-validator';
|
||||
|
||||
export class MoveWarehouseInventoryDto {
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
@IsUUID()
|
||||
warehouseId!: string;
|
||||
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
@IsUUID()
|
||||
yardId!: string;
|
||||
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
@IsUUID()
|
||||
zoneId!: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
remarks?: string;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { FilterWarehouseInventoryDto } from './dto/filter-inventory.dto';
|
||||
import { InquiryWarehouseInventoryDto } from './dto/inquiry-inventory.dto';
|
||||
import { MoveWarehouseInventoryDto } from './dto/move-inventory.dto';
|
||||
import { ReceiveWarehouseInventoryDto } from './dto/receive-inventory.dto';
|
||||
import { WarehouseInventoryService } from './warehouse-inventory.service';
|
||||
|
||||
@@ -36,6 +37,12 @@ export class WarehouseInventoryController {
|
||||
return this.inventoryService.receive(dto);
|
||||
}
|
||||
|
||||
@Post(':id/move')
|
||||
@ApiOperation({ summary: 'Move inventory to another warehouse location' })
|
||||
move(@Param('id', ParseUUIDPipe) id: string, @Body() dto: MoveWarehouseInventoryDto) {
|
||||
return this.inventoryService.move(id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id/inspect')
|
||||
@ApiOperation({ summary: 'Move inventory to UNDER_INSPECTION' })
|
||||
inspect(@Param('id', ParseUUIDPipe) id: string) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { DataSource, EntityManager, FindManyOptions, ILike } from 'typeorm';
|
||||
|
||||
import { FilterWarehouseInventoryDto } from './dto/filter-inventory.dto';
|
||||
import { InquiryWarehouseInventoryDto } from './dto/inquiry-inventory.dto';
|
||||
import { MoveWarehouseInventoryDto } from './dto/move-inventory.dto';
|
||||
import { ReceiveWarehouseInventoryDto } from './dto/receive-inventory.dto';
|
||||
import { WarehouseInventory } from './entities/warehouse-inventory.entity';
|
||||
import { WarehouseYard } from './entities/warehouse-yard.entity';
|
||||
@@ -120,6 +121,66 @@ export class WarehouseInventoryService {
|
||||
return this.findById(id);
|
||||
}
|
||||
|
||||
async move(id: string, dto: MoveWarehouseInventoryDto): Promise<WarehouseInventory> {
|
||||
const movedId = await this.dataSource.transaction(async (manager) => {
|
||||
const item = await manager.getRepository(WarehouseInventory).findOne({
|
||||
where: { id },
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
if (!item) {
|
||||
throw new NotFoundException(`Inventory item ${id} not found`);
|
||||
}
|
||||
|
||||
if (
|
||||
item.warehouseId === dto.warehouseId &&
|
||||
item.yardId === dto.yardId &&
|
||||
item.zoneId === dto.zoneId
|
||||
) {
|
||||
throw new BadRequestException('Destination location is the same as current location');
|
||||
}
|
||||
|
||||
const { warehouse, yard, zone } = await this.validateLocation(manager, dto);
|
||||
const weight = Number(item.weight) || 0;
|
||||
const containerCount = item.containerId ? Math.round(Number(item.quantity) || 0) : 0;
|
||||
|
||||
if (item.warehouseId !== dto.warehouseId) {
|
||||
this.assertCapacity('Warehouse', warehouse, weight, containerCount);
|
||||
}
|
||||
if (item.yardId !== dto.yardId) {
|
||||
this.assertCapacity('Yard', yard, weight, containerCount);
|
||||
}
|
||||
this.assertCapacity('Zone', zone, weight, containerCount);
|
||||
|
||||
await this.applyCapacityDelta(
|
||||
manager,
|
||||
{
|
||||
warehouseId: item.warehouseId,
|
||||
yardId: item.yardId,
|
||||
zoneId: item.zoneId,
|
||||
},
|
||||
-weight,
|
||||
-containerCount,
|
||||
);
|
||||
|
||||
await this.applyCapacityDelta(manager, dto, weight, containerCount);
|
||||
|
||||
item.warehouseId = dto.warehouseId;
|
||||
item.yardId = dto.yardId;
|
||||
item.zoneId = dto.zoneId;
|
||||
if (dto.remarks?.trim()) {
|
||||
const existingNotes = item.notes?.trim();
|
||||
item.notes = existingNotes
|
||||
? `${existingNotes}\nMove: ${dto.remarks.trim()}`
|
||||
: `Move: ${dto.remarks.trim()}`;
|
||||
}
|
||||
|
||||
const saved = await manager.getRepository(WarehouseInventory).save(item);
|
||||
return saved.id;
|
||||
});
|
||||
|
||||
return this.findById(movedId);
|
||||
}
|
||||
|
||||
// ── Status transitions ─────────────────────────────────────────────────
|
||||
|
||||
async inspect(id: string): Promise<WarehouseInventory> {
|
||||
@@ -234,7 +295,7 @@ export class WarehouseInventoryService {
|
||||
|
||||
private async validateLocation(
|
||||
manager: EntityManager,
|
||||
dto: ReceiveWarehouseInventoryDto,
|
||||
dto: Pick<ReceiveWarehouseInventoryDto, 'warehouseId' | 'yardId' | 'zoneId'>,
|
||||
): Promise<{ warehouse: Warehouse; yard: WarehouseYard; zone: WarehouseZone }> {
|
||||
const warehouse = await manager.getRepository(Warehouse).findOne({ where: { id: dto.warehouseId } });
|
||||
if (!warehouse) {
|
||||
@@ -306,7 +367,7 @@ export class WarehouseInventoryService {
|
||||
|
||||
private async applyCapacityDelta(
|
||||
manager: EntityManager,
|
||||
dto: ReceiveWarehouseInventoryDto,
|
||||
dto: Pick<ReceiveWarehouseInventoryDto, 'warehouseId' | 'yardId' | 'zoneId'>,
|
||||
weightAdd: number,
|
||||
containerAdd: number,
|
||||
): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user