mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
Merge pull request #1130 from Tria-plc/lastmilerequest
feat(mile): auto-estimate first/last-mile km from yard GPS to custome…
This commit is contained in:
17
apps/edr-freight-api/src/common/mile-distance.util.spec.ts
Normal file
17
apps/edr-freight-api/src/common/mile-distance.util.spec.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { haversineKm } from './mile-distance.util';
|
||||
|
||||
describe('haversineKm', () => {
|
||||
it('is zero for the same point', () => {
|
||||
expect(haversineKm(8.9, 38.6, 8.9, 38.6)).toBe(0);
|
||||
});
|
||||
|
||||
it('matches one degree of longitude at the equator (~111.19 km)', () => {
|
||||
expect(haversineKm(0, 0, 0, 1)).toBeCloseTo(111.19, 1);
|
||||
});
|
||||
|
||||
it('Sebeta yard → Indode yard is roughly 26 km', () => {
|
||||
const km = haversineKm(8.9096, 38.636, 8.7386, 38.7913);
|
||||
expect(km).toBeGreaterThan(20);
|
||||
expect(km).toBeLessThan(35);
|
||||
});
|
||||
});
|
||||
48
apps/edr-freight-api/src/common/mile-distance.util.ts
Normal file
48
apps/edr-freight-api/src/common/mile-distance.util.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
/** Great-circle distance in km between two WGS84 points (haversine). */
|
||||
export function haversineKm(lat1: number, lng1: number, lat2: number, lng2: number): number {
|
||||
const toRad = (d: number) => (d * Math.PI) / 180;
|
||||
const dLat = toRad(lat2 - lat1);
|
||||
const dLng = toRad(lng2 - lng1);
|
||||
const a =
|
||||
Math.sin(dLat / 2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLng / 2) ** 2;
|
||||
return 2 * 6371 * Math.asin(Math.sqrt(a));
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimated road-leg distance for a booking's first/last mile: straight-line km
|
||||
* from the yard (freight.yard_locations) to the customer's pickup/delivery GPS
|
||||
* point on the booking. FIRST = origin yard → pickup point, LAST = destination
|
||||
* yard → delivery point. Null when either end has no coordinates.
|
||||
*
|
||||
* ponytail: haversine straight-line, not road routing — plug a routing API in
|
||||
* here if real road km is ever required.
|
||||
*/
|
||||
export async function estimateMileKm(
|
||||
dataSource: DataSource,
|
||||
bookingId: string,
|
||||
mile: 'FIRST' | 'LAST',
|
||||
): Promise<number | null> {
|
||||
const [row] = await dataSource.query(
|
||||
mile === 'LAST'
|
||||
? `SELECT b.last_mile_delivery_lat AS lat, b.last_mile_delivery_lng AS lng,
|
||||
l.latitude AS yard_lat, l.longitude AS yard_lng
|
||||
FROM freight.bookings b
|
||||
LEFT JOIN freight.yard_locations l
|
||||
ON l.yard_id = b.destination_yard_id AND l.deleted_at IS NULL
|
||||
WHERE b.id = $1 AND b.deleted_at IS NULL`
|
||||
: `SELECT b.first_mile_pickup_lat AS lat, b.first_mile_pickup_lng AS lng,
|
||||
l.latitude AS yard_lat, l.longitude AS yard_lng
|
||||
FROM freight.bookings b
|
||||
LEFT JOIN freight.yard_locations l
|
||||
ON l.yard_id = b.origin_yard_id AND l.deleted_at IS NULL
|
||||
WHERE b.id = $1 AND b.deleted_at IS NULL`,
|
||||
[bookingId],
|
||||
);
|
||||
if (!row || row.lat == null || row.lng == null || row.yard_lat == null || row.yard_lng == null) {
|
||||
return null;
|
||||
}
|
||||
const km = haversineKm(Number(row.yard_lat), Number(row.yard_lng), Number(row.lat), Number(row.lng));
|
||||
return Math.round(km * 100) / 100;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { FindOptionsWhere, In, IsNull, Not } from 'typeorm';
|
||||
import { InjectDataSource } from '@nestjs/typeorm';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { attachMileFinancials } from '../../common/mile-financials.util';
|
||||
import { estimateMileKm } from '../../common/mile-distance.util';
|
||||
import { VehicleAvailability } from '../vehicles/entities/vehicle.entity';
|
||||
import { BookingsRepository } from "../bookings/bookings.repository";
|
||||
import { DriversService } from "../drivers/drivers.service";
|
||||
@@ -285,7 +286,7 @@ export class FirstMileService {
|
||||
status: dto.status ?? "READY_TO_TRANSIT",
|
||||
advancedPayment: dto.advancedPayment ?? 0,
|
||||
remainingPayment: dto.remainingPayment ?? 0,
|
||||
estimatedKm: dto.estimatedKm ?? null,
|
||||
estimatedKm: dto.estimatedKm ?? (await estimateMileKm(this.dataSource, dto.bookingId, 'FIRST')),
|
||||
exactKm: dto.exactKm ?? null,
|
||||
vehicleId: dto.vehicleId ?? null,
|
||||
paid: (dto as any).paid ?? false,
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
usesEdrMileService,
|
||||
} from '../../common/mile-haulage.util';
|
||||
import { attachMileFinancials } from '../../common/mile-financials.util';
|
||||
import { estimateMileKm } from '../../common/mile-distance.util';
|
||||
import {
|
||||
assertBulkTonnageRemains,
|
||||
assertTruckCountWithinContainers,
|
||||
@@ -426,7 +427,7 @@ export class LastMileService {
|
||||
status: dto.status ?? 'READY_TO_TRANSIT',
|
||||
advancedPayment: dto.advancedPayment ?? 0,
|
||||
remainingPayment: dto.remainingPayment ?? 0,
|
||||
estimatedKm: dto.estimatedKm ?? null,
|
||||
estimatedKm: dto.estimatedKm ?? (await estimateMileKm(this.dataSource, dto.bookingId, 'LAST')),
|
||||
exactKm: dto.exactKm ?? null,
|
||||
vehicleId: dto.vehicleId ?? null,
|
||||
paid: (dto as any).paid ?? false,
|
||||
|
||||
Reference in New Issue
Block a user