fix(freight): filter container returns by returned-by truck type

Top Returned Containers table ignored the EDR/Customer tab because
the backend never persisted which truck type performed the return.
Added returned_by column + DTO/entity field, wired create payload to
send it, and filtered the table by the active tab.
This commit is contained in:
Hagernesh
2026-08-04 06:54:52 +00:00
parent 5c33ea90bd
commit dfc7dfec5a
13 changed files with 198 additions and 578 deletions

View File

@@ -0,0 +1,19 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class EmptyContainerReturnedBy3210000000000 implements MigrationInterface {
name = 'EmptyContainerReturnedBy3210000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.empty_container_returns
ADD COLUMN IF NOT EXISTS returned_by varchar(20) NULL
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.empty_container_returns
DROP COLUMN IF EXISTS returned_by
`);
}
}

View File

@@ -28,6 +28,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { DataSource, In } from 'typeorm';
import { deriveTradeDirection } from '../../common/derive-trade-direction.util';
import { assertExportReceivedWithGrn } from '../../common/export-received-gate';
import { Yard } from '../rule-engine/entities/yard.entity';
import { ServiceType } from '../rule-engine/entities/service-type.entity';
import { TrainSchedule } from '../train-schedules/entities/train-schedule.entity';
@@ -239,6 +240,10 @@ export class BookingsService {
*/
async carriageAcceptanceSheet(bookingId: string): Promise<{ filename: string; buffer: Buffer }> {
const booking = await this.findById(bookingId);
// The sheet attests that EDR has taken custody. For export that happens at
// cargo receipt (GRN), so the GRN is required even when wagons are already
// allocated — an allocation is a plan, not possession.
await assertExportReceivedWithGrn(this.dataSource, booking);
let wagons: CarriageAcceptanceWagonRow[] = await this.dataSource.query(
`SELECT tsw.sequence_no AS "sequenceNo",
COALESCE(wt.code, wt.name) AS "wagonType",
@@ -291,7 +296,10 @@ export class BookingsService {
LEFT JOIN freight.containers c
ON c.id = inv.container_id AND c.deleted_at IS NULL
WHERE inv.booking_id = $1 AND inv.deleted_at IS NULL
AND COALESCE(NULLIF(TRIM(inv.grn_number), ''), '') <> ''
AND COALESCE(
NULLIF(TRIM(inv.grn_number), ''),
substring(inv.notes FROM 'GRN Number: ([^\\n\\r]+)')
) IS NOT NULL
ORDER BY inv.created_at`,
[bookingId],
)

View File

@@ -169,6 +169,11 @@ export class CreateEmptyContainerReturnDto {
@IsOptional()
@IsString()
performedBy?: string;
@ApiPropertyOptional({ enum: ['EDR', 'CUSTOMER'] })
@IsOptional()
@IsIn(['EDR', 'CUSTOMER'])
returnedBy?: 'EDR' | 'CUSTOMER';
}
export class UpdateEmptyContainerReturnStatusDto extends ImportOperationActionDto {

View File

@@ -53,4 +53,7 @@ export class EmptyContainerReturn extends BaseEntity {
@Column({ name: 'performed_by', type: 'varchar', length: 120, nullable: true })
performedBy?: string | null;
@Column({ name: 'returned_by', type: 'varchar', length: 20, nullable: true })
returnedBy?: 'EDR' | 'CUSTOMER' | null;
}

View File

@@ -161,6 +161,7 @@ export class ImportOperationsService {
condition: dto.condition ?? null,
handoverNote: dto.handoverNote ?? null,
performedBy: dto.performedBy ?? null,
returnedBy: dto.returnedBy ?? null,
}),
);
}