feat(WIP): Wire up the invoice with the booking.

This commit is contained in:
Nathnael
2026-06-29 08:29:07 +00:00
parent 36711eace4
commit 9cd24d9b51
8 changed files with 208 additions and 2 deletions

View File

@@ -12,6 +12,7 @@ import {
} from "@edr/types";
import { ServiceAuthGuard } from "../../common/guards/service-auth.guard";
import { BillingModule } from "../billing/billing.module";
import { DropdownSettingsModule } from "../dropdown-settings/dropdown-settings.module";
import { FirstMileModule } from "../first-mile/first-mile.module";
import { TrainSchedulingModule } from "../train-scheduling/train-scheduling.module";
@@ -59,6 +60,7 @@ function rabbitMQImport(): DynamicModule[] {
HttpModule.register({ timeout: 10_000 }),
ConfigModule,
DropdownSettingsModule,
BillingModule,
forwardRef(() => FirstMileModule),
forwardRef(() => TrainSchedulingModule),
TypeOrmModule.forFeature([

View File

@@ -22,11 +22,13 @@ import {
ProviderPaymentStatus,
} from "@edr/payment-providers";
import {
Freight,
PaymentService as PaymentServiceEnum,
PaymentReferenceType,
PaymentIntentSnapshot,
ProviderMethod,
} from "@edr/types";
import { BillingService } from "../billing/billing.service";
import {
InitiatePaymentDto,
InitiateResponseDto,
@@ -62,6 +64,7 @@ export class PaymentService {
private readonly bookingBatchService: BookingBatchService,
private readonly dropdownSettings: DropdownSettingsService,
private readonly firstMileService: FirstMileService,
private readonly billing: BillingService,
) { }
/** Configured general-contract ordering window in months (defaults to 3). */
@@ -167,7 +170,16 @@ export class PaymentService {
.findOneBy({ id: dto.bookingId });
if (!booking) throw new NotFoundException("Booking not found");
const amountMinor = Math.round(Number(booking.totalAmount));
// Charge the invoice (the billing document of record) so discounts,
// penalties and staff adjustments carried on it are honored. Fall back to
// the booking total only when no invoice has been generated yet.
const invoice = await this.billing.findPayable(
Freight.InvoiceSource.Booking,
booking.id,
);
const amountMinor = Math.round(
Number(invoice?.totalAmount ?? booking.totalAmount),
);
const snapshot = await this.paymentClient.initiate({
service: PaymentServiceEnum.FREIGHT,
@@ -344,6 +356,17 @@ export class PaymentService {
? { paymentStatus: "PAID", status: "CONTRACT_ACTIVE", expiresAt: contractExpiresAt }
: { paymentStatus: "PAID", status: "PAID" },
);
// Settle the booking's open invoice in the same transaction and link
// this payment. The invoice emits `booking.invoice.paid` for the source
// to react to. No-op if the booking has no open invoice.
await this.billing.settlePayable(
Freight.InvoiceSource.Booking,
input.bookingId,
intent.id,
mg,
);
await this.firstMileService.acceptBooking(input.bookingId);
});