fix asset module database migration
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
This commit is contained in:
@@ -5,11 +5,11 @@ services:
|
|||||||
image: harbor.lnh2e.com/lingniu-v1/ln-bi:main-1.0.0
|
image: harbor.lnh2e.com/lingniu-v1/ln-bi:main-1.0.0
|
||||||
network_mode: host
|
network_mode: host
|
||||||
environment:
|
environment:
|
||||||
DB_HOST: "47.101.148.99"
|
DB_HOST: "rm-bp179zbv481rnw3e2no.mysql.rds.aliyuncs.com"
|
||||||
DB_PORT: "3306"
|
DB_PORT: "3306"
|
||||||
DB_USER: "root"
|
DB_USER: "oneos_db_prod"
|
||||||
DB_PASSWORD: "LN#Passw0rd@2026"
|
DB_PASSWORD: "adASHJcviqwjkbn23ngt1"
|
||||||
DB_NAME: "lingniu_prod"
|
DB_NAME: "ln_asset_management"
|
||||||
HYDROGEN_DB_HOST: "47.99.185.173"
|
HYDROGEN_DB_HOST: "47.99.185.173"
|
||||||
HYDROGEN_DB_PORT: "3306"
|
HYDROGEN_DB_PORT: "3306"
|
||||||
HYDROGEN_DB_USER: "root"
|
HYDROGEN_DB_USER: "root"
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ export interface VehicleListItem {
|
|||||||
city: string | null;
|
city: string | null;
|
||||||
status: string;
|
status: string;
|
||||||
ownership: string;
|
ownership: string;
|
||||||
|
rentCompany?: string | null;
|
||||||
contractNo: string | null;
|
contractNo: string | null;
|
||||||
customerName: string | null;
|
customerName: string | null;
|
||||||
subjectOrg: string | null;
|
subjectOrg: string | null;
|
||||||
|
|||||||
@@ -52,11 +52,15 @@ app.get('/exchange', async (c) => {
|
|||||||
// 查询 depCode 对应的部门名称
|
// 查询 depCode 对应的部门名称
|
||||||
let depName = '';
|
let depName = '';
|
||||||
if (userInfo.depCode) {
|
if (userInfo.depCode) {
|
||||||
|
try {
|
||||||
const [rows] = await pool.execute(
|
const [rows] = await pool.execute(
|
||||||
'SELECT dep_name FROM tab_department WHERE dep_code = ? AND is_deleted = 0 LIMIT 1',
|
'SELECT dep_name FROM tab_department WHERE dep_code = ? AND is_deleted = 0 LIMIT 1',
|
||||||
[userInfo.depCode]
|
[userInfo.depCode]
|
||||||
) as [{ dep_name: string }[], unknown];
|
) as [{ dep_name: string }[], unknown];
|
||||||
depName = rows[0]?.dep_name || '';
|
depName = rows[0]?.dep_name || '';
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e?.code !== 'ER_NO_SUCH_TABLE') throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload: JwtPayload = {
|
const payload: JwtPayload = {
|
||||||
|
|||||||
@@ -149,8 +149,8 @@ async function buildPlateLookup(plates: Set<string>): Promise<Map<string, string
|
|||||||
const placeholders = arr.map(() => '?').join(',');
|
const placeholders = arr.map(() => '?').join(',');
|
||||||
const [rows] = await pool.query<RowDataPacket[]>(
|
const [rows] = await pool.query<RowDataPacket[]>(
|
||||||
`SELECT plate_number, CAST(id AS CHAR) AS truck_id
|
`SELECT plate_number, CAST(id AS CHAR) AS truck_id
|
||||||
FROM tab_truck
|
FROM vehicle_info
|
||||||
WHERE is_deleted = 0 AND plate_number IN (${placeholders})`,
|
WHERE del_flag = '0' AND plate_number IN (${placeholders})`,
|
||||||
arr,
|
arr,
|
||||||
);
|
);
|
||||||
const map = new Map<string, string>();
|
const map = new Map<string, string>();
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ interface TargetRow {
|
|||||||
async function fetchTargetRows(): Promise<TargetRow[]> {
|
async function fetchTargetRows(): Promise<TargetRow[]> {
|
||||||
return pool.execute(
|
return pool.execute(
|
||||||
`SELECT t.id, t.target_name, v.plate_number
|
`SELECT t.id, t.target_name, v.plate_number
|
||||||
FROM tab_mileage_assessment_target t
|
FROM lingniu_prod.tab_mileage_assessment_target t
|
||||||
JOIN tab_mileage_assessment_vehicle v ON v.target_id = t.id AND v.is_deleted = 0
|
JOIN lingniu_prod.tab_mileage_assessment_vehicle v ON v.target_id = t.id AND v.is_deleted = 0
|
||||||
WHERE t.is_deleted = 0`
|
WHERE t.is_deleted = 0`
|
||||||
).then(([rows]) => rows as TargetRow[]);
|
).then(([rows]) => rows as TargetRow[]);
|
||||||
}
|
}
|
||||||
@@ -102,10 +102,10 @@ function buildPlateTargetNamesMap(targetRows: TargetRow[]): Map<string, string[]
|
|||||||
|
|
||||||
async function fetchBizTotalMileageMap(): Promise<Map<string, number>> {
|
async function fetchBizTotalMileageMap(): Promise<Map<string, number>> {
|
||||||
// v_vehicle_daily_stats.total_km 对 G7S 数据源常为 NULL(G7 只回传日增量),
|
// v_vehicle_daily_stats.total_km 对 G7S 数据源常为 NULL(G7 只回传日增量),
|
||||||
// 业务库 tab_mileage_assessment_vehicle.vehicle_total_mileage 是累加后的权威累计值,
|
// 业务库 lingniu_prod.tab_mileage_assessment_vehicle.vehicle_total_mileage 是累加后的权威累计值,
|
||||||
// 用它兜底保证 totalKm 汇总完整。
|
// 用它兜底保证 totalKm 汇总完整。
|
||||||
const [rows] = await pool.execute(
|
const [rows] = await pool.execute(
|
||||||
'SELECT plate_number, vehicle_total_mileage FROM tab_mileage_assessment_vehicle WHERE is_deleted = 0'
|
'SELECT plate_number, vehicle_total_mileage FROM lingniu_prod.tab_mileage_assessment_vehicle WHERE is_deleted = 0'
|
||||||
) as [{ plate_number: string; vehicle_total_mileage: string | number | null }[], unknown];
|
) as [{ plate_number: string; vehicle_total_mileage: string | number | null }[], unknown];
|
||||||
const map = new Map<string, number>();
|
const map = new Map<string, number>();
|
||||||
for (const r of rows) {
|
for (const r of rows) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const app = new Hono();
|
|||||||
app.get('/', async (c) => {
|
app.get('/', async (c) => {
|
||||||
try {
|
try {
|
||||||
const [targets] = await pool.execute(
|
const [targets] = await pool.execute(
|
||||||
'SELECT * FROM tab_mileage_assessment_target WHERE is_deleted = 0 ORDER BY id'
|
'SELECT * FROM lingniu_prod.tab_mileage_assessment_target WHERE is_deleted = 0 ORDER BY id'
|
||||||
) as [any[], unknown];
|
) as [any[], unknown];
|
||||||
|
|
||||||
const [vehicleStats] = await pool.execute(`
|
const [vehicleStats] = await pool.execute(`
|
||||||
@@ -25,7 +25,7 @@ app.get('/', async (c) => {
|
|||||||
SUM(current_year_mileage_task) as current_year_target,
|
SUM(current_year_mileage_task) as current_year_target,
|
||||||
SUM(current_year_mileage) as current_year_completed,
|
SUM(current_year_mileage) as current_year_completed,
|
||||||
MAX(current_year_assessment_end_date) as year_end_date
|
MAX(current_year_assessment_end_date) as year_end_date
|
||||||
FROM tab_mileage_assessment_vehicle WHERE is_deleted = 0
|
FROM lingniu_prod.tab_mileage_assessment_vehicle WHERE is_deleted = 0
|
||||||
GROUP BY target_id
|
GROUP BY target_id
|
||||||
`) as [any[], unknown];
|
`) as [any[], unknown];
|
||||||
|
|
||||||
@@ -44,8 +44,8 @@ app.get('/', async (c) => {
|
|||||||
SUM(CASE WHEN v.current_mileage >= t.annual_mileage_per_vehicle * 0.5 THEN 1 ELSE 0 END) as first_year_half_qualified_count,
|
SUM(CASE WHEN v.current_mileage >= t.annual_mileage_per_vehicle * 0.5 THEN 1 ELSE 0 END) as first_year_half_qualified_count,
|
||||||
DATE_FORMAT(MIN(v.assessment_start_date), '%Y-%m-%d') as first_year_start_date,
|
DATE_FORMAT(MIN(v.assessment_start_date), '%Y-%m-%d') as first_year_start_date,
|
||||||
DATE_FORMAT(MAX(DATE_SUB(DATE_ADD(v.assessment_start_date, INTERVAL 1 YEAR), INTERVAL 1 DAY)), '%Y-%m-%d') as first_year_end_date
|
DATE_FORMAT(MAX(DATE_SUB(DATE_ADD(v.assessment_start_date, INTERVAL 1 YEAR), INTERVAL 1 DAY)), '%Y-%m-%d') as first_year_end_date
|
||||||
FROM tab_mileage_assessment_vehicle v
|
FROM lingniu_prod.tab_mileage_assessment_vehicle v
|
||||||
JOIN tab_mileage_assessment_target t ON t.id = v.target_id AND t.is_deleted = 0
|
JOIN lingniu_prod.tab_mileage_assessment_target t ON t.id = v.target_id AND t.is_deleted = 0
|
||||||
WHERE v.is_deleted = 0
|
WHERE v.is_deleted = 0
|
||||||
GROUP BY v.target_id
|
GROUP BY v.target_id
|
||||||
`) as [any[], unknown];
|
`) as [any[], unknown];
|
||||||
@@ -67,8 +67,8 @@ app.get('/', async (c) => {
|
|||||||
SUM(CASE WHEN v.current_mileage >= t.annual_mileage_per_vehicle * y.year_number * 0.5 THEN 1 ELSE 0 END) as half_qualified_count,
|
SUM(CASE WHEN v.current_mileage >= t.annual_mileage_per_vehicle * y.year_number * 0.5 THEN 1 ELSE 0 END) as half_qualified_count,
|
||||||
DATE_FORMAT(MIN(DATE_ADD(v.assessment_start_date, INTERVAL y.year_number - 1 YEAR)), '%Y-%m-%d') as start_date,
|
DATE_FORMAT(MIN(DATE_ADD(v.assessment_start_date, INTERVAL y.year_number - 1 YEAR)), '%Y-%m-%d') as start_date,
|
||||||
DATE_FORMAT(MAX(DATE_SUB(DATE_ADD(v.assessment_start_date, INTERVAL y.year_number YEAR), INTERVAL 1 DAY)), '%Y-%m-%d') as end_date
|
DATE_FORMAT(MAX(DATE_SUB(DATE_ADD(v.assessment_start_date, INTERVAL y.year_number YEAR), INTERVAL 1 DAY)), '%Y-%m-%d') as end_date
|
||||||
FROM tab_mileage_assessment_vehicle v
|
FROM lingniu_prod.tab_mileage_assessment_vehicle v
|
||||||
JOIN tab_mileage_assessment_target t ON t.id = v.target_id AND t.is_deleted = 0
|
JOIN lingniu_prod.tab_mileage_assessment_target t ON t.id = v.target_id AND t.is_deleted = 0
|
||||||
JOIN (
|
JOIN (
|
||||||
SELECT 1 as year_number UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5
|
SELECT 1 as year_number UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5
|
||||||
) y ON y.year_number <= LEAST(t.assessment_years, v.current_year_number)
|
) y ON y.year_number <= LEAST(t.assessment_years, v.current_year_number)
|
||||||
@@ -91,8 +91,8 @@ app.get('/', async (c) => {
|
|||||||
DATE_FORMAT(DATE_ADD(v.assessment_start_date, INTERVAL y.year_number - 1 YEAR), '%Y-%m-%d') as start_date,
|
DATE_FORMAT(DATE_ADD(v.assessment_start_date, INTERVAL y.year_number - 1 YEAR), '%Y-%m-%d') as start_date,
|
||||||
DATE_FORMAT(DATE_SUB(DATE_ADD(v.assessment_start_date, INTERVAL y.year_number YEAR), INTERVAL 1 DAY), '%Y-%m-%d') as end_date,
|
DATE_FORMAT(DATE_SUB(DATE_ADD(v.assessment_start_date, INTERVAL y.year_number YEAR), INTERVAL 1 DAY), '%Y-%m-%d') as end_date,
|
||||||
COUNT(*) as cnt
|
COUNT(*) as cnt
|
||||||
FROM tab_mileage_assessment_vehicle v
|
FROM lingniu_prod.tab_mileage_assessment_vehicle v
|
||||||
JOIN tab_mileage_assessment_target t ON t.id = v.target_id AND t.is_deleted = 0
|
JOIN lingniu_prod.tab_mileage_assessment_target t ON t.id = v.target_id AND t.is_deleted = 0
|
||||||
JOIN (
|
JOIN (
|
||||||
SELECT 1 as year_number UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5
|
SELECT 1 as year_number UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5
|
||||||
) y ON y.year_number <= LEAST(t.assessment_years, v.current_year_number)
|
) y ON y.year_number <= LEAST(t.assessment_years, v.current_year_number)
|
||||||
@@ -114,7 +114,7 @@ app.get('/', async (c) => {
|
|||||||
DATE_FORMAT(assessment_start_date, '%Y-%m-%d') as start_date,
|
DATE_FORMAT(assessment_start_date, '%Y-%m-%d') as start_date,
|
||||||
DATE_FORMAT(assessment_end_date, '%Y-%m-%d') as end_date,
|
DATE_FORMAT(assessment_end_date, '%Y-%m-%d') as end_date,
|
||||||
COUNT(*) as cnt
|
COUNT(*) as cnt
|
||||||
FROM tab_mileage_assessment_vehicle WHERE is_deleted = 0
|
FROM lingniu_prod.tab_mileage_assessment_vehicle WHERE is_deleted = 0
|
||||||
GROUP BY target_id, assessment_start_date, assessment_end_date
|
GROUP BY target_id, assessment_start_date, assessment_end_date
|
||||||
ORDER BY target_id, assessment_start_date
|
ORDER BY target_id, assessment_start_date
|
||||||
`) as [any[], unknown];
|
`) as [any[], unknown];
|
||||||
@@ -135,7 +135,7 @@ app.get('/', async (c) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [targetVehicleRows] = await pool.execute(
|
const [targetVehicleRows] = await pool.execute(
|
||||||
'SELECT target_id, plate_number FROM tab_mileage_assessment_vehicle WHERE is_deleted = 0'
|
'SELECT target_id, plate_number FROM lingniu_prod.tab_mileage_assessment_vehicle WHERE is_deleted = 0'
|
||||||
) as [{ target_id: number; plate_number: string }[], unknown];
|
) as [{ target_id: number; plate_number: string }[], unknown];
|
||||||
|
|
||||||
const targetIdPlatesMap = new Map<number, string[]>();
|
const targetIdPlatesMap = new Map<number, string[]>();
|
||||||
@@ -245,7 +245,7 @@ app.get('/:id/vehicles', async (c) => {
|
|||||||
`SELECT plate_number, today_mileage, vehicle_total_mileage,
|
`SELECT plate_number, today_mileage, vehicle_total_mileage,
|
||||||
completion_rate, is_qualified, current_year_is_qualified,
|
completion_rate, is_qualified, current_year_is_qualified,
|
||||||
daily_required_mileage
|
daily_required_mileage
|
||||||
FROM tab_mileage_assessment_vehicle
|
FROM lingniu_prod.tab_mileage_assessment_vehicle
|
||||||
WHERE target_id = ? AND is_deleted = 0
|
WHERE target_id = ? AND is_deleted = 0
|
||||||
ORDER BY today_mileage DESC`,
|
ORDER BY today_mileage DESC`,
|
||||||
[targetId]
|
[targetId]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ app.get('/', async (c) => {
|
|||||||
let plates: string[] = [];
|
let plates: string[] = [];
|
||||||
if (targetId) {
|
if (targetId) {
|
||||||
const [vehicleRows] = await pool.execute(
|
const [vehicleRows] = await pool.execute(
|
||||||
'SELECT plate_number FROM tab_mileage_assessment_vehicle WHERE target_id = ? AND is_deleted = 0',
|
'SELECT plate_number FROM lingniu_prod.tab_mileage_assessment_vehicle WHERE target_id = ? AND is_deleted = 0',
|
||||||
[targetId]
|
[targetId]
|
||||||
) as [{ plate_number: string }[], unknown];
|
) as [{ plate_number: string }[], unknown];
|
||||||
plates = vehicleRows.map(r => r.plate_number);
|
plates = vehicleRows.map(r => r.plate_number);
|
||||||
|
|||||||
@@ -3,24 +3,42 @@ import type { VehicleInfoRow } from './types.js';
|
|||||||
|
|
||||||
/** 车辆关联信息 SQL(客户名、部门、经理、租赁状态、主体、项目) */
|
/** 车辆关联信息 SQL(客户名、部门、经理、租赁状态、主体、项目) */
|
||||||
export const VEHICLE_INFO_SQL = `SELECT
|
export const VEHICLE_INFO_SQL = `SELECT
|
||||||
truck.plate_number AS plate,
|
vi.plate_number AS plate,
|
||||||
cus.customer_name AS customer,
|
COALESCE(c.customer_name, vor.customer_name, ci.customer_name) AS customer,
|
||||||
dep.dep_name AS department,
|
COALESCE(c.business_department_name, vor.business_dept) AS department,
|
||||||
u.user_name AS manager,
|
COALESCE(c.business_manager_name, vor.business_manager) AS manager,
|
||||||
CAST(c.bd AS CHAR) AS manager_id,
|
CAST(COALESCE(c.business_manager_id, vi.business_id) AS CHAR) AS manager_id,
|
||||||
dic_status.dic_name AS rent_status,
|
CASE vs.operation_status
|
||||||
org_truck.org_name AS entity,
|
WHEN '1' THEN '租赁'
|
||||||
c.project_name AS project
|
WHEN '2' THEN '自营'
|
||||||
FROM tab_truck truck
|
WHEN '3' THEN '可运营'
|
||||||
LEFT JOIN tab_truck_status_info si ON si.truck_id = truck.id AND si.is_deleted = 0
|
WHEN '4' THEN '待运营'
|
||||||
LEFT JOIN tab_contract c ON c.id = si.contract_id AND c.is_deleted = 0
|
WHEN '5' THEN '退出运营'
|
||||||
LEFT JOIN tab_customer cus ON cus.id = c.customer_id AND cus.is_deleted = 0
|
ELSE vs.operation_status
|
||||||
LEFT JOIN tab_user u ON u.id = c.bd AND u.is_deleted = 0
|
END AS rent_status,
|
||||||
LEFT JOIN tab_department dep ON dep.id = u.dep_id AND dep.is_deleted = 0
|
NULLIF(vi.registered_ownership, '') AS entity,
|
||||||
LEFT JOIN tab_dic dic_status ON dic_status.parent_code = 'dic_truck_rent_status'
|
COALESCE(c.project_name, vor.project_name) AS project
|
||||||
AND dic_status.dic_code = truck.truck_rent_status AND dic_status.is_deleted = 0
|
FROM vehicle_info vi
|
||||||
LEFT JOIN tab_org org_truck ON org_truck.id = truck.org_id AND org_truck.is_deleted = 0
|
LEFT JOIN vehicle_status vs
|
||||||
WHERE truck.is_deleted = 0 AND truck.is_operation = 1`;
|
ON vs.vehicle_id = vi.id
|
||||||
|
AND vs.del_flag = 0
|
||||||
|
LEFT JOIN vehicle_lease_order_record vor
|
||||||
|
ON vor.vehicle_id = vi.id
|
||||||
|
AND vor.del_flag = '0'
|
||||||
|
AND vor.id = (
|
||||||
|
SELECT MAX(vor2.id)
|
||||||
|
FROM vehicle_lease_order_record vor2
|
||||||
|
WHERE vor2.vehicle_id = vi.id
|
||||||
|
AND vor2.del_flag = '0'
|
||||||
|
)
|
||||||
|
LEFT JOIN vehicle_lease_contract_info c
|
||||||
|
ON c.order_id = vor.contract_id
|
||||||
|
AND c.del_flag = '0'
|
||||||
|
LEFT JOIN customer_info ci
|
||||||
|
ON ci.id = vi.customer_id
|
||||||
|
AND ci.del_flag = '0'
|
||||||
|
WHERE vi.del_flag = '0'
|
||||||
|
AND COALESCE(vs.operation_status, '') <> '5'`;
|
||||||
|
|
||||||
/** 查询所有车辆关联信息,返回 plate→info 的 Map */
|
/** 查询所有车辆关联信息,返回 plate→info 的 Map */
|
||||||
export async function fetchVehicleInfoMap(): Promise<Map<string, VehicleInfoRow>> {
|
export async function fetchVehicleInfoMap(): Promise<Map<string, VehicleInfoRow>> {
|
||||||
@@ -36,7 +54,7 @@ export async function fetchVehicleInfoMap(): Promise<Map<string, VehicleInfoRow>
|
|||||||
export async function fetchVehicleInfoByPlates(plates: string[]): Promise<Map<string, VehicleInfoRow>> {
|
export async function fetchVehicleInfoByPlates(plates: string[]): Promise<Map<string, VehicleInfoRow>> {
|
||||||
if (plates.length === 0) return new Map();
|
if (plates.length === 0) return new Map();
|
||||||
const [rows] = await pool.execute(
|
const [rows] = await pool.execute(
|
||||||
`${VEHICLE_INFO_SQL} AND truck.plate_number IN (${plates.map(() => '?').join(',')})`,
|
`${VEHICLE_INFO_SQL} AND vi.plate_number IN (${plates.map(() => '?').join(',')})`,
|
||||||
plates
|
plates
|
||||||
) as [VehicleInfoRow[], unknown];
|
) as [VehicleInfoRow[], unknown];
|
||||||
const map = new Map<string, VehicleInfoRow>();
|
const map = new Map<string, VehicleInfoRow>();
|
||||||
|
|||||||
@@ -28,16 +28,17 @@ function inferTypeFromTargetName(targetName: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Classify vehicle type from dic_type.dic_name (e.g. "4.5吨冷链车", "4.5吨货车", "18吨双飞翼货车").
|
* Classify vehicle type from ln_asset_management.vehicle_model.
|
||||||
* The typeName is the full label from the dictionary, modelRaw is the numeric dic_code.
|
* modelRaw is vehicle_model.vehicle_type, which is not the old dic_truck_type code.
|
||||||
*/
|
*/
|
||||||
function classifyVehicleType(typeName: string, _modelRaw: string): string {
|
function classifyVehicleType(typeName: string, _modelRaw: string): string {
|
||||||
const t = (typeName || '').trim();
|
const t = (typeName || '').trim();
|
||||||
if (t.includes('4.5') && t.includes('冷链')) return '4.5T冷链';
|
if (t.includes('4.5') && t.includes('冷链')) return '4.5T冷链';
|
||||||
if (t.includes('4.5')) return '4.5T普货';
|
if (t.includes('4.5')) return '4.5T普货';
|
||||||
if (t.includes('18')) return '18T';
|
if (t.includes('18')) return '18T';
|
||||||
if (t.includes('49') || t.includes('牵引')) return '49T';
|
|
||||||
if (t.includes('挂车')) return '挂车';
|
if (t.includes('挂车')) return '挂车';
|
||||||
|
if (t.includes('49')) return '49T';
|
||||||
|
if (t.includes('35')) return '35T';
|
||||||
return t || '其他';
|
return t || '其他';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ app.get('/', async (c) => {
|
|||||||
|
|
||||||
// ---- Query 1: Assessment targets ----
|
// ---- Query 1: Assessment targets ----
|
||||||
const [targets] = await pool.execute(
|
const [targets] = await pool.execute(
|
||||||
'SELECT id, target_name, annual_mileage_per_vehicle FROM tab_mileage_assessment_target WHERE is_deleted = 0 ORDER BY id',
|
'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];
|
) as [any[], unknown];
|
||||||
|
|
||||||
const targetMap = new Map<number, { targetName: string; annualMileage: number }>();
|
const targetMap = new Map<number, { targetName: string; annualMileage: number }>();
|
||||||
@@ -71,21 +72,20 @@ app.get('/', async (c) => {
|
|||||||
current_mileage, current_year_mileage, current_year_mileage_task,
|
current_mileage, current_year_mileage, current_year_mileage_task,
|
||||||
completion_rate, is_qualified, current_year_is_qualified,
|
completion_rate, is_qualified, current_year_is_qualified,
|
||||||
daily_required_mileage, current_year_assessment_end_date
|
daily_required_mileage, current_year_assessment_end_date
|
||||||
FROM tab_mileage_assessment_vehicle WHERE is_deleted = 0
|
FROM lingniu_prod.tab_mileage_assessment_vehicle WHERE is_deleted = 0
|
||||||
`) as [any[], unknown];
|
`) as [any[], unknown];
|
||||||
|
|
||||||
// ---- Query 3: Vehicle info (customer, dept, manager) ----
|
// ---- Query 3: Vehicle info (customer, dept, manager) ----
|
||||||
const vehicleInfoMap = await fetchVehicleInfoMap();
|
const vehicleInfoMap = await fetchVehicleInfoMap();
|
||||||
|
|
||||||
// ---- Query 4: Vehicle types from tab_truck ----
|
// ---- Query 4: Vehicle types from vehicle_info ----
|
||||||
// Include soft-deleted trucks: many assessment vehicles have is_deleted=1 in tab_truck
|
|
||||||
// but are still active in the assessment. We need their type info.
|
|
||||||
const [truckTypeRows] = await pool.execute(`
|
const [truckTypeRows] = await pool.execute(`
|
||||||
SELECT truck.plate_number, dic_type.dic_name AS type_name, truck.model AS model_raw
|
SELECT vi.plate_number, vm.model AS type_name, vm.vehicle_type AS model_raw
|
||||||
FROM tab_truck truck
|
FROM vehicle_info vi
|
||||||
LEFT JOIN tab_dic dic_type ON dic_type.parent_code = 'dic_truck_type'
|
LEFT JOIN vehicle_status vs ON vs.vehicle_id = vi.id AND vs.del_flag = 0
|
||||||
AND dic_type.dic_code = truck.model AND dic_type.is_deleted = 0
|
LEFT JOIN vehicle_model vm ON vm.id = vi.vehicle_model_id AND vm.del_flag = '0'
|
||||||
WHERE truck.is_operation = 1
|
WHERE vi.del_flag = '0'
|
||||||
|
AND COALESCE(vs.operation_status, '') <> '5'
|
||||||
`) as [any[], unknown];
|
`) as [any[], unknown];
|
||||||
|
|
||||||
const truckTypeMap = new Map<string, { typeName: string; modelRaw: string }>();
|
const truckTypeMap = new Map<string, { typeName: string; modelRaw: string }>();
|
||||||
@@ -161,12 +161,13 @@ app.get('/', async (c) => {
|
|||||||
|
|
||||||
// ---- Query 7: Inventory vehicles (rent_status = 0) ----
|
// ---- Query 7: Inventory vehicles (rent_status = 0) ----
|
||||||
const [inventoryTruckRows] = await pool.execute(`
|
const [inventoryTruckRows] = await pool.execute(`
|
||||||
SELECT truck.plate_number, dic_type.dic_name AS type_name, truck.model AS model_raw
|
SELECT vi.plate_number, vm.model AS type_name, vm.vehicle_type AS model_raw
|
||||||
FROM tab_truck truck
|
FROM vehicle_info vi
|
||||||
LEFT JOIN tab_dic dic_type ON dic_type.parent_code = 'dic_truck_type'
|
LEFT JOIN vehicle_status vs ON vs.vehicle_id = vi.id AND vs.del_flag = 0
|
||||||
AND dic_type.dic_code = truck.model AND dic_type.is_deleted = 0
|
LEFT JOIN vehicle_model vm ON vm.id = vi.vehicle_model_id AND vm.del_flag = '0'
|
||||||
WHERE truck.is_deleted = 0 AND truck.is_operation = 1
|
WHERE vi.del_flag = '0'
|
||||||
AND truck.truck_rent_status = 0
|
AND COALESCE(vs.operation_status, '') IN ('3','4')
|
||||||
|
AND COALESCE(vs.vehicle_status, '') <> '4'
|
||||||
`) as [any[], unknown];
|
`) as [any[], unknown];
|
||||||
|
|
||||||
// ---- Build assessment vehicle lookup for inventory cross-reference ----
|
// ---- Build assessment vehicle lookup for inventory cross-reference ----
|
||||||
|
|||||||
@@ -17,75 +17,100 @@ import type { Context } from 'hono';
|
|||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
|
|
||||||
const MAIN_SQL = `SELECT
|
const MAIN_SQL = `SELECT
|
||||||
CAST(truck.id AS CHAR) AS id,
|
CAST(vi.id AS CHAR) AS id,
|
||||||
truck.plate_number AS 车牌号,
|
vi.plate_number AS 车牌号,
|
||||||
truck.vin AS vin,
|
vi.vin AS vin,
|
||||||
truck.brand AS 车辆品牌,
|
vm.brand AS 车辆品牌,
|
||||||
truck.model AS 车辆型号,
|
vm.model AS 车辆型号,
|
||||||
truck.color AS 车辆颜色,
|
vi.body_color AS 车辆颜色,
|
||||||
truck.rent_from_company AS 租赁公司,
|
vi.rental_company AS 租赁公司,
|
||||||
dic_ascription_status.dic_name AS 车辆归属状态Label,
|
CASE vi.vehicle_source
|
||||||
dic_type.dic_name AS 车辆型号Label,
|
WHEN '0' THEN '自有'
|
||||||
truck.stock_area AS 库存区域,
|
WHEN '1' THEN '外租'
|
||||||
truck.truck_rent_status AS 车辆租赁状态,
|
WHEN '2' THEN '挂靠'
|
||||||
dic_status.dic_name AS 车辆租赁状态Label,
|
ELSE vi.actual_ownership
|
||||||
truck.is_operation AS 是否营运,
|
END AS 车辆归属状态Label,
|
||||||
info.province AS 省,
|
vm.model AS 车辆型号Label,
|
||||||
info.city AS 市,
|
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.lat AS 纬度,
|
||||||
info.lng AS 经度,
|
info.lng AS 经度,
|
||||||
dic_brand.dic_name AS 车辆品牌Label,
|
CASE vm.brand
|
||||||
si.contract_id AS 合同ID,
|
WHEN 'hyundai' THEN CASE WHEN vm.model LIKE '%帕力安%' OR vm.model LIKE '%冷链%' OR vm.model LIKE '%双飞翼%' THEN '帕力安牌' ELSE '现代' END
|
||||||
COALESCE(c.contract_no, si.contract_no) AS 合同编码,
|
WHEN 'yuejin' THEN '跃进'
|
||||||
cus.customer_name AS 客户名称,
|
WHEN 'feichi' THEN '飞驰'
|
||||||
org.org_name AS 合同归属公司,
|
WHEN 'sulong' THEN '苏龙'
|
||||||
dep.dep_name AS 合同归属部门,
|
WHEN 'higer' THEN '海格'
|
||||||
org_truck.org_name AS 主体,
|
WHEN 'dongfeng' THEN '东风'
|
||||||
c.project_name AS 项目名称,
|
WHEN 'yutong' THEN '宇通'
|
||||||
u.user_name AS 客户经理,
|
WHEN 'chufeng' THEN '楚风'
|
||||||
CAST(c.bd AS CHAR) AS 经理ID
|
WHEN 'tonghua' THEN '通华'
|
||||||
FROM tab_truck truck
|
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,
|
||||||
|
COALESCE(c.contract_code, vor.contract_code, vi.contract_code) AS 合同编码,
|
||||||
|
COALESCE(c.customer_name, vor.customer_name, ci.customer_name) AS 客户名称,
|
||||||
|
c.signing_company AS 合同归属公司,
|
||||||
|
COALESCE(c.business_department_name, vor.business_dept) AS 合同归属部门,
|
||||||
|
NULLIF(vi.registered_ownership, '') AS 主体,
|
||||||
|
COALESCE(c.project_name, vor.project_name) AS 项目名称,
|
||||||
|
COALESCE(c.business_manager_name, 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
|
LEFT JOIN tab_truck_remote_sync_realtime_info info
|
||||||
ON info.id = truck.id
|
ON info.plate_number = vi.plate_number
|
||||||
LEFT JOIN tab_dic dic_type
|
AND info.is_deleted = 0
|
||||||
ON dic_type.parent_code = 'dic_truck_type'
|
LEFT JOIN common_district info_province
|
||||||
AND dic_type.dic_code = truck.model
|
ON info_province.CODE = info.province COLLATE utf8mb4_unicode_ci
|
||||||
AND dic_type.is_deleted = 0
|
AND info_province.STATUS = 'VALID'
|
||||||
LEFT JOIN tab_dic dic_status
|
LEFT JOIN common_district info_city
|
||||||
ON dic_status.parent_code = 'dic_truck_rent_status'
|
ON info_city.CODE = info.city COLLATE utf8mb4_unicode_ci
|
||||||
AND dic_status.dic_code = truck.truck_rent_status
|
AND info_city.STATUS = 'VALID'
|
||||||
AND dic_status.is_deleted = 0
|
LEFT JOIN common_district vi_province
|
||||||
LEFT JOIN tab_dic dic_brand
|
ON vi_province.CODE = vi.province COLLATE utf8mb4_unicode_ci
|
||||||
ON dic_brand.parent_code = 'dic_vehicle_brand'
|
AND vi_province.STATUS = 'VALID'
|
||||||
AND dic_brand.dic_code = truck.brand
|
LEFT JOIN common_district vi_city
|
||||||
AND dic_brand.is_deleted = 0
|
ON vi_city.CODE = vi.city COLLATE utf8mb4_unicode_ci
|
||||||
LEFT JOIN tab_truck_status_info si
|
AND vi_city.STATUS = 'VALID'
|
||||||
ON si.truck_id = truck.id
|
LEFT JOIN common_district vi_operation_city
|
||||||
AND si.is_deleted = 0
|
ON vi_operation_city.CODE = vi.operation_city COLLATE utf8mb4_unicode_ci
|
||||||
LEFT JOIN tab_contract c
|
AND vi_operation_city.STATUS = 'VALID'
|
||||||
ON c.id = si.contract_id
|
LEFT JOIN vehicle_lease_order_record vor
|
||||||
AND c.is_deleted = 0
|
ON vor.vehicle_id = vi.id
|
||||||
LEFT JOIN tab_customer cus
|
AND vor.del_flag = '0'
|
||||||
ON cus.id = c.customer_id
|
AND vor.id = (
|
||||||
AND cus.is_deleted = 0
|
SELECT MAX(vor2.id)
|
||||||
LEFT JOIN tab_org org
|
FROM vehicle_lease_order_record vor2
|
||||||
ON org.id = c.org_id
|
WHERE vor2.vehicle_id = vi.id
|
||||||
AND org.is_deleted = 0
|
AND vor2.del_flag = '0'
|
||||||
LEFT JOIN tab_org org_truck
|
)
|
||||||
ON org_truck.id = truck.org_id
|
LEFT JOIN vehicle_lease_contract_info c
|
||||||
AND org_truck.is_deleted = 0
|
ON c.order_id = vor.contract_id
|
||||||
LEFT JOIN tab_dic dic_ascription_status
|
AND c.del_flag = '0'
|
||||||
ON dic_ascription_status.parent_code = 'dic_truck_ascription_status'
|
LEFT JOIN customer_info ci
|
||||||
AND dic_ascription_status.dic_code = truck.ascription_status
|
ON ci.id = vi.customer_id
|
||||||
AND dic_ascription_status.is_deleted = 0
|
AND ci.del_flag = '0'
|
||||||
LEFT JOIN tab_user u
|
WHERE vi.del_flag = '0'
|
||||||
ON u.id = c.bd
|
AND COALESCE(vs.operation_status, '') <> '5'`;
|
||||||
AND u.is_deleted = 0
|
|
||||||
LEFT JOIN tab_department dep
|
|
||||||
ON dep.id = u.dep_id
|
|
||||||
AND dep.is_deleted = 0
|
|
||||||
WHERE truck.is_deleted = 0
|
|
||||||
AND truck.is_operation = 1`;
|
|
||||||
|
|
||||||
// Region mapping: province/city -> display region
|
// Region mapping: province/city -> display region
|
||||||
const REGIONS = ['嘉兴', '广东', '北京', '新疆', '其他'] as const;
|
const REGIONS = ['嘉兴', '广东', '北京', '新疆', '其他'] as const;
|
||||||
@@ -148,23 +173,32 @@ function countByType(vehicles: Vehicle[]): VehicleTypeCounts {
|
|||||||
return counts;
|
return counts;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map rental status to frontend status
|
// Map operation status to frontend status.
|
||||||
// Actual DB values: 在库(0), 自营(1), 租赁(2), 待交车(7), 挂靠(8), 异动(12)
|
// ln_asset_management.vehicle_status.operation_status:
|
||||||
function mapStatus(rentStatus: string | null): 'Operating' | 'Inventory' | 'Pending' | 'Abnormal' {
|
// 1=租赁, 2=自营, 3=可运营, 4=待运营, 5=退出运营.
|
||||||
if (!rentStatus) return 'Inventory';
|
function mapStatus(operationStatus: string | null, vehicleStatus: string | null): 'Operating' | 'Inventory' | 'Pending' | 'Abnormal' {
|
||||||
const s = rentStatus.trim();
|
const op = (operationStatus || '').trim();
|
||||||
if (s === '租赁' || s === '自营' || s === '挂靠') return 'Operating';
|
const vehicle = (vehicleStatus || '').trim();
|
||||||
if (s === '在库') return 'Inventory';
|
if (vehicle === '4') return 'Pending';
|
||||||
if (s === '待交车') return 'Pending';
|
if (vehicle === '14') return 'Abnormal';
|
||||||
if (s === '异动') return 'Abnormal';
|
if (op === '1' || op === '2') return 'Operating';
|
||||||
|
if (op === '3' || op === '4') return 'Inventory';
|
||||||
|
if (op === '租赁' || op === '自营') return 'Operating';
|
||||||
|
if (op === '可运营' || op === '待运营' || op === '在库') return 'Inventory';
|
||||||
|
if (op === '异动') return 'Abnormal';
|
||||||
return 'Inventory';
|
return 'Inventory';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map ownership from truck_rent_status (rentStatusLabel)
|
// Map ownership from vehicle_info.vehicle_source.
|
||||||
// DB values: 自营(1), 租赁(2), 挂靠(8) → these are the operating subtypes
|
// DB values: 0/自有, 1/外租, 2/挂靠.
|
||||||
function mapOwnership(rentStatusLabel: string | null): string {
|
function mapOwnership(rentStatusLabel: string | null): string {
|
||||||
if (!rentStatusLabel) return 'Unknown';
|
if (!rentStatusLabel) return 'Unknown';
|
||||||
const s = rentStatusLabel.trim();
|
const s = rentStatusLabel.trim();
|
||||||
|
if (s === '0') return 'Self';
|
||||||
|
if (s === '1') return 'Leased';
|
||||||
|
if (s === '2') return 'Hanging';
|
||||||
|
if (s === '自有') return 'Self';
|
||||||
|
if (s === '外租') return 'Leased';
|
||||||
if (s === '自营') return 'Self';
|
if (s === '自营') return 'Self';
|
||||||
if (s === '租赁') return 'Leased';
|
if (s === '租赁') return 'Leased';
|
||||||
if (s === '挂靠') return 'Hanging';
|
if (s === '挂靠') return 'Hanging';
|
||||||
@@ -179,26 +213,38 @@ function resolveCity(city: string | null, province: string | null): string {
|
|||||||
return p || '其他';
|
return p || '其他';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Derive vehicle type category from model label
|
// Derive page category from ln_asset_management.vehicle_model.
|
||||||
// Actual DB values: 4.5吨冷链车, 4.5吨货车, 18吨双飞翼货车, 18吨厢式货车, 49吨牵引车头, 35吨牵引车头,
|
// vehicle_type is a new-system category code, not the old lingniu_prod.dic_truck_type code:
|
||||||
// 重型集装箱半挂车, 重型平板半挂车, 氢能叉车, SJ型蓄电池观光车, 公务用车/小客车, 挂靠油车
|
// 1 = 4.5T, 2 = 18T / other truck-like models, 3 = tractor head, 5/6 = trailers.
|
||||||
function deriveType(modelLabel: string | null, brandLabel: string | null): string {
|
function deriveType(modelLabel: string | null, vehicleTypeCode: string | null): string {
|
||||||
const label = (modelLabel || '').trim();
|
const label = (modelLabel || '').trim();
|
||||||
|
const code = (vehicleTypeCode || '').trim();
|
||||||
|
if (label.includes('半挂车') || code === '5' || code === '6') return '挂车';
|
||||||
if (label.includes('4.5吨')) return '4.5T';
|
if (label.includes('4.5吨')) return '4.5T';
|
||||||
if (label.includes('18吨')) return '18T';
|
if (label.includes('18吨')) return '18T';
|
||||||
if (label.includes('49吨')) return '49T';
|
if (label.includes('49吨')) return '49T';
|
||||||
if (label.includes('35吨')) return '35T';
|
if (label.includes('35吨')) return '35T';
|
||||||
|
if (code === '1') return '4.5T';
|
||||||
|
if (code === '3') return '49T';
|
||||||
if (label.includes('叉车')) return '叉车';
|
if (label.includes('叉车')) return '叉车';
|
||||||
if (label.includes('半挂车')) return '挂车';
|
|
||||||
return '其他车型';
|
return '其他车型';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeModelLabel(modelLabel: string | null): string | null {
|
||||||
|
const label = (modelLabel || '').trim();
|
||||||
|
if (label === '帕力安牌4.5吨冷链车') return '4.5吨冷链车';
|
||||||
|
if (label === '帕力安牌18吨双飞翼货车') return '18吨双飞翼货车';
|
||||||
|
if (label === '海格牌18吨双飞翼货车') return '18吨双飞翼货车';
|
||||||
|
return label || null;
|
||||||
|
}
|
||||||
|
|
||||||
// Tag → alias mapping with sort order
|
// Tag → alias mapping with sort order
|
||||||
// tag is generated as: brand-modelLabel-color[+rentCompany if 外租]
|
// tag is generated as: brand-modelLabel-color[+rentCompany if 外租]
|
||||||
// Some tags are merged (e.g. 嘉氢 red + 嘉氢 blue/green → one alias)
|
// Some tags are merged (e.g. 嘉氢 red + 嘉氢 blue/green → one alias)
|
||||||
const MODEL_ALIAS_MAP: Record<string, { alias: string; order: number }> = {
|
const MODEL_ALIAS_MAP: Record<string, { alias: string; order: number }> = {
|
||||||
// 4.5T 普货
|
// 4.5T 普货
|
||||||
'现代-4.5吨货车-白色广州开发区交投氢能运营管理有限公司': { alias: '现代4.5T普货(交投)', order: 101 },
|
'现代-4.5吨货车-白色广州开发区交投氢能运营管理有限公司': { alias: '现代4.5T普货(交投)', order: 101 },
|
||||||
|
'现代-4.5吨货车-白色': { alias: '现代4.5T普货', order: 101 },
|
||||||
'现代-4.5吨货车-白': { alias: '现代4.5T普货(恒运)', order: 102 },
|
'现代-4.5吨货车-白': { alias: '现代4.5T普货(恒运)', order: 102 },
|
||||||
// 4.5T 冷链
|
// 4.5T 冷链
|
||||||
'帕力安牌-4.5吨冷链车-白色广州开发区交投氢能运营管理有限公司': { alias: '现代4.5T冷链(交投)', order: 201 },
|
'帕力安牌-4.5吨冷链车-白色广州开发区交投氢能运营管理有限公司': { alias: '现代4.5T冷链(交投)', order: 201 },
|
||||||
@@ -216,9 +262,11 @@ const MODEL_ALIAS_MAP: Record<string, { alias: string; order: number }> = {
|
|||||||
'宇通-49吨牵引车头-白': { alias: '49T宇通', order: 401 },
|
'宇通-49吨牵引车头-白': { alias: '49T宇通', order: 401 },
|
||||||
'飞驰-49吨牵引车头-白/蓝/绿': { alias: '49T飞驰', order: 402 },
|
'飞驰-49吨牵引车头-白/蓝/绿': { alias: '49T飞驰', order: 402 },
|
||||||
'飞驰-49吨牵引车头-白/蓝/绿嘉兴氢能产业发展股份有限公司': { alias: '49T飞驰(嘉氢)', order: 403 },
|
'飞驰-49吨牵引车头-白/蓝/绿嘉兴氢能产业发展股份有限公司': { alias: '49T飞驰(嘉氢)', order: 403 },
|
||||||
|
'飞驰-49吨牵引车头-红': { alias: '49T飞驰(红)', order: 402 },
|
||||||
'飞驰-49吨牵引车头-红嘉兴氢能产业发展股份有限公司': { alias: '49T飞驰(嘉氢)', order: 403 }, // merge with above
|
'飞驰-49吨牵引车头-红嘉兴氢能产业发展股份有限公司': { alias: '49T飞驰(嘉氢)', order: 403 }, // merge with above
|
||||||
'飞驰-49吨牵引车头-红浙江氢能产业发展有限公司': { alias: '49T飞驰(浙氢-红)', order: 404 },
|
'飞驰-49吨牵引车头-红浙江氢能产业发展有限公司': { alias: '49T飞驰(浙氢-红)', order: 404 },
|
||||||
'飞驰-49吨牵引车头-白/蓝/绿浙江氢能产业发展有限公司': { alias: '49T飞驰(浙氢-蓝白绿)', order: 405 },
|
'飞驰-49吨牵引车头-白/蓝/绿浙江氢能产业发展有限公司': { alias: '49T飞驰(浙氢-蓝白绿)', order: 405 },
|
||||||
|
'楚风-49吨牵引车头-蓝/黑': { alias: '49T楚风', order: 406 },
|
||||||
'楚风-49吨牵引车头-蓝/黑海珀特科技(北京)有限公司': { alias: '49T楚风(海珀特)', order: 406 },
|
'楚风-49吨牵引车头-蓝/黑海珀特科技(北京)有限公司': { alias: '49T楚风(海珀特)', order: 406 },
|
||||||
// 其他
|
// 其他
|
||||||
'红岩-35吨牵引车头-红色': { alias: '35T油车', order: 501 },
|
'红岩-35吨牵引车头-红色': { alias: '35T油车', order: 501 },
|
||||||
@@ -232,9 +280,11 @@ const MODEL_ALIAS_MAP: Record<string, { alias: string; order: number }> = {
|
|||||||
'万风-重型平板半挂车-红': { alias: '挂车', order: 503 },
|
'万风-重型平板半挂车-红': { alias: '挂车', order: 503 },
|
||||||
'舒捷-SJ型蓄电池观光车-蓝白': { alias: '观光车', order: 504 },
|
'舒捷-SJ型蓄电池观光车-蓝白': { alias: '观光车', order: 504 },
|
||||||
'东风-挂靠油车-白色': { alias: '公务车/挂靠车', order: 505 },
|
'东风-挂靠油车-白色': { alias: '公务车/挂靠车', order: 505 },
|
||||||
|
'东风-挂靠油车-白': { alias: '公务车/挂靠车', order: 505 },
|
||||||
'腾势-公务用车/小客车-黑': { alias: '公务车/挂靠车', order: 505 },
|
'腾势-公务用车/小客车-黑': { alias: '公务车/挂靠车', order: 505 },
|
||||||
'腾势-公务用车/小客车-白': { alias: '公务车/挂靠车', order: 505 },
|
'腾势-公务用车/小客车-白': { alias: '公务车/挂靠车', order: 505 },
|
||||||
'其他-公务用车/小客车-蓝色': { alias: '公务车/挂靠车', order: 505 },
|
'其他-公务用车/小客车-蓝色': { alias: '公务车/挂靠车', order: 505 },
|
||||||
|
'其他-公务用车/小客车-灰': { alias: '公务车/挂靠车', order: 505 },
|
||||||
'远程牌-公务用车/小客车-白': { alias: '公务车/挂靠车', order: 505 },
|
'远程牌-公务用车/小客车-白': { alias: '公务车/挂靠车', order: 505 },
|
||||||
'大通-公务用车/小客车-灰': { alias: '公务车/挂靠车', order: 505 },
|
'大通-公务用车/小客车-灰': { alias: '公务车/挂靠车', order: 505 },
|
||||||
};
|
};
|
||||||
@@ -247,7 +297,7 @@ function deriveModelTag(
|
|||||||
rentCompany: string | null,
|
rentCompany: string | null,
|
||||||
): string {
|
): string {
|
||||||
const brand = (brandLabel || '').trim();
|
const brand = (brandLabel || '').trim();
|
||||||
const model = (modelLabel || '').trim();
|
const model = (normalizeModelLabel(modelLabel) || '').trim();
|
||||||
const c = (color || '').trim();
|
const c = (color || '').trim();
|
||||||
const isRented = ownershipLabel?.trim() === '外租';
|
const isRented = ownershipLabel?.trim() === '外租';
|
||||||
const company = isRented ? (rentCompany || '').trim() : '';
|
const company = isRented ? (rentCompany || '').trim() : '';
|
||||||
@@ -272,15 +322,16 @@ function transformRow(row: VehicleRow): Vehicle {
|
|||||||
id: row.id,
|
id: row.id,
|
||||||
plateNumber: row.车牌号 || '',
|
plateNumber: row.车牌号 || '',
|
||||||
vin: row.vin || '',
|
vin: row.vin || '',
|
||||||
type: deriveType(row.车辆型号Label, row.车辆品牌Label),
|
type: deriveType(row.车辆型号Label, row.车辆类型参数),
|
||||||
model: deriveModelTag(row.车辆品牌Label, row.车辆型号Label, row.车辆颜色, row.车辆归属状态Label, row.租赁公司),
|
model: deriveModelTag(row.车辆品牌Label, row.车辆型号Label, row.车辆颜色, row.车辆归属状态Label, row.租赁公司),
|
||||||
color: row.车辆颜色 || '',
|
color: row.车辆颜色 || '',
|
||||||
location: region,
|
location: region,
|
||||||
region,
|
region,
|
||||||
province: row.省,
|
province: row.省,
|
||||||
city: row.市,
|
city: row.市,
|
||||||
status: mapStatus(row.车辆租赁状态Label),
|
status: mapStatus(row.车辆租赁状态Label, row.车辆租赁状态),
|
||||||
ownership: mapOwnership(row.车辆租赁状态Label),
|
operationStatus: row.车辆租赁状态Label,
|
||||||
|
ownership: mapOwnership(row.车辆归属状态Label),
|
||||||
rentCompany: row.租赁公司 || '',
|
rentCompany: row.租赁公司 || '',
|
||||||
contractNo: row.合同编码,
|
contractNo: row.合同编码,
|
||||||
customerName: row.客户名称,
|
customerName: row.客户名称,
|
||||||
@@ -318,7 +369,7 @@ async function getVehiclesForUser(c: Context): Promise<Vehicle[]> {
|
|||||||
return maskCustomerNames(list);
|
return maskCustomerNames(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 归属公司筛选(所属公司 = tab_truck.org_id → org_name, 即 Vehicle.subjectOrg)
|
// 归属公司筛选(所属公司 = vehicle_info.registered_ownership, 即 Vehicle.subjectOrg)
|
||||||
function getSubjectParam(c: Context): string | null {
|
function getSubjectParam(c: Context): string | null {
|
||||||
const raw = (c.req.query('subject') || '').trim();
|
const raw = (c.req.query('subject') || '').trim();
|
||||||
return raw ? raw : null;
|
return raw ? raw : null;
|
||||||
@@ -354,24 +405,27 @@ async function getWeeklyTruckIds(): Promise<WeeklyTruckIds> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [[pendingRows], [deliveredRows], [returnedRows], [replacedRows]] = await Promise.all([
|
const [[pendingRows], [deliveredRows], [returnedRows], [replacedRows]] = await Promise.all([
|
||||||
pool.query<any[]>(`SELECT CAST(id AS CHAR) AS truck_id FROM tab_truck WHERE is_deleted=0 AND is_operation=1 AND truck_rent_status=7`),
|
pool.query<any[]>(`SELECT CAST(vehicle_id AS CHAR) AS truck_id
|
||||||
pool.query<any[]>(`SELECT CAST(rent_truck.truck_id AS CHAR) AS truck_id FROM tab_truck_rent_take take
|
FROM vehicle_status
|
||||||
LEFT JOIN tab_truck_rent_task task ON task.id = take.truck_rent_task_id
|
WHERE del_flag=0 AND vehicle_status='4' AND COALESCE(operation_status, '') <> '5'`),
|
||||||
LEFT JOIN tab_contract_rent_truck rent_truck ON rent_truck.id = task.contract_rent_truck_id
|
pool.query<any[]>(`SELECT CAST(vehicle_id AS CHAR) AS truck_id
|
||||||
WHERE take.is_deleted=0 AND take.take_name IS NOT NULL
|
FROM delivery_vehicle
|
||||||
AND task.task_type=1 AND task.task_status=1 AND take.update_time IS NOT NULL
|
WHERE del_flag='0'
|
||||||
AND take.handover_date >= ${WEEK_START_SQL} AND take.handover_date < ${WEEK_END_SQL}`),
|
AND vehicle_id IS NOT NULL
|
||||||
pool.query<any[]>(`SELECT CAST(rent_truck.truck_id AS CHAR) AS truck_id FROM tab_truck_rent_return r
|
AND delivery_time >= ${WEEK_START_SQL} AND delivery_time < ${WEEK_END_SQL}
|
||||||
LEFT JOIN tab_truck_rent_task task ON task.id = r.truck_rent_task_id
|
AND delivery_status IN (2,3,5)`),
|
||||||
LEFT JOIN tab_contract_rent_truck rent_truck ON rent_truck.id = task.contract_rent_truck_id
|
pool.query<any[]>(`SELECT CAST(vehicle_id AS CHAR) AS truck_id
|
||||||
WHERE r.is_deleted=0 AND r.return_date IS NOT NULL
|
FROM return_vehicle_task
|
||||||
AND r.return_date >= ${WEEK_START_SQL} AND r.return_date < ${WEEK_END_SQL}`),
|
WHERE del_flag='0'
|
||||||
pool.query<any[]>(`SELECT CAST(rent_truck.truck_id AS CHAR) AS truck_id FROM tab_truck_rent_take take
|
AND vehicle_id IS NOT NULL
|
||||||
LEFT JOIN tab_truck_rent_task task ON task.id = take.truck_rent_task_id
|
AND arrival_time >= ${WEEK_START_SQL} AND arrival_time < ${WEEK_END_SQL}
|
||||||
LEFT JOIN tab_contract_rent_truck rent_truck ON rent_truck.id = task.contract_rent_truck_id
|
AND status IN (2,3,5)`),
|
||||||
WHERE take.is_deleted=0 AND take.take_name IS NOT NULL
|
pool.query<any[]>(`SELECT CAST(new_vehicle_id AS CHAR) AS truck_id
|
||||||
AND task.task_type=3 AND task.task_status=1 AND take.update_time IS NOT NULL
|
FROM vehicle_replacement
|
||||||
AND take.handover_date >= ${WEEK_START_SQL} AND take.handover_date < ${WEEK_END_SQL}`),
|
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`),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const toSet = (rows: any[]) => new Set((rows as any[]).map((r) => String(r.truck_id)).filter((s) => s && s !== 'null'));
|
const toSet = (rows: any[]) => new Set((rows as any[]).map((r) => String(r.truck_id)).filter((s) => s && s !== 'null'));
|
||||||
@@ -419,59 +473,54 @@ interface WeeklyStats {
|
|||||||
|
|
||||||
// 交车单 SQL
|
// 交车单 SQL
|
||||||
const DELIVERED_SQL = `SELECT
|
const DELIVERED_SQL = `SELECT
|
||||||
take.id, DATE(take.handover_date) AS handover_date,
|
dv.id, DATE(dv.delivery_time) AS handover_date,
|
||||||
CAST(truck.id AS CHAR) AS truck_id, truck.plate_number,
|
CAST(dv.vehicle_id AS CHAR) AS truck_id, dv.plate_number,
|
||||||
dic_contract_type.dic_name AS contract_type,
|
c.contract_type,
|
||||||
customer.customer_name
|
COALESCE(dts.customer_name, c.customer_name) AS customer_name
|
||||||
FROM tab_truck_rent_take take
|
FROM delivery_vehicle dv
|
||||||
LEFT JOIN tab_truck_rent_task task ON task.id = take.truck_rent_task_id
|
LEFT JOIN delivery_task_subject dts
|
||||||
LEFT JOIN tab_contract_rent_truck rent_truck ON rent_truck.id = task.contract_rent_truck_id
|
ON dts.id = dv.delivery_task_subject_id
|
||||||
LEFT JOIN tab_truck truck ON rent_truck.truck_id = truck.id
|
AND dts.del_flag = '0'
|
||||||
LEFT JOIN tab_contract contract ON task.contract_id = contract.id
|
LEFT JOIN vehicle_lease_contract_info c
|
||||||
LEFT JOIN tab_customer customer ON contract.customer_id = customer.id
|
ON c.order_id = dv.contract_id
|
||||||
LEFT JOIN tab_dic dic_contract_type
|
AND c.del_flag = '0'
|
||||||
ON dic_contract_type.parent_code = 'dic_contract_type'
|
WHERE dv.del_flag = '0'
|
||||||
AND dic_contract_type.dic_code = contract.contract_type
|
AND dv.vehicle_id IS NOT NULL
|
||||||
AND dic_contract_type.is_deleted = 0
|
AND dv.delivery_time IS NOT NULL
|
||||||
WHERE take.is_deleted = 0 AND take.take_name IS NOT NULL
|
AND dv.delivery_status IN (2,3,5)`;
|
||||||
AND task.task_type = 1 AND task.task_status = 1 AND take.update_time IS NOT NULL`;
|
|
||||||
|
|
||||||
// 还车单 SQL
|
// 还车单 SQL
|
||||||
const RETURNED_SQL = `SELECT
|
const RETURNED_SQL = `SELECT
|
||||||
r.id, DATE(r.return_date) AS handover_date,
|
r.id, DATE(r.arrival_time) AS handover_date,
|
||||||
CAST(truck.id AS CHAR) AS truck_id, truck.plate_number,
|
CAST(r.vehicle_id AS CHAR) AS truck_id, r.plate_number,
|
||||||
dic_contract_type.dic_name AS contract_type,
|
c.contract_type,
|
||||||
customer.customer_name
|
COALESCE(dts.customer_name, c.customer_name) AS customer_name
|
||||||
FROM tab_truck_rent_return r
|
FROM return_vehicle_task r
|
||||||
LEFT JOIN tab_truck_rent_task task ON task.id = r.truck_rent_task_id
|
LEFT JOIN delivery_task_subject dts
|
||||||
LEFT JOIN tab_contract_rent_truck rent_truck ON rent_truck.id = task.contract_rent_truck_id
|
ON dts.id = r.delivery_task_subject_id
|
||||||
LEFT JOIN tab_truck truck ON rent_truck.truck_id = truck.id
|
AND dts.del_flag = '0'
|
||||||
LEFT JOIN tab_contract contract ON task.contract_id = contract.id
|
LEFT JOIN vehicle_lease_contract_info c
|
||||||
LEFT JOIN tab_customer customer ON contract.customer_id = customer.id
|
ON c.order_id = r.contract_id
|
||||||
LEFT JOIN tab_dic dic_contract_type
|
AND c.del_flag = '0'
|
||||||
ON dic_contract_type.parent_code = 'dic_contract_type'
|
WHERE r.del_flag = '0'
|
||||||
AND dic_contract_type.dic_code = contract.contract_type
|
AND r.vehicle_id IS NOT NULL
|
||||||
AND dic_contract_type.is_deleted = 0
|
AND r.arrival_time IS NOT NULL
|
||||||
WHERE r.is_deleted = 0 AND r.return_date IS NOT NULL`;
|
AND r.status IN (2,3,5)`;
|
||||||
|
|
||||||
// 替换车单 SQL
|
// 替换车单 SQL
|
||||||
const REPLACED_SQL = `SELECT
|
const REPLACED_SQL = `SELECT
|
||||||
take.id, DATE(take.handover_date) AS handover_date,
|
vr.id, DATE(vr.replace_time) AS handover_date,
|
||||||
CAST(truck.id AS CHAR) AS truck_id, truck.plate_number,
|
CAST(vr.new_vehicle_id AS CHAR) AS truck_id, vr.new_vehicle_plate AS plate_number,
|
||||||
dic_contract_type.dic_name AS contract_type,
|
c.contract_type,
|
||||||
customer.customer_name
|
c.customer_name
|
||||||
FROM tab_truck_rent_take take
|
FROM vehicle_replacement vr
|
||||||
LEFT JOIN tab_truck_rent_task task ON task.id = take.truck_rent_task_id
|
LEFT JOIN vehicle_lease_contract_info c
|
||||||
LEFT JOIN tab_contract_rent_truck rent_truck ON rent_truck.id = task.contract_rent_truck_id
|
ON c.id = vr.contract_id
|
||||||
LEFT JOIN tab_truck truck ON rent_truck.truck_id = truck.id
|
AND c.del_flag = '0'
|
||||||
LEFT JOIN tab_contract contract ON task.contract_id = contract.id
|
WHERE vr.del_flag = '0'
|
||||||
LEFT JOIN tab_customer customer ON contract.customer_id = customer.id
|
AND vr.new_vehicle_id IS NOT NULL
|
||||||
LEFT JOIN tab_dic dic_contract_type
|
AND vr.replace_time IS NOT NULL
|
||||||
ON dic_contract_type.parent_code = 'dic_contract_type'
|
AND vr.status = 20`;
|
||||||
AND dic_contract_type.dic_code = contract.contract_type
|
|
||||||
AND dic_contract_type.is_deleted = 0
|
|
||||||
WHERE take.is_deleted = 0 AND take.take_name IS NOT NULL
|
|
||||||
AND task.task_type = 3 AND task.task_status = 1 AND take.update_time IS NOT NULL`;
|
|
||||||
|
|
||||||
let cachedWeeklyStats: WeeklyStats | null = null;
|
let cachedWeeklyStats: WeeklyStats | null = null;
|
||||||
let weeklyStatsLastFetch = 0;
|
let weeklyStatsLastFetch = 0;
|
||||||
@@ -483,23 +532,18 @@ async function getWeeklyStats(): Promise<WeeklyStats> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [[pendingRows], [newRows], [removedRows], [deliveredRows], [returnedRows], [replacedRows]] = await Promise.all([
|
const [[pendingRows], [newRows], [removedRows], [deliveredRows], [returnedRows], [replacedRows]] = await Promise.all([
|
||||||
pool.query<any[]>(`SELECT COUNT(*) AS cnt FROM tab_truck WHERE is_deleted=0 AND is_operation=1 AND truck_rent_status=7`),
|
pool.query<any[]>(`SELECT COUNT(*) AS cnt FROM vehicle_status WHERE del_flag=0 AND vehicle_status='4' AND COALESCE(operation_status, '') <> '5'`),
|
||||||
pool.query<any[]>(`SELECT COUNT(*) AS cnt FROM tab_truck WHERE is_deleted=0 AND is_operation=1 AND create_time >= ${WEEK_START_SQL} AND create_time < ${WEEK_END_SQL}`),
|
pool.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}`),
|
||||||
pool.query<any[]>(`SELECT COUNT(*) AS cnt FROM tab_truck WHERE (is_operation=0 OR is_deleted=1) AND update_time >= ${WEEK_START_SQL} AND update_time < ${WEEK_END_SQL}`),
|
pool.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}`),
|
||||||
pool.query<any[]>(`SELECT COUNT(*) AS cnt FROM tab_truck_rent_take take
|
pool.query<any[]>(`SELECT COUNT(*) AS cnt FROM delivery_vehicle
|
||||||
LEFT JOIN tab_truck_rent_task task ON task.id = take.truck_rent_task_id
|
WHERE del_flag='0' AND delivery_time IS NOT NULL AND delivery_status IN (2,3,5)
|
||||||
WHERE take.is_deleted=0 AND take.take_name IS NOT NULL
|
AND delivery_time >= ${WEEK_START_SQL} AND delivery_time < ${WEEK_END_SQL}`),
|
||||||
AND task.task_type=1 AND task.task_status=1 AND take.update_time IS NOT NULL
|
pool.query<any[]>(`SELECT COUNT(*) AS cnt FROM return_vehicle_task
|
||||||
AND take.handover_date >= ${WEEK_START_SQL} AND take.handover_date < ${WEEK_END_SQL}`),
|
WHERE del_flag='0' AND arrival_time IS NOT NULL AND status IN (2,3,5)
|
||||||
pool.query<any[]>(`SELECT COUNT(*) AS cnt FROM tab_truck_rent_return r
|
AND arrival_time >= ${WEEK_START_SQL} AND arrival_time < ${WEEK_END_SQL}`),
|
||||||
LEFT JOIN tab_truck_rent_task task ON task.id = r.truck_rent_task_id
|
pool.query<any[]>(`SELECT COUNT(*) AS cnt FROM vehicle_replacement
|
||||||
WHERE r.is_deleted=0 AND r.return_date IS NOT NULL
|
WHERE del_flag='0' AND replace_time IS NOT NULL AND status=20
|
||||||
AND r.return_date >= ${WEEK_START_SQL} AND r.return_date < ${WEEK_END_SQL}`),
|
AND replace_time >= ${WEEK_START_SQL} AND replace_time < ${WEEK_END_SQL}`),
|
||||||
pool.query<any[]>(`SELECT COUNT(*) AS cnt FROM tab_truck_rent_take take
|
|
||||||
LEFT JOIN tab_truck_rent_task task ON task.id = take.truck_rent_task_id
|
|
||||||
WHERE take.is_deleted=0 AND take.take_name IS NOT NULL
|
|
||||||
AND task.task_type=3 AND task.task_status=1 AND take.update_time IS NOT NULL
|
|
||||||
AND take.handover_date >= ${WEEK_START_SQL} AND take.handover_date < ${WEEK_END_SQL}`),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
cachedWeeklyStats = {
|
cachedWeeklyStats = {
|
||||||
@@ -521,11 +565,11 @@ app.get('/summary', async (c) => {
|
|||||||
const summary: SummaryData = {
|
const summary: SummaryData = {
|
||||||
totalAssets: vehicles.length,
|
totalAssets: vehicles.length,
|
||||||
operating: {
|
operating: {
|
||||||
total: vehicles.filter((v) => v.status === 'Operating').length,
|
total: vehicles.filter((v) => v.status === 'Operating' && (v.operationStatus === '1' || v.operationStatus === '2')).length,
|
||||||
self: vehicles.filter((v) => v.status === 'Operating' && v.ownership === 'Self').length,
|
self: vehicles.filter((v) => v.status === 'Operating' && v.operationStatus === '2').length,
|
||||||
leased: vehicles.filter((v) => v.status === 'Operating' && v.ownership === 'Leased').length,
|
leased: vehicles.filter((v) => v.status === 'Operating' && v.operationStatus === '1').length,
|
||||||
public: vehicles.filter((v) => v.status === 'Operating' && v.ownership === 'Public').length,
|
public: vehicles.filter((v) => v.status === 'Operating' && v.ownership === 'Public').length,
|
||||||
hanging: vehicles.filter((v) => v.status === 'Operating' && v.ownership === 'Hanging').length,
|
hanging: 0,
|
||||||
},
|
},
|
||||||
inventory: {
|
inventory: {
|
||||||
total: vehicles.filter((v) => v.status === 'Inventory' || v.status === 'Abnormal').length,
|
total: vehicles.filter((v) => v.status === 'Inventory' || v.status === 'Abnormal').length,
|
||||||
@@ -695,7 +739,8 @@ app.get('/dept-stats', async (c) => {
|
|||||||
|
|
||||||
const deptMap = new Map<string, Map<string, Vehicle[]>>();
|
const deptMap = new Map<string, Map<string, Vehicle[]>>();
|
||||||
for (const v of withManager) {
|
for (const v of withManager) {
|
||||||
const dept = v.departmentName || '公务车';
|
const isPublicServiceVehicle = v.model === '公务车/挂靠车';
|
||||||
|
const dept = isPublicServiceVehicle ? '公务车' : (v.departmentName || '未分配部门');
|
||||||
const mgr = v.customerManager || '未分配';
|
const mgr = v.customerManager || '未分配';
|
||||||
if (EXCLUDED_MANAGERS.has(mgr)) continue;
|
if (EXCLUDED_MANAGERS.has(mgr)) continue;
|
||||||
if (!deptMap.has(dept)) deptMap.set(dept, new Map());
|
if (!deptMap.has(dept)) deptMap.set(dept, new Map());
|
||||||
@@ -704,29 +749,6 @@ app.get('/dept-stats', async (c) => {
|
|||||||
mgrMap.get(mgr)!.push(v);
|
mgrMap.get(mgr)!.push(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 补齐:业务部门内所有在职用户,即使当前无车辆也需显示
|
|
||||||
const deptNames = Array.from(deptMap.keys()).filter((d) => d !== '公务车');
|
|
||||||
if (deptNames.length > 0) {
|
|
||||||
const placeholders = deptNames.map(() => '?').join(',');
|
|
||||||
const [userRows] = await pool.query<any[]>(
|
|
||||||
`SELECT u.user_name, dep.dep_name
|
|
||||||
FROM tab_user u
|
|
||||||
LEFT JOIN tab_department dep ON dep.id = u.dep_id AND dep.is_deleted = 0
|
|
||||||
WHERE u.is_deleted = 0
|
|
||||||
AND dep.dep_name IN (${placeholders})`,
|
|
||||||
deptNames,
|
|
||||||
);
|
|
||||||
for (const r of userRows as any[]) {
|
|
||||||
const dept = r.dep_name as string | null;
|
|
||||||
const mgr = r.user_name as string | null;
|
|
||||||
if (!dept || !mgr) continue;
|
|
||||||
if (EXCLUDED_MANAGERS.has(mgr)) continue;
|
|
||||||
const mgrMap = deptMap.get(dept);
|
|
||||||
if (!mgrMap) continue;
|
|
||||||
if (!mgrMap.has(mgr)) mgrMap.set(mgr, []);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute attendance & avg mileage from realtime data
|
// Compute attendance & avg mileage from realtime data
|
||||||
const getMileageStats = (vList: Vehicle[]) => {
|
const getMileageStats = (vList: Vehicle[]) => {
|
||||||
const todayActive = vList.filter((v) => (todayMileageMap.get(v.plateNumber) || 0) > 0).length;
|
const todayActive = vList.filter((v) => (todayMileageMap.get(v.plateNumber) || 0) > 0).length;
|
||||||
@@ -971,7 +993,11 @@ app.get('/list', async (c) => {
|
|||||||
filtered = filtered.filter((v) => customer === '未分配客户' ? !v.customerName : v.customerName === customer);
|
filtered = filtered.filter((v) => customer === '未分配客户' ? !v.customerName : v.customerName === customer);
|
||||||
}
|
}
|
||||||
if (department) {
|
if (department) {
|
||||||
filtered = filtered.filter((v) => department === '公务车' ? !v.departmentName : v.departmentName === department);
|
filtered = filtered.filter((v) => {
|
||||||
|
if (department === '公务车') return v.model === '公务车/挂靠车';
|
||||||
|
if (department === '未分配部门') return v.model !== '公务车/挂靠车' && !v.departmentName;
|
||||||
|
return v.departmentName === department;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (isColdChain !== undefined) {
|
if (isColdChain !== undefined) {
|
||||||
const wantCold = isColdChain === 'true';
|
const wantCold = isColdChain === 'true';
|
||||||
@@ -994,6 +1020,7 @@ app.get('/list', async (c) => {
|
|||||||
city: v.city,
|
city: v.city,
|
||||||
status: v.status,
|
status: v.status,
|
||||||
ownership: v.ownership,
|
ownership: v.ownership,
|
||||||
|
rentCompany: v.rentCompany,
|
||||||
contractNo: v.contractNo,
|
contractNo: v.contractNo,
|
||||||
customerName: v.customerName,
|
customerName: v.customerName,
|
||||||
subjectOrg: v.subjectOrg,
|
subjectOrg: v.subjectOrg,
|
||||||
@@ -1049,18 +1076,22 @@ app.get('/weekly-detail', async (c) => {
|
|||||||
const source = c.req.query('source');
|
const source = c.req.query('source');
|
||||||
let sql: string;
|
let sql: string;
|
||||||
if (type === 'delivered') {
|
if (type === 'delivered') {
|
||||||
sql = `${DELIVERED_SQL} AND take.handover_date >= ${WEEK_START_SQL} AND take.handover_date < ${WEEK_END_SQL} ORDER BY take.handover_date DESC`;
|
sql = `${DELIVERED_SQL} AND dv.delivery_time >= ${WEEK_START_SQL} AND dv.delivery_time < ${WEEK_END_SQL} ORDER BY dv.delivery_time DESC`;
|
||||||
} else if (type === 'returned') {
|
} else if (type === 'returned') {
|
||||||
sql = `${RETURNED_SQL} AND r.return_date >= ${WEEK_START_SQL} AND r.return_date < ${WEEK_END_SQL} ORDER BY r.return_date DESC`;
|
sql = `${RETURNED_SQL} AND r.arrival_time >= ${WEEK_START_SQL} AND r.arrival_time < ${WEEK_END_SQL} ORDER BY r.arrival_time DESC`;
|
||||||
} else if (type === 'replaced') {
|
} else if (type === 'replaced') {
|
||||||
sql = `${REPLACED_SQL} AND take.handover_date >= ${WEEK_START_SQL} AND take.handover_date < ${WEEK_END_SQL} ORDER BY take.handover_date DESC`;
|
sql = `${REPLACED_SQL} AND vr.replace_time >= ${WEEK_START_SQL} AND vr.replace_time < ${WEEK_END_SQL} ORDER BY vr.replace_time DESC`;
|
||||||
} else if (type === 'pending') {
|
} else if (type === 'pending') {
|
||||||
sql = `SELECT CAST(truck.id AS CHAR) AS truck_id, truck.plate_number, NULL AS handover_date, NULL AS contract_type, NULL AS customer_name
|
sql = `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 tab_truck truck WHERE truck.is_deleted=0 AND truck.is_operation=1 AND truck.truck_rent_status=7`;
|
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'`;
|
||||||
} else if (type === 'new') {
|
} else if (type === 'new') {
|
||||||
sql = `SELECT CAST(truck.id AS CHAR) AS truck_id, truck.plate_number, truck.create_time AS handover_date, NULL AS contract_type, NULL AS customer_name
|
sql = `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 tab_truck truck WHERE truck.is_deleted=0 AND truck.is_operation=1
|
FROM vehicle_info vi
|
||||||
AND truck.create_time >= ${WEEK_START_SQL} AND truck.create_time < ${WEEK_END_SQL} ORDER BY truck.create_time DESC`;
|
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`;
|
||||||
} else {
|
} else {
|
||||||
return c.json([]);
|
return c.json([]);
|
||||||
}
|
}
|
||||||
@@ -1124,20 +1155,18 @@ app.get('/debug', async (c) => {
|
|||||||
${WEEK_END_SQL} AS week_end,
|
${WEEK_END_SQL} AS week_end,
|
||||||
CURDATE() AS today,
|
CURDATE() AS today,
|
||||||
WEEKDAY(CURDATE()) AS weekday`);
|
WEEKDAY(CURDATE()) AS weekday`);
|
||||||
const [[deliveredAll]] = await pool.query<any[]>(`SELECT COUNT(*) AS cnt FROM tab_truck_rent_take take
|
const [[deliveredAll]] = await pool.query<any[]>(`SELECT COUNT(*) AS cnt FROM delivery_vehicle
|
||||||
LEFT JOIN tab_truck_rent_task task ON task.id = take.truck_rent_task_id
|
WHERE del_flag='0' AND delivery_time IS NOT NULL AND delivery_status IN (2,3,5)`);
|
||||||
WHERE take.is_deleted=0 AND take.take_name IS NOT NULL AND task.task_type=1 AND task.task_status=1`);
|
const [[deliveredRecent]] = await pool.query<any[]>(`SELECT COUNT(*) AS cnt FROM delivery_vehicle
|
||||||
const [[deliveredRecent]] = await pool.query<any[]>(`SELECT COUNT(*) AS cnt FROM tab_truck_rent_take take
|
WHERE del_flag='0' AND delivery_time IS NOT NULL AND delivery_status IN (2,3,5)
|
||||||
LEFT JOIN tab_truck_rent_task task ON task.id = take.truck_rent_task_id
|
AND delivery_time >= ${WEEK_START_SQL} AND delivery_time < ${WEEK_END_SQL}`);
|
||||||
WHERE take.is_deleted=0 AND take.take_name IS NOT NULL AND task.task_type=1 AND task.task_status=1
|
const [[latestTake]] = await pool.query<any[]>(`SELECT MAX(delivery_time) AS latest FROM delivery_vehicle
|
||||||
AND take.handover_date >= ${WEEK_START_SQL} AND take.handover_date < ${WEEK_END_SQL}`);
|
WHERE del_flag='0' AND delivery_time IS NOT NULL AND delivery_status IN (2,3,5)`);
|
||||||
const [[latestTake]] = await pool.query<any[]>(`SELECT MAX(take.handover_date) AS latest FROM tab_truck_rent_take take
|
const [[returnedRecent]] = await pool.query<any[]>(`SELECT COUNT(*) AS cnt FROM return_vehicle_task
|
||||||
LEFT JOIN tab_truck_rent_task task ON task.id = take.truck_rent_task_id
|
WHERE del_flag='0' AND arrival_time IS NOT NULL AND status IN (2,3,5)
|
||||||
WHERE take.is_deleted=0 AND take.take_name IS NOT NULL AND task.task_type=1 AND task.task_status=1`);
|
AND arrival_time >= ${WEEK_START_SQL} AND arrival_time < ${WEEK_END_SQL}`);
|
||||||
const [[returnedRecent]] = await pool.query<any[]>(`SELECT COUNT(*) AS cnt FROM tab_truck_rent_return r
|
const [[latestReturn]] = await pool.query<any[]>(`SELECT MAX(arrival_time) AS latest FROM return_vehicle_task
|
||||||
WHERE r.is_deleted=0 AND r.return_date IS NOT NULL
|
WHERE del_flag='0' AND arrival_time IS NOT NULL AND status IN (2,3,5)`);
|
||||||
AND r.return_date >= ${WEEK_START_SQL} AND r.return_date < ${WEEK_END_SQL}`);
|
|
||||||
const [[latestReturn]] = await pool.query<any[]>(`SELECT MAX(r.return_date) AS latest FROM tab_truck_rent_return r WHERE r.is_deleted=0 AND r.return_date IS NOT NULL`);
|
|
||||||
|
|
||||||
return c.json({
|
return c.json({
|
||||||
weekRange: dateRange,
|
weekRange: dateRange,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export interface VehicleRow {
|
|||||||
租赁公司: string;
|
租赁公司: string;
|
||||||
车辆归属状态Label: string | null;
|
车辆归属状态Label: string | null;
|
||||||
车辆型号Label: string | null;
|
车辆型号Label: string | null;
|
||||||
|
车辆类型参数: string | null;
|
||||||
库存区域: string | null;
|
库存区域: string | null;
|
||||||
车辆租赁状态: string | null;
|
车辆租赁状态: string | null;
|
||||||
车辆租赁状态Label: string | null;
|
车辆租赁状态Label: string | null;
|
||||||
@@ -40,6 +41,7 @@ export interface Vehicle {
|
|||||||
province: string | null;
|
province: string | null;
|
||||||
city: string | null;
|
city: string | null;
|
||||||
status: 'Operating' | 'Inventory' | 'Pending' | 'Abnormal';
|
status: 'Operating' | 'Inventory' | 'Pending' | 'Abnormal';
|
||||||
|
operationStatus: string | null;
|
||||||
ownership: string;
|
ownership: string;
|
||||||
rentCompany: string;
|
rentCompany: string;
|
||||||
contractNo: string | null;
|
contractNo: string | null;
|
||||||
|
|||||||
Reference in New Issue
Block a user