feat: add wagon usage computation and maintenance logging features

- Implemented  utility to calculate wagon usage metrics for train schedules.
- Created  for sending wagons to maintenance with optional notes.
- Added unit tests for train builder maintenance functionalities, including formatting train run labels and building maintenance notes.
- Developed  component for merging train schedules with detailed previews and reasons for merging.
- Introduced  component for selecting wagons with search functionality and selection limits.
- Created  for displaying and filtering audit logs, including detailed views of individual log entries.
- Added  for handling API interactions related to audit logs, including fetching logs and entity types.
This commit is contained in:
marshalyordanos
2026-08-12 09:36:50 +03:00
parent 35e5404b41
commit 5da36eb128
77 changed files with 6275 additions and 296 deletions

View File

@@ -6,6 +6,7 @@ import {
} from '@nestjs/common';
import {
ContractDocPhase,
isDeliveryOrderFileCode,
type ClearanceFinalInvoiceSummary,
type ClearanceOffloadState,
type ClearanceSecondDuty,
@@ -36,7 +37,7 @@ import { Contract } from './entities/contract.entity';
import { ContractDocReviewStatus } from './entities/contract-document-review.entity';
import { FilterContractDto } from './dto/filter-contract.dto';
import { AdviseContractDutyDto } from './dto/phased-clearance.dto';
import { buildWorkflowFiles, persistDeclarationUploads, persistTransitPermitUploads, PHASED_CUSTOMS_CONTRACT_QUEUE_STATUSES } from './phased-clearance.util';
import { buildWorkflowFiles, persistDeclarationUploads, persistDeliveryOrderUploads, persistReleaseOrderUploads, persistTransitPermitUploads, PHASED_CUSTOMS_CONTRACT_QUEUE_STATUSES } from './phased-clearance.util';
const RO_VESSEL_MIN_DAYS_CODE = 'ro_vessel_min_days';
@@ -1449,7 +1450,7 @@ export class ContractClearanceService {
// GL Djibouti may have uploaded the DO early (un-gated) — count it now.
const files = await this.filesService.findByResource(contractId, 'contracts');
if (files.some((f) => f.code === 'delivery_order')) {
if (files.some((f) => isDeliveryOrderFileCode(f.code))) {
await this.workflowService.completeMilestone(contractId, 'DO_COLLECTED');
await this.workflowService.markReadyForBooking(contractId);
}
@@ -1460,7 +1461,7 @@ export class ContractClearanceService {
async uploadDeliveryOrder(
contractId: string,
file: Express.Multer.File,
files: Express.Multer.File[],
userId?: string,
dates?: { vesselArrivalDate?: string; doCollectedDate?: string },
): Promise<Contract> {
@@ -1470,19 +1471,12 @@ export class ContractClearanceService {
throw new BadRequestException('Delivery Order applies only to import contracts.');
}
if (!file) throw new BadRequestException('No Delivery Order uploaded');
const { vesselArrivalDate, doCollectedDate } = assertDoCollectionDates(dates);
// DO upload is deliberately un-gated: GL Djibouti may attach it at any point,
// any file type. The DO_COLLECTED milestone (and booking readiness) still waits
// for GL Ethiopia to finalize pre-clearance so the workflow order holds.
await this.filesService.upsertByCode({
resourceId: contractId,
resource: 'contracts',
code: 'delivery_order',
file,
});
await persistDeliveryOrderUploads(this.filesService, contractId, 'contracts', files ?? []);
const cycle = await this.contractsRepository.currentCycle(contractId);
if (cycle) {
@@ -1520,7 +1514,7 @@ export class ContractClearanceService {
async uploadReleaseOrder(
contractId: string,
file: Express.Multer.File,
files: Express.Multer.File[],
vesselDepartureDate: string,
userId?: string,
): Promise<{ contract: Contract; hold: boolean; holdReason?: string }> {
@@ -1535,7 +1529,6 @@ export class ContractClearanceService {
'RELEASE_ORDER_SECURED',
);
if (!file) throw new BadRequestException('No Release Order uploaded');
if (!vesselDepartureDate?.trim()) {
throw new BadRequestException('Vessel departure date is required');
}
@@ -1545,12 +1538,7 @@ export class ContractClearanceService {
const cycle = await this.contractsRepository.currentCycle(contractId);
if (!cycle) throw new BadRequestException('No clearance cycle found');
await this.filesService.upsertByCode({
resourceId: contractId,
resource: 'contracts',
code: 'release_order',
file,
});
await persistReleaseOrderUploads(this.filesService, contractId, 'contracts', files ?? []);
await this.contractsRepository.updateCycle(cycle.id, {
vesselDepartureDate,