This commit is contained in:
natib21
2026-07-06 13:49:30 +00:00
parent 458198be11
commit 42710261e2
14 changed files with 517 additions and 78 deletions

View File

@@ -1,4 +1,4 @@
import { Injectable, Logger } from '@nestjs/common';
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { Freight } from '@edr/types';
@@ -66,13 +66,37 @@ export class FirstMileInvoiceService {
return null;
}
// Reject mixed-currency truck sets — a single invoice can only be one
// currency, and amounts across currencies can't be summed.
const billableTrucks = (record.vehicleAssignments ?? []).filter(
(a) => Number(a.distanceKm) > 0,
);
const currencies = [
...new Set(
billableTrucks
.map((a) => (a.vehicle as { currency?: string } | undefined)?.currency)
.filter((c): c is string => Boolean(c)),
),
];
if (currencies.length > 1) {
throw new BadRequestException(
`Cannot generate invoice: assigned trucks use mixed currencies (${currencies.join(', ')}). Assign trucks that share one currency.`,
);
}
// Currency follows the truck (price/km is quoted per vehicle), falling back
// to the booking's currency, then ETB.
const truckCurrency =
(record.vehicle as { currency?: string } | undefined)?.currency ||
(record.vehicleAssignments?.[0]?.vehicle as { currency?: string } | undefined)?.currency;
return this.billing.generateInvoice({
source: 'first_mile' as Freight.InvoiceSource,
sourceId: record.id,
type: 'DELIVERY_FEE',
companyId: fm.booking!.companyId,
companyProfileId: fm.booking!.companyProfileId || '',
currency: fm.booking!.paymentCurrency || 'ETB',
currency: truckCurrency || fm.booking!.paymentCurrency || 'ETB',
lines: [
{
chargeType: 'DELIVERY',

View File

@@ -640,10 +640,24 @@ export class FirstMileService {
{ distanceKm: d.distanceKm },
);
}
const total = distances.reduce((s, d) => s + (Number(d.distanceKm) || 0), 0);
// Billing is per truck: amount = Σ (truck distance × truck price/km). The
// per-vehicle rate + currency live on the vehicle, so we ignore the legacy
// FIRST_MILE flat rate and any client-sent amount. `remainingPayment` param
// kept only for signature back-compat.
void remainingPayment;
const assignments = await this.dataSource.manager.find(FirstMileVehicleAssignment, {
where: { firstMileId: id },
relations: { vehicle: true },
});
const total = assignments.reduce((s, a) => s + (Number(a.distanceKm) || 0), 0);
const amount = assignments.reduce(
(s, a) => s + (Number(a.distanceKm) || 0) * (Number(a.vehicle?.pricePerKm) || 0),
0,
);
await this.firstMileRepository.update(id, {
exactKm: total,
...(remainingPayment != null ? { remainingPayment } : {}),
remainingPayment: amount,
} as any);
return this.findById(id);
}