Warehouse

This commit is contained in:
Hagernesh
2026-06-17 12:44:57 +00:00
parent d5a536956f
commit f528b75737
26 changed files with 790 additions and 51 deletions

View File

@@ -0,0 +1,19 @@
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
/**
* Proof of Delivery (customer pickup) capture on cargoes:
* receiver name, delivered/picked-up timestamp, and delivery remarks.
*/
export class AddProofOfDeliveryToCargoes1750000000002 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumns('freight.cargoes', [
new TableColumn({ name: 'receiver_name', type: 'varchar', isNullable: true }),
new TableColumn({ name: 'delivered_at', type: 'timestamp', isNullable: true }),
new TableColumn({ name: 'delivery_remarks', type: 'text', isNullable: true }),
]);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumns('freight.cargoes', ['receiver_name', 'delivered_at', 'delivery_remarks']);
}
}

View File

@@ -157,7 +157,9 @@ export class CargoesService {
}
cargo.status = 'DELIVERED';
if (dto?.deliveryRemarks) cargo.description = dto.deliveryRemarks;
cargo.deliveredAt = dto?.pickupDate ? new Date(dto.pickupDate) : new Date();
if (dto?.receiverName) cargo.receiverName = dto.receiverName;
if (dto?.deliveryRemarks) cargo.deliveryRemarks = dto.deliveryRemarks;
const remaining = await this.cargoRepo.count({
where: { containerId: cargo.containerId, status: 'LOADED' },

View File

@@ -1,6 +1,16 @@
import { IsOptional, IsString } from 'class-validator';
import { IsDateString, IsOptional, IsString } from 'class-validator';
export class DeliverCargoDto {
/** Name of the person who received / picked up the cargo (Proof of Delivery). */
@IsOptional()
@IsString()
receiverName?: string;
/** When the cargo was picked up / delivered. Defaults to now. */
@IsOptional()
@IsDateString()
pickupDate?: string;
@IsOptional()
@IsString()
deliveryRemarks?: string;

View File

@@ -38,6 +38,16 @@ export class Cargo extends BaseEntity {
@Column({ name: 'unloaded_at', type: 'timestamp', nullable: true })
unloadedAt!: Date | null;
// Proof of Delivery (customer pickup) capture.
@Column({ name: 'receiver_name', type: 'varchar', nullable: true })
receiverName!: string | null;
@Column({ name: 'delivered_at', type: 'timestamp', nullable: true })
deliveredAt!: Date | null;
@Column({ name: 'delivery_remarks', type: 'text', nullable: true })
deliveryRemarks!: string | null;
// Relationship to Container
@ManyToOne(() => Container, (container) => container.cargoes, { onDelete: 'RESTRICT' })
@JoinColumn({ name: 'container_id' })

View File

@@ -23,6 +23,11 @@ export class CreateWarehouseDto {
@IsUUID()
stationId?: string;
@ApiPropertyOptional({ format: 'uuid', description: 'Parent facility / port this warehouse belongs to.' })
@IsOptional()
@IsUUID()
facilityId?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()

View File

@@ -86,7 +86,7 @@ export class WarehouseInventoryService {
return this.inventoryRepository.findAll({
where,
relations: { warehouse: true, yard: true, zone: true },
relations: { warehouse: { facility: true }, yard: true, zone: true, booking: true },
order: { createdAt: 'DESC' },
});
}

View File

@@ -29,13 +29,14 @@ export class WarehousesService {
return this.warehousesRepository.findAll({
where: whereClauses,
relations: { facility: true },
order: { code: 'ASC' },
});
}
async findById(id: string): Promise<Warehouse> {
const warehouse = await this.warehousesRepository.findById(id, {
relations: { yards: { zones: true } },
relations: { facility: true, yards: { zones: true } },
});
if (!warehouse) {
@@ -53,6 +54,7 @@ export class WarehousesService {
code: dto.code.trim(),
type: dto.type,
stationId: dto.stationId ?? null,
facilityId: dto.facilityId ?? null,
locationName: dto.locationName?.trim() ?? null,
capacityWeight: dto.capacityWeight ?? null,
capacityContainers: dto.capacityContainers ?? null,
@@ -80,6 +82,7 @@ export class WarehousesService {
code: dto.code?.trim() ?? existing.code,
type: dto.type ?? existing.type,
stationId: dto.stationId ?? existing.stationId,
facilityId: dto.facilityId ?? existing.facilityId,
locationName: dto.locationName?.trim() ?? existing.locationName,
capacityWeight: dto.capacityWeight ?? existing.capacityWeight,
capacityContainers: dto.capacityContainers ?? existing.capacityContainers,

View File

@@ -1,7 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { DataSource } from 'typeorm';
import { Booking } from '../modules/bookings/entities/booking.entity';
import { Facility } from '../modules/facilities/entities/facility.entity';
import { Warehouse } from '../modules/warehouses/entities/warehouse.entity';
import { WarehouseInventory } from '../modules/warehouses/entities/warehouse-inventory.entity';
@@ -148,12 +147,11 @@ export class Batch14TestDataSeeder {
warehouseId: warehouse.id,
yardId: zone.yardId,
zoneId: zone.id,
code: `TEST_INV_${status}_001`,
status: status as any,
quantity: 100 + i * 10,
weight: 500 + i * 50,
volume: 100 + i * 10,
arrivedAt: new Date(now.getTime() - i * 3600000), // Staggered arrival times
arrivedAt: new Date(now.getTime() - i * 3600000),
storedAt: status !== 'RECEIVED' ? new Date(now.getTime() - (i - 1) * 3600000) : null,
reservedAt: ['RESERVED', 'READY_FOR_LOADING', 'LOADED', 'DISPATCHED'].includes(status) ? new Date() : null,
readyForLoadingAt: ['READY_FOR_LOADING', 'LOADED', 'DISPATCHED'].includes(status) ? new Date() : null,