Move within warehouses

This commit is contained in:
hagiye
2026-06-16 12:46:02 +03:00
parent f5d6d8e7da
commit 63f177e6d0
11 changed files with 412 additions and 213 deletions

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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> {