Files
tripz-llc/backend/src/modules/drivers/drivers.service.ts
T
HamzaandClaude Opus 4.8 00870a8cc3 P1: drivers + trips(★state machine) + matching(Redis GEO) + tariff engine + maps + realtime + seed
- drivers: apply/approve/status/location (+Redis GEO presence)
- trips: request→accept→status transitions + trip_events audit + offers via socket
- matching: Redis GEO nearby drivers
- tariff: JSON-rule engine (time_or_distance/window/rounding) + quote endpoint
- maps: straight-line routing seam (انطلق later)
- realtime: Socket.IO gateway (JWT auth, tenant rooms)
- seed: demo tenant siro + default tariff on boot
- migration InitP1 (drivers/tariffs/trips/trip_events)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 16:41:58 +03:00

94 lines
3.3 KiB
TypeScript

import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Driver } from './entities/driver.entity';
import { UsersService } from '../users/users.service';
import { MatchingService } from '../matching/matching.service';
@Injectable()
export class DriversService {
constructor(
@InjectRepository(Driver)
private readonly repo: Repository<Driver>,
private readonly users: UsersService,
private readonly matching: MatchingService,
) {}
findByUser(tenantId: string, userId: string): Promise<Driver | null> {
return this.repo.findOne({ where: { tenant_id: tenantId, user_id: userId } });
}
findById(tenantId: string, id: string): Promise<Driver | null> {
return this.repo.findOne({ where: { tenant_id: tenantId, id } });
}
/** تسجيل السائق بالوثائق — ينشئ سجل driver ويرفع دور المستخدم إلى driver. */
async apply(
tenantId: string,
userId: string,
data: Partial<Driver>,
): Promise<Driver> {
let driver = await this.findByUser(tenantId, userId);
if (!driver) {
driver = this.repo.create({
tenant_id: tenantId,
user_id: userId,
service_class: data.service_class ?? 'economy',
vehicle_make: data.vehicle_make,
vehicle_model: data.vehicle_model,
vehicle_plate: data.vehicle_plate,
vehicle_color: data.vehicle_color,
docs: data.docs ?? {},
verification_status: 'pending',
});
driver = await this.repo.save(driver);
}
// يصير دور المستخدم "driver" (يظهر في التوكن عند إعادة الدخول).
await this.users.setRole(tenantId, userId, 'driver');
return driver;
}
async approve(tenantId: string, driverId: string): Promise<Driver> {
const driver = await this.findById(tenantId, driverId);
if (!driver) throw new NotFoundException('Driver not found');
driver.verification_status = 'approved';
return this.repo.save(driver);
}
/** يبدّل حالة الاتصال؛ عند الاتصال يُضاف لفهرس GEO، وعند الفصل يُزال. */
async setOnline(
tenantId: string,
userId: string,
online: boolean,
): Promise<Driver> {
const driver = await this.findByUser(tenantId, userId);
if (!driver) throw new NotFoundException('Driver profile not found');
driver.is_online = online;
await this.repo.save(driver);
if (!online) {
await this.matching.removeDriver(tenantId, driver.id);
} else if (driver.last_lat != null && driver.last_lng != null) {
await this.matching.addDriver(tenantId, driver.id, driver.last_lat, driver.last_lng);
}
return driver;
}
/** تحديث الموقع الجاري: Redis GEO + لقطة في Postgres. */
async updateLocation(
tenantId: string,
userId: string,
lat: number,
lng: number,
): Promise<{ ok: boolean }> {
const driver = await this.findByUser(tenantId, userId);
if (!driver) throw new NotFoundException('Driver profile not found');
driver.last_lat = lat;
driver.last_lng = lng;
await this.repo.save(driver);
if (driver.is_online) {
await this.matching.addDriver(tenantId, driver.id, lat, lng);
}
return { ok: true };
}
}