feat(billing): USD offline bank-transfer payments

This commit is contained in:
Marshal
2026-08-08 13:37:05 +00:00
parent 83b9e32670
commit a42d32c27c
31 changed files with 923 additions and 11 deletions

View File

@@ -60,7 +60,25 @@ import { BookingWindowService } from "./booking-window.service";
import { IntercityService } from "./intercity.service";
import { BillingService } from "../billing/billing.service";
<<<<<<< Updated upstream:apps/edr-freight-api/src/modules/train-scheduling/train-scheduling.controller.ts
@ApiTags("train-scheduling")
=======
import { TrainSchedulingManage, TrainSchedulingView } from '../../../common/booking-guards';
import { AssignBookingsDto } from '../dto/assign-bookings.dto';
import { CreateContainerTrainScheduleDto } from '../dto/create-container-train-schedule.dto';
import { GetEligibleBookingsDto } from '../dto/get-eligible-bookings.dto';
import { GetEligibleBulkBookingsDto } from '../dto/get-eligible-bulk-bookings.dto';
import { GetEligibleContainerBookingsDto } from '../dto/get-eligible-container-bookings.dto';
import { PinWagonsDto } from '../dto/pin-wagons.dto';
import { PreviewBulkTrainScheduleDto } from '../dto/preview-bulk-train-schedule.dto';
import { PreviewContainerTrainScheduleDto } from '../dto/preview-container-train-schedule.dto';
import { PreviewTrainScheduleDto } from '../dto/preview-train-schedule.dto';
import { RecordCheckpointDto } from '../dto/record-checkpoint.dto';
import { UpdateTrainSchedulingGlobalRulesDto } from '../dto/update-train-scheduling-global-rules.dto';
import { TrainSchedulingService } from '../services/train-scheduling.service';
@ApiTags('train-scheduling')
>>>>>>> Stashed changes:apps/edr-freight-api/src/modules/train-scheduling/controllers/train-scheduling.controller.ts
@ApiBearerAuth()
@Controller("train-scheduling")
export class TrainSchedulingController {

View File

@@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { TrainCheckpointEvent } from './entities/train-checkpoint-event.entity';
import { TrainCheckpointEvent } from '../entities/train-checkpoint-event.entity';
@Injectable()
export class TrainCheckpointEventsRepository extends BaseRepository<TrainCheckpointEvent> {

View File

@@ -1,11 +1,18 @@
import { BadRequestException, ConflictException } from '@nestjs/common';
import { WagonStatus } from '@edr/types';
<<<<<<< Updated upstream:apps/edr-freight-api/src/modules/train-scheduling/train-scheduling.service.spec.ts
import { Wagon } from '../wagons/entities/wagon.entity';
import { WagonType } from '../wagon-types/entities/wagon-type.entity';
import { TrainSetWagon } from '../train-sets/entities/train-set-wagon.entity';
import { TrainSchedulingGlobalRules } from './entities/train-scheduling-global-rules.entity';
import { WagonBookingAllocation } from '../train-schedules/entities/wagon-booking-allocation.entity';
=======
import { Wagon } from '../../wagons/entities/wagon.entity';
import { WagonType } from '../../wagon-types/entities/wagon-type.entity';
import { TrainSetWagon } from '../../train-sets/entities/train-set-wagon.entity';
import { TrainSchedulingGlobalRules } from '../entities/train-scheduling-global-rules.entity';
>>>>>>> Stashed changes:apps/edr-freight-api/src/modules/train-scheduling/services/train-scheduling.service.spec.ts
import { TrainSchedulingService } from './train-scheduling.service';
const nw5 = {

View File

@@ -34,6 +34,7 @@ import {
Raw,
} from 'typeorm';
<<<<<<< Updated upstream:apps/edr-freight-api/src/modules/train-scheduling/train-scheduling.service.ts
import {
buildPaginationMeta,
normalizePagination,
@@ -86,6 +87,39 @@ import { PreviewBulkTrainScheduleDto } from './dto/preview-bulk-train-schedule.d
import { PreviewContainerTrainScheduleDto } from './dto/preview-container-train-schedule.dto';
import { PreviewTrainScheduleDto } from './dto/preview-train-schedule.dto';
import { TrainSchedulingGlobalRules } from './entities/train-scheduling-global-rules.entity';
=======
import { BookingsRepository } from '../../bookings/bookings.repository';
import { Booking } from '../../bookings/entities/booking.entity';
import { BookingContainer } from '../../bookings/entities/booking-container.entity';
import { Container } from '../../container-management/entities/container.entity';
import { Locomotive } from '../../locomotives/entities/locomotive.entity';
import { LocomotivesRepository } from '../../locomotives/locomotives.repository';
import { Route } from '../../routes/entities/route.entity';
import { TrainSetWagon } from '../../train-sets/entities/train-set-wagon.entity';
import { TrainSet } from '../../train-sets/entities/train-set.entity';
import { TrainScheduleBooking } from '../../train-schedules/entities/train-schedule-booking.entity';
import { TrainSchedule } from '../../train-schedules/entities/train-schedule.entity';
import { WagonBookingAllocation } from '../../train-schedules/entities/wagon-booking-allocation.entity';
import { TrainScheduleBookingsRepository } from '../../train-schedules/train-schedule-bookings.repository';
import { TrainSchedulesRepository } from '../../train-schedules/train-schedules.repository';
import { WagonAllocationBulkLoadsRepository } from '../../train-schedules/wagon-allocation-bulk-loads.repository';
import { WagonAllocationContainerItemsRepository } from '../../train-schedules/wagon-allocation-container-items.repository';
import { WagonBookingAllocationsRepository } from '../../train-schedules/wagon-booking-allocations.repository';
import { WagonType } from '../../wagon-types/entities/wagon-type.entity';
import { WagonTypesRepository } from '../../wagon-types/wagon-types.repository';
import { Wagon } from '../../wagons/entities/wagon.entity';
import { AssignBookingsDto } from '../dto/assign-bookings.dto';
import { CreateContainerTrainScheduleDto } from '../dto/create-container-train-schedule.dto';
import { GetEligibleBookingsDto } from '../dto/get-eligible-bookings.dto';
import { GetEligibleBulkBookingsDto } from '../dto/get-eligible-bulk-bookings.dto';
import { GetEligibleContainerBookingsDto } from '../dto/get-eligible-container-bookings.dto';
import { PinWagonsDto } from '../dto/pin-wagons.dto';
import { PreviewBulkTrainScheduleDto } from '../dto/preview-bulk-train-schedule.dto';
import { PreviewContainerTrainScheduleDto } from '../dto/preview-container-train-schedule.dto';
import { PreviewTrainScheduleDto } from '../dto/preview-train-schedule.dto';
import { TrainSchedulingGlobalRules } from '../entities/train-scheduling-global-rules.entity';
import { UpdateTrainSchedulingGlobalRulesDto } from '../dto/update-train-scheduling-global-rules.dto';
>>>>>>> Stashed changes:apps/edr-freight-api/src/modules/train-scheduling/services/train-scheduling.service.ts
import {
ImportDjiboutiOperation,
type ImportDjiboutiDocumentType,
@@ -110,7 +144,7 @@ import {
type BookingWagonShortage,
type DeferredBookingRow,
type FleetAvailabilityRow,
} from './fleet-plan.util';
} from '../utils/fleet-plan.util';
import {
applyWagonOrderReversal,
planWagonsWithStock,
@@ -130,6 +164,7 @@ import {
validateMixedTrainLimitsPerEdge,
type ContainerPlacementInput,
type WagonPlanSlot,
<<<<<<< Updated upstream:apps/edr-freight-api/src/modules/train-scheduling/train-scheduling.service.ts
} from './wagon-plan.util';
import { CorridorBudget } from './corridor-capacity.util';
import { deriveScheduleDirection } from './derive-schedule-direction.util';
@@ -179,6 +214,19 @@ import {
placementsForBookings,
type ContainerUnitForPlacement,
} from './container-placement.util';
=======
} from '../utils/wagon-plan.util';
import {
getDefaultContainerWagonTypeCode,
pickBulkWagonType,
} from '../utils/wagon-type-resolver.util';
import { deriveScheduleDirection } from '../utils/derive-schedule-direction.util';
import { flipReadiness, wagonReadinessMatchesSchedule } from '../utils/wagon-readiness.util';
import { TrainCheckpointEvent } from '../entities/train-checkpoint-event.entity';
import { TrainCheckpointEventsRepository } from '../repositories/train-checkpoint-events.repository';
import { RecordCheckpointDto } from '../dto/record-checkpoint.dto';
import { RouteMilestone } from '../../routes/entities/route-milestone.entity';
>>>>>>> Stashed changes:apps/edr-freight-api/src/modules/train-scheduling/services/train-scheduling.service.ts
const SCHEDULABLE_BOOKING_STATUSES = ['PAID'] as const;
@@ -5767,7 +5815,7 @@ export class TrainSchedulingService {
}
private resolveScheduleFreightType(
schedule: import('../train-schedules/entities/train-schedule.entity').TrainSchedule,
schedule: import('../../train-schedules/entities/train-schedule.entity').TrainSchedule,
): 'CONTAINER' | 'BULK' | 'MIXED' | null {
const types = new Set(
(schedule.scheduleBookings ?? [])
@@ -5779,6 +5827,7 @@ export class TrainSchedulingService {
return null;
}
<<<<<<< Updated upstream:apps/edr-freight-api/src/modules/train-scheduling/train-scheduling.service.ts
/**
* Insert a schedule with a freshly generated S-<year>-NNNNN reference, retrying
* past a concurrent insert that grabbed the same sequence (the unique index
@@ -5810,6 +5859,9 @@ export class TrainSchedulingService {
}
private mapScheduleListItem(schedule: import('../train-schedules/entities/train-schedule.entity').TrainSchedule) {
=======
private mapScheduleListItem(schedule: import('../../train-schedules/entities/train-schedule.entity').TrainSchedule) {
>>>>>>> Stashed changes:apps/edr-freight-api/src/modules/train-scheduling/services/train-scheduling.service.ts
return {
id: schedule.id,
reference: schedule.reference ?? null,
@@ -7555,7 +7607,7 @@ export class TrainSchedulingService {
}
private async mapScheduleDetail(
schedule: import('../train-schedules/entities/train-schedule.entity').TrainSchedule,
schedule: import('../../train-schedules/entities/train-schedule.entity').TrainSchedule,
) {
const allocations = (schedule.trainSet?.wagons ?? []).flatMap(
(w) => w.allocations ?? [],

View File

@@ -24,6 +24,7 @@ import { WarehousesModule } from '../warehouses/warehouses.module';
import { TrainCheckpointEvent } from './entities/train-checkpoint-event.entity';
import { ImportDjiboutiOperation } from './entities/import-djibouti-operation.entity';
import { TrainSchedulingGlobalRules } from './entities/train-scheduling-global-rules.entity';
<<<<<<< Updated upstream
import { TrainCheckpointEventsRepository } from './train-checkpoint-events.repository';
import { TrainSchedulingController } from './train-scheduling.controller';
import { TrainSchedulingService } from './train-scheduling.service';
@@ -41,6 +42,11 @@ import { WagonMovement } from '../wagons/entities/wagon-movement.entity';
import { NotificationsModule } from '../notifications/notifications.module';
import { NotificationInboxModule } from '../notification-inbox/notification-inbox.module';
import { ContractsModule } from '../contracts/contracts.module';
=======
import { TrainCheckpointEventsRepository } from './repositories/train-checkpoint-events.repository';
import { TrainSchedulingController } from './controllers/train-scheduling.controller';
import { TrainSchedulingService } from './services/train-scheduling.service';
>>>>>>> Stashed changes
@Module({
imports: [

View File

@@ -1,5 +1,5 @@
import { Booking } from '../bookings/entities/booking.entity';
import { WagonType } from '../wagon-types/entities/wagon-type.entity';
import { Booking } from '../../bookings/entities/booking.entity';
import { WagonType } from '../../wagon-types/entities/wagon-type.entity';
import {
computeFleetAvailability,
selectBookingsWithinFleetCap,

View File

@@ -1,6 +1,11 @@
<<<<<<< Updated upstream:apps/edr-freight-api/src/modules/train-scheduling/fleet-plan.util.ts
import { bookingCargoTons, bulkWagonsForAllowedTypes } from './train-capacity.util';
import type { Booking } from '../bookings/entities/booking.entity';
import type { WagonType } from '../wagon-types/entities/wagon-type.entity';
=======
import type { Booking } from '../../bookings/entities/booking.entity';
import type { WagonType } from '../../wagon-types/entities/wagon-type.entity';
>>>>>>> Stashed changes:apps/edr-freight-api/src/modules/train-scheduling/utils/fleet-plan.util.ts
import {
buildBulkWagonPlan,
buildContainerWagonPlan,

View File

@@ -1,7 +1,7 @@
import { AllocationLoadType } from '@edr/types';
import { Booking } from '../bookings/entities/booking.entity';
import { WagonType } from '../wagon-types/entities/wagon-type.entity';
import { Booking } from '../../bookings/entities/booking.entity';
import { WagonType } from '../../wagon-types/entities/wagon-type.entity';
import {
buildBulkWagonPlan,
buildContainerWagonPlan,

View File

@@ -1,5 +1,6 @@
import { AllocationLoadType } from '@edr/types';
<<<<<<< Updated upstream:apps/edr-freight-api/src/modules/train-scheduling/wagon-plan.util.ts
import { Booking } from '../bookings/entities/booking.entity';
import { containersPerWagonForSize, wagonsPerUnitForSize } from '../rule-engine/container-type.util';
import { WagonType } from '../wagon-types/entities/wagon-type.entity';
@@ -11,6 +12,10 @@ import {
bulkTonWagonsRequired,
consistViolations,
} from './train-capacity.util';
=======
import { Booking } from '../../bookings/entities/booking.entity';
import { WagonType } from '../../wagon-types/entities/wagon-type.entity';
>>>>>>> Stashed changes:apps/edr-freight-api/src/modules/train-scheduling/utils/wagon-plan.util.ts
export const MAX_TRAIN_WEIGHT_TONS = 3500;
export const MAX_TRAIN_LENGTH_METERS = 760;

View File

@@ -0,0 +1,49 @@
import { WagonType } from '../../wagon-types/entities/wagon-type.entity';
const CARGO_CODE_TO_WAGON_TYPE: Record<string, string> = {
COFFEE: 'KW2',
GRAIN: 'KW2',
WHEAT: 'KW2',
SORGHUM: 'KW2',
CORN: 'KW2',
FERTILIZER: 'PW2',
SUGAR: 'PW2',
COAL: 'KW3',
STEEL: 'CW3',
ORE: 'CW3',
};
const DEFAULT_BULK_WAGON_TYPE = 'CW3';
const DEFAULT_CONTAINER_WAGON_TYPE = 'NW5';
/**
* Resolve wagon type code from cargo type code for bulk freight.
*/
export function resolveBulkWagonTypeCode(cargoTypeCode?: string | null): string {
if (!cargoTypeCode) return DEFAULT_BULK_WAGON_TYPE;
const normalized = cargoTypeCode.trim().toUpperCase();
return CARGO_CODE_TO_WAGON_TYPE[normalized] ?? DEFAULT_BULK_WAGON_TYPE;
}
/**
* Pick the best matching wagon type entity for bulk cargo.
*/
export function pickBulkWagonType(
wagonTypes: WagonType[],
cargoTypeCode?: string | null,
): WagonType | undefined {
const preferredCode = resolveBulkWagonTypeCode(cargoTypeCode);
const direct = wagonTypes.find((wt) => wt.code === preferredCode && wt.isActive);
if (direct) return direct;
return wagonTypes.find(
(wt) =>
wt.isActive &&
!wt.supportsContainer &&
wt.code !== DEFAULT_CONTAINER_WAGON_TYPE,
);
}
export function getDefaultContainerWagonTypeCode(): string {
return DEFAULT_CONTAINER_WAGON_TYPE;
}