diff --git a/apps/edr-freight-api/src/modules/first-mile/first-mile.controller.ts b/apps/edr-freight-api/src/modules/first-mile/first-mile.controller.ts index 928882a7f..49175a6f3 100644 --- a/apps/edr-freight-api/src/modules/first-mile/first-mile.controller.ts +++ b/apps/edr-freight-api/src/modules/first-mile/first-mile.controller.ts @@ -1,4 +1,5 @@ import { + BadRequestException, Body, Controller, Delete, @@ -93,7 +94,13 @@ export class FirstMileController { @ApiOperation({ summary: 'Generate the first-mile delivery-fee invoice' }) async generateInvoice(@Param('id', ParseUUIDPipe) id: string) { const record = await this.firstMileService.findById(id); - return this.firstMileInvoiceService.ensureInvoiceFor(record); + const invoice = await this.firstMileInvoiceService.ensureInvoiceFor(record); + if (!invoice) { + throw new BadRequestException( + 'Cannot generate invoice: the leg has no billable amount. Add distance and ensure a FIRST_MILE rate is configured, and the booking has a company.', + ); + } + return invoice; } @Delete(':id') diff --git a/apps/edr-freight-api/src/modules/last-mile/last-mile.controller.ts b/apps/edr-freight-api/src/modules/last-mile/last-mile.controller.ts index 0b2ec1dbe..2931a1f85 100644 --- a/apps/edr-freight-api/src/modules/last-mile/last-mile.controller.ts +++ b/apps/edr-freight-api/src/modules/last-mile/last-mile.controller.ts @@ -1,4 +1,5 @@ import { + BadRequestException, Body, Controller, Delete, @@ -118,6 +119,12 @@ export class LastMileController { @ApiOperation({ summary: 'Generate the delivery-fee invoice for a last-mile leg' }) async generateInvoice(@Param('id', ParseUUIDPipe) id: string) { const record = await this.lastMileService.findById(id); - return this.lastMileInvoiceService.ensureInvoiceFor(record); + const invoice = await this.lastMileInvoiceService.ensureInvoiceFor(record); + if (!invoice) { + throw new BadRequestException( + 'Cannot generate invoice: the leg has no billable amount. Add distance and ensure a LAST_MILE rate is configured, and the booking has a company.', + ); + } + return invoice; } } diff --git a/apps/edr-freight-api/src/seed/pricing-data.seeder.ts b/apps/edr-freight-api/src/seed/pricing-data.seeder.ts index 14ccb3efb..aa3f2c9bb 100644 --- a/apps/edr-freight-api/src/seed/pricing-data.seeder.ts +++ b/apps/edr-freight-api/src/seed/pricing-data.seeder.ts @@ -415,6 +415,9 @@ private async seedWeightLimits(wlRepo: any, ctRepo: any): Promise { { appliesTo: "OTHER", trigger: "REEFER", rateType: "REEFER_SURCHARGE", rateValue: 2, rateUnit: "PER_TON" }, { appliesTo: "OTHER", trigger: "SHIPPING_LINE", rateType: "DOUBLE_HANDLING", rateValue: 100, rateUnit: "PER_CONTAINER" }, { appliesTo: "OTHER", trigger: "CONSOLIDATION", rateType: "LASHING", rateValue: 50, rateUnit: "PER_CONTAINER" }, + // ── First/last-mile road haulage (per km) — drives the mile invoices ── + { appliesTo: "OTHER", trigger: "ALWAYS", rateType: "FIRST_MILE", rateValue: 20, rateUnit: "PER_KM" }, + { appliesTo: "OTHER", trigger: "ALWAYS", rateType: "LAST_MILE", rateValue: 25, rateUnit: "PER_KM" }, ]; // Idempotent: insert each canonical rate only if no row with the same