mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 09:42:53 +00:00
Merge branch 'freight/feature/first_mile_invoice' of github.com:Tria-plc/edr-platform into freight/feature/first_mile_invoice
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
/**
|
||||
* Split the mixed vehicle status into two fields:
|
||||
* - status: operational state (ACTIVE, MAINTENANCE, RETIRED, OUT_OF_SERVICE)
|
||||
* - availability: assignment state (FREE, BUSY)
|
||||
*
|
||||
* Existing FREE/BUSY statuses are moved to availability and the status is
|
||||
* normalized back to ACTIVE.
|
||||
*/
|
||||
export class SeparateVehicleAvailability1890000000000 implements MigrationInterface {
|
||||
name = "SeparateVehicleAvailability1890000000000";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE freight.vehicles
|
||||
ADD COLUMN IF NOT EXISTS availability varchar DEFAULT 'FREE'
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
UPDATE freight.vehicles SET availability = 'BUSY' WHERE status = 'BUSY'
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
UPDATE freight.vehicles SET availability = 'FREE' WHERE availability IS NULL
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
UPDATE freight.vehicles SET status = 'ACTIVE' WHERE status IN ('FREE', 'BUSY')
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
// Fold availability back into status before dropping the column
|
||||
await queryRunner.query(`
|
||||
UPDATE freight.vehicles SET status = availability
|
||||
WHERE status = 'ACTIVE' AND availability IN ('FREE', 'BUSY')
|
||||
`);
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE freight.vehicles DROP COLUMN IF EXISTS availability
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { BookingsRepository } from '../bookings/bookings.repository';
|
||||
import { DriversService } from '../drivers/drivers.service';
|
||||
import { SmsClientService } from '../notifications/sms-client.service';
|
||||
import { VehiclesService } from '../vehicles/vehicles.service';
|
||||
import { VehicleStatus } from '../vehicles/entities/vehicle.entity';
|
||||
import { VehicleAvailability } from '../vehicles/entities/vehicle.entity';
|
||||
import { CreateFirstMileDto } from './dto/create-first-mile.dto';
|
||||
import { UpdateFirstMileDto } from './dto/update-first-mile.dto';
|
||||
import { FirstMile, FirstMileStatus } from './entities/first-mile.entity';
|
||||
@@ -193,7 +193,7 @@ export class FirstMileService {
|
||||
});
|
||||
|
||||
if (dto.vehicleId) {
|
||||
await this.vehiclesService.setStatus(dto.vehicleId, VehicleStatus.BUSY);
|
||||
await this.vehiclesService.setAvailability(dto.vehicleId, VehicleAvailability.BUSY);
|
||||
}
|
||||
|
||||
return record;
|
||||
@@ -245,7 +245,7 @@ export class FirstMileService {
|
||||
// Keep vehicle statuses in sync: new vehicle goes BUSY, replaced one goes back to FREE
|
||||
if (dto.vehicleId !== undefined && dto.vehicleId !== existing.vehicleId) {
|
||||
if (dto.vehicleId) {
|
||||
await this.vehiclesService.setStatus(dto.vehicleId, VehicleStatus.BUSY);
|
||||
await this.vehiclesService.setAvailability(dto.vehicleId, VehicleAvailability.BUSY);
|
||||
}
|
||||
if (existing.vehicleId) {
|
||||
await this.vehiclesService.releaseIfUnused([existing.vehicleId]);
|
||||
@@ -373,7 +373,7 @@ export class FirstMileService {
|
||||
|
||||
const vehicleIds = new Set(allocations.map((a) => a.vehicleId));
|
||||
await Promise.all(
|
||||
[...vehicleIds].map((vehicleId) => this.vehiclesService.setStatus(vehicleId, VehicleStatus.BUSY)),
|
||||
[...vehicleIds].map((vehicleId) => this.vehiclesService.setAvailability(vehicleId, VehicleAvailability.BUSY)),
|
||||
);
|
||||
await this.vehiclesService.releaseIfUnused(
|
||||
previousVehicleIds.filter((id) => !vehicleIds.has(id)),
|
||||
|
||||
@@ -5,7 +5,7 @@ import { BookingsRepository } from '../bookings/bookings.repository';
|
||||
import { DriversService } from '../drivers/drivers.service';
|
||||
import { SmsClientService } from '../notifications/sms-client.service';
|
||||
import { VehiclesService } from '../vehicles/vehicles.service';
|
||||
import { VehicleStatus } from '../vehicles/entities/vehicle.entity';
|
||||
import { VehicleAvailability } from '../vehicles/entities/vehicle.entity';
|
||||
import { CreateLastMileDto } from './dto/create-last-mile.dto';
|
||||
import { UpdateLastMileDto } from './dto/update-last-mile.dto';
|
||||
import { LastMile, LastMileStatus } from './entities/last-mile.entity';
|
||||
@@ -144,7 +144,7 @@ export class LastMileService {
|
||||
});
|
||||
|
||||
if (dto.vehicleId) {
|
||||
await this.vehiclesService.setStatus(dto.vehicleId, VehicleStatus.BUSY);
|
||||
await this.vehiclesService.setAvailability(dto.vehicleId, VehicleAvailability.BUSY);
|
||||
}
|
||||
|
||||
return record;
|
||||
@@ -184,7 +184,7 @@ export class LastMileService {
|
||||
// Keep vehicle statuses in sync: new vehicle goes BUSY, replaced one goes back to FREE
|
||||
if (dto.vehicleId !== undefined && dto.vehicleId !== existing.vehicleId) {
|
||||
if (dto.vehicleId) {
|
||||
await this.vehiclesService.setStatus(dto.vehicleId, VehicleStatus.BUSY);
|
||||
await this.vehiclesService.setAvailability(dto.vehicleId, VehicleAvailability.BUSY);
|
||||
}
|
||||
if (existing.vehicleId) {
|
||||
await this.vehiclesService.releaseIfUnused([existing.vehicleId]);
|
||||
@@ -302,7 +302,7 @@ export class LastMileService {
|
||||
|
||||
const vehicleIds = new Set(allocations.map((a) => a.vehicleId));
|
||||
await Promise.all(
|
||||
[...vehicleIds].map((vehicleId) => this.vehiclesService.setStatus(vehicleId, VehicleStatus.BUSY)),
|
||||
[...vehicleIds].map((vehicleId) => this.vehiclesService.setAvailability(vehicleId, VehicleAvailability.BUSY)),
|
||||
);
|
||||
await this.vehiclesService.releaseIfUnused(
|
||||
previousVehicleIds.filter((id) => !vehicleIds.has(id)),
|
||||
|
||||
@@ -20,13 +20,16 @@ export enum FuelType {
|
||||
|
||||
export enum VehicleStatus {
|
||||
ACTIVE = 'ACTIVE',
|
||||
FREE = 'FREE',
|
||||
BUSY = 'BUSY',
|
||||
MAINTENANCE = 'MAINTENANCE',
|
||||
RETIRED = 'RETIRED',
|
||||
OUT_OF_SERVICE = 'OUT_OF_SERVICE',
|
||||
}
|
||||
|
||||
export enum VehicleAvailability {
|
||||
FREE = 'FREE',
|
||||
BUSY = 'BUSY',
|
||||
}
|
||||
|
||||
@Entity({ name: 'vehicles', schema: 'freight' })
|
||||
export class Vehicle extends BaseEntity {
|
||||
@Column({ name: 'plate_number', unique: true, nullable: true })
|
||||
@@ -56,6 +59,9 @@ export class Vehicle extends BaseEntity {
|
||||
@Column({ name: 'status', type: 'varchar', default: VehicleStatus.ACTIVE, nullable: true })
|
||||
status?: VehicleStatus;
|
||||
|
||||
@Column({ name: 'availability', type: 'varchar', default: VehicleAvailability.FREE, nullable: true })
|
||||
availability?: VehicleAvailability;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
description?: string | null;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ export class VehiclesController {
|
||||
findAll(
|
||||
@Query('search') search?: string,
|
||||
@Query('status') status?: string,
|
||||
@Query('availability') availability?: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('limit') limit?: string,
|
||||
@Query('sortBy') sortBy?: string,
|
||||
@@ -42,6 +43,7 @@ export class VehiclesController {
|
||||
return this.vehiclesService.findAll({
|
||||
search,
|
||||
status: status as any,
|
||||
availability: availability as any,
|
||||
page: page ? parseInt(page) : undefined,
|
||||
limit: limit ? parseInt(limit) : undefined,
|
||||
sortBy,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Not, Repository } from 'typeorm';
|
||||
import { CreateVehicleDto } from './dto/create-vehicle.dto';
|
||||
import { UpdateVehicleDto } from './dto/update-vehicle.dto';
|
||||
import { Vehicle, VehicleStatus } from './entities/vehicle.entity';
|
||||
import { Vehicle, VehicleAvailability, VehicleStatus } from './entities/vehicle.entity';
|
||||
import { FirstMile, FirstMileStatus } from '../first-mile/entities/first-mile.entity';
|
||||
import { FirstMileContainerAllocation } from '../first-mile/entities/first-mile-container-allocation.entity';
|
||||
import { LastMile, LastMileStatus } from '../last-mile/entities/last-mile.entity';
|
||||
@@ -39,6 +39,7 @@ export class VehiclesService {
|
||||
async findAll(query: {
|
||||
search?: string;
|
||||
status?: VehicleStatus | string;
|
||||
availability?: VehicleAvailability | string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
sortBy?: string;
|
||||
@@ -57,6 +58,10 @@ export class VehiclesService {
|
||||
qb = qb.andWhere('v.status = :status', { status: query.status });
|
||||
}
|
||||
|
||||
if (query.availability) {
|
||||
qb = qb.andWhere('v.availability = :availability', { availability: query.availability });
|
||||
}
|
||||
|
||||
const sortBy = ['plateNumber', 'status', 'year', 'createdAt'].includes(
|
||||
query.sortBy ?? '',
|
||||
)
|
||||
@@ -95,8 +100,8 @@ export class VehiclesService {
|
||||
return this.vehicleRepo.save(vehicle);
|
||||
}
|
||||
|
||||
async setStatus(id: string, status: VehicleStatus): Promise<void> {
|
||||
await this.vehicleRepo.update(id, { status });
|
||||
async setAvailability(id: string, availability: VehicleAvailability): Promise<void> {
|
||||
await this.vehicleRepo.update(id, { availability });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +136,7 @@ export class VehiclesService {
|
||||
.getCount(),
|
||||
]);
|
||||
if (fmRecords + lmRecords + fmAllocations + lmAllocations === 0) {
|
||||
await this.setStatus(vehicleId, VehicleStatus.FREE);
|
||||
await this.setAvailability(vehicleId, VehicleAvailability.FREE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export function ContainerAllocationTable({
|
||||
|
||||
const { data: vehicles = [], isLoading: vehiclesLoading } = useQuery({
|
||||
queryKey: ["vehicles", "free"],
|
||||
queryFn: () => vehiclesService.getAll({ status: "FREE" }),
|
||||
queryFn: () => vehiclesService.getAll({ status: "ACTIVE", availability: "FREE" }),
|
||||
});
|
||||
|
||||
const vehicleOptions = useMemo(
|
||||
|
||||
@@ -43,7 +43,7 @@ export function FirstMileContainerAllocationTable({
|
||||
|
||||
const { data: vehicles = [], isLoading: vehiclesLoading } = useQuery({
|
||||
queryKey: ["vehicles", "free"],
|
||||
queryFn: () => vehiclesService.getAll({ status: "FREE" }),
|
||||
queryFn: () => vehiclesService.getAll({ status: "ACTIVE", availability: "FREE" }),
|
||||
});
|
||||
|
||||
const vehicleOptions = useMemo(
|
||||
|
||||
@@ -43,7 +43,7 @@ export function LastMileContainerAllocationTable({
|
||||
|
||||
const { data: vehicles = [], isLoading: vehiclesLoading } = useQuery({
|
||||
queryKey: ["vehicles", "free"],
|
||||
queryFn: () => vehiclesService.getAll({ status: "FREE" }),
|
||||
queryFn: () => vehiclesService.getAll({ status: "ACTIVE", availability: "FREE" }),
|
||||
});
|
||||
|
||||
const vehicleOptions = useMemo(
|
||||
|
||||
@@ -19,13 +19,16 @@ const FUEL_TYPE_OPTIONS = [
|
||||
|
||||
const VEHICLE_STATUS_OPTIONS = [
|
||||
{ label: "Active", value: "ACTIVE" },
|
||||
{ label: "Free", value: "FREE" },
|
||||
{ label: "Busy", value: "BUSY" },
|
||||
{ label: "Maintenance", value: "MAINTENANCE" },
|
||||
{ label: "Retired", value: "RETIRED" },
|
||||
{ label: "Out of service", value: "OUT_OF_SERVICE" },
|
||||
];
|
||||
|
||||
const VEHICLE_AVAILABILITY_OPTIONS = [
|
||||
{ label: "Free", value: "FREE" },
|
||||
{ label: "Busy", value: "BUSY" },
|
||||
];
|
||||
|
||||
export const vehiclesConfig: FleetResourceConfig = {
|
||||
slug: "vehicles",
|
||||
label: "Vehicles",
|
||||
@@ -58,6 +61,7 @@ export const vehiclesConfig: FleetResourceConfig = {
|
||||
{ id: "capacity", header: "Capacity (tons)", accessorKey: "capacity", format: "number", size: 100 },
|
||||
{ id: "locationId", header: "Location", accessorKey: "locationId", size: 140 },
|
||||
{ id: "status", header: "Status", accessorKey: "status", format: "statusBadge", size: 100 },
|
||||
{ id: "availability", header: "Availability", accessorKey: "availability", format: "statusBadge", size: 100 },
|
||||
],
|
||||
formFields: [
|
||||
{ name: "code", label: "Code", type: "text" },
|
||||
@@ -95,4 +99,4 @@ export const vehiclesConfig: FleetResourceConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
export { VEHICLE_TYPE_OPTIONS, FUEL_TYPE_OPTIONS, VEHICLE_STATUS_OPTIONS };
|
||||
export { VEHICLE_TYPE_OPTIONS, FUEL_TYPE_OPTIONS, VEHICLE_STATUS_OPTIONS, VEHICLE_AVAILABILITY_OPTIONS };
|
||||
|
||||
@@ -357,7 +357,7 @@ const FirstMilePage = () => {
|
||||
const { data: vehiclesData } = useQuery({
|
||||
queryKey: ["vehicles", "free"],
|
||||
queryFn: async () => {
|
||||
const res = await vehiclesService.getAll({ status: "FREE" });
|
||||
const res = await vehiclesService.getAll({ status: "ACTIVE", availability: "FREE" });
|
||||
return res.data;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -341,7 +341,7 @@ const LastMilePage = () => {
|
||||
const { data: vehiclesData } = useQuery({
|
||||
queryKey: ["vehicles", "free"],
|
||||
queryFn: async () => {
|
||||
const res = await vehiclesService.getAll({ status: "FREE" });
|
||||
const res = await vehiclesService.getAll({ status: "ACTIVE", availability: "FREE" });
|
||||
return res.data;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,10 +3,12 @@ import { URL_CONSTANTS } from '@/constants/URLS';
|
||||
|
||||
export type VehicleType = 'TRUCK' | 'VAN' | 'CAR' | 'BUS' | 'TRAILER' | 'TANKER' | 'FLATBED';
|
||||
export type FuelType = 'PETROL' | 'DIESEL' | 'ELECTRIC' | 'HYBRID';
|
||||
export type VehicleStatus = 'ACTIVE' | 'FREE' | 'BUSY' | 'MAINTENANCE' | 'RETIRED' | 'OUT_OF_SERVICE';
|
||||
export type VehicleStatus = 'ACTIVE' | 'MAINTENANCE' | 'RETIRED' | 'OUT_OF_SERVICE';
|
||||
export type VehicleAvailability = 'FREE' | 'BUSY';
|
||||
|
||||
export interface VehicleListFilters {
|
||||
status?: VehicleStatus;
|
||||
availability?: VehicleAvailability;
|
||||
search?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
@@ -25,6 +27,7 @@ export interface Vehicle {
|
||||
fuelType: FuelType;
|
||||
capacity: number;
|
||||
status: VehicleStatus;
|
||||
availability: VehicleAvailability;
|
||||
description?: string | null;
|
||||
code?: string | null;
|
||||
powerPlateNo?: string | null;
|
||||
@@ -43,6 +46,7 @@ export const vehiclesService = {
|
||||
getAll: (filters: VehicleListFilters = {}) => {
|
||||
const params = new URLSearchParams();
|
||||
if (filters.status) params.set('status', filters.status);
|
||||
if (filters.availability) params.set('availability', filters.availability);
|
||||
if (filters.search) params.set('search', filters.search);
|
||||
if (filters.page) params.set('page', filters.page.toString());
|
||||
if (filters.limit) params.set('limit', filters.limit.toString());
|
||||
|
||||
Reference in New Issue
Block a user