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:
co-authored by
Claude Opus 4.8
parent
4e9d4d9be1
commit
c2a4b9d1a6
@@ -6,6 +6,7 @@ import { UsersService } from '../users/users.service';
|
||||
import { DriverLocationService } from '../locations/driver-location.service';
|
||||
import { DriverCreditService } from '../credit/driver-credit.service';
|
||||
import { EntitlementsService } from '../../common/entitlements/entitlements.service';
|
||||
import { blindIndex } from '../../common/crypto/crypto.util';
|
||||
|
||||
@Injectable()
|
||||
export class DriversService {
|
||||
@@ -63,6 +64,44 @@ export class DriversService {
|
||||
return driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* تحديث الملف الشخصي للسائق (docs/17 — C2/C3). الرقم الوطني يُخزَّن مشفّراً
|
||||
* مع فهرس أعمى للتفرّد (نفس نمط الهاتف — docs/16)، وما يُدخله السائق يُحفظ
|
||||
* في `user_input` للمقارنة لاحقاً بمخرجات Gemini.
|
||||
*/
|
||||
async setProfile(
|
||||
tenantId: string,
|
||||
userId: string,
|
||||
data: Partial<Driver> & { national_number?: string },
|
||||
): Promise<Driver> {
|
||||
const driver = await this.findByUser(tenantId, userId);
|
||||
if (!driver) throw new NotFoundException('Driver profile not found');
|
||||
|
||||
const patch: Partial<Driver> = {};
|
||||
for (const f of [
|
||||
'gender', 'name_arabic', 'birthdate', 'address',
|
||||
'license_type', 'license_categories', 'license_issue', 'license_expiry',
|
||||
] as const) {
|
||||
if (data[f] !== undefined) (patch as any)[f] = data[f];
|
||||
}
|
||||
if (data.national_number !== undefined) {
|
||||
patch.national_number = data.national_number;
|
||||
patch.national_number_bidx = data.national_number ? blindIndex(data.national_number) : null;
|
||||
}
|
||||
// نراكم مدخلات السائق لا نستبدلها — للمقارنة مع Gemini حقلاً بحقل.
|
||||
patch.user_input = { ...(driver.user_input ?? {}), ...(data.user_input ?? {}) };
|
||||
|
||||
await this.repo.update({ tenant_id: tenantId, id: driver.id }, patch);
|
||||
return (await this.findById(tenantId, driver.id))!;
|
||||
}
|
||||
|
||||
/** بحث بالرقم الوطني عبر الفهرس الأعمى (لا بالعمود المشفّر). */
|
||||
findByNationalNumber(tenantId: string, nationalNumber: string): Promise<Driver | null> {
|
||||
return this.repo.findOne({
|
||||
where: { tenant_id: tenantId, national_number_bidx: blindIndex(nationalNumber) },
|
||||
});
|
||||
}
|
||||
|
||||
async setRating(tenantId: string, driverId: string, rating: number): Promise<void> {
|
||||
await this.repo.update(
|
||||
{ tenant_id: tenantId, id: driverId },
|
||||
|
||||
Reference in New Issue
Block a user