mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 19:58:11 +00:00
feat(warehouse): batch 3 — loading, dispatch & train-departure visibility
- WarehouseLoading entity + Batch3 migration (wagon loading records) - wagon-aware load() with validation; dispatch moved to PATCH - read-only SchedulingReadFacade (schedule/wagon/departure) — never writes scheduling - new endpoints: GET /warehouse-loadings, loadable-wagons, booking schedule - Loading Queue / Loaded Inventory / Dispatch Queue pages + routes + sidebar - FreightVisual illustrations (page heroes + empty states) - booking detail: loaded/dispatched/wagon + read-only train schedule Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
/**
|
||||
* Batch 3 — Warehouse → Loading → Train Departure visibility.
|
||||
* Adds the warehouse_loadings record (inventory ↔ wagon). Does NOT touch any
|
||||
* scheduling / wagon tables — the warehouse only reads from those.
|
||||
*/
|
||||
export class WarehouseBatch31790000000002 implements MigrationInterface {
|
||||
name = 'WarehouseBatch31790000000002';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE IF NOT EXISTS freight.warehouse_loadings (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
warehouse_inventory_id UUID NOT NULL REFERENCES freight.warehouse_inventory(id) ON DELETE CASCADE,
|
||||
booking_id UUID NULL,
|
||||
wagon_id UUID NOT NULL,
|
||||
loaded_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
loaded_by VARCHAR(120) NULL,
|
||||
loaded_weight NUMERIC(14,3) NULL,
|
||||
notes TEXT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
deleted_at TIMESTAMPTZ NULL
|
||||
);
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS idx_warehouse_loadings_inventory_id
|
||||
ON freight.warehouse_loadings(warehouse_inventory_id);
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS idx_warehouse_loadings_booking_id
|
||||
ON freight.warehouse_loadings(booking_id);
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
CREATE INDEX IF NOT EXISTS idx_warehouse_loadings_wagon_id
|
||||
ON freight.warehouse_loadings(wagon_id);
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS freight.warehouse_loadings;`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsNumber, IsOptional, IsString, IsUUID, MaxLength, Min } from 'class-validator';
|
||||
|
||||
export class LoadInventoryDto {
|
||||
@ApiProperty({ format: 'uuid', description: 'Physical wagon the item is loaded onto' })
|
||||
@IsUUID()
|
||||
wagonId!: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Weight loaded onto the wagon (kg)' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
loadedWeight?: number;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
loadedBy?: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
notes?: string;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { BaseEntity } from '@edr/api-common';
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
|
||||
|
||||
import { Booking } from '../../bookings/entities/booking.entity';
|
||||
import { WarehouseInventory } from './warehouse-inventory.entity';
|
||||
|
||||
/**
|
||||
* Batch 3 — a record that a warehouse inventory item was physically loaded onto a wagon.
|
||||
* The warehouse OWNS this record. It only READS wagon/schedule data from the scheduling
|
||||
* domain (via SchedulingReadFacade); it never writes to wagons or train schedules.
|
||||
*/
|
||||
@Entity({ schema: 'freight', name: 'warehouse_loadings' })
|
||||
@Index(['warehouseInventoryId'])
|
||||
@Index(['bookingId'])
|
||||
@Index(['wagonId'])
|
||||
export class WarehouseLoading extends BaseEntity {
|
||||
@Column({ name: 'warehouse_inventory_id', type: 'uuid' })
|
||||
warehouseInventoryId!: string;
|
||||
|
||||
@ManyToOne(() => WarehouseInventory)
|
||||
@JoinColumn({ name: 'warehouse_inventory_id' })
|
||||
inventory?: WarehouseInventory;
|
||||
|
||||
@Column({ name: 'booking_id', type: 'uuid', nullable: true })
|
||||
bookingId?: string | null;
|
||||
|
||||
@ManyToOne(() => Booking, { nullable: true })
|
||||
@JoinColumn({ name: 'booking_id' })
|
||||
booking?: Booking | null;
|
||||
|
||||
/** Physical wagon the item was loaded onto. References freight.wagons (read-only link). */
|
||||
@Column({ name: 'wagon_id', type: 'uuid' })
|
||||
wagonId!: string;
|
||||
|
||||
@Column({ name: 'loaded_at', type: 'timestamptz' })
|
||||
loadedAt!: Date;
|
||||
|
||||
@Column({ name: 'loaded_by', type: 'varchar', length: 120, nullable: true })
|
||||
loadedBy?: string | null;
|
||||
|
||||
@Column({ name: 'loaded_weight', type: 'numeric', precision: 14, scale: 3, nullable: true })
|
||||
loadedWeight?: number | null;
|
||||
|
||||
@Column({ name: 'notes', type: 'text', nullable: true })
|
||||
notes?: string | null;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
/**
|
||||
* READ-ONLY view into the train-scheduling / wagons domain for the warehouse module.
|
||||
*
|
||||
* IMPORTANT: this facade only ever runs SELECTs. The warehouse must never modify
|
||||
* wagon assignment, rescheduling, import_ready/export_ready, or locomotive flow.
|
||||
* It is intentionally decoupled (raw SQL) so it does not import the scheduling
|
||||
* services/entities and cannot accidentally write to them.
|
||||
*/
|
||||
export interface WagonView {
|
||||
id: string;
|
||||
wagonNumber: string;
|
||||
status: string;
|
||||
trainId: string | null;
|
||||
}
|
||||
|
||||
export interface BookingScheduleView {
|
||||
schedule: {
|
||||
id: string;
|
||||
status: string;
|
||||
scheduledDepartureDate: string | null;
|
||||
scheduledArrivalDate: string | null;
|
||||
originStationId: string | null;
|
||||
destinationStationId: string | null;
|
||||
} | null;
|
||||
wagon: {
|
||||
wagonId: string | null;
|
||||
wagonNumber: string | null;
|
||||
sequenceNo: number | null;
|
||||
allocatedWeightTons: number | null;
|
||||
} | null;
|
||||
/** Mirror of schedule.status — the headline "where is the train" indicator. */
|
||||
departureStatus: string | null;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SchedulingReadFacade {
|
||||
constructor(private readonly dataSource: DataSource) {}
|
||||
|
||||
/** Look up a single physical wagon. Returns null if it does not exist. */
|
||||
async findWagon(wagonId: string): Promise<WagonView | null> {
|
||||
const rows = await this.dataSource.query(
|
||||
`SELECT id, wagon_number AS "wagonNumber", status, train_id AS "trainId"
|
||||
FROM freight.wagons
|
||||
WHERE id = $1 AND deleted_at IS NULL
|
||||
LIMIT 1`,
|
||||
[wagonId],
|
||||
);
|
||||
return rows?.[0] ?? null;
|
||||
}
|
||||
|
||||
/** True when the wagon is already part of a train set (selected by an existing schedule). */
|
||||
async isWagonScheduled(wagonId: string): Promise<boolean> {
|
||||
const rows = await this.dataSource.query(
|
||||
`SELECT 1 FROM freight.train_set_wagons
|
||||
WHERE physical_wagon_id = $1 AND deleted_at IS NULL
|
||||
LIMIT 1`,
|
||||
[wagonId],
|
||||
);
|
||||
return (rows?.length ?? 0) > 0;
|
||||
}
|
||||
|
||||
/** List wagons usable for loading (available, or already assigned to a schedule). */
|
||||
listLoadableWagons(): Promise<WagonView[]> {
|
||||
return this.dataSource.query(
|
||||
`SELECT id, wagon_number AS "wagonNumber", status, train_id AS "trainId"
|
||||
FROM freight.wagons
|
||||
WHERE deleted_at IS NULL
|
||||
AND status NOT IN ('RETIRED', 'MAINTENANCE')
|
||||
ORDER BY wagon_number ASC`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a booking, return its related schedule, wagon assignment and departure status.
|
||||
* All fields are read straight from the scheduling tables — nothing is written.
|
||||
*/
|
||||
async getBookingSchedule(bookingId: string): Promise<BookingScheduleView> {
|
||||
const scheduleRows = await this.dataSource.query(
|
||||
`SELECT ts.id,
|
||||
ts.status,
|
||||
ts.scheduled_departure_date AS "scheduledDepartureDate",
|
||||
ts.scheduled_arrival_date AS "scheduledArrivalDate",
|
||||
ts.origin_station_id AS "originStationId",
|
||||
ts.destination_station_id AS "destinationStationId"
|
||||
FROM freight.train_schedule_bookings tsb
|
||||
INNER JOIN freight.train_schedules ts ON ts.id = tsb.train_schedule_id
|
||||
WHERE tsb.booking_id = $1 AND ts.deleted_at IS NULL
|
||||
ORDER BY ts.scheduled_departure_date DESC NULLS LAST
|
||||
LIMIT 1`,
|
||||
[bookingId],
|
||||
);
|
||||
const schedule = scheduleRows?.[0] ?? null;
|
||||
|
||||
const wagonRows = await this.dataSource.query(
|
||||
`SELECT w.id AS "wagonId",
|
||||
w.wagon_number AS "wagonNumber",
|
||||
tsw.sequence_no AS "sequenceNo",
|
||||
wba.allocated_weight_tons AS "allocatedWeightTons"
|
||||
FROM freight.wagon_booking_allocations wba
|
||||
INNER JOIN freight.train_set_wagons tsw ON tsw.id = wba.train_set_wagon_id
|
||||
LEFT JOIN freight.wagons w ON w.id = tsw.physical_wagon_id
|
||||
WHERE wba.booking_id = $1
|
||||
ORDER BY tsw.sequence_no ASC NULLS LAST
|
||||
LIMIT 1`,
|
||||
[bookingId],
|
||||
);
|
||||
const wagon = wagonRows?.[0] ?? null;
|
||||
|
||||
return {
|
||||
schedule,
|
||||
wagon,
|
||||
departureStatus: schedule?.status ?? null,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,23 @@
|
||||
import { Body, Controller, Get, Param, ParseUUIDPipe, Post, Query } from '@nestjs/common';
|
||||
import { Body, Controller, Get, Param, ParseUUIDPipe, Patch, Post, Query } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { FilterWarehouseInventoryDto } from './dto/filter-inventory.dto';
|
||||
import { InquiryWarehouseInventoryDto } from './dto/inquiry-inventory.dto';
|
||||
import { LoadInventoryDto } from './dto/load-inventory.dto';
|
||||
import { MoveInventoryDto } from './dto/move-inventory.dto';
|
||||
import { ReceiveWarehouseInventoryDto } from './dto/receive-inventory.dto';
|
||||
import { ReserveInventoryDto } from './dto/reserve-inventory.dto';
|
||||
import { SchedulingReadFacade } from './scheduling-read.facade';
|
||||
import { WarehouseInventoryService } from './warehouse-inventory.service';
|
||||
|
||||
@ApiTags('warehouse-inventory')
|
||||
@ApiBearerAuth()
|
||||
@Controller('warehouse-inventory')
|
||||
export class WarehouseInventoryController {
|
||||
constructor(private readonly inventoryService: WarehouseInventoryService) {}
|
||||
constructor(
|
||||
private readonly inventoryService: WarehouseInventoryService,
|
||||
private readonly scheduling: SchedulingReadFacade,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'List warehouse inventory' })
|
||||
@@ -32,6 +37,18 @@ export class WarehouseInventoryController {
|
||||
return this.inventoryService.inquiry(filter);
|
||||
}
|
||||
|
||||
@Get('loadable-wagons')
|
||||
@ApiOperation({ summary: 'List wagons usable for loading (read-only from scheduling)' })
|
||||
loadableWagons() {
|
||||
return this.scheduling.listLoadableWagons();
|
||||
}
|
||||
|
||||
@Get('booking/:bookingId/schedule')
|
||||
@ApiOperation({ summary: 'Read-only schedule + wagon + departure status for a booking' })
|
||||
bookingSchedule(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
|
||||
return this.scheduling.getBookingSchedule(bookingId);
|
||||
}
|
||||
|
||||
@Post('receive')
|
||||
@ApiOperation({ summary: 'Receive inventory at a warehouse location' })
|
||||
receive(@Body() dto: ReceiveWarehouseInventoryDto) {
|
||||
@@ -56,6 +73,12 @@ export class WarehouseInventoryController {
|
||||
return this.inventoryService.findActivity(id);
|
||||
}
|
||||
|
||||
@Get(':id/loadings')
|
||||
@ApiOperation({ summary: 'Loading records for an inventory item' })
|
||||
loadings(@Param('id', ParseUUIDPipe) id: string) {
|
||||
return this.inventoryService.findLoadingsByInventory(id);
|
||||
}
|
||||
|
||||
@Post(':id/move')
|
||||
@ApiOperation({ summary: 'Move inventory to another warehouse/yard/zone' })
|
||||
move(@Param('id', ParseUUIDPipe) id: string, @Body() dto: MoveInventoryDto) {
|
||||
@@ -75,13 +98,13 @@ export class WarehouseInventoryController {
|
||||
}
|
||||
|
||||
@Post(':id/load')
|
||||
@ApiOperation({ summary: 'Mark inventory LOADED' })
|
||||
load(@Param('id', ParseUUIDPipe) id: string, @Body('performedBy') performedBy?: string) {
|
||||
return this.inventoryService.load(id, performedBy);
|
||||
@ApiOperation({ summary: 'Load READY_FOR_LOADING inventory onto a wagon' })
|
||||
load(@Param('id', ParseUUIDPipe) id: string, @Body() dto: LoadInventoryDto) {
|
||||
return this.inventoryService.load(id, dto);
|
||||
}
|
||||
|
||||
@Post(':id/dispatch')
|
||||
@ApiOperation({ summary: 'Mark inventory DISPATCHED' })
|
||||
@Patch(':id/dispatch')
|
||||
@ApiOperation({ summary: 'Mark loaded inventory DISPATCHED (left the terminal)' })
|
||||
dispatch(@Param('id', ParseUUIDPipe) id: string, @Body('performedBy') performedBy?: string) {
|
||||
return this.inventoryService.dispatch(id, performedBy);
|
||||
}
|
||||
|
||||
@@ -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 { LoadInventoryDto } from './dto/load-inventory.dto';
|
||||
import { MoveInventoryDto } from './dto/move-inventory.dto';
|
||||
import { ReceiveWarehouseInventoryDto } from './dto/receive-inventory.dto';
|
||||
import { ReserveInventoryDto } from './dto/reserve-inventory.dto';
|
||||
@@ -13,11 +14,17 @@ import {
|
||||
WarehouseInventory,
|
||||
WarehouseInventoryStatus,
|
||||
} from './entities/warehouse-inventory.entity';
|
||||
import { WarehouseLoading } from './entities/warehouse-loading.entity';
|
||||
import { WarehouseYard } from './entities/warehouse-yard.entity';
|
||||
import { WarehouseZone } from './entities/warehouse-zone.entity';
|
||||
import { Warehouse } from './entities/warehouse.entity';
|
||||
import { SchedulingReadFacade } from './scheduling-read.facade';
|
||||
import { WarehouseActivityLogService } from './warehouse-activity-log.service';
|
||||
import { WarehouseInventoryRepository } from './warehouse-inventory.repository';
|
||||
import { WarehouseLoadingRepository } from './warehouse-loading.repository';
|
||||
|
||||
/** Wagon states that may receive a load (besides being part of an existing schedule). */
|
||||
const LOADABLE_WAGON_STATUSES = ['AVAILABLE', 'IMPORT_READY', 'EXPORT_READY', 'ASSIGNED'];
|
||||
|
||||
export interface InventoryInquiryResult {
|
||||
id: string;
|
||||
@@ -53,7 +60,9 @@ export class WarehouseInventoryService {
|
||||
constructor(
|
||||
private readonly dataSource: DataSource,
|
||||
private readonly inventoryRepository: WarehouseInventoryRepository,
|
||||
private readonly loadingRepository: WarehouseLoadingRepository,
|
||||
private readonly activityLog: WarehouseActivityLogService,
|
||||
private readonly scheduling: SchedulingReadFacade,
|
||||
) {}
|
||||
|
||||
// ── Listing ────────────────────────────────────────────────────────────
|
||||
@@ -215,12 +224,112 @@ export class WarehouseInventoryService {
|
||||
});
|
||||
}
|
||||
|
||||
load(id: string, performedBy?: string): Promise<WarehouseInventory> {
|
||||
return this.transition(id, 'LOADED', {
|
||||
timestampField: 'loadedAt',
|
||||
activityType: 'INVENTORY_LOADED',
|
||||
description: 'Inventory loaded',
|
||||
performedBy,
|
||||
/**
|
||||
* Load READY_FOR_LOADING inventory onto a wagon. Creates a WarehouseLoading record.
|
||||
* Reads wagon/schedule data read-only — never modifies scheduling.
|
||||
*/
|
||||
async load(id: string, dto: LoadInventoryDto): Promise<WarehouseInventory> {
|
||||
const item = await this.findById(id);
|
||||
|
||||
// 1. inventory status must be READY_FOR_LOADING (and not already LOADED).
|
||||
this.assertTransition(item.status, 'LOADED');
|
||||
|
||||
// 2. inventory is at a valid warehouse/yard/zone location.
|
||||
if (!item.warehouseId || !item.yardId || !item.zoneId) {
|
||||
throw new BadRequestException('Inventory must be at a warehouse/yard/zone before loading');
|
||||
}
|
||||
|
||||
// 3. wagon must exist.
|
||||
const wagon = await this.scheduling.findWagon(dto.wagonId);
|
||||
if (!wagon) {
|
||||
throw new NotFoundException(`Wagon ${dto.wagonId} not found`);
|
||||
}
|
||||
|
||||
// 4. wagon must be available, or already selected by an existing train schedule.
|
||||
const scheduled = await this.scheduling.isWagonScheduled(dto.wagonId);
|
||||
if (!LOADABLE_WAGON_STATUSES.includes(wagon.status) && !scheduled) {
|
||||
throw new BadRequestException(
|
||||
`Wagon ${wagon.wagonNumber} is not available for loading (status: ${wagon.status})`,
|
||||
);
|
||||
}
|
||||
|
||||
// 5. inventory must not already have a loading record.
|
||||
const existing = await this.loadingRepository.findAll({ where: { warehouseInventoryId: id } });
|
||||
if (existing.length > 0) {
|
||||
throw new BadRequestException('Inventory has already been loaded');
|
||||
}
|
||||
|
||||
const loadedWeight = dto.loadedWeight ?? (Number(item.weight) || 0);
|
||||
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
const now = new Date();
|
||||
await manager.getRepository(WarehouseInventory).update(id, {
|
||||
status: 'LOADED',
|
||||
loadedAt: now,
|
||||
});
|
||||
|
||||
await manager.getRepository(WarehouseLoading).save(
|
||||
manager.getRepository(WarehouseLoading).create({
|
||||
warehouseInventoryId: id,
|
||||
bookingId: item.bookingId ?? null,
|
||||
wagonId: dto.wagonId,
|
||||
loadedAt: now,
|
||||
loadedBy: dto.loadedBy ?? null,
|
||||
loadedWeight,
|
||||
notes: dto.notes?.trim() ?? null,
|
||||
}),
|
||||
);
|
||||
|
||||
await this.activityLog.record(
|
||||
{
|
||||
activityType: 'INVENTORY_LOADED',
|
||||
inventoryId: id,
|
||||
warehouseId: item.warehouseId,
|
||||
description: `Loaded onto wagon ${wagon.wagonNumber}`,
|
||||
performedBy: dto.loadedBy,
|
||||
},
|
||||
manager,
|
||||
);
|
||||
});
|
||||
|
||||
return this.findById(id);
|
||||
}
|
||||
|
||||
// ── Loading records (Batch 3) ─────────────────────────────────────────────
|
||||
|
||||
async findLoadings(
|
||||
filter: { bookingId?: string; wagonId?: string },
|
||||
): Promise<Array<WarehouseLoading & { wagonNumber: string | null }>> {
|
||||
const where = {
|
||||
...(filter.bookingId ? { bookingId: filter.bookingId } : {}),
|
||||
...(filter.wagonId ? { wagonId: filter.wagonId } : {}),
|
||||
};
|
||||
const loadings = await this.loadingRepository.findAll({
|
||||
where,
|
||||
relations: { inventory: { warehouse: true, yard: true, zone: true } },
|
||||
order: { loadedAt: 'DESC' },
|
||||
});
|
||||
|
||||
// Enrich with wagon numbers (read-only lookup into the scheduling domain).
|
||||
const wagonIds = [...new Set(loadings.map((l) => l.wagonId))];
|
||||
const wagonNumbers = new Map<string, string>();
|
||||
if (wagonIds.length > 0) {
|
||||
const rows: Array<{ id: string; wagon_number: string }> = await this.dataSource.query(
|
||||
'SELECT id, wagon_number FROM freight.wagons WHERE id = ANY($1)',
|
||||
[wagonIds],
|
||||
);
|
||||
rows.forEach((r) => wagonNumbers.set(r.id, r.wagon_number));
|
||||
}
|
||||
|
||||
return loadings.map((loading) =>
|
||||
Object.assign(loading, { wagonNumber: wagonNumbers.get(loading.wagonId) ?? null }),
|
||||
);
|
||||
}
|
||||
|
||||
findLoadingsByInventory(inventoryId: string): Promise<WarehouseLoading[]> {
|
||||
return this.loadingRepository.findAll({
|
||||
where: { warehouseInventoryId: inventoryId },
|
||||
order: { loadedAt: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { BaseRepository } from '@edr/api-common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { WarehouseLoading } from './entities/warehouse-loading.entity';
|
||||
|
||||
@Injectable()
|
||||
export class WarehouseLoadingRepository extends BaseRepository<WarehouseLoading> {
|
||||
constructor(@InjectRepository(WarehouseLoading) repository: Repository<WarehouseLoading>) {
|
||||
super(repository);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Controller, Get, Query } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { WarehouseInventoryService } from './warehouse-inventory.service';
|
||||
|
||||
@ApiTags('warehouse-loadings')
|
||||
@ApiBearerAuth()
|
||||
@Controller('warehouse-loadings')
|
||||
export class WarehouseLoadingsController {
|
||||
constructor(private readonly inventoryService: WarehouseInventoryService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'List wagon loading records' })
|
||||
findAll(@Query('bookingId') bookingId?: string, @Query('wagonId') wagonId?: string) {
|
||||
return this.inventoryService.findLoadings({ bookingId, wagonId });
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,11 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { WarehouseActivityLog } from './entities/warehouse-activity-log.entity';
|
||||
import { WarehouseInventory } from './entities/warehouse-inventory.entity';
|
||||
import { WarehouseInventoryMovement } from './entities/warehouse-inventory-movement.entity';
|
||||
import { WarehouseLoading } from './entities/warehouse-loading.entity';
|
||||
import { WarehouseYard } from './entities/warehouse-yard.entity';
|
||||
import { WarehouseZone } from './entities/warehouse-zone.entity';
|
||||
import { Warehouse } from './entities/warehouse.entity';
|
||||
import { SchedulingReadFacade } from './scheduling-read.facade';
|
||||
import { WarehouseActivityLogRepository } from './warehouse-activity-log.repository';
|
||||
import { WarehouseActivityLogService } from './warehouse-activity-log.service';
|
||||
import { WarehouseDashboardService } from './warehouse-dashboard.service';
|
||||
@@ -14,6 +16,8 @@ import { WarehouseInventoryController } from './warehouse-inventory.controller';
|
||||
import { WarehouseInventoryMovementRepository } from './warehouse-inventory-movement.repository';
|
||||
import { WarehouseInventoryRepository } from './warehouse-inventory.repository';
|
||||
import { WarehouseInventoryService } from './warehouse-inventory.service';
|
||||
import { WarehouseLoadingRepository } from './warehouse-loading.repository';
|
||||
import { WarehouseLoadingsController } from './warehouse-loadings.controller';
|
||||
import { WarehouseSchedulingAdapterService } from './warehouse-scheduling-adapter.service';
|
||||
import { WarehouseYardsController } from './warehouse-yards.controller';
|
||||
import { WarehouseYardsRepository } from './warehouse-yards.repository';
|
||||
@@ -34,6 +38,7 @@ import { WarehousesService } from './warehouses.service';
|
||||
WarehouseInventory,
|
||||
WarehouseInventoryMovement,
|
||||
WarehouseActivityLog,
|
||||
WarehouseLoading,
|
||||
]),
|
||||
],
|
||||
controllers: [
|
||||
@@ -41,6 +46,7 @@ import { WarehousesService } from './warehouses.service';
|
||||
WarehouseYardsController,
|
||||
WarehouseZonesController,
|
||||
WarehouseInventoryController,
|
||||
WarehouseLoadingsController,
|
||||
],
|
||||
providers: [
|
||||
WarehousesRepository,
|
||||
@@ -49,6 +55,7 @@ import { WarehousesService } from './warehouses.service';
|
||||
WarehouseInventoryRepository,
|
||||
WarehouseInventoryMovementRepository,
|
||||
WarehouseActivityLogRepository,
|
||||
WarehouseLoadingRepository,
|
||||
WarehousesService,
|
||||
WarehouseYardsService,
|
||||
WarehouseZonesService,
|
||||
@@ -56,6 +63,7 @@ import { WarehousesService } from './warehouses.service';
|
||||
WarehouseActivityLogService,
|
||||
WarehouseDashboardService,
|
||||
WarehouseSchedulingAdapterService,
|
||||
SchedulingReadFacade,
|
||||
],
|
||||
exports: [
|
||||
WarehousesService,
|
||||
|
||||
Reference in New Issue
Block a user