This commit is contained in:
natib21
2026-07-03 13:49:55 +00:00
parent 48db0b240b
commit 782f4184ef
8 changed files with 142 additions and 2 deletions

View File

@@ -0,0 +1,39 @@
import { MigrationInterface, QueryRunner } from "typeorm";
/**
* Allow more than one vehicle per last-mile delivery. Junction table joins
* last_mile ⇄ vehicles; existing single vehicle_id values are backfilled as
* the first assignment so nothing is lost.
*/
export class AddLastMileVehicleAssignments1890000000004 implements MigrationInterface {
name = "AddLastMileVehicleAssignments1890000000004";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE IF NOT EXISTS freight.last_mile_vehicle_assignments (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
last_mile_id uuid NOT NULL REFERENCES freight.last_mile(id) ON DELETE CASCADE,
vehicle_id uuid NOT NULL REFERENCES freight.vehicles(id),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz,
CONSTRAINT "UQ_LAST_MILE_VEHICLE" UNIQUE (last_mile_id, vehicle_id)
)
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS "IDX_LM_VEHICLE_ASSIGNMENTS_VEHICLE"
ON freight.last_mile_vehicle_assignments (vehicle_id)
`);
// Backfill: existing single-vehicle assignments become the first row
await queryRunner.query(`
INSERT INTO freight.last_mile_vehicle_assignments (last_mile_id, vehicle_id)
SELECT id, vehicle_id FROM freight.last_mile
WHERE vehicle_id IS NOT NULL AND deleted_at IS NULL
ON CONFLICT (last_mile_id, vehicle_id) DO NOTHING
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE IF EXISTS freight.last_mile_vehicle_assignments`);
}
}

View File

@@ -0,0 +1,24 @@
import { MigrationInterface, QueryRunner } from "typeorm";
/**
* Store the driver's gender. Prefilled from the Fayda VERIFY response
* (Male/Female) but editable; nullable so existing rows and manual,
* non-Fayda driver records stay valid.
*/
export class AddDriverGender1890000000005 implements MigrationInterface {
name = "AddDriverGender1890000000005";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.drivers
ADD COLUMN IF NOT EXISTS gender varchar
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE freight.drivers
DROP COLUMN IF EXISTS gender
`);
}
}

View File

@@ -1,5 +1,5 @@
import { IsString, IsEmail, IsDateString, IsEnum, IsOptional, IsArray, IsBoolean } from 'class-validator';
import { DriverStatus } from '../entities/driver.entity';
import { DriverStatus, DriverGender } from '../entities/driver.entity';
export class CreateDriverDto {
@IsString()
@@ -20,6 +20,10 @@ export class CreateDriverDto {
@IsDateString()
dateOfBirth!: string;
@IsOptional()
@IsEnum(DriverGender)
gender?: DriverGender;
@IsDateString()
licenseExpiryDate!: string;

View File

@@ -8,6 +8,12 @@ export enum DriverStatus {
ON_LEAVE = 'ON_LEAVE',
}
export enum DriverGender {
MALE = 'MALE',
FEMALE = 'FEMALE',
OTHER = 'OTHER',
}
@Entity({ name: 'drivers', schema: 'freight' })
export class Driver extends BaseEntity {
@Column({ name: 'license_number', unique: true, nullable: true })
@@ -28,6 +34,9 @@ export class Driver extends BaseEntity {
@Column({ name: 'date_of_birth', type: 'date', nullable: true })
dateOfBirth?: Date;
@Column({ type: 'varchar', nullable: true })
gender?: DriverGender | null;
@Column({ name: 'license_expiry_date', type: 'date', nullable: true })
licenseExpiryDate?: Date;

View File

@@ -0,0 +1,30 @@
import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, JoinColumn, ManyToOne, Unique } from 'typeorm';
import { Vehicle } from '../../vehicles/entities/vehicle.entity';
import { LastMile } from './last-mile.entity';
/**
* One row per vehicle assigned to a last-mile delivery. A delivery can be
* served by several vehicles at once (multi-truck bookings); the legacy
* `last_mile.vehicle_id` column keeps pointing at the first assignment for
* backward compatibility.
*/
@Entity({ name: 'last_mile_vehicle_assignments', schema: 'freight' })
@Unique(['lastMileId', 'vehicleId'])
@Index(['vehicleId'])
export class LastMileVehicleAssignment extends BaseEntity {
@Column({ name: 'last_mile_id', type: 'uuid' })
lastMileId!: string;
@ManyToOne(() => LastMile, (lm) => lm.vehicleAssignments, { nullable: false, onDelete: 'CASCADE' })
@JoinColumn({ name: 'last_mile_id' })
lastMile?: LastMile;
@Column({ name: 'vehicle_id', type: 'uuid' })
vehicleId!: string;
@ManyToOne(() => Vehicle, { nullable: false, eager: false })
@JoinColumn({ name: 'vehicle_id' })
vehicle?: Vehicle;
}

View File

@@ -4,6 +4,7 @@ import { Column, Entity, Index, JoinColumn, ManyToOne, OneToMany } from 'typeorm
import { Booking } from '../../bookings/entities/booking.entity';
import { Vehicle } from '../../vehicles/entities/vehicle.entity';
import { LastMileContainerAllocation } from './last-mile-container-allocation.entity';
import { LastMileVehicleAssignment } from './last-mile-vehicle-assignment.entity';
export const LAST_MILE_STATUSES = [
'PAYMENT_PENDING',
@@ -57,4 +58,7 @@ export class LastMile extends BaseEntity {
@OneToMany(() => LastMileContainerAllocation, (ca) => ca.lastMile)
containerAllocations?: LastMileContainerAllocation[];
@OneToMany(() => LastMileVehicleAssignment, (va) => va.lastMile)
vehicleAssignments?: LastMileVehicleAssignment[];
}