mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
Warehouse Enhancemendt
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
export * from "./payments";
|
||||
export * from "./payment-messaging";
|
||||
|
||||
export interface BaseEntity {
|
||||
id: string;
|
||||
|
||||
55
packages/types/src/common/payment-messaging.ts
Normal file
55
packages/types/src/common/payment-messaging.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/* ------------------------------------------------------------------------------------------------
|
||||
* Payment event messaging contract (RabbitMQ)
|
||||
*
|
||||
* The single source of truth for the broker topology shared between the payment microservice
|
||||
* (publisher) and the domain apps (consumers). Both sides import these constants/helpers so the
|
||||
* exchange name, routing keys, and queue names can never drift apart.
|
||||
*
|
||||
* Topology (see docs/payment-service/rabbitmq/):
|
||||
* exchange payment.events (topic, durable) ← every payment event is published here
|
||||
* exchange payment.events.dlx (topic, durable) ← dead-letter for events a consumer rejects
|
||||
* routing payment.<service>.<outcome> e.g. payment.passenger.succeeded
|
||||
* queue <service>.payment-events bound to payment.<service>.*
|
||||
* queue <service>.payment-events.dlq dead-letter queue (bound on the dlx)
|
||||
* ---------------------------------------------------------------------------------------------- */
|
||||
|
||||
import { PaymentEventType, PaymentService } from "./payments";
|
||||
|
||||
/** Topic exchange every payment event is published to. */
|
||||
export const PAYMENT_EVENTS_EXCHANGE = "payment.events";
|
||||
|
||||
/** Dead-letter exchange for payment events a consumer could not process (poison messages). */
|
||||
export const PAYMENT_EVENTS_DLX = "payment.events.dlx";
|
||||
|
||||
/**
|
||||
* Routing key for a payment event: `payment.<service>.<outcome>`.
|
||||
* e.g. `payment.passenger.succeeded`, `payment.freight.failed`.
|
||||
*/
|
||||
export function paymentRoutingKey(
|
||||
service: PaymentService,
|
||||
eventType: PaymentEventType,
|
||||
): string {
|
||||
// "payment.succeeded" -> "succeeded", "payment.failed" -> "failed"
|
||||
const outcome = eventType.split(".")[1];
|
||||
return `payment.${service.toLowerCase()}.${outcome}`;
|
||||
}
|
||||
|
||||
/** Binding pattern a service's queue uses so it receives only its own events. */
|
||||
export function paymentServiceBindingPattern(service: PaymentService): string {
|
||||
return `payment.${service.toLowerCase()}.*`;
|
||||
}
|
||||
|
||||
/** Durable queue names per owning service: the main work queue and its dead-letter queue. */
|
||||
export const PAYMENT_QUEUES: Record<
|
||||
PaymentService,
|
||||
{ main: string; dlq: string }
|
||||
> = {
|
||||
[PaymentService.PASSENGER]: {
|
||||
main: "passenger.payment-events",
|
||||
dlq: "passenger.payment-events.dlq",
|
||||
},
|
||||
[PaymentService.FREIGHT]: {
|
||||
main: "freight.payment-events",
|
||||
dlq: "freight.payment-events.dlq",
|
||||
},
|
||||
};
|
||||
@@ -23,6 +23,7 @@ export enum ProviderMethod {
|
||||
WAAFI = "WAAFI",
|
||||
CARD = "CARD",
|
||||
DMONEY = "DMONEY",
|
||||
CAC_BANK = "CAC_BANK",
|
||||
}
|
||||
|
||||
export type PaymentPlatform = "web" | "mobile";
|
||||
@@ -34,6 +35,11 @@ export type ClientAction =
|
||||
appId: string;
|
||||
receiveCode?: string;
|
||||
shortCode: string;
|
||||
}
|
||||
| {
|
||||
type: "COLLECT_OTP";
|
||||
providerOrderId: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
export interface ProviderInitiationInput {
|
||||
@@ -43,8 +49,17 @@ export interface ProviderInitiationInput {
|
||||
amountMinor: number;
|
||||
currency: string;
|
||||
platform?: PaymentPlatform;
|
||||
returnUrl?: string
|
||||
redirectUrl?: string
|
||||
/**
|
||||
* Payer account identifier (e.g. mobile-wallet MSISDN in full international format).
|
||||
* Optional and provider-specific: some wallet providers (e.g. Waafi HPP with
|
||||
* MWALLET_ACCOUNT) require the payer's phone number up front to pre-fill the hosted page.
|
||||
*/
|
||||
payerAccount?: string;
|
||||
/** Optional caller-supplied redirect targets for redirect/HPP-style providers. */
|
||||
returnUrl?: string;
|
||||
redirectUrl?: string;
|
||||
/** Where the browser lands when the hosted page fails/cancels (UX only — never trusted). */
|
||||
failureUrl?: string;
|
||||
}
|
||||
|
||||
export interface ProviderInitiationResult {
|
||||
@@ -67,3 +82,108 @@ export interface PaymentProvider {
|
||||
initiate(input: ProviderInitiationInput): Promise<ProviderInitiationResult>;
|
||||
queryStatus(merchantOrderId: string): Promise<ProviderStatus>;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------
|
||||
* Payment microservice contracts (docs/payment-service)
|
||||
*
|
||||
* Shared shapes exchanged between the payment microservice (apps/edr-payment-api) and the
|
||||
* domain apps (passenger/freight). Both sides import these so the wire format cannot drift.
|
||||
* ---------------------------------------------------------------------------------------------- */
|
||||
|
||||
/** Which domain app owns the order being paid for. Routing discriminator on every intent. */
|
||||
export enum PaymentService {
|
||||
PASSENGER = "PASSENGER",
|
||||
FREIGHT = "FREIGHT",
|
||||
}
|
||||
|
||||
/** What kind of domain order the intent references (soft reference — never a cross-schema FK). */
|
||||
export enum PaymentReferenceType {
|
||||
BOOKING = "BOOKING",
|
||||
SHIPMENT = "SHIPMENT",
|
||||
}
|
||||
|
||||
|
||||
/** Body of `POST /payments/initiate` on the payment service (internal, service-authenticated). */
|
||||
export interface InitiatePaymentRequest {
|
||||
service: PaymentService;
|
||||
referenceType: PaymentReferenceType;
|
||||
/** Domain order id (booking/shipment id). Soft reference; the app has already validated it. */
|
||||
referenceId: string;
|
||||
/** Human-readable order ref (e.g. booking ref) shown on provider pages. Defaults to referenceId. */
|
||||
orderRef?: string;
|
||||
/** App-asserted authoritative amount in minor units (computed server-side by the domain app). */
|
||||
amountMinor: number;
|
||||
currency: string;
|
||||
provider: ProviderMethod;
|
||||
platform?: PaymentPlatform;
|
||||
payerAccount?: string;
|
||||
/**
|
||||
* Where the provider's hosted page sends the BROWSER back after success — each calling app
|
||||
* passes its own UI URL (passenger portal vs freight portal). Per-transaction and UX-only:
|
||||
* the redirect never confirms payment (only the webhook / status query does), so per-app
|
||||
* values are safe even though the server-to-server webhook URL is one per merchant.
|
||||
* Falls back to the payment service's provider config when omitted.
|
||||
*/
|
||||
returnUrl?: string;
|
||||
/** Failure/cancel counterpart of returnUrl. */
|
||||
failureUrl?: string;
|
||||
/** Optional caller key to dedupe retried initiations beyond the per-reference upsert. */
|
||||
idempotencyKey?: string;
|
||||
}
|
||||
|
||||
/** Body of `POST /payments/intents/:id/confirm` (OTP-based providers such as CAC Bank). */
|
||||
export interface ConfirmPaymentRequest {
|
||||
otp: string;
|
||||
}
|
||||
|
||||
/** Response of `POST /payments/initiate` and shape of intent lookups. */
|
||||
export type PaymentIntentSnapshot ={
|
||||
intentId: string;
|
||||
service: PaymentService;
|
||||
referenceType: PaymentReferenceType;
|
||||
referenceId: string;
|
||||
merchantOrderId: string;
|
||||
provider: ProviderMethod;
|
||||
status: ProviderPaymentStatus;
|
||||
amountMinor: number;
|
||||
currency: string;
|
||||
clientAction?: ClientAction;
|
||||
providerTxnId?: string;
|
||||
paidAt?: string;
|
||||
failureCode?: string;
|
||||
failureMessage?: string;
|
||||
expiresAt?: string;
|
||||
}
|
||||
|
||||
export type PaymentEventType = "payment.succeeded" | "payment.failed";
|
||||
|
||||
/** Versioned envelope delivered (at-least-once) to the owning app's mark-paid consumer. */
|
||||
interface PaymentEventBase {
|
||||
version: 1;
|
||||
/** Outbox row id — stable across redeliveries; consumers may use it as a dedupe key. */
|
||||
eventId: string;
|
||||
eventType: PaymentEventType;
|
||||
occurredAt: string;
|
||||
service: PaymentService;
|
||||
intentId: string;
|
||||
referenceType: PaymentReferenceType;
|
||||
referenceId: string;
|
||||
merchantOrderId: string;
|
||||
provider: ProviderMethod;
|
||||
amountMinor: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface PaymentSucceededEvent extends PaymentEventBase {
|
||||
eventType: "payment.succeeded";
|
||||
providerTxnId?: string;
|
||||
paidAt: string;
|
||||
}
|
||||
|
||||
export interface PaymentFailedEvent extends PaymentEventBase {
|
||||
eventType: "payment.failed";
|
||||
failureCode?: string;
|
||||
failureMessage?: string;
|
||||
}
|
||||
|
||||
export type PaymentEvent = PaymentSucceededEvent | PaymentFailedEvent;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import type { BaseEntity } from "../common";
|
||||
|
||||
export * from "./file_upload_settings";
|
||||
export * from "./dropdown_settings";
|
||||
export * from "./file_upload_settings";
|
||||
export * from "./overview";
|
||||
|
||||
export enum TradeDirection {
|
||||
IMPORT = 'IMPORT',
|
||||
EXPORT = 'EXPORT',
|
||||
BOTH = 'BOTH',
|
||||
IMPORT = "IMPORT",
|
||||
EXPORT = "EXPORT",
|
||||
BOTH = "BOTH",
|
||||
}
|
||||
|
||||
export enum PriorityType {
|
||||
USD_PAYER = 'USD_PAYER',
|
||||
RAIL_AND_FORWARDING = 'RAIL_AND_FORWARDING',
|
||||
GOVERNMENT_ACCOUNT = 'GOVERNMENT_ACCOUNT',
|
||||
HIGH_VOLUME_SHIPMENT = 'HIGH_VOLUME_SHIPMENT',
|
||||
USD_PAYER = "USD_PAYER",
|
||||
RAIL_AND_FORWARDING = "RAIL_AND_FORWARDING",
|
||||
GOVERNMENT_ACCOUNT = "GOVERNMENT_ACCOUNT",
|
||||
HIGH_VOLUME_SHIPMENT = "HIGH_VOLUME_SHIPMENT",
|
||||
}
|
||||
|
||||
/** Bonus applied to government bookings so they outrank commercial priority. */
|
||||
@@ -22,23 +22,23 @@ export const GOVERNMENT_PRIORITY_BONUS = 50_000;
|
||||
|
||||
export interface GovernmentBookingFields {
|
||||
isGovernment: boolean;
|
||||
governmentInstitution?: string | null;
|
||||
governmentInstitution?: string | null;
|
||||
}
|
||||
|
||||
export enum ExceededAction {
|
||||
WARNING_ONLY = 'WARNING_ONLY',
|
||||
HARD_BLOCK = 'HARD_BLOCK',
|
||||
WARNING_ONLY = "WARNING_ONLY",
|
||||
HARD_BLOCK = "HARD_BLOCK",
|
||||
}
|
||||
|
||||
export enum CalculationMethod {
|
||||
PER_TON = 'PER_TON',
|
||||
FLAT_FEE = 'FLAT_FEE',
|
||||
PERCENTAGE = 'PERCENTAGE',
|
||||
PER_TON = "PER_TON",
|
||||
FLAT_FEE = "FLAT_FEE",
|
||||
PERCENTAGE = "PERCENTAGE",
|
||||
}
|
||||
|
||||
export enum FreightType {
|
||||
Container = 'CONTAINER',
|
||||
Bulk = 'BULK',
|
||||
Container = "CONTAINER",
|
||||
Bulk = "BULK",
|
||||
}
|
||||
|
||||
export enum BookingStatus {
|
||||
@@ -53,6 +53,12 @@ export enum BookingStatus {
|
||||
FullyExecuted = "FULLY_EXECUTED",
|
||||
PnrGenerated = "PNR_GENERATED",
|
||||
PaymentVerificationInProgress = "PAYMENT_VERIFICATION_IN_PROGRESS",
|
||||
/** Selected in a batch and notified to pay within the pay window. */
|
||||
SelectedForBatch = "SELECTED_FOR_BATCH",
|
||||
/** @deprecated Use SelectedForBatch */
|
||||
AwaitingPayment = "SELECTED_FOR_BATCH",
|
||||
/** Missed the 1h pay window — recoverable via move/cancel (no re-approval). */
|
||||
Expired = "EXPIRED",
|
||||
Paid = "PAID",
|
||||
InTransit = "IN_TRANSIT",
|
||||
Completed = "COMPLETED",
|
||||
@@ -113,6 +119,13 @@ export enum TrainScheduleStatus {
|
||||
Cancelled = "CANCELLED",
|
||||
}
|
||||
|
||||
/** Whether a schedule is still accepting / holding bookings (orthogonal to its operational status). */
|
||||
export enum ScheduleBookingWindow {
|
||||
Open = "OPEN",
|
||||
Full = "FULL",
|
||||
Closed = "CLOSED",
|
||||
}
|
||||
|
||||
export enum AllocationLoadType {
|
||||
Container = "CONTAINER",
|
||||
Bulk = "BULK",
|
||||
@@ -148,6 +161,22 @@ export enum WagonReadiness {
|
||||
|
||||
export type ScheduleTradeDirection = "IMPORT" | "EXPORT" | "DOMESTIC";
|
||||
|
||||
export enum TrainCheckpointKind {
|
||||
Departed = "DEPARTED",
|
||||
Passed = "PASSED",
|
||||
Arrived = "ARRIVED",
|
||||
}
|
||||
|
||||
export interface ITrainCheckpointEvent extends BaseEntity {
|
||||
trainScheduleId: string;
|
||||
yardId: string;
|
||||
sequenceNo: number;
|
||||
kind: TrainCheckpointKind;
|
||||
occurredAt: string;
|
||||
note?: string | null;
|
||||
recordedByUserId?: string | null;
|
||||
}
|
||||
|
||||
export enum BulkPricingUnit {
|
||||
PerWagon = "PER_WAGON",
|
||||
PerTon = "PER_TON",
|
||||
@@ -255,6 +284,9 @@ export interface IBooking extends BaseEntity {
|
||||
totalAmount: number;
|
||||
paymentStatus: PaymentStatus;
|
||||
|
||||
shippingLineId?: string | null;
|
||||
serviceTypeId: string;
|
||||
|
||||
contractType: "NEW" | "RENEWAL";
|
||||
previousContractId?: string | null;
|
||||
serviceType: "RAIL_ONLY" | "RAIL_AND_FORWARDING";
|
||||
@@ -284,6 +316,11 @@ export interface IBooking extends BaseEntity {
|
||||
endDate?: string | null;
|
||||
financialTerms?: string | null;
|
||||
|
||||
/** When the batch engine picked this booking and opened the pay window. */
|
||||
selectedForBatchAt?: string | null;
|
||||
/** End of the pay window once the booking is SELECTED_FOR_BATCH. */
|
||||
paymentDeadline?: string | null;
|
||||
|
||||
containers?: Array<{ type: string; qty: number; vgm: number }> | null;
|
||||
|
||||
versionNumber: number;
|
||||
@@ -363,6 +400,18 @@ export interface BookingReferenceService {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string;
|
||||
serviceName: string;
|
||||
description?: string | null | undefined;
|
||||
canBeBookedAlone: boolean;
|
||||
includesFirstMile: boolean;
|
||||
includesLastMile: boolean;
|
||||
includesCustoms: boolean;
|
||||
priorityBonusPoints: number;
|
||||
isActive: boolean;
|
||||
displayOrder: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt?: string | null | undefined;
|
||||
}
|
||||
|
||||
export interface BookingReferenceShippingLine {
|
||||
@@ -393,7 +442,40 @@ export interface BookingReferenceData {
|
||||
cargo_type: BookingReferenceCargoTypeGroup[];
|
||||
}
|
||||
|
||||
// ── DTOs ───────────────────────────────────────────────────────────────────────
|
||||
// ── Train Scheduling (bookable schedules) ──────────────────────────────────────
|
||||
|
||||
export interface BookableSchedulesQuery {
|
||||
originYardId?: string;
|
||||
destinationYardId?: string;
|
||||
}
|
||||
|
||||
export interface BookableScheduleLocomotive {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string | null;
|
||||
readiness: string | null;
|
||||
}
|
||||
|
||||
export interface BookableScheduleItem {
|
||||
id: string;
|
||||
scheduleDate: string;
|
||||
trainNumber: string | null;
|
||||
routeName: string | null;
|
||||
origin: string | null;
|
||||
destination: string | null;
|
||||
locomotive: BookableScheduleLocomotive | null;
|
||||
wagonCount: number;
|
||||
totalWeightTons: number;
|
||||
totalLengthMeters: number;
|
||||
bookingsCount: number;
|
||||
freightType: FreightType | "MIXED" | null;
|
||||
status: TrainScheduleStatus;
|
||||
bookingWindowStatus: ScheduleBookingWindow;
|
||||
maxWagons: number;
|
||||
remainingWagons: number;
|
||||
}
|
||||
|
||||
// ── DTOs ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface CreateBookingContainerDto {
|
||||
containerTypeId: string;
|
||||
@@ -402,31 +484,34 @@ export interface CreateBookingContainerDto {
|
||||
}
|
||||
|
||||
export interface CreateBookingDto {
|
||||
reference?: string;
|
||||
customerId?: string;
|
||||
companyId?: string;
|
||||
trainId?: string;
|
||||
freightShapeValidation?: boolean | undefined;
|
||||
reference?: string | undefined;
|
||||
isGovernment?: boolean | undefined;
|
||||
governmentInstitution?: string | undefined;
|
||||
companyId?: string | undefined;
|
||||
trainId?: string | undefined;
|
||||
trainScheduleId?: string | undefined;
|
||||
scheduledDate: string;
|
||||
contractType: "NEW" | "RENEWAL";
|
||||
previousContractId?: string;
|
||||
contractType: string;
|
||||
previousContractId?: string | undefined;
|
||||
serviceTypeId: string;
|
||||
firstMilePickupAddress?: string;
|
||||
lastMileDeliveryAddress?: string;
|
||||
equipmentReturn: "WITH_RETURN" | "WITHOUT_RETURN" | "NA";
|
||||
firstMilePickupAddress?: string | undefined;
|
||||
lastMileDeliveryAddress?: string | undefined;
|
||||
equipmentReturn: string;
|
||||
originYardId: string;
|
||||
destinationYardId: string;
|
||||
tradeDirection: "IMPORT" | "EXPORT" | "DOMESTIC";
|
||||
freightType: FreightType;
|
||||
cargoTypeId?: string;
|
||||
cargoFreeText?: string;
|
||||
shippingLineId?: string;
|
||||
tradeDirection: string;
|
||||
freightType: string;
|
||||
cargoTypeId?: string | undefined;
|
||||
cargoFreeText?: string | undefined;
|
||||
shippingLineId?: string | undefined;
|
||||
cargoTotalWeightVgm: number;
|
||||
isHazardous?: boolean;
|
||||
paymentCurrency: "ETB" | "USD";
|
||||
pnrCode?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
financialTerms?: string;
|
||||
isHazardous?: boolean | undefined;
|
||||
paymentCurrency: string;
|
||||
pnrCode?: string | undefined;
|
||||
startDate?: string | undefined;
|
||||
endDate?: string | undefined;
|
||||
financialTerms?: string | undefined;
|
||||
containers?: CreateBookingContainerDto[];
|
||||
allowConsolidation?: boolean;
|
||||
}
|
||||
|
||||
@@ -2,3 +2,5 @@ export * from "./common/index";
|
||||
export * from "./freight/index";
|
||||
export * as Freight from "./freight/index";
|
||||
export * as Passenger from "./passenger/index";
|
||||
export type { PaymentEvent, PaymentEventType, PaymentFailedEvent, PaymentSucceededEvent, PaymentIntentSnapshot, InitiatePaymentRequest } from "./common/payments";
|
||||
export { PaymentReferenceType, PaymentService } from "./common/payments";
|
||||
|
||||
Reference in New Issue
Block a user