feat(assets): migrate asset views and reconcile inventory drilldowns

This commit is contained in:
shishengliang
2026-09-23 14:16:22 +08:00
parent b0cba26fbb
commit 822aabf2ca
77 changed files with 14669 additions and 3856 deletions
@@ -0,0 +1,80 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import app from '../vehicles.js';
import { vehicleRepository } from './repository.js';
import type { Vehicle } from '../../types.js';
// Repository stubs keep this regression entirely offline.
test('asset drill-down preserves company, region scope, vehicle type and actual cities', async (t) => {
const make = (id: string, overrides: Partial<Vehicle> = {}): Vehicle => ({
id, plateNumber: `TEST-${id}`, vin: id, type: '4.5T', model: '测试普货', color: '白',
location: '嘉兴', region: '嘉兴', province: '浙江省', city: `测试城市${id}`,
status: 'Operating', operationStatus: '1', ownership: 'Rental', rentCompany: '',
contractNo: null, customerName: null, orgName: null, departmentName: null,
subjectOrg: '公司甲', projectName: null, customerManager: null, managerId: null, brandLabel: null,
...overrides,
});
const vehicles = Array.from({ length: 9 }, (_, i) => make(String(i + 1)));
vehicles.push(make('10', { operationStatus: '3', status: 'Inventory' }));
vehicles.push(make('11', { type: '挂车', model: '测试挂车' }));
vehicles.push(make('12', { subjectOrg: '公司乙' }));
t.mock.method(vehicleRepository, 'getVehicles', async () => vehicles);
t.mock.method(vehicleRepository, 'getWeeklyDetailRows', async () => [
{ truck_id: '1', plate_number: 'TEST-1', handover_date: null, contract_type: null, customer_name: null },
{ truck_id: '12', plate_number: 'TEST-12', handover_date: null, contract_type: null, customer_name: null },
]);
const read = async (path: string) => {
const response = await app.request(path);
assert.equal(response.status, 200);
return response.json();
};
const regions = await read('/region-stats?subject=公司甲');
assert.equal(regions[0].totalAssets, 10);
assert.equal(regions[0].cities.length, 10, 'cities beyond the first eight stay individually addressable');
const total = await read('/list?source=region&location=华东&subject=公司甲');
assert.equal(total.length, regions[0].totalAssets);
assert.ok(total.every((v: Vehicle) => v.id !== '10' && v.id !== '12'));
const light = await read('/list?source=region&location=华东&subject=公司甲&vehicleType=4.5T');
assert.equal(light.length, 9);
const other = await read('/list?source=region&location=华东&subject=公司甲&vehicleType=区域其他');
assert.deepEqual(other.map((v: Vehicle) => v.id), ['11']);
const weekly = await read('/weekly-detail?type=delivered&subject=公司甲');
assert.deepEqual(weekly.map((v: {truck_id: string}) => v.truck_id), ['1']);
});
test('inventory brand drill-down matches each same-model row, including unknown brand', async (t) => {
const make = (id: string, brandLabel: string | null, subjectOrg = '公司甲'): Vehicle => ({
id, plateNumber: `TEST-${id}`, vin: id, type: '挂车', model: '挂车', color: '白',
location: '嘉兴', region: '嘉兴', province: '浙江省', city: '嘉兴市',
status: 'Inventory', operationStatus: '3', ownership: 'Rental', rentCompany: '',
contractNo: null, customerName: null, orgName: null, departmentName: null,
subjectOrg, projectName: null, customerManager: null, managerId: null, brandLabel,
});
t.mock.method(vehicleRepository, 'getVehicles', async () => [make('1', '明威'), make('2', '明威'), make('3', '万风'), make('4', null), make('5', '明威', '公司乙')]);
const summary = await (await app.request('/inventory-stats?subject=公司甲')).json();
for (const row of summary) {
const query = new URLSearchParams({ source: 'asset', subject: '公司甲', category: 'Inventory', model: row.model, location: row.city, vehicleType: row.type, brand: row.brand });
const result = await app.request(`/list?${query}`);
assert.equal(result.status, 200);
const detail = await result.json();
assert.equal(detail.length, row.quantity);
assert.ok(detail.every((vehicle: { brandLabel: string | null }) => (vehicle.brandLabel || '未知') === row.brand));
}
});
test('inventory composition and color variants reconcile with permission-filtered list keys', async (t) => {
const make = (id: string, operationStatus: string, overrides: Partial<Vehicle> = {}): Vehicle => ({
id,plateNumber:`TEST-${id}`,vin:id,type:'18T',model:'现代18T双飞翼(羚牛)',color:'白',location:'嘉兴',region:'嘉兴',province:'浙江省',city:'嘉兴市',status:'Inventory',operationStatus,ownership:'Self',rentCompany:'',contractNo:null,customerName:null,orgName:null,departmentName:null,subjectOrg:'公司甲',projectName:null,customerManager:null,managerId:null,brandLabel:'帕力安牌',...overrides,
});
t.mock.method(vehicleRepository,'getVehicles',async () => [make('1','3'),make('2','可运营',{status:'Abnormal',abnormalMove:{type:'维修'} as Vehicle['abnormalMove']}),make('3','4',{status:'Pending',color:'红'}),make('4','待运营'),make('5','1'),make('6','2'),make('7','3',{subjectOrg:'公司乙'})]);
const stats = await (await app.request('/inventory-stats?subject=公司甲')).json();
assert.equal(stats.length,1);
const row = stats[0];
assert.equal(row.quantity,4); assert.equal(row.available,2); assert.equal(row.awaiting,2); assert.equal(row.unclassified,0);
assert.equal(row.abnormal,1); assert.equal(row.pending,1); assert.deepEqual(row.abnormalTypes,{维修:1});
assert.equal(row.variants['白色'].quantity,3); assert.equal(row.variants['红色'].quantity,1);
const list = await (await app.request('/list?subject=公司甲&category=Inventory')).json();
assert.equal(list.length,4);
assert.ok(list.every((vehicle: {inventoryKey:string}) => vehicle.inventoryKey === JSON.stringify([row.region,row.city,row.brand,row.type,row.model])));
assert.equal(list.filter((vehicle: {color:string}) => vehicle.color === '红').length,1);
});