feat(bookings): add contract validity window and customs clearing features

- Introduced contract validity days, valid from and valid until fields in the booking model.
- Updated booking acceptance logic to enforce validity window constraints.
- Added customs clearing agent and first/last mile pickup/delivery coordinates to the booking model.
- Implemented LocationPicker component for address selection with map integration using Leaflet.
- Enhanced booking review step to display customs clearing agent details.
- Updated API and DTOs to accommodate new booking fields.
- Added migration scripts for database schema changes.
- Implemented tests for booking acceptance and DTO transformations.
This commit is contained in:
Marshal
2026-06-24 00:21:01 +00:00
parent 07cd7dc111
commit 8ef7641048
31 changed files with 1251 additions and 226 deletions

View File

@@ -177,6 +177,21 @@ export class Booking extends BaseEntity {
@Column({ name: 'adjustment_reason', type: 'text', nullable: true })
adjustmentReason?: string | null;
/**
* Contract validity window, set by the backoffice at the accept step. The
* staff enter a number of days; the contract is valid from contractValidFrom
* (the accept moment) through contractValidUntil (validFrom + N days). Outside
* this window the contract is expired and the booking cannot proceed.
*/
@Column({ name: 'contract_validity_days', type: 'int', nullable: true })
contractValidityDays?: number | null;
@Column({ name: 'contract_valid_from', type: 'timestamptz', nullable: true })
contractValidFrom?: Date | null;
@Column({ name: 'contract_valid_until', type: 'timestamptz', nullable: true })
contractValidUntil?: Date | null;
@Column({ name: 'payment_status', type: 'varchar', length: 20, default: 'PENDING' })
paymentStatus!: string;
@@ -200,9 +215,27 @@ export class Booking extends BaseEntity {
@Column({ name: 'first_mile_pickup_address', type: 'text', nullable: true })
firstMilePickupAddress?: string | null;
@Column({ name: 'first_mile_pickup_lat', type: 'numeric', precision: 10, scale: 7, nullable: true })
firstMilePickupLat?: number | null;
@Column({ name: 'first_mile_pickup_lng', type: 'numeric', precision: 10, scale: 7, nullable: true })
firstMilePickupLng?: number | null;
@Column({ name: 'last_mile_delivery_address', type: 'text', nullable: true })
lastMileDeliveryAddress?: string | null;
@Column({ name: 'last_mile_delivery_lat', type: 'numeric', precision: 10, scale: 7, nullable: true })
lastMileDeliveryLat?: number | null;
@Column({ name: 'last_mile_delivery_lng', type: 'numeric', precision: 10, scale: 7, nullable: true })
lastMileDeliveryLng?: number | null;
@Column({ name: 'customs_clearing_enabled', type: 'boolean', default: false })
customsClearingEnabled!: boolean;
@Column({ name: 'customs_clearing_agent', type: 'varchar', length: 200, nullable: true })
customsClearingAgent?: string | null;
@Column({ name: 'equipment_return', type: 'varchar', length: 20 })
equipmentReturn!: string;