This commit is contained in:
@@ -1,19 +1,11 @@
|
||||
import { Hono } from 'hono';
|
||||
import mysqlPool from '../../db/mysql.js';
|
||||
import pool from '../../db.js';
|
||||
import { fetchVehicleInfoMap } from '../mileage/vehicle-info.js';
|
||||
import { fetchOneOsMileageDates } from '../mileage/oneos-api.js';
|
||||
import { mapRegion } from '../vehicles/model.js';
|
||||
import { mapRegion } from '../vehicles.js';
|
||||
import { filterByPermission, maskCustomerNames } from '../../auth/permissions.js';
|
||||
import { classifyVehicle, generateSuggestions } from './algorithm.js';
|
||||
import { fetchActiveNotificationMap, fetchRecentInterventionCount } from './notify.js';
|
||||
import {
|
||||
loadAssessmentTargets,
|
||||
loadAssessmentVehicles,
|
||||
loadInventoryVehicles,
|
||||
loadRealtimeLocations,
|
||||
loadVehicleTypes,
|
||||
type Database,
|
||||
} from './repository.js';
|
||||
import type { EnrichedVehicle, InventoryVehicle, SchedulingResponse, SchedulingSummary } from './types.js';
|
||||
import type { AuthUser } from '../../auth/types.js';
|
||||
|
||||
@@ -72,28 +64,17 @@ function recentCompletedDates(count: number): string[] {
|
||||
// Route
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface SuggestionsDependencies {
|
||||
db: Database;
|
||||
/**
|
||||
* 跨域数据源(里程域的车辆信息与 OneOS 里程)。
|
||||
* 显式作为依赖注入:既让本域可独立测试,也让"建议依赖里程数据"这件事在类型上可见。
|
||||
*/
|
||||
loadVehicleInfoMap?: typeof fetchVehicleInfoMap;
|
||||
loadOneOsMileageDates?: typeof fetchOneOsMileageDates;
|
||||
}
|
||||
const app = new Hono();
|
||||
|
||||
export function registerSuggestionsRoutes(app: Hono, deps: SuggestionsDependencies): void {
|
||||
const { db } = deps;
|
||||
const loadVehicleInfoMap = deps.loadVehicleInfoMap ?? fetchVehicleInfoMap;
|
||||
const loadOneOsMileageDates = deps.loadOneOsMileageDates ?? fetchOneOsMileageDates;
|
||||
|
||||
app.get('/', async (c) => {
|
||||
app.get('/', async (c) => {
|
||||
try {
|
||||
const targetIdParam = c.req.query('targetId');
|
||||
const filterTargetId = targetIdParam ? Number(targetIdParam) : null;
|
||||
|
||||
// ---- Query 1: Assessment targets ----
|
||||
const targets = await loadAssessmentTargets(db);
|
||||
const [targets] = await pool.execute(
|
||||
'SELECT id, target_name, annual_mileage_per_vehicle FROM lingniu_prod.tab_mileage_assessment_target WHERE is_deleted = 0 ORDER BY id',
|
||||
) as [any[], unknown];
|
||||
|
||||
const targetMap = new Map<number, { targetName: string; annualMileage: number }>();
|
||||
for (const t of targets) {
|
||||
@@ -104,13 +85,26 @@ export function registerSuggestionsRoutes(app: Hono, deps: SuggestionsDependenci
|
||||
}
|
||||
|
||||
// ---- Query 2: Assessment vehicles ----
|
||||
const assessmentRows = await loadAssessmentVehicles(db);
|
||||
const [assessmentRows] = await pool.execute(`
|
||||
SELECT target_id, plate_number, today_mileage, vehicle_total_mileage,
|
||||
current_mileage, current_year_mileage, current_year_mileage_task,
|
||||
completion_rate, is_qualified, current_year_is_qualified,
|
||||
daily_required_mileage, current_year_assessment_end_date
|
||||
FROM lingniu_prod.tab_mileage_assessment_vehicle WHERE is_deleted = 0
|
||||
`) as [any[], unknown];
|
||||
|
||||
// ---- Query 3: Vehicle info (customer, dept, manager) ----
|
||||
const vehicleInfoMap = await loadVehicleInfoMap();
|
||||
const vehicleInfoMap = await fetchVehicleInfoMap();
|
||||
|
||||
// ---- Query 4: Vehicle types from vehicle_info ----
|
||||
const truckTypeRows = await loadVehicleTypes(db);
|
||||
const [truckTypeRows] = await pool.execute(`
|
||||
SELECT vi.plate_number, vm.model AS type_name, vm.vehicle_type AS model_raw
|
||||
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'
|
||||
WHERE vi.del_flag = '0'
|
||||
AND COALESCE(vs.operation_status, '') <> '5'
|
||||
`) as [any[], unknown];
|
||||
|
||||
const truckTypeMap = new Map<string, { typeName: string; modelRaw: string }>();
|
||||
for (const row of truckTypeRows) {
|
||||
@@ -121,7 +115,11 @@ export function registerSuggestionsRoutes(app: Hono, deps: SuggestionsDependenci
|
||||
}
|
||||
|
||||
// ---- Query 5: Real-time location ----
|
||||
const locationRows = await loadRealtimeLocations(db);
|
||||
const [locationRows] = await pool.execute(`
|
||||
SELECT plate_number, province, city
|
||||
FROM tab_truck_remote_sync_realtime_info
|
||||
WHERE is_deleted = 0 AND plate_number IS NOT NULL
|
||||
`) as [any[], unknown];
|
||||
|
||||
const locationMap = new Map<string, { province: string; city: string }>();
|
||||
for (const row of locationRows) {
|
||||
@@ -141,7 +139,7 @@ export function registerSuggestionsRoutes(app: Hono, deps: SuggestionsDependenci
|
||||
const dates = recentCompletedDates(30);
|
||||
const sevenDayDates = new Set(dates.slice(-7));
|
||||
const allowedPlates = new Set(allPlates);
|
||||
const rowsByDate = await loadOneOsMileageDates(dates);
|
||||
const rowsByDate = await fetchOneOsMileageDates(dates);
|
||||
const aggregates = new Map<string, { sum30: number; count30: number; sum7: number; count7: number }>();
|
||||
for (const [date, rows] of rowsByDate) {
|
||||
for (const row of rows) {
|
||||
@@ -184,7 +182,15 @@ export function registerSuggestionsRoutes(app: Hono, deps: SuggestionsDependenci
|
||||
}
|
||||
|
||||
// ---- Query 7: Inventory vehicles (rent_status = 0) ----
|
||||
const inventoryTruckRows = await loadInventoryVehicles(db);
|
||||
const [inventoryTruckRows] = await pool.execute(`
|
||||
SELECT vi.plate_number, vm.model AS type_name, vm.vehicle_type AS model_raw
|
||||
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'
|
||||
WHERE vi.del_flag = '0'
|
||||
AND COALESCE(vs.operation_status, '') IN ('3','4')
|
||||
AND COALESCE(vs.vehicle_status, '') <> '4'
|
||||
`) as [any[], unknown];
|
||||
|
||||
// ---- Build assessment vehicle lookup for inventory cross-reference ----
|
||||
const assessmentByPlate = new Map<string, any>();
|
||||
@@ -305,7 +311,7 @@ export function registerSuggestionsRoutes(app: Hono, deps: SuggestionsDependenci
|
||||
const { suggestions, summary } = generateSuggestions(enrichedVehicles, inventoryVehicles);
|
||||
|
||||
// ---- Attach notification status to candidates ----
|
||||
const notificationMap = await fetchActiveNotificationMap(db);
|
||||
const notificationMap = await fetchActiveNotificationMap();
|
||||
for (const s of suggestions) {
|
||||
for (const c of s.candidates) {
|
||||
const key = `${s.id}::${c.plateNumber}`;
|
||||
@@ -359,7 +365,7 @@ export function registerSuggestionsRoutes(app: Hono, deps: SuggestionsDependenci
|
||||
// Recalculate summary based on permission-filtered results
|
||||
const filteredQualified = masked.filter((s: any) => s.type === 'replace_qualified').length;
|
||||
const filteredHopeless = masked.filter((s: any) => s.type === 'rescue_hopeless').length;
|
||||
const recentInterventionCount = await fetchRecentInterventionCount(db);
|
||||
const recentInterventionCount = await fetchRecentInterventionCount();
|
||||
const filteredSummary: SchedulingSummary = {
|
||||
qualifiedCount: filteredQualified,
|
||||
hopelessCount: filteredHopeless,
|
||||
@@ -390,14 +396,4 @@ export function registerSuggestionsRoutes(app: Hono, deps: SuggestionsDependenci
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/** 生产用路由器:绑定真实连接池。 */
|
||||
export function createSuggestionsRouter(): Hono {
|
||||
const app = new Hono();
|
||||
registerSuggestionsRoutes(app, { db: mysqlPool });
|
||||
return app;
|
||||
}
|
||||
|
||||
const app = createSuggestionsRouter();
|
||||
export default app;
|
||||
|
||||
Reference in New Issue
Block a user