This commit is contained in:
+255
-226
@@ -17,75 +17,100 @@ import type { Context } from 'hono';
|
||||
const app = new Hono();
|
||||
|
||||
const MAIN_SQL = `SELECT
|
||||
CAST(truck.id AS CHAR) AS id,
|
||||
truck.plate_number AS 车牌号,
|
||||
truck.vin AS vin,
|
||||
truck.brand AS 车辆品牌,
|
||||
truck.model AS 车辆型号,
|
||||
truck.color AS 车辆颜色,
|
||||
truck.rent_from_company AS 租赁公司,
|
||||
dic_ascription_status.dic_name AS 车辆归属状态Label,
|
||||
dic_type.dic_name AS 车辆型号Label,
|
||||
truck.stock_area AS 库存区域,
|
||||
truck.truck_rent_status AS 车辆租赁状态,
|
||||
dic_status.dic_name AS 车辆租赁状态Label,
|
||||
truck.is_operation AS 是否营运,
|
||||
info.province AS 省,
|
||||
info.city AS 市,
|
||||
CAST(vi.id AS CHAR) AS id,
|
||||
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 '挂靠'
|
||||
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 经度,
|
||||
dic_brand.dic_name AS 车辆品牌Label,
|
||||
si.contract_id AS 合同ID,
|
||||
COALESCE(c.contract_no, si.contract_no) AS 合同编码,
|
||||
cus.customer_name AS 客户名称,
|
||||
org.org_name AS 合同归属公司,
|
||||
dep.dep_name AS 合同归属部门,
|
||||
org_truck.org_name AS 主体,
|
||||
c.project_name AS 项目名称,
|
||||
u.user_name AS 客户经理,
|
||||
CAST(c.bd AS CHAR) AS 经理ID
|
||||
FROM tab_truck truck
|
||||
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,
|
||||
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
|
||||
ON info.id = truck.id
|
||||
LEFT JOIN tab_dic dic_type
|
||||
ON dic_type.parent_code = 'dic_truck_type'
|
||||
AND dic_type.dic_code = truck.model
|
||||
AND dic_type.is_deleted = 0
|
||||
LEFT JOIN tab_dic dic_status
|
||||
ON dic_status.parent_code = 'dic_truck_rent_status'
|
||||
AND dic_status.dic_code = truck.truck_rent_status
|
||||
AND dic_status.is_deleted = 0
|
||||
LEFT JOIN tab_dic dic_brand
|
||||
ON dic_brand.parent_code = 'dic_vehicle_brand'
|
||||
AND dic_brand.dic_code = truck.brand
|
||||
AND dic_brand.is_deleted = 0
|
||||
LEFT JOIN tab_truck_status_info si
|
||||
ON si.truck_id = truck.id
|
||||
AND si.is_deleted = 0
|
||||
LEFT JOIN tab_contract c
|
||||
ON c.id = si.contract_id
|
||||
AND c.is_deleted = 0
|
||||
LEFT JOIN tab_customer cus
|
||||
ON cus.id = c.customer_id
|
||||
AND cus.is_deleted = 0
|
||||
LEFT JOIN tab_org org
|
||||
ON org.id = c.org_id
|
||||
AND org.is_deleted = 0
|
||||
LEFT JOIN tab_org org_truck
|
||||
ON org_truck.id = truck.org_id
|
||||
AND org_truck.is_deleted = 0
|
||||
LEFT JOIN tab_dic dic_ascription_status
|
||||
ON dic_ascription_status.parent_code = 'dic_truck_ascription_status'
|
||||
AND dic_ascription_status.dic_code = truck.ascription_status
|
||||
AND dic_ascription_status.is_deleted = 0
|
||||
LEFT JOIN tab_user u
|
||||
ON u.id = c.bd
|
||||
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`;
|
||||
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'
|
||||
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'`;
|
||||
|
||||
// Region mapping: province/city -> display region
|
||||
const REGIONS = ['嘉兴', '广东', '北京', '新疆', '其他'] as const;
|
||||
@@ -148,23 +173,32 @@ function countByType(vehicles: Vehicle[]): VehicleTypeCounts {
|
||||
return counts;
|
||||
}
|
||||
|
||||
// Map rental status to frontend status
|
||||
// Actual DB values: 在库(0), 自营(1), 租赁(2), 待交车(7), 挂靠(8), 异动(12)
|
||||
function mapStatus(rentStatus: string | null): 'Operating' | 'Inventory' | 'Pending' | 'Abnormal' {
|
||||
if (!rentStatus) return 'Inventory';
|
||||
const s = rentStatus.trim();
|
||||
if (s === '租赁' || s === '自营' || s === '挂靠') return 'Operating';
|
||||
if (s === '在库') return 'Inventory';
|
||||
if (s === '待交车') return 'Pending';
|
||||
if (s === '异动') return 'Abnormal';
|
||||
// Map operation status to frontend status.
|
||||
// ln_asset_management.vehicle_status.operation_status:
|
||||
// 1=租赁, 2=自营, 3=可运营, 4=待运营, 5=退出运营.
|
||||
function mapStatus(operationStatus: string | null, vehicleStatus: string | null): 'Operating' | 'Inventory' | 'Pending' | 'Abnormal' {
|
||||
const op = (operationStatus || '').trim();
|
||||
const vehicle = (vehicleStatus || '').trim();
|
||||
if (vehicle === '4') return 'Pending';
|
||||
if (vehicle === '14') 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';
|
||||
}
|
||||
|
||||
// Map ownership from truck_rent_status (rentStatusLabel)
|
||||
// DB values: 自营(1), 租赁(2), 挂靠(8) → these are the operating subtypes
|
||||
// Map ownership from vehicle_info.vehicle_source.
|
||||
// DB values: 0/自有, 1/外租, 2/挂靠.
|
||||
function mapOwnership(rentStatusLabel: string | null): string {
|
||||
if (!rentStatusLabel) return 'Unknown';
|
||||
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 'Leased';
|
||||
if (s === '挂靠') return 'Hanging';
|
||||
@@ -179,26 +213,38 @@ function resolveCity(city: string | null, province: string | null): string {
|
||||
return p || '其他';
|
||||
}
|
||||
|
||||
// Derive vehicle type category from model label
|
||||
// Actual DB values: 4.5吨冷链车, 4.5吨货车, 18吨双飞翼货车, 18吨厢式货车, 49吨牵引车头, 35吨牵引车头,
|
||||
// 重型集装箱半挂车, 重型平板半挂车, 氢能叉车, SJ型蓄电池观光车, 公务用车/小客车, 挂靠油车
|
||||
function deriveType(modelLabel: string | null, brandLabel: string | null): string {
|
||||
// Derive page category from ln_asset_management.vehicle_model.
|
||||
// vehicle_type is a new-system category code, not the old lingniu_prod.dic_truck_type code:
|
||||
// 1 = 4.5T, 2 = 18T / other truck-like models, 3 = tractor head, 5/6 = trailers.
|
||||
function deriveType(modelLabel: string | null, vehicleTypeCode: string | null): string {
|
||||
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('18吨')) return '18T';
|
||||
if (label.includes('49吨')) return '49T';
|
||||
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 '挂车';
|
||||
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 is generated as: brand-modelLabel-color[+rentCompany if 外租]
|
||||
// Some tags are merged (e.g. 嘉氢 red + 嘉氢 blue/green → one alias)
|
||||
const MODEL_ALIAS_MAP: Record<string, { alias: string; order: number }> = {
|
||||
// 4.5T 普货
|
||||
'现代-4.5吨货车-白色广州开发区交投氢能运营管理有限公司': { alias: '现代4.5T普货(交投)', order: 101 },
|
||||
'现代-4.5吨货车-白色': { alias: '现代4.5T普货', order: 101 },
|
||||
'现代-4.5吨货车-白': { alias: '现代4.5T普货(恒运)', order: 102 },
|
||||
// 4.5T 冷链
|
||||
'帕力安牌-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: 402 },
|
||||
'飞驰-49吨牵引车头-白/蓝/绿嘉兴氢能产业发展股份有限公司': { alias: '49T飞驰(嘉氢)', order: 403 },
|
||||
'飞驰-49吨牵引车头-红': { alias: '49T飞驰(红)', order: 402 },
|
||||
'飞驰-49吨牵引车头-红嘉兴氢能产业发展股份有限公司': { alias: '49T飞驰(嘉氢)', order: 403 }, // merge with above
|
||||
'飞驰-49吨牵引车头-红浙江氢能产业发展有限公司': { alias: '49T飞驰(浙氢-红)', order: 404 },
|
||||
'飞驰-49吨牵引车头-白/蓝/绿浙江氢能产业发展有限公司': { alias: '49T飞驰(浙氢-蓝白绿)', order: 405 },
|
||||
'楚风-49吨牵引车头-蓝/黑': { alias: '49T楚风', order: 406 },
|
||||
'楚风-49吨牵引车头-蓝/黑海珀特科技(北京)有限公司': { alias: '49T楚风(海珀特)', order: 406 },
|
||||
// 其他
|
||||
'红岩-35吨牵引车头-红色': { alias: '35T油车', order: 501 },
|
||||
@@ -232,9 +280,11 @@ const MODEL_ALIAS_MAP: Record<string, { alias: string; order: number }> = {
|
||||
'万风-重型平板半挂车-红': { alias: '挂车', order: 503 },
|
||||
'舒捷-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 },
|
||||
};
|
||||
@@ -247,7 +297,7 @@ function deriveModelTag(
|
||||
rentCompany: string | null,
|
||||
): string {
|
||||
const brand = (brandLabel || '').trim();
|
||||
const model = (modelLabel || '').trim();
|
||||
const model = (normalizeModelLabel(modelLabel) || '').trim();
|
||||
const c = (color || '').trim();
|
||||
const isRented = ownershipLabel?.trim() === '外租';
|
||||
const company = isRented ? (rentCompany || '').trim() : '';
|
||||
@@ -272,15 +322,16 @@ function transformRow(row: VehicleRow): Vehicle {
|
||||
id: row.id,
|
||||
plateNumber: row.车牌号 || '',
|
||||
vin: row.vin || '',
|
||||
type: deriveType(row.车辆型号Label, row.车辆品牌Label),
|
||||
type: deriveType(row.车辆型号Label, row.车辆类型参数),
|
||||
model: deriveModelTag(row.车辆品牌Label, row.车辆型号Label, row.车辆颜色, row.车辆归属状态Label, row.租赁公司),
|
||||
color: row.车辆颜色 || '',
|
||||
location: region,
|
||||
region,
|
||||
province: row.省,
|
||||
city: row.市,
|
||||
status: mapStatus(row.车辆租赁状态Label),
|
||||
ownership: mapOwnership(row.车辆租赁状态Label),
|
||||
status: mapStatus(row.车辆租赁状态Label, row.车辆租赁状态),
|
||||
operationStatus: row.车辆租赁状态Label,
|
||||
ownership: mapOwnership(row.车辆归属状态Label),
|
||||
rentCompany: row.租赁公司 || '',
|
||||
contractNo: row.合同编码,
|
||||
customerName: row.客户名称,
|
||||
@@ -318,7 +369,7 @@ async function getVehiclesForUser(c: Context): Promise<Vehicle[]> {
|
||||
return maskCustomerNames(list);
|
||||
}
|
||||
|
||||
// 归属公司筛选(所属公司 = tab_truck.org_id → org_name, 即 Vehicle.subjectOrg)
|
||||
// 归属公司筛选(所属公司 = vehicle_info.registered_ownership, 即 Vehicle.subjectOrg)
|
||||
function getSubjectParam(c: Context): string | null {
|
||||
const raw = (c.req.query('subject') || '').trim();
|
||||
return raw ? raw : null;
|
||||
@@ -354,24 +405,27 @@ async function getWeeklyTruckIds(): Promise<WeeklyTruckIds> {
|
||||
}
|
||||
|
||||
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(rent_truck.truck_id AS CHAR) AS truck_id FROM tab_truck_rent_take take
|
||||
LEFT JOIN tab_truck_rent_task task ON task.id = take.truck_rent_task_id
|
||||
LEFT JOIN tab_contract_rent_truck rent_truck ON rent_truck.id = task.contract_rent_truck_id
|
||||
WHERE take.is_deleted=0 AND take.take_name IS NOT NULL
|
||||
AND task.task_type=1 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}`),
|
||||
pool.query<any[]>(`SELECT CAST(rent_truck.truck_id AS CHAR) AS truck_id FROM tab_truck_rent_return r
|
||||
LEFT JOIN tab_truck_rent_task task ON task.id = r.truck_rent_task_id
|
||||
LEFT JOIN tab_contract_rent_truck rent_truck ON rent_truck.id = task.contract_rent_truck_id
|
||||
WHERE r.is_deleted=0 AND r.return_date IS NOT NULL
|
||||
AND r.return_date >= ${WEEK_START_SQL} AND r.return_date < ${WEEK_END_SQL}`),
|
||||
pool.query<any[]>(`SELECT CAST(rent_truck.truck_id AS CHAR) AS truck_id FROM tab_truck_rent_take take
|
||||
LEFT JOIN tab_truck_rent_task task ON task.id = take.truck_rent_task_id
|
||||
LEFT JOIN tab_contract_rent_truck rent_truck ON rent_truck.id = task.contract_rent_truck_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}`),
|
||||
pool.query<any[]>(`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'`),
|
||||
pool.query<any[]>(`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)`),
|
||||
pool.query<any[]>(`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)`),
|
||||
pool.query<any[]>(`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`),
|
||||
]);
|
||||
|
||||
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
|
||||
const DELIVERED_SQL = `SELECT
|
||||
take.id, DATE(take.handover_date) AS handover_date,
|
||||
CAST(truck.id AS CHAR) AS truck_id, truck.plate_number,
|
||||
dic_contract_type.dic_name AS contract_type,
|
||||
customer.customer_name
|
||||
FROM tab_truck_rent_take take
|
||||
LEFT JOIN tab_truck_rent_task task ON task.id = take.truck_rent_task_id
|
||||
LEFT JOIN tab_contract_rent_truck rent_truck ON rent_truck.id = task.contract_rent_truck_id
|
||||
LEFT JOIN tab_truck truck ON rent_truck.truck_id = truck.id
|
||||
LEFT JOIN tab_contract contract ON task.contract_id = contract.id
|
||||
LEFT JOIN tab_customer customer ON contract.customer_id = customer.id
|
||||
LEFT JOIN tab_dic dic_contract_type
|
||||
ON dic_contract_type.parent_code = 'dic_contract_type'
|
||||
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 = 1 AND task.task_status = 1 AND take.update_time IS NOT NULL`;
|
||||
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.return_date) AS handover_date,
|
||||
CAST(truck.id AS CHAR) AS truck_id, truck.plate_number,
|
||||
dic_contract_type.dic_name AS contract_type,
|
||||
customer.customer_name
|
||||
FROM tab_truck_rent_return r
|
||||
LEFT JOIN tab_truck_rent_task task ON task.id = r.truck_rent_task_id
|
||||
LEFT JOIN tab_contract_rent_truck rent_truck ON rent_truck.id = task.contract_rent_truck_id
|
||||
LEFT JOIN tab_truck truck ON rent_truck.truck_id = truck.id
|
||||
LEFT JOIN tab_contract contract ON task.contract_id = contract.id
|
||||
LEFT JOIN tab_customer customer ON contract.customer_id = customer.id
|
||||
LEFT JOIN tab_dic dic_contract_type
|
||||
ON dic_contract_type.parent_code = 'dic_contract_type'
|
||||
AND dic_contract_type.dic_code = contract.contract_type
|
||||
AND dic_contract_type.is_deleted = 0
|
||||
WHERE r.is_deleted = 0 AND r.return_date IS NOT NULL`;
|
||||
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
|
||||
take.id, DATE(take.handover_date) AS handover_date,
|
||||
CAST(truck.id AS CHAR) AS truck_id, truck.plate_number,
|
||||
dic_contract_type.dic_name AS contract_type,
|
||||
customer.customer_name
|
||||
FROM tab_truck_rent_take take
|
||||
LEFT JOIN tab_truck_rent_task task ON task.id = take.truck_rent_task_id
|
||||
LEFT JOIN tab_contract_rent_truck rent_truck ON rent_truck.id = task.contract_rent_truck_id
|
||||
LEFT JOIN tab_truck truck ON rent_truck.truck_id = truck.id
|
||||
LEFT JOIN tab_contract contract ON task.contract_id = contract.id
|
||||
LEFT JOIN tab_customer customer ON contract.customer_id = customer.id
|
||||
LEFT JOIN tab_dic dic_contract_type
|
||||
ON dic_contract_type.parent_code = 'dic_contract_type'
|
||||
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`;
|
||||
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`;
|
||||
|
||||
let cachedWeeklyStats: WeeklyStats | null = null;
|
||||
let weeklyStatsLastFetch = 0;
|
||||
@@ -483,23 +532,18 @@ async function getWeeklyStats(): Promise<WeeklyStats> {
|
||||
}
|
||||
|
||||
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 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 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 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=1 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}`),
|
||||
pool.query<any[]>(`SELECT COUNT(*) AS cnt FROM tab_truck_rent_return r
|
||||
LEFT JOIN tab_truck_rent_task task ON task.id = r.truck_rent_task_id
|
||||
WHERE r.is_deleted=0 AND r.return_date IS NOT NULL
|
||||
AND r.return_date >= ${WEEK_START_SQL} AND r.return_date < ${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}`),
|
||||
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 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 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 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}`),
|
||||
pool.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}`),
|
||||
pool.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}`),
|
||||
]);
|
||||
|
||||
cachedWeeklyStats = {
|
||||
@@ -520,13 +564,13 @@ app.get('/summary', async (c) => {
|
||||
const vehicleIds = new Set(vehicles.map(v => String(v.id)));
|
||||
const summary: SummaryData = {
|
||||
totalAssets: vehicles.length,
|
||||
operating: {
|
||||
total: vehicles.filter((v) => v.status === 'Operating').length,
|
||||
self: vehicles.filter((v) => v.status === 'Operating' && v.ownership === 'Self').length,
|
||||
leased: vehicles.filter((v) => v.status === 'Operating' && v.ownership === 'Leased').length,
|
||||
public: vehicles.filter((v) => v.status === 'Operating' && v.ownership === 'Public').length,
|
||||
hanging: vehicles.filter((v) => v.status === 'Operating' && v.ownership === 'Hanging').length,
|
||||
},
|
||||
operating: {
|
||||
total: vehicles.filter((v) => v.status === 'Operating' && (v.operationStatus === '1' || v.operationStatus === '2')).length,
|
||||
self: vehicles.filter((v) => v.status === 'Operating' && v.operationStatus === '2').length,
|
||||
leased: vehicles.filter((v) => v.status === 'Operating' && v.operationStatus === '1').length,
|
||||
public: vehicles.filter((v) => v.status === 'Operating' && v.ownership === 'Public').length,
|
||||
hanging: 0,
|
||||
},
|
||||
inventory: {
|
||||
total: vehicles.filter((v) => v.status === 'Inventory' || v.status === 'Abnormal').length,
|
||||
inStock: vehicles.filter((v) => v.status === 'Inventory').length,
|
||||
@@ -695,7 +739,8 @@ app.get('/dept-stats', async (c) => {
|
||||
|
||||
const deptMap = new Map<string, Map<string, Vehicle[]>>();
|
||||
for (const v of withManager) {
|
||||
const dept = v.departmentName || '公务车';
|
||||
const isPublicServiceVehicle = v.model === '公务车/挂靠车';
|
||||
const dept = isPublicServiceVehicle ? '公务车' : (v.departmentName || '未分配部门');
|
||||
const mgr = v.customerManager || '未分配';
|
||||
if (EXCLUDED_MANAGERS.has(mgr)) continue;
|
||||
if (!deptMap.has(dept)) deptMap.set(dept, new Map());
|
||||
@@ -704,29 +749,6 @@ app.get('/dept-stats', async (c) => {
|
||||
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
|
||||
const getMileageStats = (vList: Vehicle[]) => {
|
||||
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);
|
||||
}
|
||||
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) {
|
||||
const wantCold = isColdChain === 'true';
|
||||
@@ -992,9 +1018,10 @@ app.get('/list', async (c) => {
|
||||
location: v.location,
|
||||
province: v.province,
|
||||
city: v.city,
|
||||
status: v.status,
|
||||
ownership: v.ownership,
|
||||
contractNo: v.contractNo,
|
||||
status: v.status,
|
||||
ownership: v.ownership,
|
||||
rentCompany: v.rentCompany,
|
||||
contractNo: v.contractNo,
|
||||
customerName: v.customerName,
|
||||
subjectOrg: v.subjectOrg,
|
||||
departmentName: v.departmentName,
|
||||
@@ -1049,18 +1076,22 @@ app.get('/weekly-detail', async (c) => {
|
||||
const source = c.req.query('source');
|
||||
let sql: string;
|
||||
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') {
|
||||
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') {
|
||||
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') {
|
||||
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
|
||||
FROM tab_truck truck WHERE truck.is_deleted=0 AND truck.is_operation=1 AND truck.truck_rent_status=7`;
|
||||
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 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') {
|
||||
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
|
||||
FROM tab_truck truck WHERE truck.is_deleted=0 AND truck.is_operation=1
|
||||
AND truck.create_time >= ${WEEK_START_SQL} AND truck.create_time < ${WEEK_END_SQL} ORDER BY truck.create_time DESC`;
|
||||
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 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`;
|
||||
} else {
|
||||
return c.json([]);
|
||||
}
|
||||
@@ -1124,20 +1155,18 @@ app.get('/debug', async (c) => {
|
||||
${WEEK_END_SQL} AS week_end,
|
||||
CURDATE() AS today,
|
||||
WEEKDAY(CURDATE()) AS weekday`);
|
||||
const [[deliveredAll]] = await 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=1 AND task.task_status=1`);
|
||||
const [[deliveredRecent]] = await 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=1 AND task.task_status=1
|
||||
AND take.handover_date >= ${WEEK_START_SQL} AND take.handover_date < ${WEEK_END_SQL}`);
|
||||
const [[latestTake]] = await pool.query<any[]>(`SELECT MAX(take.handover_date) AS latest 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=1 AND task.task_status=1`);
|
||||
const [[returnedRecent]] = await pool.query<any[]>(`SELECT COUNT(*) AS cnt FROM tab_truck_rent_return r
|
||||
WHERE r.is_deleted=0 AND r.return_date IS NOT NULL
|
||||
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`);
|
||||
const [[deliveredAll]] = await pool.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 pool.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 pool.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 pool.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 pool.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 c.json({
|
||||
weekRange: dateRange,
|
||||
|
||||
Reference in New Issue
Block a user