Files
edr-platform/apps/edr-freight-api/src/modules/bookings/dto/request-changes.dto.ts
2026-09-06 13:50:07 +00:00

216 lines
5.3 KiB
TypeScript

import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import {
IsDateString,
IsIn,
IsInt,
IsOptional,
IsString,
IsUUID,
Max,
Min,
MinLength,
} from 'class-validator';
export class RequestChangesDto {
@ApiProperty({ description: 'Staff note explaining what the customer must fix' })
@IsString()
@MinLength(1)
note!: string;
}
export class AcceptIntakeDto {
@ApiProperty({
description:
'How many days the contract stays valid, counted from the accept date. ' +
'The contract is valid from now through now + validityDays.',
minimum: 1,
maximum: 365,
example: 30,
})
@IsInt()
@Min(1)
@Max(365)
validityDays!: number;
}
export class StaffRejectDto {
@ApiProperty()
@IsString()
@MinLength(1)
reason!: string;
}
export class ApproveStepDto {
@ApiProperty({ description: 'LINE_STAFF | DIRECTOR | CEO' })
@IsString()
requiredRole!: string;
}
export class RejectStepDto {
@ApiProperty()
@IsString()
@MinLength(1)
reason!: string;
}
export class CancelBookingDto {
@ApiProperty()
@IsString()
@MinLength(1)
reason!: string;
}
export class RejectBookingDto {
@ApiPropertyOptional({
description: 'Optional reason the customer rejected the price estimate',
})
@IsOptional()
@IsString()
reason?: string;
}
export class ReviewDocumentDto {
@ApiProperty({ description: 'The document fileKey being reviewed' })
@IsString()
@MinLength(1)
fileKey!: string;
@ApiProperty({ enum: ['APPROVED', 'QUERIED'] })
@IsIn(['APPROVED', 'QUERIED'])
status!: 'APPROVED' | 'QUERIED';
@ApiPropertyOptional({ description: 'Required when querying a document' })
@IsOptional()
@IsString()
note?: string;
}
export class RequestOperationDto {
@ApiProperty({
description:
'The schedule day (train departure day) the customer selects for this ' +
'shipment. ISO date — the booking enters the batch pool for this route + day.',
example: '2026-07-15',
})
@IsDateString()
scheduledDate!: string;
@ApiPropertyOptional({
description:
'EXPORT rail only: the specific train (schedule id) the customer picked ' +
'from GET /bookings/:id/export-trains. The reserve path locks onto this ' +
'train instead of earliest-first; 409 if it no longer fits. Ignored for ' +
'import/domestic/road bookings.',
})
@IsOptional()
@IsUUID()
trainScheduleId?: string;
}
/**
* Operations changes a pending operation request's shipment day and/or train
* on the customer's behalf (instead of returning it for changes).
*/
export class RescheduleOperationDto {
@ApiProperty({
description:
'The new shipment day (train departure day). ISO date — must have an ' +
'open departure on the booking route that can carry the cargo.',
example: '2026-07-15',
})
@IsDateString()
scheduledDate!: string;
@ApiPropertyOptional({
description:
'EXPORT rail only: the train (schedule id) to ride, from ' +
'GET /bookings/:id/export-trains for the new day. Required for export ' +
'rail; ignored for import/domestic/road bookings.',
})
@IsOptional()
@IsUUID()
trainScheduleId?: string;
@ApiPropertyOptional({
description: 'Optional note to the customer explaining the change.',
})
@IsOptional()
@IsString()
note?: string;
}
export class OperationReviewDto {
@ApiProperty({
description:
'The operations decision: ACCEPT enters the batch pool; REQUEST_CHANGES ' +
'returns it to the customer with a note. The booking price is computed ' +
'from the contract and cannot be adjusted by staff.',
enum: ['ACCEPT', 'REQUEST_CHANGES'],
})
@IsIn(['ACCEPT', 'REQUEST_CHANGES'])
decision!: 'ACCEPT' | 'REQUEST_CHANGES';
@ApiPropertyOptional({
description: 'Required for REQUEST_CHANGES (what the customer must fix).',
})
@IsOptional()
@IsString()
note?: string;
}
/**
* A staff decision applied to BOTH halves of a consolidated pair. The two
* bookings share a wagon, so they advance or cancel together — never one alone.
*/
export class PairedDecisionDto {
@ApiProperty({
enum: ["accept", "cancel", "operationAccept", "requestChanges"],
description: 'Which staff decision to apply to both bookings.',
})
@IsIn(["accept", "cancel", "operationAccept", "requestChanges"])
decision!: "accept" | "cancel" | "operationAccept" | "requestChanges";
@ApiPropertyOptional({ description: "Cancellation reason (decision=cancel)." })
@IsOptional()
@IsString()
reason?: string;
@ApiPropertyOptional({
description: "Message to the customer (decision=requestChanges).",
})
@IsOptional()
@IsString()
note?: string;
@ApiPropertyOptional({
description: "Contract validity window in days (decision=accept).",
})
@IsOptional()
@IsInt()
@Min(1)
validityDays?: number;
}
/** Approve a shared-wagon pairing. The note is optional context for the audit. */
export class ApproveConsolidationDto {
@ApiPropertyOptional({
description: "Optional note recorded with the approval.",
maxLength: 500,
})
@IsOptional()
@IsString()
note?: string;
}
/** Reject a shared-wagon pairing. A reason is mandatory — GL has to act on it. */
export class RejectConsolidationDto {
@ApiProperty({
description:
"Why the pairing is rejected. Sent back to GL on both bookings.",
maxLength: 500,
})
@IsString()
@MinLength(1)
reason!: string;
}