mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 04:08:11 +00:00
merge
This commit is contained in:
@@ -279,7 +279,9 @@ export class BookingPricingService {
|
||||
|
||||
return {
|
||||
lineItems,
|
||||
totalAmount: total,
|
||||
// Grand total is billed in whole currency units — fractional line sums
|
||||
// (rate × tons can yield e.g. 260519.2) round to the nearest whole birr/USD.
|
||||
totalAmount: Math.round(total),
|
||||
currency: booking.paymentCurrency,
|
||||
usedRates: [...usedRatesMap.values()],
|
||||
appliedModifiers: ruleResult.appliedModifiers,
|
||||
|
||||
@@ -15,15 +15,15 @@ import {
|
||||
UnauthorizedException,
|
||||
UploadedFile,
|
||||
UploadedFiles,
|
||||
UseGuards,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { CurrentUser } from '@edr/api-common';
|
||||
import type { TCurrentUser } from '@tria-plc/api-common/modules/auth/types/current-user.type';
|
||||
import { JwtGuard } from '@tria-plc/api-common/modules/auth/services/jwt.guard';
|
||||
import {
|
||||
BookingStaff,
|
||||
BookingView,
|
||||
MixedAudience,
|
||||
PortalCustomer,
|
||||
WagonCancellationView,
|
||||
} from '../../common/booking-guards';
|
||||
import { FREIGHT_PERMS } from '../../seed/freight-permissions.registry';
|
||||
@@ -167,6 +167,7 @@ export class BookingsController {
|
||||
) {}
|
||||
|
||||
@Post()
|
||||
@MixedAudience([])
|
||||
@UseInterceptors(AnyFilesInterceptor())
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiOperation({ summary: "Create a new freight booking (DRAFT)" })
|
||||
@@ -207,6 +208,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Patch(":id")
|
||||
@MixedAudience([])
|
||||
@UseInterceptors(AnyFilesInterceptor())
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiOperation({
|
||||
@@ -223,6 +225,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get()
|
||||
@MixedAudience(FREIGHT_PERMS.bookings.view)
|
||||
@ApiOperation({ summary: "List freight bookings (paginated)" })
|
||||
async findAll(
|
||||
@Query() filter: FilterBookingDto,
|
||||
@@ -298,6 +301,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get("my")
|
||||
@PortalCustomer()
|
||||
@ApiOperation({
|
||||
summary: "List the current customer's bookings ready for payment",
|
||||
description:
|
||||
@@ -328,6 +332,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get("reference-data")
|
||||
@MixedAudience([])
|
||||
@ApiOperation({ summary: "Booking form catalog" })
|
||||
@ApiOkResponse({ type: BookingReferenceDataDto })
|
||||
getReferenceData(): Promise<BookingReferenceDataDto> {
|
||||
@@ -335,6 +340,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get("by-reference/:reference")
|
||||
@MixedAudience(FREIGHT_PERMS.bookings.view)
|
||||
@ApiOperation({ summary: "Get booking by reference" })
|
||||
async findByReference(
|
||||
@Param("reference") reference: string,
|
||||
@@ -352,6 +358,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get(":id")
|
||||
@MixedAudience(FREIGHT_PERMS.bookings.view)
|
||||
@ApiOperation({ summary: "Get booking by ID" })
|
||||
async findOne(
|
||||
@Param("id", ParseUUIDPipe) id: string,
|
||||
@@ -373,6 +380,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get(':id/available-days')
|
||||
@MixedAudience([])
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Days bookable for THIS booking (cargo-aware wagon-TYPE gate; days only, no capacity counts)',
|
||||
@@ -395,6 +403,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get(':id/day-availability')
|
||||
@MixedAudience([])
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Advisory free-wagon count for a shipment day (planning hint, not enforced). ' +
|
||||
@@ -420,6 +429,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get(':id/mile-summary')
|
||||
@MixedAudience(FREIGHT_PERMS.bookings.view)
|
||||
@ApiOperation({
|
||||
summary: 'First/last-mile operational summary for a booking (customer-safe)',
|
||||
})
|
||||
@@ -447,6 +457,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(':id/customer-truck-assignment')
|
||||
@PortalCustomer()
|
||||
@ApiOperation({ summary: 'Customer assigns external truck and driver for terminal pickup' })
|
||||
async assignCustomerTruck(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@@ -462,6 +473,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get(':id/customer-truck-assignment/freight-order')
|
||||
@MixedAudience(FREIGHT_PERMS.bookings.view)
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Download freight order copies. The 2 gate copies always print; ?copies=1,2,8 adds waybill-style copies (catalog indexes 1-8).',
|
||||
@@ -488,6 +500,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get(':id/carriage-acceptance-sheet')
|
||||
@MixedAudience(FREIGHT_PERMS.bookings.view)
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Download the carriage acceptance sheet (one per booking, lists every allocated wagon)',
|
||||
@@ -652,6 +665,11 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get(':id/customer-trucks')
|
||||
@MixedAudience([
|
||||
FREIGHT_PERMS.bookings.view,
|
||||
FREIGHT_PERMS.bookings.operations,
|
||||
FREIGHT_PERMS.warehouseInventory.view,
|
||||
])
|
||||
@ApiOperation({ summary: 'List customer self-haul trucks (multi-truck) for a booking' })
|
||||
async listCustomerTrucks(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@@ -665,6 +683,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(':id/customer-trucks')
|
||||
@PortalCustomer()
|
||||
@ApiOperation({ summary: 'Add a customer self-haul truck carrying 1–2 of the booking containers' })
|
||||
async addCustomerTruck(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@@ -679,6 +698,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(':id/customer-trucks/bulk')
|
||||
@PortalCustomer()
|
||||
@ApiOperation({ summary: 'Bulk add customer trucks from array payload (Excel parsed)' })
|
||||
async bulkAddCustomerTrucks(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@@ -693,6 +713,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Patch(':id/customer-trucks/:assignmentId')
|
||||
@PortalCustomer()
|
||||
@ApiOperation({ summary: 'Edit a not-yet-arrived customer truck (plate/driver/type + containers)' })
|
||||
async updateCustomerTruck(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@@ -708,6 +729,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Delete(':id/customer-trucks/:assignmentId')
|
||||
@PortalCustomer()
|
||||
@ApiOperation({ summary: 'Remove a not-yet-arrived customer truck from a booking' })
|
||||
async removeCustomerTruck(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@@ -722,6 +744,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get(':id/customer-trucks/loadable-containers')
|
||||
@MixedAudience(FREIGHT_PERMS.bookings.view)
|
||||
@ApiOperation({ summary: 'Booking containers not yet loaded onto a truck' })
|
||||
async loadableContainers(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@@ -735,6 +758,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(':id/customer-trucks/:assignmentId/load')
|
||||
@BookingStaff(FREIGHT_PERMS.bookings.operations)
|
||||
@ApiOperation({ summary: 'Truck_dispatch: load selected containers onto a truck (staff)' })
|
||||
async loadCustomerTruck(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@@ -749,6 +773,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(':id/customer-trucks/:assignmentId/depart')
|
||||
@BookingStaff(FREIGHT_PERMS.bookings.operations)
|
||||
@ApiOperation({
|
||||
summary: 'Register an import truck leaving: containers loaded + weighed gross (staff)',
|
||||
})
|
||||
@@ -766,6 +791,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get(':id/received-pending-grn')
|
||||
@MixedAudience(FREIGHT_PERMS.bookings.view)
|
||||
@ApiOperation({ summary: 'Containers received into port but not yet on a GRN' })
|
||||
async receivedPendingGrn(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@@ -779,6 +805,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(':id/generate-grn')
|
||||
@BookingStaff(FREIGHT_PERMS.bookings.operations)
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Generate a GRN over the received containers (all received, or a subset) — one GRN per batch',
|
||||
@@ -796,6 +823,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get(':id/tracking')
|
||||
@MixedAudience(FREIGHT_PERMS.bookings.view)
|
||||
@ApiOperation({
|
||||
summary: "Shipment tracking timeline for a booking",
|
||||
description:
|
||||
@@ -818,6 +846,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Delete(":id")
|
||||
@MixedAudience([])
|
||||
@HttpCode(204)
|
||||
@ApiOperation({ summary: "Soft-delete DRAFT booking" })
|
||||
remove(@Param("id", ParseUUIDPipe) id: string) {
|
||||
@@ -825,6 +854,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(":id/documents")
|
||||
@MixedAudience([])
|
||||
@UseInterceptors(AnyFilesInterceptor())
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiOperation({ summary: "Upload documents for a booking (DRAFT only)" })
|
||||
@@ -837,6 +867,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(":id/generate-price")
|
||||
@MixedAudience([])
|
||||
@ApiOperation({
|
||||
summary: "Generate price preview (DRAFT or CHANGES_REQUESTED)",
|
||||
description:
|
||||
@@ -848,6 +879,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(":id/submit")
|
||||
@MixedAudience([])
|
||||
@ApiOperation({
|
||||
summary: "Customer submit booking",
|
||||
description:
|
||||
@@ -859,6 +891,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(":id/confirm-submit")
|
||||
@MixedAudience([])
|
||||
@ApiOperation({
|
||||
summary: "Confirm submit after price change",
|
||||
description:
|
||||
@@ -870,6 +903,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(":id/reject")
|
||||
@PortalCustomer()
|
||||
@ApiOperation({
|
||||
summary: "Customer reject price estimate",
|
||||
description:
|
||||
@@ -900,6 +934,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get(':id/clearance')
|
||||
@MixedAudience([FREIGHT_PERMS.bookings.clearanceView, FREIGHT_PERMS.bookings.reviewDocuments])
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"Document-clearance grid (required docs + upload + GL review status)",
|
||||
@@ -909,6 +944,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(":id/clearance/documents")
|
||||
@PortalCustomer()
|
||||
@UseInterceptors(AnyFilesInterceptor())
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiOperation({
|
||||
@@ -925,7 +961,10 @@ export class BookingsController {
|
||||
return this.transitionService.enrichBookingResponse(booking);
|
||||
}
|
||||
|
||||
// Customer requests the operation; GL ET also resubmits here on the
|
||||
// customer's behalf after operations requests changes (BookingChangesRequestedAlert).
|
||||
@Post(":id/clearance/proceed")
|
||||
@MixedAudience(FREIGHT_PERMS.contracts.createBooking)
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"Customer requests operation with a schedule day " +
|
||||
@@ -944,6 +983,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get(":id/export-trains")
|
||||
@MixedAudience([])
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"Export train picker: the day's export trains on the booking's corridor " +
|
||||
@@ -1145,6 +1185,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(':id/clearance/draft-declaration/accept')
|
||||
@PortalCustomer()
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Customer accepts the draft customs declaration — unlocks the real customs declaration step for GL Ethiopia',
|
||||
@@ -1155,6 +1196,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(':id/clearance/draft-declaration/change')
|
||||
@PortalCustomer()
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Customer requests a change to the draft customs declaration with a reason — GL Ethiopia sends a corrected draft (repeatable)',
|
||||
@@ -1181,6 +1223,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(':id/clearance/duty-slip')
|
||||
@PortalCustomer()
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@ApiOperation({ summary: 'Customer uploads duty/tax payment slip on booking' })
|
||||
@@ -1332,7 +1375,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(":id/government-expedite")
|
||||
@BookingStaff(FREIGHT_PERMS.bookings.staffAccept)
|
||||
@BookingStaff(FREIGHT_PERMS.bookings.governmentExpedite)
|
||||
@ApiOperation({
|
||||
summary: "Expedite government booking to PAID / ELIGIBLE for scheduling",
|
||||
})
|
||||
@@ -1356,6 +1399,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get(":id/contract/view")
|
||||
@MixedAudience(FREIGHT_PERMS.bookings.view)
|
||||
@ApiOkResponse({ type: ContractViewDto })
|
||||
@ApiOperation({ summary: "Contract HTML view for portal and backoffice" })
|
||||
getContractView(
|
||||
@@ -1367,6 +1411,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get(":id/contract/document")
|
||||
@MixedAudience(FREIGHT_PERMS.bookings.view)
|
||||
@ApiOperation({ summary: "Download contract PDF" })
|
||||
async downloadContractDocument(
|
||||
@Param("id", ParseUUIDPipe) id: string,
|
||||
@@ -1382,6 +1427,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get(":id/contract")
|
||||
@MixedAudience(FREIGHT_PERMS.bookings.view)
|
||||
@ApiOperation({ summary: "Download contract file (alias)" })
|
||||
async downloadContract(
|
||||
@Param("id", ParseUUIDPipe) id: string,
|
||||
@@ -1391,7 +1437,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(":id/contract/sign")
|
||||
@UseGuards(JwtGuard)
|
||||
@MixedAudience(FREIGHT_PERMS.bookings.signStaff)
|
||||
@ApiOperation({ summary: "Apply digital signature (customer or staff)" })
|
||||
async signContract(
|
||||
@Param("id", ParseUUIDPipe) id: string,
|
||||
@@ -1412,18 +1458,21 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Get(":id/contract/signatures")
|
||||
@MixedAudience(FREIGHT_PERMS.bookings.view)
|
||||
@ApiOperation({ summary: "List contract signatures" })
|
||||
getContractSignatures(@Param("id", ParseUUIDPipe) id: string) {
|
||||
return this.contractService.getSignatures(id);
|
||||
}
|
||||
|
||||
@Get(":id/summary")
|
||||
@MixedAudience(FREIGHT_PERMS.bookings.view)
|
||||
@ApiOperation({ summary: "Contract summary string for dashboard" })
|
||||
getSummary(@Param("id", ParseUUIDPipe) id: string) {
|
||||
return this.contractService.getSummary(id);
|
||||
}
|
||||
|
||||
@Post(":id/customer/sign")
|
||||
@PortalCustomer()
|
||||
@ApiOperation({
|
||||
summary: "Customer digital signature (deprecated — use POST contract/sign)",
|
||||
})
|
||||
@@ -1504,6 +1553,7 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(":id/cancel-hold")
|
||||
@PortalCustomer()
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"Customer cancels an unpaid hold (SELECTED_FOR_BATCH → CANCELLED); " +
|
||||
@@ -1518,18 +1568,21 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(":id/consolidation")
|
||||
@PortalCustomer()
|
||||
@ApiOperation({ summary: "Request freight consolidation" })
|
||||
requestConsolidation(@Param("id", ParseUUIDPipe) id: string) {
|
||||
return this.bookingsService.requestConsolidation(id);
|
||||
}
|
||||
|
||||
@Delete(":id/consolidation")
|
||||
@PortalCustomer()
|
||||
@ApiOperation({ summary: "Remove consolidation pairing" })
|
||||
removeConsolidation(@Param("id", ParseUUIDPipe) id: string) {
|
||||
return this.bookingsService.removeConsolidation(id);
|
||||
}
|
||||
|
||||
@Get(":id/consolidation")
|
||||
@MixedAudience(FREIGHT_PERMS.bookings.view)
|
||||
@ApiOperation({ summary: "Get consolidation details" })
|
||||
getConsolidationDetails(@Param("id", ParseUUIDPipe) id: string) {
|
||||
return this.bookingsService.getConsolidationDetails(id);
|
||||
|
||||
@@ -25,6 +25,7 @@ import { BookingLifecycleNotifierService } from './booking-lifecycle-notifier.se
|
||||
import { BookingTransitionService } from './booking-transition.service';
|
||||
import { NotificationsModule } from '../notifications/notifications.module';
|
||||
import { NotificationInboxModule } from '../notification-inbox/notification-inbox.module';
|
||||
import { BookingAllocationController } from './booking-allocation.controller';
|
||||
import { BookingsController } from './bookings.controller';
|
||||
// import { PayController } from './pay.controller';
|
||||
import { BookingsRepository } from './bookings.repository';
|
||||
@@ -92,7 +93,7 @@ import { VehiclesModule } from "../vehicles/vehicles.module";
|
||||
SignaturesModule,
|
||||
registerExchangeModule(),
|
||||
],
|
||||
controllers: [BookingsController],
|
||||
controllers: [BookingsController, BookingAllocationController],
|
||||
providers: [
|
||||
BookingsService,
|
||||
BookingsRepository,
|
||||
|
||||
Reference in New Issue
Block a user