From 4ddf03e3572555bc75d7fed49b13e25450fa11eb Mon Sep 17 00:00:00 2001 From: natib21 Date: Fri, 3 Jul 2026 20:43:26 +0000 Subject: [PATCH] mile --- .../src/modules/billing/billing.service.ts | 10 +++++ .../modules/last-mile/last-mile.service.ts | 26 +++++++++++- .../src/pages/operations/LastMilePage.tsx | 42 +++++++------------ .../src/services/last-mile.service.ts | 2 + 4 files changed, 53 insertions(+), 27 deletions(-) diff --git a/apps/edr-freight-api/src/modules/billing/billing.service.ts b/apps/edr-freight-api/src/modules/billing/billing.service.ts index 536129122..fdd230d33 100644 --- a/apps/edr-freight-api/src/modules/billing/billing.service.ts +++ b/apps/edr-freight-api/src/modules/billing/billing.service.ts @@ -307,6 +307,16 @@ export class BillingService { }); } + /** Invoices for a batch of source records (e.g. many last-mile legs), so a + * list can show which records already have an invoice without N+1 queries. */ + findBySourceIds(source: string, sourceIds: string[]): Promise { + if (!sourceIds.length) return Promise.resolve([]); + return this.invoices.findAll({ + where: { source, sourceId: In(sourceIds) }, + order: { createdAt: "DESC" }, + }); + } + /** Invoices for the signed-in customer; empty when they have no company. */ async findForUser( userId: string, diff --git a/apps/edr-freight-api/src/modules/last-mile/last-mile.service.ts b/apps/edr-freight-api/src/modules/last-mile/last-mile.service.ts index fdc8a4c48..b5341b0a1 100644 --- a/apps/edr-freight-api/src/modules/last-mile/last-mile.service.ts +++ b/apps/edr-freight-api/src/modules/last-mile/last-mile.service.ts @@ -12,7 +12,7 @@ import { LastMile, LastMileStatus } from './entities/last-mile.entity'; import { LastMileContainerAllocation } from './entities/last-mile-container-allocation.entity'; import { LastMileVehicleAssignment } from './entities/last-mile-vehicle-assignment.entity'; import { LastMileRepository } from './last-mile.repository'; -import { InvoiceEventPayload } from '../billing/billing.service'; +import { BillingService, InvoiceEventPayload } from '../billing/billing.service'; import { OnEvent } from '@nestjs/event-emitter'; import { FleetHistoryService } from '../fleet-history/fleet-history.service'; import { FleetEventType } from '../fleet-history/entities/fleet-event.entity'; @@ -46,8 +46,28 @@ export class LastMileService { private readonly smsClient: SmsClientService, private readonly dataSource: DataSource, private readonly history: FleetHistoryService, + private readonly billing: BillingService, ) {} + /** Attach real invoice info (number/status) to records so the UI can show an + * invoice link only when one actually exists — NOT merely because distance + * was entered. Batched to avoid N+1. */ + private async attachInvoices(records: LastMile[]): Promise { + const invoices = await this.billing.findBySourceIds( + 'last_mile', + records.map((r) => r.id), + ); + const byId = new Map(); + for (const inv of invoices) { + if (!byId.has(inv.sourceId)) { + byId.set(inv.sourceId, { number: inv.invoiceNumber, status: String(inv.status) }); + } + } + for (const r of records) { + (r as LastMile & { invoice?: unknown }).invoice = byId.get(r.id) ?? null; + } + } + /** Resolve a vehicle's driver + human labels, for stamping mile events onto * the driver's timeline and naming the vehicle. Best-effort — never throws. */ private async vehicleInfo( @@ -159,6 +179,8 @@ export class LastMileService { take: pageSize, }); + await this.attachInvoices(data); + return { data, meta: { @@ -183,6 +205,8 @@ export class LastMileService { throw new NotFoundException(`Last-mile record ${id} not found`); } + await this.attachInvoices([record]); + return record; } diff --git a/apps/edr-freight-web/backoffice/src/pages/operations/LastMilePage.tsx b/apps/edr-freight-web/backoffice/src/pages/operations/LastMilePage.tsx index 21b57f0d5..3ba71148e 100644 --- a/apps/edr-freight-web/backoffice/src/pages/operations/LastMilePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/operations/LastMilePage.tsx @@ -1086,35 +1086,25 @@ const LastMilePage = () => { header: "Invoice", meta: { headerClassName, cellClassName }, cell: ({ row }) => { - const hasDistance = row.original.exactKm != null && row.original.exactKm > 0; - const isPaid = (row.original as any).paid; - if (!hasDistance) { + // Only show an invoice once it's actually been generated — NOT merely + // because distance was entered. + const invoice = row.original.invoice; + if (!invoice) { return ; } - if (isPaid) { - return ( - - openInvoice(row.original)} - c="blue" - fw={500} - style={{ textDecoration: "underline", cursor: "pointer" }} - > - #345 - - Paid - - ); - } + const isPaid = (row.original as any).paid || invoice.status === "Paid"; return ( - openInvoice(row.original)} - c="blue" - fw={500} - style={{ textDecoration: "underline", cursor: "pointer" }} - > - #345 - + + openInvoice(row.original)} + c="blue" + fw={500} + style={{ textDecoration: "underline", cursor: "pointer" }} + > + {invoice.number} + + {isPaid && Paid} + ); }, }, diff --git a/apps/edr-freight-web/backoffice/src/services/last-mile.service.ts b/apps/edr-freight-web/backoffice/src/services/last-mile.service.ts index 9eb6d03a5..d800c2331 100644 --- a/apps/edr-freight-web/backoffice/src/services/last-mile.service.ts +++ b/apps/edr-freight-web/backoffice/src/services/last-mile.service.ts @@ -64,6 +64,8 @@ export interface LastMileRecord { distanceKm?: number | null; vehicle?: LastMileVehicle | null; }>; + /** Present only when an invoice has actually been generated (not on distance). */ + invoice?: { number: string; status: string } | null; createdAt: string; updatedAt: string; }