import type { Vehicle, VehicleRow } from '../../types.js'; import type { VehicleTypeCategory } from './types.js'; export const REGIONS = ['嘉兴', '广东', '北京', '新疆', '其他'] as const; export const INVENTORY_REGIONS = ['江浙沪', '广东', '新疆', '其它'] as const; export function mapRegion(province: string | null, city: string | null): string { if (!province && !city) return '其他'; const loc = (city || province || '').trim(); if (loc.includes('嘉兴') || loc.includes('浙江') || loc.includes('上海') || loc.includes('江苏')) return '嘉兴'; if (loc.includes('广东') || loc.includes('广州') || loc.includes('深圳') || loc.includes('佛山') || loc.includes('东莞')) return '广东'; if (loc.includes('北京')) return '北京'; if (loc.includes('新疆') || loc.includes('乌鲁木齐')) return '新疆'; const prov = (province || '').trim(); if (prov.includes('浙江') || prov.includes('上海') || prov.includes('江苏')) return '嘉兴'; if (prov.includes('广东')) return '广东'; if (prov.includes('北京')) return '北京'; if (prov.includes('新疆')) return '新疆'; return '其他'; } export function mapInventoryRegion(region: string): string { if (region === '嘉兴') return '江浙沪'; if (region === '广东') return '广东'; if (region === '新疆') return '新疆'; return '其它'; } export function mapMacroRegion(province: string | null, city: string | null): string { const prov = (province || '').trim(); const cityName = (city || '').trim(); const location = prov + cityName; if (/上海|江苏|浙江|安徽|福建|江西|山东|南京|杭州|合肥|济南|青岛|苏州|宁波|厦门|嘉兴|无锡/.test(location)) return '华东'; if (/广东|广西|海南|广州|深圳|佛山|东莞|珠海|惠州|中山|南宁/.test(location)) return '华南'; if (/北京|天津|河北|山西|内蒙古|石家庄|太原|呼和浩特/.test(location)) return '华北'; if (/河南|湖北|湖南|郑州|武汉|长沙/.test(location)) return '华中'; if (/重庆|四川|贵州|云南|西藏|成都|昆明|贵阳/.test(location)) return '西南'; if (/陕西|甘肃|青海|宁夏|新疆|西安|兰州|乌鲁木齐/.test(location)) return '西北'; return '其他'; } export function classifyVehicleType(vehicle: Vehicle): VehicleTypeCategory { if (vehicle.type === '4.5T' && !vehicle.model.includes('冷链')) return 't4_5'; if (vehicle.type === '4.5T' && vehicle.model.includes('冷链')) return 't4_5c'; if (vehicle.type === '18T') return 't18'; if (vehicle.type === '49T') return 't49'; if (vehicle.type === '挂车' || vehicle.model.includes('挂车')) return 'trailer'; return 'other'; } // vehicle_status has priority because pending (4) and abnormal (14) override operation_status. // operation_status: 1=租赁, 2=自营, 3=可运营, 4=待运营, 5=退出运营. export function mapStatus(operationStatus: string | null, vehicleStatus: string | null): Vehicle['status'] { const operation = (operationStatus || '').trim(); const vehicle = (vehicleStatus || '').trim(); if (vehicle === '4') return 'Pending'; if (vehicle === '14') return 'Abnormal'; if (operation === '1' || operation === '2') return 'Operating'; if (operation === '3' || operation === '4') return 'Inventory'; if (operation === '租赁' || operation === '自营') return 'Operating'; if (operation === '可运营' || operation === '待运营' || operation === '在库') return 'Inventory'; if (operation === '异动') return 'Abnormal'; return 'Inventory'; } export function mapOwnership(rentStatusLabel: string | null): string { if (!rentStatusLabel) return 'Unknown'; const status = rentStatusLabel.trim(); if (status === '0') return 'Self'; if (status === '1') return 'Hanging'; if (status === '2') return 'Leased'; if (status === '3') return 'Self'; if (status === '自有') return 'Self'; if (status === '外租') return 'Leased'; if (status === '自营') return 'Self'; if (status === '租赁') return 'Leased'; if (status === '挂靠') return 'Hanging'; return 'Unknown'; } export function resolveCity(city: string | null, province: string | null): string { const cityName = (city || '').replace(/[\[\]]/g, '').trim(); if (cityName) return cityName; const provinceName = (province || '').replace(/[\[\]]/g, '').trim(); return provinceName || '其他'; } // vehicle_type is the new-system category code: 1=4.5T, 3=牵引车头, 5/6=挂车. export 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 '叉车'; return '其他车型'; } export 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; } // Key format: brand-model-color[+rentCompany when ownership is 外租]. // Multiple source tags intentionally share aliases and order values to merge dirty or equivalent data. const MODEL_ALIAS_MAP: Record = { // 4.5T 普货 '现代-4.5吨货车-白色广州开发区交投氢能运营管理有限公司': { alias: '现代4.5T普货(交投)', order: 101 }, '现代-4.5吨货车-白恒运': { alias: '现代4.5T普货(恒运)', order: 102 }, '现代-4.5吨货车-白色恒运': { alias: '现代4.5T普货(恒运)', order: 102 }, '现代-4.5吨货车-白色': { alias: '现代4.5T普货', order: 101 }, '现代-4.5吨货车-白': { alias: '现代4.5T普货', order: 101 }, // 4.5T 冷链 '帕力安牌-4.5吨冷链车-白色广州开发区交投氢能运营管理有限公司': { alias: '现代4.5T冷链(交投)', order: 201 }, '帕力安牌-4.5吨冷链车-白色': { alias: '现代4.5T冷链(羚牛)', order: 202 }, '跃进-4.5吨冷链车-白/绿/灰': { alias: '跃进4.5T冷链', order: 203 }, // 18T '飞驰-18吨厢式货车-红': { alias: '飞驰18T(红车)', order: 301 }, '飞驰-18吨厢式货车-白/绿': { alias: '飞驰18T(白车)', order: 302 }, '楚风-18吨厢式货车-白': { alias: '楚风18T厢货', order: 303 }, '苏龙-18吨双飞翼货车-白': { alias: '苏龙18T飞翼', order: 304 }, '苏龙-18吨双飞翼货车-白色': { alias: '苏龙18T飞翼', order: 304 }, '苏龙-18吨双飞翼货车-白安吉天地物流科技有限公司': { alias: '苏龙18T飞翼(安吉)', order: 305 }, '帕力安牌-18吨双飞翼货车-白': { alias: '现代18T双飞翼(羚牛)', order: 306 }, '帕力安牌-18吨双飞翼货车-白/绿': { alias: '现代18T双飞翼(羚牛)', order: 306 }, // 49T '宇通-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 }, '飞驰-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 }, '其他-氢能叉车-蓝白绿': { alias: '氢能叉车', order: 502 }, '通华-重型集装箱半挂车-红色浙江锦昌仓储有限公司': { alias: '挂车', order: 503 }, '通华-重型集装箱半挂车-红色嘉兴市鼎义物流有限公司': { alias: '挂车', order: 503 }, '通华-重型集装箱半挂车-红色': { alias: '挂车', order: 503 }, '大通-重型集装箱半挂车-红色': { alias: '挂车', order: 503 }, '明威-重型集装箱半挂车-红色': { alias: '挂车', order: 503 }, '明威-重型集装箱半挂车-红': { alias: '挂车', order: 503 }, '万风-重型平板半挂车-红': { 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 }, }; export function deriveModelTag( brandLabel: string | null, modelLabel: string | null, color: string | null, ownershipLabel: string | null, rentCompany: string | null, ): string { const brand = (brandLabel || '').trim(); const model = (normalizeModelLabel(modelLabel) || '').trim(); const vehicleColor = (color || '').trim(); if (model === '公务用车/小客车') return '公务车/挂靠车'; const company = ownershipLabel?.trim() === '外租' ? (rentCompany || '').trim() : ''; if (!brand && !model) return '未知车型'; const tag = `${brand}-${model}-${vehicleColor}${company}`; return MODEL_ALIAS_MAP[tag]?.alias || tag; } export function getModelOrder(model: string): number { for (const entry of Object.values(MODEL_ALIAS_MAP)) { if (entry.alias === model) return entry.order; } return 999; } export function compareModelNames(left: string, right: string): number { return getModelOrder(left) - getModelOrder(right); } export function transformRow(row: VehicleRow): Vehicle { const region = mapRegion(row.省, row.市); return { id: row.id, plateNumber: row.车牌号 || '', vin: row.vin || '', 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, row.车辆租赁状态), operationStatus: row.车辆租赁状态Label, ownership: mapOwnership(row.车辆归属状态Label), rentCompany: row.租赁公司 || '', contractNo: row.合同编码, customerName: row.客户名称, orgName: row.合同归属公司, departmentName: row.合同归属部门, subjectOrg: row.主体, projectName: row.项目名称, customerManager: row.客户经理, managerId: row.经理ID || null, brandLabel: row.车辆品牌Label, }; } export const SUMMARY_TYPE_FILTERS: ReadonlyArray<{ name: string; filter: (vehicle: Vehicle) => boolean }> = [ { name: '4.5T普货', filter: (vehicle) => vehicle.type === '4.5T' && !vehicle.model.includes('冷链') }, { name: '4.5T冷链', filter: (vehicle) => vehicle.type === '4.5T' && vehicle.model.includes('冷链') }, { name: '18T', filter: (vehicle) => vehicle.type === '18T' }, { name: '49T', filter: (vehicle) => vehicle.type === '49T' }, { name: '其他', filter: (vehicle) => !['4.5T', '18T', '49T'].includes(vehicle.type) }, ]; // List filtering keeps trailers separate, while summary grouping intentionally includes them in “其他”. export const VEHICLE_TYPE_FILTERS: Record boolean> = { '4.5T普货': (vehicle) => vehicle.type === '4.5T' && !vehicle.model.includes('冷链'), '4.5T冷链': (vehicle) => vehicle.type === '4.5T' && vehicle.model.includes('冷链'), '18T': (vehicle) => vehicle.type === '18T', '49T': (vehicle) => vehicle.type === '49T', '挂车': (vehicle) => vehicle.type === '挂车' || vehicle.model.includes('挂车'), '其他': (vehicle) => classifyVehicleType(vehicle) === 'other', }; export function filterByLocation(vehicles: Vehicle[], location: string, source?: string): Vehicle[] { const macroRegions = ['华东', '华南', '华北', '华中', '西南', '西北']; const isMacro = macroRegions.includes(location) || (location === '其他' && source !== 'asset'); if (isMacro) { return vehicles.filter((vehicle) => mapMacroRegion(vehicle.province, vehicle.city) === location); } const inventoryRegionMap: Record = { '江浙沪': '嘉兴', '其它': '其他' }; const mappedLocation = inventoryRegionMap[location] || location; return vehicles.filter( (vehicle) => vehicle.location === mappedLocation || vehicle.city === location || resolveCity(vehicle.city, vehicle.province) === location, ); }