feat: المجموعة C — بيانات السائق والمركبة (كيان مركبة + ملف السائق)

C1: جدول vehicles (مكافئ CarRegistration عند سيرو) — plate/vin مشفَّران،
make/model/year، color+color_hex (لتلوين السيارة في فلاتر)، fuel/owner/
category، is_default، status. سائق قد يملك أكثر من مركبة؛ VehiclesService
يضمن افتراضية واحدة دائماً (أول مركبة تلقائياً، حذف الافتراضية يرقّي غيرها).

C2: حقول الملف على drivers — gender، national_number (مشفَّر + فهرس أعمى
فريد لكل مستأجر، نفس نمط الهاتف)، name_arabic (مشفَّر)، birthdate، address،
الرخصة (type/categories/issue/expiry)، rejected_reason. عبر PATCH
/drivers/profile.

C3: ai_data + user_input (jsonb) على drivers و vehicles — مخرجات Gemini
مقابل مدخلات السائق، تُراكَم للمقارنة حقلاً بحقل.

C4: vehicle_photo min:2 في كتالوج الوثائق.
C5: نوع وثيقة face_liveness (فيديو) — الرفع يقبله بلا قيد mime.

قرار: المركبة كيان مستقل لا حقول مسطّحة (سيرو يفصلها بـisDefault). الحقول
القديمة على drivers تبقى للتوافق؛ المصدر الجديد vehicles.

هجرة: VehiclesAndDriverProfile (جدول vehicles + أعمدة السائق + فهرس فريد
جزئي على الرقم الوطني).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-17 19:43:47 +03:00
co-authored by Claude Opus 4.8
parent 4e9d4d9be1
commit c2a4b9d1a6
13 changed files with 588 additions and 10 deletions
@@ -0,0 +1,83 @@
import {
Column,
CreateDateColumn,
Entity,
Index,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { EncryptedTransformer } from '../../../common/crypto/crypto.util';
export type VehicleStatus = 'pending' | 'approved' | 'rejected';
/**
* مركبة السائق (مكافئ `CarRegistration` عند سيرو — docs/17 — C1).
* الجدول: tripz_vehicles. سائق قد يملك أكثر من مركبة (`is_default` يحدّد الفعّالة).
*/
@Entity('vehicles')
@Index(['tenant_id', 'driver_id'])
export class Vehicle {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid' })
tenant_id: string;
@Column({ type: 'uuid' })
driver_id: string;
// ---- بيانات التسجيل ----
// اللوحة والـVIN معرِّفان حساسان — مشفَّران at-rest (docs/16).
@Column({ type: 'varchar', nullable: true, transformer: EncryptedTransformer })
plate: string | null;
@Column({ type: 'varchar', nullable: true, transformer: EncryptedTransformer })
vin: string | null;
@Column({ type: 'varchar', nullable: true })
make: string | null; // الصانع (Toyota…)
@Column({ type: 'varchar', nullable: true })
model: string | null;
@Column({ type: 'int', nullable: true })
year: number | null;
// اللون النصّي + الكود السداسي لتلوين السيارة في فلاتر (طلب المالك — C1).
@Column({ type: 'varchar', nullable: true })
color: string | null;
@Column({ type: 'varchar', nullable: true })
color_hex: string | null;
@Column({ type: 'varchar', nullable: true })
fuel: string | null; // petrol | diesel | electric | hybrid
@Column({ type: 'varchar', nullable: true })
owner: string | null; // اسم المالك إن اختلف عن السائق
@Column({ type: 'date', nullable: true })
registration_expiry: string | null;
@Column({ type: 'varchar', nullable: true })
category: string | null; // فئة المركبة (economy/comfort/van…)
@Column({ default: false })
is_default: boolean;
@Column({ type: 'varchar', default: 'pending' })
status: VehicleStatus;
// مخرجات Gemini الخام مقابل ما أدخله السائق (docs/17 — C3) — للمقارنة والتدقيق.
@Column({ type: 'jsonb', default: {} })
ai_data: Record<string, any>;
@Column({ type: 'jsonb', default: {} })
user_input: Record<string, any>;
@CreateDateColumn()
created_at: Date;
@UpdateDateColumn()
updated_at: Date;
}