This commit is contained in:
natib21
2026-07-03 20:43:26 +00:00
parent f5e68f5a61
commit 4ddf03e357
4 changed files with 53 additions and 27 deletions

View File

@@ -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<Invoice[]> {
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,

View File

@@ -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<void> {
const invoices = await this.billing.findBySourceIds(
'last_mile',
records.map((r) => r.id),
);
const byId = new Map<string, { number: string; status: string }>();
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;
}