Files
ln-bi/src/server/routes/vehicles/repository.ts
T
2026-09-28 17:03:59 +08:00

467 lines
20 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import pool from '../../db.js';
import { LATEST_VEHICLE_LEASE_JOIN_SQL } from '../../vehicle-lease-sql.js';
import type { Vehicle, VehicleRow } from '../../types.js';
import { transformRow } from './model.js';
import type { WeeklyTruckIds } from './types.js';
interface Database {
query<T = any[]>(sql: string, values?: any[]): Promise<[T, ...any[]]>;
}
type FlowType = 'delivered' | 'returned' | 'replaced';
export type WeeklyDetailType = FlowType | 'pending' | 'new';
export interface FlowDetailRow {
id: string;
type: FlowType;
type_label: string;
stat_date: string;
truck_id: string;
plate_number: string;
event_time: string | null;
submit_time: string | null;
department: string | null;
manager: string | null;
customer_name: string | null;
contract_no: string | null;
project_name: string | null;
}
interface WeeklyStats {
pendingDelivery: number;
weeklyNew: number;
weeklyRemoved: number;
weeklyDelivered: number;
weeklyReturned: number;
weeklyReplaced: number;
}
export const MAIN_SQL = `SELECT
CAST(vi.id AS CHAR) AS id,
CAST(am.id AS CHAR) AS abnormal_move_id,
am.move_type AS abnormal_move_type,
NULLIF(TRIM(am_applicant.nick_name), '') AS abnormal_move_applicant,
CAST(pdv.id AS CHAR) AS pending_delivery_id,
pdts.contract_code AS pending_contract_no,
pdts.customer_name AS pending_customer_name,
am.move_status AS abnormal_move_status,
CONCAT_WS(';', NULLIF(TRIM(am.remark), ''), NULLIF(TRIM(amr.remark), '')) AS abnormal_move_work,
DATE_FORMAT(am.move_plan_end_time, '%Y-%m-%d %H:%i') AS abnormal_move_target_time,
DATE_FORMAT(am.move_end_time, '%Y-%m-%d %H:%i') AS abnormal_move_actual_time,
vi.plate_number AS 车牌号,
vi.vin AS vin,
vm.brand AS 车辆品牌,
vm.model AS 车辆型号,
vi.body_color AS 车辆颜色,
vi.rental_company AS 租赁公司,
CASE vi.vehicle_source
WHEN '0' THEN '自有'
WHEN '1' THEN '挂靠'
WHEN '2' THEN '外租'
WHEN '3' THEN '自有'
ELSE vi.actual_ownership
END AS 车辆归属状态Label,
vm.model AS 车辆型号Label,
vm.vehicle_type AS 车辆类型参数,
vi.operation_city AS 库存区域,
vs.vehicle_status AS 车辆租赁状态,
vs.operation_status AS 车辆租赁状态Label,
CASE WHEN COALESCE(vs.operation_status, '') = '5' THEN 0 ELSE 1 END AS 是否营运,
COALESCE(info_province.NAME, NULLIF(info.province, ''), vi.province_name, vi_province.NAME, NULLIF(vi.province, '')) AS 省,
COALESCE(info_city.NAME, NULLIF(info.city, ''), vi.city_name, vi_city.NAME, vi_operation_city.NAME, NULLIF(vi.city, ''), NULLIF(vi.operation_city, '')) AS 市,
info.lat AS 纬度,
info.lng AS 经度,
CASE vm.brand
WHEN 'hyundai' THEN CASE WHEN vm.model LIKE '%帕力安%' OR vm.model LIKE '%冷链%' OR vm.model LIKE '%双飞翼%' THEN '帕力安牌' ELSE '现代' END
WHEN 'yuejin' THEN '跃进'
WHEN 'feichi' THEN '飞驰'
WHEN 'sulong' THEN '苏龙'
WHEN 'higer' THEN '海格'
WHEN 'dongfeng' THEN '东风'
WHEN 'yutong' THEN '宇通'
WHEN 'chufeng' THEN '楚风'
WHEN 'tonghua' THEN '通华'
WHEN 'maxus' THEN '大通'
WHEN 'mingwei' THEN '明威'
WHEN 'wanfeng' THEN '万风'
WHEN 'shujie' THEN '舒捷'
WHEN 'denza' THEN '腾势'
WHEN 'hongyan' THEN '红岩'
WHEN 'yuanchang brand' THEN '远程牌'
WHEN 'others' THEN '其他'
ELSE vm.brand
END AS 车辆品牌Label,
c.id AS 合同ID,
vor.contract_code AS 合同编码,
vor.customer_name AS 客户名称,
c.signing_company AS 合同归属公司,
vor.business_dept AS 合同归属部门,
NULLIF(vi.registered_ownership, '') AS 主体,
vor.project_name AS 项目名称,
vor.business_manager AS 客户经理,
CAST(COALESCE(c.business_manager_id, vi.business_id) AS CHAR) AS 经理ID
FROM vehicle_info vi
LEFT JOIN vehicle_status vs
ON vs.vehicle_id = vi.id
AND vs.del_flag = 0
LEFT JOIN vehicle_model vm
ON vm.id = vi.vehicle_model_id
AND vm.del_flag = '0'
LEFT JOIN tab_truck_remote_sync_realtime_info info
ON info.plate_number = vi.plate_number
AND info.is_deleted = 0
LEFT JOIN common_district info_province
ON info_province.CODE = info.province COLLATE utf8mb4_unicode_ci
AND info_province.STATUS = 'VALID'
LEFT JOIN common_district info_city
ON info_city.CODE = info.city COLLATE utf8mb4_unicode_ci
AND info_city.STATUS = 'VALID'
LEFT JOIN common_district vi_province
ON vi_province.CODE = vi.province COLLATE utf8mb4_unicode_ci
AND vi_province.STATUS = 'VALID'
LEFT JOIN common_district vi_city
ON vi_city.CODE = vi.city COLLATE utf8mb4_unicode_ci
AND vi_city.STATUS = 'VALID'
LEFT JOIN common_district vi_operation_city
ON vi_operation_city.CODE = vi.operation_city COLLATE utf8mb4_unicode_ci
AND vi_operation_city.STATUS = 'VALID'
-- Pending delivery uses the current active task, never a completed historical lease.
LEFT JOIN delivery_vehicle pdv ON pdv.id = (
SELECT dv.id
FROM delivery_vehicle dv
JOIN delivery_task_subject dts ON dts.id = dv.delivery_task_subject_id
AND dts.del_flag = '0' AND dts.status = 0
WHERE dv.vehicle_id = vi.id AND dv.del_flag = '0' AND dv.status = 0
AND dv.delivery_status IN (0, 1) AND vs.vehicle_status = '4'
ORDER BY dv.create_time DESC, dv.id DESC
LIMIT 1
)
LEFT JOIN delivery_task_subject pdts ON pdts.id = pdv.delivery_task_subject_id
-- Select one effective movement per vehicle; drafts and withdrawals are not operational work.
-- An ongoing movement takes precedence over completed history.
LEFT JOIN vehicle_abnormal_move_record amr ON amr.id = (
SELECT mr.id
FROM vehicle_abnormal_move_record mr
JOIN vehicle_abnormal_move m ON m.id = mr.move_id AND m.del_flag = '0'
WHERE mr.vehicle_id = vi.id AND mr.del_flag = '0' AND m.move_status IN (3, 5)
AND vs.vehicle_status = '14'
ORDER BY (m.move_status = 3) DESC, COALESCE(m.move_start_time, m.create_time) DESC, m.id DESC, mr.id DESC
LIMIT 1
)
LEFT JOIN vehicle_abnormal_move am ON am.id = amr.move_id
LEFT JOIN \`ry-cloud\`.sys_user am_applicant ON am_applicant.user_id = am.create_by
${LATEST_VEHICLE_LEASE_JOIN_SQL}
WHERE vi.del_flag = '0'
AND COALESCE(vs.operation_status, '') <> '5'`;
// Week range: last Saturday 00:00 to this Friday 23:59
// MySQL: WEEKDAY() returns 0=Monday..6=Sunday, Saturday=5
// "上周六-本周五": offset from today to last Saturday
export const WEEK_START_SQL = `DATE_SUB(CURDATE(), INTERVAL (WEEKDAY(CURDATE()) + 2) % 7 DAY)`;
export const WEEK_END_SQL = `DATE_ADD(${WEEK_START_SQL}, INTERVAL 7 DAY)`;
export const WEEKLY_TRUCK_ID_SQLS = [
`SELECT CAST(vehicle_id AS CHAR) AS truck_id
FROM vehicle_status
WHERE del_flag=0 AND vehicle_status='4' AND COALESCE(operation_status, '') <> '5'`,
`SELECT CAST(vehicle_id AS CHAR) AS truck_id
FROM delivery_vehicle
WHERE del_flag='0'
AND vehicle_id IS NOT NULL
AND delivery_time >= ${WEEK_START_SQL} AND delivery_time < ${WEEK_END_SQL}
AND delivery_status IN (2,3,5)`,
`SELECT CAST(vehicle_id AS CHAR) AS truck_id
FROM return_vehicle_task
WHERE del_flag='0'
AND vehicle_id IS NOT NULL
AND arrival_time >= ${WEEK_START_SQL} AND arrival_time < ${WEEK_END_SQL}
AND status IN (2,3,5)`,
`SELECT CAST(new_vehicle_id AS CHAR) AS truck_id
FROM vehicle_replacement
WHERE del_flag='0'
AND new_vehicle_id IS NOT NULL
AND replace_time >= ${WEEK_START_SQL} AND replace_time < ${WEEK_END_SQL}
AND status=20`,
] as const;
// 交车单 SQL
const DELIVERED_SQL = `SELECT
dv.id, DATE(dv.delivery_time) AS handover_date,
CAST(dv.vehicle_id AS CHAR) AS truck_id, dv.plate_number,
c.contract_type,
COALESCE(dts.customer_name, c.customer_name) AS customer_name
FROM delivery_vehicle dv
LEFT JOIN delivery_task_subject dts
ON dts.id = dv.delivery_task_subject_id
AND dts.del_flag = '0'
LEFT JOIN vehicle_lease_contract_info c
ON c.order_id = dv.contract_id
AND c.del_flag = '0'
WHERE dv.del_flag = '0'
AND dv.vehicle_id IS NOT NULL
AND dv.delivery_time IS NOT NULL
AND dv.delivery_status IN (2,3,5)`;
// 还车单 SQL
const RETURNED_SQL = `SELECT
r.id, DATE(r.arrival_time) AS handover_date,
CAST(r.vehicle_id AS CHAR) AS truck_id, r.plate_number,
c.contract_type,
COALESCE(dts.customer_name, c.customer_name) AS customer_name
FROM return_vehicle_task r
LEFT JOIN delivery_task_subject dts
ON dts.id = r.delivery_task_subject_id
AND dts.del_flag = '0'
LEFT JOIN vehicle_lease_contract_info c
ON c.order_id = r.contract_id
AND c.del_flag = '0'
WHERE r.del_flag = '0'
AND r.vehicle_id IS NOT NULL
AND r.arrival_time IS NOT NULL
AND r.status IN (2,3,5)`;
// 替换车单 SQL
const REPLACED_SQL = `SELECT
vr.id, DATE(vr.replace_time) AS handover_date,
CAST(vr.new_vehicle_id AS CHAR) AS truck_id, vr.new_vehicle_plate AS plate_number,
c.contract_type,
c.customer_name
FROM vehicle_replacement vr
LEFT JOIN vehicle_lease_contract_info c
ON c.id = vr.contract_id
AND c.del_flag = '0'
WHERE vr.del_flag = '0'
AND vr.new_vehicle_id IS NOT NULL
AND vr.replace_time IS NOT NULL
AND vr.status = 20`;
export const WEEKLY_DETAIL_SQL: Record<WeeklyDetailType, string> = {
delivered: `${DELIVERED_SQL} AND dv.delivery_time >= ${WEEK_START_SQL} AND dv.delivery_time < ${WEEK_END_SQL} ORDER BY dv.delivery_time DESC`,
returned: `${RETURNED_SQL} AND r.arrival_time >= ${WEEK_START_SQL} AND r.arrival_time < ${WEEK_END_SQL} ORDER BY r.arrival_time DESC`,
replaced: `${REPLACED_SQL} AND vr.replace_time >= ${WEEK_START_SQL} AND vr.replace_time < ${WEEK_END_SQL} ORDER BY vr.replace_time DESC`,
pending: `SELECT CAST(vi.id AS CHAR) AS truck_id, vi.plate_number, NULL AS handover_date, NULL AS contract_type, NULL AS customer_name
FROM vehicle_info vi
LEFT JOIN vehicle_status vs ON vs.vehicle_id=vi.id AND vs.del_flag=0
WHERE vi.del_flag='0' AND vs.vehicle_status='4' AND COALESCE(vs.operation_status, '') <> '5'`,
new: `SELECT CAST(vi.id AS CHAR) AS truck_id, vi.plate_number, vi.create_time AS handover_date, NULL AS contract_type, NULL AS customer_name
FROM vehicle_info vi
LEFT JOIN vehicle_status vs ON vs.vehicle_id=vi.id AND vs.del_flag=0
WHERE vi.del_flag='0' AND COALESCE(vs.operation_status, '') <> '5'
AND vi.create_time >= ${WEEK_START_SQL} AND vi.create_time < ${WEEK_END_SQL} ORDER BY vi.create_time DESC`,
};
export const FLOW_STATS_SQL = `
SELECT *
FROM (
SELECT
CONCAT(CASE WHEN dv.vehicle_replacement_id IS NOT NULL THEN 'replaced-' ELSE 'delivered-' END, dv.id) AS id,
CASE WHEN dv.vehicle_replacement_id IS NOT NULL THEN 'replaced' ELSE 'delivered' END AS type,
CASE WHEN dv.vehicle_replacement_id IS NOT NULL THEN '替换交车' ELSE '交车' END AS type_label,
DATE_FORMAT(dv.delivery_completion_processed_time, '%Y-%m-%d') AS stat_date,
CAST(dv.vehicle_id AS CHAR) AS truck_id,
dv.plate_number,
DATE_FORMAT(dv.delivery_completion_processed_time, '%Y-%m-%d %H:%i:%s') AS event_time,
DATE_FORMAT(dv.create_time, '%Y-%m-%d %H:%i:%s') AS submit_time,
c.business_department_name AS department,
c.business_manager_name AS manager,
COALESCE(dts.customer_name, c.customer_name) AS customer_name,
COALESCE(NULLIF(dts.contract_code, ''), c.contract_code) AS contract_no,
COALESCE(NULLIF(dts.project_name, ''), c.project_name) AS project_name
FROM delivery_vehicle dv
LEFT JOIN delivery_task_subject dts
ON dts.id = dv.delivery_task_subject_id
AND dts.del_flag = '0'
LEFT JOIN vehicle_lease_contract_info c
ON c.order_id = dv.contract_id
AND c.del_flag = '0'
WHERE dv.del_flag = '0'
AND dv.vehicle_id IS NOT NULL
AND dv.delivery_completion_processed_time IS NOT NULL
-- 待客户签章也计入交车闭环,日期仍按运维签章时间归属。
AND dv.delivery_status IN (2,3,5,20)
AND (dts.status IS NULL OR dts.status <> 10)
AND dv.delivery_completion_processed_time >= ?
AND dv.delivery_completion_processed_time < DATE_ADD(?, INTERVAL 1 DAY)
UNION ALL
SELECT
CONCAT('returned-', r.id) AS id,
'returned' AS type,
'还车' AS type_label,
DATE_FORMAT(r.return_completion_processed_time, '%Y-%m-%d') AS stat_date,
CAST(r.vehicle_id AS CHAR) AS truck_id,
r.plate_number,
DATE_FORMAT(r.return_completion_processed_time, '%Y-%m-%d %H:%i:%s') AS event_time,
DATE_FORMAT(r.create_time, '%Y-%m-%d %H:%i:%s') AS submit_time,
c.business_department_name AS department,
c.business_manager_name AS manager,
COALESCE(dts.customer_name, c.customer_name) AS customer_name,
COALESCE(NULLIF(dts.contract_code, ''), c.contract_code) AS contract_no,
COALESCE(NULLIF(dts.project_name, ''), c.project_name) AS project_name
FROM return_vehicle_task r
LEFT JOIN delivery_task_subject dts
ON dts.id = r.delivery_task_subject_id
AND dts.del_flag = '0'
LEFT JOIN vehicle_lease_contract_info c
ON c.order_id = r.contract_id
AND c.del_flag = '0'
WHERE r.del_flag = '0'
AND r.vehicle_id IS NOT NULL
AND r.return_completion_processed_time IS NOT NULL
-- 与还车管理历史记录一致:完成、已签章、待签署;日期按运维签章时间归属。
AND r.status IN (2,3,5)
AND r.return_completion_processed_time >= ?
AND r.return_completion_processed_time < DATE_ADD(?, INTERVAL 1 DAY)
) flow
ORDER BY flow.event_time DESC, flow.id DESC
`;
const CACHE_TTL = 5 * 1000;
export class VehicleRepository {
private cachedVehicles: Vehicle[] = [];
private lastFetchTime = 0;
private cachedWeeklyTruckIds: WeeklyTruckIds | null = null;
private weeklyTruckIdsLastFetch = 0;
private cachedWeeklyStats: WeeklyStats | null = null;
private weeklyStatsLastFetch = 0;
constructor(
private readonly db: Database,
private readonly now: () => number = Date.now,
private readonly mapVehicleRow: (row: VehicleRow) => Vehicle = transformRow,
) {}
async getVehicles(): Promise<Vehicle[]> {
const now = this.now();
if (this.cachedVehicles.length > 0 && now - this.lastFetchTime < CACHE_TTL) {
return this.cachedVehicles;
}
const [rows] = await this.db.query<any[]>(MAIN_SQL);
this.cachedVehicles = (rows as VehicleRow[]).map(this.mapVehicleRow);
this.lastFetchTime = now;
return this.cachedVehicles;
}
async getWeeklyTruckIds(): Promise<WeeklyTruckIds> {
const now = this.now();
if (this.cachedWeeklyTruckIds && now - this.weeklyTruckIdsLastFetch < CACHE_TTL) {
return this.cachedWeeklyTruckIds;
}
const [[pendingRows], [deliveredRows], [returnedRows], [replacedRows]] = await Promise.all(
WEEKLY_TRUCK_ID_SQLS.map((sql) => this.db.query<any[]>(sql)),
);
const toSet = (rows: any[]) => new Set(rows.map((r) => String(r.truck_id)).filter((s) => s && s !== 'null'));
this.cachedWeeklyTruckIds = {
pending: toSet(pendingRows as any[]),
delivered: toSet(deliveredRows as any[]),
returned: toSet(returnedRows as any[]),
replaced: toSet(replacedRows as any[]),
};
this.weeklyTruckIdsLastFetch = now;
return this.cachedWeeklyTruckIds;
}
async getWeeklyStats(): Promise<WeeklyStats> {
const now = this.now();
if (this.cachedWeeklyStats && now - this.weeklyStatsLastFetch < CACHE_TTL) {
return this.cachedWeeklyStats;
}
const [[pendingRows], [newRows], [removedRows], [deliveredRows], [returnedRows], [replacedRows]] = await Promise.all([
this.db.query<any[]>(`SELECT COUNT(*) AS cnt FROM vehicle_status WHERE del_flag=0 AND vehicle_status='4' AND COALESCE(operation_status, '') <> '5'`),
this.db.query<any[]>(`SELECT COUNT(*) AS cnt FROM vehicle_info WHERE del_flag='0' AND create_time >= ${WEEK_START_SQL} AND create_time < ${WEEK_END_SQL}`),
this.db.query<any[]>(`SELECT COUNT(*) AS cnt FROM vehicle_info vi LEFT JOIN vehicle_status vs ON vs.vehicle_id=vi.id AND vs.del_flag=0 WHERE (vi.del_flag='1' OR vs.operation_status='5') AND vi.update_time >= ${WEEK_START_SQL} AND vi.update_time < ${WEEK_END_SQL}`),
this.db.query<any[]>(`SELECT COUNT(*) AS cnt FROM delivery_vehicle
WHERE del_flag='0' AND delivery_time IS NOT NULL AND delivery_status IN (2,3,5)
AND delivery_time >= ${WEEK_START_SQL} AND delivery_time < ${WEEK_END_SQL}`),
this.db.query<any[]>(`SELECT COUNT(*) AS cnt FROM return_vehicle_task
WHERE del_flag='0' AND arrival_time IS NOT NULL AND status IN (2,3,5)
AND arrival_time >= ${WEEK_START_SQL} AND arrival_time < ${WEEK_END_SQL}`),
this.db.query<any[]>(`SELECT COUNT(*) AS cnt FROM vehicle_replacement
WHERE del_flag='0' AND replace_time IS NOT NULL AND status=20
AND replace_time >= ${WEEK_START_SQL} AND replace_time < ${WEEK_END_SQL}`),
]);
this.cachedWeeklyStats = {
pendingDelivery: (pendingRows as any[])[0]?.cnt || 0,
weeklyNew: (newRows as any[])[0]?.cnt || 0,
weeklyRemoved: (removedRows as any[])[0]?.cnt || 0,
weeklyDelivered: (deliveredRows as any[])[0]?.cnt || 0,
weeklyReturned: (returnedRows as any[])[0]?.cnt || 0,
weeklyReplaced: (replacedRows as any[])[0]?.cnt || 0,
};
this.weeklyStatsLastFetch = now;
return this.cachedWeeklyStats;
}
invalidateRefreshCaches(): void {
this.lastFetchTime = 0;
this.weeklyStatsLastFetch = 0;
}
async getRealtimeMileageRows(): Promise<any[]> {
const [rows] = await this.db.query<any[]>(`
SELECT plate_number, day_mileage
FROM tab_truck_remote_sync_realtime_info
WHERE is_deleted = 0 AND plate_number IS NOT NULL
`);
return rows;
}
async getAttendanceMileageRows(): Promise<any[]> {
const [rows] = await this.db.query<any[]>(`SELECT plate_number, day_mileage FROM tab_truck_remote_sync_realtime_info WHERE is_deleted = 0 AND plate_number IS NOT NULL`);
return rows;
}
async getWeeklyDetailRows(type: WeeklyDetailType): Promise<any[]> {
const [rows] = await this.db.query<any[]>(WEEKLY_DETAIL_SQL[type]);
return rows;
}
async getFlowDetailRows(start: string, end: string): Promise<FlowDetailRow[]> {
const [rows] = await this.db.query<any[]>(FLOW_STATS_SQL, [start, end, start, end]);
return rows as FlowDetailRow[];
}
async getWeeklyDebugData(): Promise<{
dateRange: any;
deliveredAll: any;
deliveredRecent: any;
latestTake: any;
returnedRecent: any;
latestReturn: any;
}> {
const [[dateRange]] = await this.db.query<any[]>(`SELECT
${WEEK_START_SQL} AS week_start,
${WEEK_END_SQL} AS week_end,
CURDATE() AS today,
WEEKDAY(CURDATE()) AS weekday`);
const [[deliveredAll]] = await this.db.query<any[]>(`SELECT COUNT(*) AS cnt FROM delivery_vehicle
WHERE del_flag='0' AND delivery_time IS NOT NULL AND delivery_status IN (2,3,5)`);
const [[deliveredRecent]] = await this.db.query<any[]>(`SELECT COUNT(*) AS cnt FROM delivery_vehicle
WHERE del_flag='0' AND delivery_time IS NOT NULL AND delivery_status IN (2,3,5)
AND delivery_time >= ${WEEK_START_SQL} AND delivery_time < ${WEEK_END_SQL}`);
const [[latestTake]] = await this.db.query<any[]>(`SELECT MAX(delivery_time) AS latest FROM delivery_vehicle
WHERE del_flag='0' AND delivery_time IS NOT NULL AND delivery_status IN (2,3,5)`);
const [[returnedRecent]] = await this.db.query<any[]>(`SELECT COUNT(*) AS cnt FROM return_vehicle_task
WHERE del_flag='0' AND arrival_time IS NOT NULL AND status IN (2,3,5)
AND arrival_time >= ${WEEK_START_SQL} AND arrival_time < ${WEEK_END_SQL}`);
const [[latestReturn]] = await this.db.query<any[]>(`SELECT MAX(arrival_time) AS latest FROM return_vehicle_task
WHERE del_flag='0' AND arrival_time IS NOT NULL AND status IN (2,3,5)`);
return { dateRange, deliveredAll, deliveredRecent, latestTake, returnedRecent, latestReturn };
}
async getRealtimeProvinceRows(): Promise<any[]> {
const [rows] = await this.db.query<any[]>(`SELECT plate_number, province FROM tab_truck_remote_sync_realtime_info WHERE is_deleted = 0 AND plate_number IS NOT NULL`);
return rows;
}
}
export const vehicleRepository = new VehicleRepository(pool as unknown as Database);