Merge pull request #878 from Tria-plc/testfixes

Mile records are created with advanced_payment
This commit is contained in:
Hagernesh Tadesse
2026-07-21 16:00:08 +03:00
committed by GitHub
4 changed files with 73 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
import { DataSource } from 'typeorm';
type MileRecord = {
bookingId?: string | null;
advancedPayment?: number | string | null;
booking?: {
cargoTotalWeightVgm?: number | string | null;
bookingContainers?: Array<{
units?: Array<{ vgmTons?: number | string | null }> | null;
}> | null;
} | null;
};
/**
* Display enrichment for first/last-mile lists (Assign Vehicle modal etc.):
* - Advance payment: mile records are created with advanced_payment 0 — the
* real advance is the FIRST_MILE/LAST_MILE line the customer already paid
* on the booking invoice.
* - Cargo tons: container bookings often carry tonnage on the per-unit VGMs
* while cargo_total_weight_vgm stays 0 — fall back to the summed units.
* Fills both in-memory on the loaded records; nothing is persisted.
*/
export async function attachMileFinancials(
dataSource: DataSource,
records: MileRecord[],
chargeType: 'FIRST_MILE' | 'LAST_MILE',
): Promise<void> {
for (const r of records) {
const b = r.booking;
if (!b || Number(b.cargoTotalWeightVgm) > 0) continue;
const unitTons = (b.bookingContainers ?? []).reduce(
(sum, bc) =>
sum + (bc.units ?? []).reduce((s, u) => s + (Number(u.vgmTons) || 0), 0),
0,
);
if (unitTons > 0) b.cargoTotalWeightVgm = Number(unitTons.toFixed(3));
}
const needAdvance = records.filter(
(r) => r.bookingId && !(Number(r.advancedPayment) > 0),
);
if (!needAdvance.length) return;
const rows: Array<{ bookingId: string; amount: string }> = await dataSource.query(
`SELECT i.source_id AS "bookingId", SUM(il.amount) AS amount
FROM freight.invoice_lines il
JOIN freight.invoices i ON i.id = il.invoice_id AND i.deleted_at IS NULL
WHERE i.source = 'booking'
AND i.status = 'PAID'
AND i.source_id = ANY($1::text[])
AND il.charge_type = $2
AND il.deleted_at IS NULL
GROUP BY i.source_id`,
[needAdvance.map((r) => r.bookingId), chargeType],
);
const byBooking = new Map(rows.map((r) => [r.bookingId, Number(r.amount)]));
for (const r of needAdvance) {
const paid = byBooking.get(r.bookingId as string);
if (paid) r.advancedPayment = paid;
}
}

View File

@@ -2,6 +2,7 @@ import { BadRequestException, Injectable, Logger, NotFoundException } from '@nes
import { FindOptionsWhere, In, IsNull, Not } from 'typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { attachMileFinancials } from '../../common/mile-financials.util';
import { VehicleAvailability } from '../vehicles/entities/vehicle.entity';
import { BookingsRepository } from "../bookings/bookings.repository";
import { DriversService } from "../drivers/drivers.service";
@@ -66,6 +67,7 @@ export class FirstMileService {
for (const r of records) {
(r as FirstMile & { invoice?: unknown }).invoice = byId.get(r.id) ?? null;
}
await attachMileFinancials(this.dataSource, records, 'FIRST_MILE');
}
/** Resolve a vehicle's driver + human labels, for stamping mile events onto

View File

@@ -12,6 +12,7 @@ import {
SELF_HAUL_CONFLICT_MESSAGE,
usesEdrMileService,
} from '../../common/mile-haulage.util';
import { attachMileFinancials } from '../../common/mile-financials.util';
import {
assertBulkTonnageRemains,
assertTruckCountWithinContainers,
@@ -88,6 +89,7 @@ export class LastMileService {
for (const r of records) {
(r as LastMile & { invoice?: unknown }).invoice = byId.get(r.id) ?? null;
}
await attachMileFinancials(this.dataSource, records, 'LAST_MILE');
}
/** Resolve a vehicle's driver + human labels, for stamping mile events onto

View File

@@ -4005,6 +4005,14 @@ export class WarehouseInventoryService {
[handoverId],
);
if (!h) throw new NotFoundException(`Handover ${handoverId} not found`);
// Self-haul stays a single booking-level signature via approve-delivery,
// which also enforces inspection-passed + truck-arrived. Per-truck signing
// is an EDR last-mile flow only.
if (h.mileType !== 'EDR_LAST_MILE') {
throw new BadRequestException(
'This handover is signed through Approve delivery, not per truck',
);
}
// Same gate as approve-delivery: storage/demurrage must be settled first.
const [inv]: Array<{ id: string; warehouseId: string | null }> = await this.dataSource.query(