mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-06 11:15:03 +00:00
feat(warehouses): backoffice UI for container stack positions
Surfaces the physical stack/slot model in the staff app. - ZoneLayoutModal: stacks drawn level by level with occupancy colours, configured-vs-built-vs-occupied counts, and stack create/delete plus block/reserve/free on empty levels - SlotPicker in the store and move modals, offering only the next fillable level of each stack so the form cannot suggest a position the API will refuse - move modal warns when a container is buried, lists the blockers, and disables the action instead of firing a 409 - fix: move() now asserts accessibility server-side, matching release — both are exits from a stack
This commit is contained in:
@@ -1875,9 +1875,12 @@ export class BookingWagonCancellationService {
|
||||
// the same cargo); number/seal/VGM come from the override when given.
|
||||
units: sized.map((u, i) => ({
|
||||
containerNumber: replacement?.[i]?.containerNumber ?? u.containerNumber,
|
||||
// A credit snapshot taken before seals were mandatory can carry
|
||||
// none; the booking service normalizes the blank back to null
|
||||
// rather than blocking the rebook of already-paid cargo.
|
||||
sealNumber: replacement
|
||||
? (replacement[i]?.sealNumber ?? undefined)
|
||||
: (u.sealNumber ?? undefined),
|
||||
? (replacement[i]?.sealNumber ?? '')
|
||||
: (u.sealNumber ?? ''),
|
||||
vgmTons: replacement?.[i]?.vgmTons ?? u.vgmTons,
|
||||
isHazardous: u.isHazardous,
|
||||
isReefer: u.isReefer,
|
||||
|
||||
@@ -2250,7 +2250,9 @@ export class ContractBookingService {
|
||||
unitRepo.create({
|
||||
bookingContainerId: containerRow.id,
|
||||
containerNumber: unit.containerNumber,
|
||||
sealNumber: unit.sealNumber ?? null,
|
||||
// Legacy units recovered by the remainder placement can still
|
||||
// arrive sealless — keep those null rather than empty-string.
|
||||
sealNumber: unit.sealNumber?.trim() || null,
|
||||
vgmTons: unit.vgmTons,
|
||||
isHazardous: unit.isHazardous ?? false,
|
||||
isReefer: unit.isReefer ?? false,
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
IsEmail,
|
||||
IsIn,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
@@ -32,10 +33,12 @@ export class CreateContainerUnitDto {
|
||||
})
|
||||
containerNumber!: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@ApiProperty({ description: 'Seal number — required on every container, import and export alike.' })
|
||||
@IsString()
|
||||
sealNumber?: string;
|
||||
@Transform(({ value }) => (typeof value === 'string' ? value.trim() : value))
|
||||
@IsNotEmpty({ message: 'sealNumber is required' })
|
||||
@MaxLength(64)
|
||||
sealNumber!: string;
|
||||
|
||||
@ApiProperty({ description: 'VGM in tons', minimum: 0 })
|
||||
@IsNumber()
|
||||
|
||||
@@ -333,7 +333,9 @@ export class RemainderPlacementService {
|
||||
|
||||
return deferred.map((u) => ({
|
||||
containerNumber: u.containerNumber,
|
||||
sealNumber: u.sealNumber ?? undefined,
|
||||
// Deferred units predate the seal requirement; the booking service
|
||||
// normalizes the blank back to null rather than rejecting the re-book.
|
||||
sealNumber: u.sealNumber ?? '',
|
||||
vgmTons: Number(u.vgmTons),
|
||||
isHazardous: u.isHazardous,
|
||||
isReefer: u.isReefer,
|
||||
|
||||
@@ -3140,6 +3140,13 @@ export class WarehouseInventoryService {
|
||||
|
||||
const { warehouse, yard, zone } = await this.validateLocation(manager, dto);
|
||||
|
||||
// Taking a box out of a stack is physically impossible while others stand
|
||||
// on top of it — the same rule the release path enforces. Moving is one of
|
||||
// those exits, so it is checked here rather than only at release.
|
||||
if (item.slotId) {
|
||||
await this.placement.assertAccessible(item.id, manager);
|
||||
}
|
||||
|
||||
// A move that names a slot is validated against the hierarchy it claims;
|
||||
// one that does not clears the old slot, because the box has left it.
|
||||
if (dto.slotId) {
|
||||
|
||||
Reference in New Issue
Block a user