revert: restore v1.1.14 (37a9f303)
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
shishengliang
2026-09-16 09:29:30 +08:00
parent c49a02e3d9
commit 6ea1b3d5dd
210 changed files with 4448 additions and 36179 deletions
+40 -44
View File
@@ -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;