[200~feat: add CustomsPaymentsCard and PaymentsTab components for handling customs payments and payment summaries

This commit is contained in:
Marshal
2026-08-20 15:45:42 +00:00
parent 8d7551bb8e
commit 9e1d5ee9f2
58 changed files with 3324 additions and 810 deletions

View File

@@ -40,8 +40,12 @@ import {
import type { Response } from "express";
import { BookingClearanceChargeService } from './booking-clearance-charge.service';
import { BookingPayablesService } from './booking-payables.service';
import { ClearanceEventService } from './clearance-event.service';
import { BillClearanceChargeDto } from './dto/clearance-charge.dto';
import {
BillClearanceChargeDto,
RejectClearanceChargeDto,
} from './dto/clearance-charge.dto';
import { BookingContractService } from './booking-contract.service';
import { BookingPricingService } from './booking-pricing.service';
import { BookingTransitionService } from './booking-transition.service';
@@ -175,6 +179,7 @@ export class BookingsController {
private readonly wagonCancellationService: BookingWagonCancellationService,
private readonly consolidationApprovalService: ConsolidationApprovalService,
private readonly clearanceChargeService: BookingClearanceChargeService,
private readonly bookingPayablesService: BookingPayablesService,
private readonly clearanceEventService: ClearanceEventService,
) {}
@@ -312,6 +317,21 @@ export class BookingsController {
return this.bookingsService.getListSummary(filter);
}
@Get("my-payables")
@PortalCustomer()
@ApiOperation({
summary:
"Outstanding customer payments per booking — invoices to pay, prices to accept, duty slips to upload",
})
async findMyPayables(@CurrentUser() user: AuthUserPayload) {
const companyId = await this.bookingsService.resolveCustomerCompanyId(
resolveAuthUserId(user),
);
return companyId
? this.bookingPayablesService.summarizeForCompany(companyId)
: [];
}
@Get("my")
@PortalCustomer()
@ApiOperation({
@@ -1118,15 +1138,63 @@ export class BookingsController {
// ── Clearance charges (post-finalization customer billing) ────────────────
@Get(":id/clearance/charges")
@BookingStaff([
@MixedAudience([
FREIGHT_PERMS.contracts.clearanceEtActions,
FREIGHT_PERMS.contracts.clearanceDjActions,
])
@ApiOperation({
summary: "Clearance charges billed to the customer (port + miscellaneous)",
summary:
"Clearance charges billed to the customer (port + miscellaneous); customers see only the charges sent to them",
})
getClearanceCharges(@Param("id", ParseUUIDPipe) id: string) {
return this.clearanceChargeService.list(id);
async getClearanceCharges(
@Param("id", ParseUUIDPipe) id: string,
@CurrentUser() user: TCurrentUser,
) {
const isStaff =
hasFreightPermission(user, FREIGHT_PERMS.contracts.clearanceEtActions) ||
hasFreightPermission(user, FREIGHT_PERMS.contracts.clearanceDjActions);
if (isStaff) return this.clearanceChargeService.list(id);
const booking = await this.bookingsService.findById(id);
await this.bookingsService.assertCustomerCanAccessBooking(user?.id, booking);
return this.clearanceChargeService.listForCustomer(id);
}
@Post(":id/clearance/charges/:chargeId/accept")
@PortalCustomer()
@ApiOperation({
summary:
"Customer accepts a proposed clearance charge — issues the payable invoice and locks the charge",
})
acceptClearanceCharge(
@Param("id", ParseUUIDPipe) id: string,
@Param("chargeId", ParseUUIDPipe) chargeId: string,
@CurrentUser() user: AuthUserPayload,
) {
return this.clearanceChargeService.customerAccept(
id,
chargeId,
resolveAuthUserId(user),
);
}
@Post(":id/clearance/charges/:chargeId/reject")
@PortalCustomer()
@ApiOperation({
summary:
"Customer rejects a proposed clearance charge with a reason — GL Ethiopia revises and re-sends",
})
rejectClearanceCharge(
@Param("id", ParseUUIDPipe) id: string,
@Param("chargeId", ParseUUIDPipe) chargeId: string,
@Body() dto: RejectClearanceChargeDto,
@CurrentUser() user: AuthUserPayload,
) {
return this.clearanceChargeService.customerReject(
id,
chargeId,
dto.note,
resolveAuthUserId(user),
);
}
@Post(":id/clearance/charges/port-document")
@@ -1153,7 +1221,7 @@ export class BookingsController {
@BookingStaff(FREIGHT_PERMS.contracts.clearanceEtActions)
@ApiOperation({
summary:
"GL Ethiopia sets or revises the charge's amount + currency (revising a sent charge cancels its unpaid invoice)",
"GL Ethiopia sets or revises the charge's amount, currency and description (locked once the customer accepts)",
})
billClearanceCharge(
@Param("id", ParseUUIDPipe) id: string,
@@ -1173,7 +1241,7 @@ export class BookingsController {
@BookingStaff(FREIGHT_PERMS.contracts.clearanceEtActions)
@ApiOperation({
summary:
"GL Ethiopia issues the charge's payable invoice to the customer (ETB pays via gateway, other currencies via manual settlement)",
"GL Ethiopia sends the priced charge to the customer for approval (the invoice is issued when they accept)",
})
sendClearanceCharge(
@Param("id", ParseUUIDPipe) id: string,
@@ -1193,7 +1261,7 @@ export class BookingsController {
@ApiConsumes("multipart/form-data")
@ApiOperation({
summary:
"GL Ethiopia creates the miscellaneous charge (document + amount + currency); unlocked once the port charge is paid",
"GL Ethiopia creates a miscellaneous charge (document + amount + currency + description) as a draft to send",
})
createMiscellaneousCharge(
@Param("id", ParseUUIDPipe) id: string,