refactor: modularize application domains
This commit is contained in:
@@ -0,0 +1,310 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import type { FlowStatsResponse, WeeklyDetailItem } from './api';
|
||||
import type {
|
||||
CustomerStats,
|
||||
DeptGroup,
|
||||
ManagerStats,
|
||||
RegionalInventoryStats,
|
||||
VehicleListItem,
|
||||
} from './types';
|
||||
import {
|
||||
buildCustomerPieData,
|
||||
buildVehicleModalRequest,
|
||||
deriveCustomerView,
|
||||
deriveDepartmentView,
|
||||
deriveInventoryView,
|
||||
deriveModalVehicleView,
|
||||
formatLocalDate,
|
||||
formatLocalDateTime,
|
||||
getWeeklyFlowRange,
|
||||
groupInventoryByModel,
|
||||
selectFlowDetails,
|
||||
} from './model';
|
||||
|
||||
const emptyInventoryFilters = { region: '', city: '', brand: '', type: '', model: '' };
|
||||
const emptyCustomerFilters = { customer: [], brand: '', department: '', manager: '', region: '' };
|
||||
const emptyModalFilters = { plateNumber: '', model: '', brand: '', location: '' };
|
||||
|
||||
function inventory(overrides: Partial<RegionalInventoryStats>): RegionalInventoryStats {
|
||||
return {
|
||||
region: '广东',
|
||||
city: '广州',
|
||||
brand: '现代',
|
||||
type: '18T',
|
||||
model: 'M1',
|
||||
batch: 'B1',
|
||||
quantity: 1,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function manager(overrides: Partial<ManagerStats>): ManagerStats {
|
||||
return {
|
||||
manager: '负责人甲',
|
||||
department: '业务一部',
|
||||
t4_5: 0,
|
||||
t4_5c: 0,
|
||||
t18: 0,
|
||||
t49: 0,
|
||||
trailer: 0,
|
||||
other: 0,
|
||||
total: 0,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function department(name: string, managers: ManagerStats[]): DeptGroup {
|
||||
return {
|
||||
department: name,
|
||||
totalAssets: managers.reduce((sum, item) => sum + item.total, 0),
|
||||
operatingCount: 0,
|
||||
idleCount: 0,
|
||||
attendanceRate: 0,
|
||||
avgMileage: 0,
|
||||
managers,
|
||||
};
|
||||
}
|
||||
|
||||
function customer(overrides: Partial<CustomerStats>): CustomerStats {
|
||||
return {
|
||||
customer: '客户甲',
|
||||
manager: '负责人甲',
|
||||
brand: '现代',
|
||||
department: '业务一部',
|
||||
region: '广东',
|
||||
city: '广州',
|
||||
t4_5: 0,
|
||||
t4_5c: 0,
|
||||
t18: 0,
|
||||
t49: 0,
|
||||
trailer: 0,
|
||||
other: 0,
|
||||
total: 1,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function vehicle(overrides: Partial<VehicleListItem>): VehicleListItem {
|
||||
return {
|
||||
id: 1,
|
||||
plateNumber: '粤A00001',
|
||||
vin: 'VIN-1',
|
||||
type: '18T',
|
||||
model: 'M1',
|
||||
location: '广州',
|
||||
province: '广东',
|
||||
city: '广州',
|
||||
status: '运营',
|
||||
ownership: '自营',
|
||||
contractNo: null,
|
||||
customerName: null,
|
||||
subjectOrg: null,
|
||||
departmentName: null,
|
||||
customerManager: null,
|
||||
brandLabel: '现代',
|
||||
orgName: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test('本地日期格式不引入 UTC 偏移', () => {
|
||||
const date = new Date(2026, 7, 3, 9, 7, 5);
|
||||
assert.equal(formatLocalDate(date), '2026-08-03');
|
||||
assert.equal(formatLocalDateTime(date), '2026-08-03 09:07:05');
|
||||
});
|
||||
|
||||
test('周流转区间按周六至周五计算', () => {
|
||||
assert.deepEqual(getWeeklyFlowRange(new Date(2026, 7, 12)), {
|
||||
start: '2026-08-08',
|
||||
end: '2026-08-14',
|
||||
});
|
||||
assert.deepEqual(getWeeklyFlowRange(new Date(2026, 7, 15)), {
|
||||
start: '2026-08-08',
|
||||
end: '2026-08-14',
|
||||
});
|
||||
assert.deepEqual(getWeeklyFlowRange(new Date(2026, 7, 16)), {
|
||||
start: '2026-08-08',
|
||||
end: '2026-08-14',
|
||||
});
|
||||
});
|
||||
|
||||
test('交还替明细走周接口且原样保留周筛选参数', () => {
|
||||
assert.deepEqual(
|
||||
buildVehicleModalRequest({
|
||||
batch: 'All',
|
||||
model: 'M1',
|
||||
location: '广州',
|
||||
category: 'Delivered',
|
||||
source: 'asset',
|
||||
}, '主体甲'),
|
||||
{
|
||||
kind: 'weekly',
|
||||
type: 'delivered',
|
||||
filters: { model: 'M1', batch: 'All', location: '广州', source: 'asset' },
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('待交付与车型映射继续走车辆列表参数', () => {
|
||||
assert.deepEqual(
|
||||
buildVehicleModalRequest({
|
||||
batch: 'B1',
|
||||
model: 'M1',
|
||||
location: '广州',
|
||||
category: 'Pending',
|
||||
type: '4.5T',
|
||||
isColdChain: true,
|
||||
isTrailer: false,
|
||||
manager: '负责人甲',
|
||||
}, '主体甲'),
|
||||
{
|
||||
kind: 'vehicles',
|
||||
params: {
|
||||
batch: 'B1',
|
||||
model: 'M1',
|
||||
location: '广州',
|
||||
category: 'Pending',
|
||||
manager: '负责人甲',
|
||||
vehicleType: '4.5T冷链',
|
||||
subject: '主体甲',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
buildVehicleModalRequest({
|
||||
batch: 'All',
|
||||
model: 'All',
|
||||
location: 'All',
|
||||
isColdChain: false,
|
||||
isTrailer: true,
|
||||
}, null),
|
||||
{
|
||||
kind: 'vehicles',
|
||||
params: { isColdChain: 'false', isTrailer: 'true', subject: null },
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('库存筛选与区域、车型分组保持既有顺序', () => {
|
||||
const rows = [
|
||||
inventory({ region: '广东', city: '广州', type: '18T', model: 'M1', quantity: 2 }),
|
||||
inventory({ region: '浙江', city: '嘉兴', type: '4.5T普货', model: 'M2', quantity: 3 }),
|
||||
inventory({ region: '广东', city: '佛山', type: '18T', model: 'M3', quantity: 4 }),
|
||||
inventory({ region: '广东', city: '广州', type: '新增车型', model: 'M4', quantity: 5 }),
|
||||
];
|
||||
const view = deriveInventoryView(
|
||||
rows,
|
||||
{ ...emptyInventoryFilters, region: '广东', type: '18T' },
|
||||
'18T',
|
||||
);
|
||||
|
||||
assert.deepEqual(view.filtered.map((item) => item.model), ['M1', 'M3']);
|
||||
assert.deepEqual(view.modelsForType, ['M1', 'M3']);
|
||||
assert.deepEqual(Object.keys(view.byRegion), ['广东']);
|
||||
assert.deepEqual(Object.keys(view.byRegion['广东']), ['广州', '佛山']);
|
||||
assert.deepEqual(Object.keys(view.byModel), ['18T']);
|
||||
assert.deepEqual(
|
||||
Object.keys(groupInventoryByModel(rows)),
|
||||
['4.5T普货', '18T', '新增车型'],
|
||||
);
|
||||
});
|
||||
|
||||
test('部门负责人去重、分组并按车辆数倒序', () => {
|
||||
const departments = [
|
||||
department('业务二部', [
|
||||
manager({ manager: '负责人乙', department: '业务二部', total: 3 }),
|
||||
manager({ manager: '负责人甲', department: '业务二部', total: 8 }),
|
||||
]),
|
||||
department('业务一部', [manager({ manager: '负责人甲', total: 5 })]),
|
||||
];
|
||||
const view = deriveDepartmentView(departments, 'All');
|
||||
|
||||
assert.deepEqual(view.managers, ['负责人乙', '负责人甲']);
|
||||
assert.deepEqual(view.groupedManagers, [
|
||||
{ department: '业务二部', managers: ['负责人乙', '负责人甲'] },
|
||||
{ department: '业务一部', managers: ['负责人甲'] },
|
||||
]);
|
||||
assert.deepEqual(view.managerStats.map((item) => item.total), [8, 5, 3]);
|
||||
});
|
||||
|
||||
test('客户筛选与负责人分组合并部门和客户来源', () => {
|
||||
const departments = [
|
||||
department('业务二部', [manager({ manager: '负责人乙', department: '业务二部' })]),
|
||||
department('业务一部', [manager({ manager: '负责人甲' })]),
|
||||
];
|
||||
const customers = [
|
||||
customer({ customer: '客户甲', department: '业务一部', manager: '负责人甲', total: 2 }),
|
||||
customer({ customer: '客户乙', department: '业务二部', manager: '负责人丙', region: '浙江', city: '嘉兴', total: 3 }),
|
||||
customer({ customer: '公务车', department: '公务车', manager: '负责人丁', total: 1 }),
|
||||
];
|
||||
const view = deriveCustomerView(
|
||||
customers,
|
||||
departments,
|
||||
{ ...emptyCustomerFilters, customer: ['客户乙'], region: '浙江' },
|
||||
);
|
||||
|
||||
assert.deepEqual(view.filtered.map((item) => item.customer), ['客户乙']);
|
||||
assert.deepEqual(view.departments, ['业务一部', '业务二部', '公务车']);
|
||||
assert.deepEqual(view.managersByDepartment, [
|
||||
{ department: '业务一部', managers: ['负责人甲'] },
|
||||
{ department: '业务二部', managers: ['负责人乙', '负责人丙'] },
|
||||
{ department: '公务车', managers: ['负责人丁'] },
|
||||
]);
|
||||
});
|
||||
|
||||
test('弹窗筛选沿用车牌为空时回退 VIN 的规则', () => {
|
||||
const vehicles = [
|
||||
vehicle({ id: 1 }),
|
||||
vehicle({ id: 2, plateNumber: '', vin: 'VIN-2', model: 'M2', brandLabel: '福田', location: '嘉兴' }),
|
||||
];
|
||||
const weeklyDetails: WeeklyDetailItem[] = [
|
||||
{ truck_id: 1, plate_number: '粤A00001', handover_date: null, contract_type: null, customer_name: null },
|
||||
{ truck_id: 2, plate_number: 'VIN-2', handover_date: null, contract_type: null, customer_name: null },
|
||||
];
|
||||
const view = deriveModalVehicleView(
|
||||
vehicles,
|
||||
weeklyDetails,
|
||||
{ ...emptyModalFilters, plateNumber: 'VIN-2' },
|
||||
);
|
||||
|
||||
assert.deepEqual(view.plates, ['粤A00001', 'VIN-2']);
|
||||
assert.deepEqual(view.filteredVehicles.map((item) => item.id), [2]);
|
||||
assert.deepEqual(view.filteredWeeklyDetails.map((item) => item.truck_id), [2]);
|
||||
});
|
||||
|
||||
test('流转明细按日期和类型同时筛选,客户饼图按区域汇总', () => {
|
||||
const flowStats: FlowStatsResponse = {
|
||||
start: '2026-08-08',
|
||||
end: '2026-08-14',
|
||||
daily: [],
|
||||
totals: { delivered: 1, returned: 1, replaced: 0, total: 2 },
|
||||
details: [
|
||||
{
|
||||
id: '1', type: 'delivered', typeLabel: '交车', date: '2026-08-12', truckId: '1',
|
||||
plateNumber: '粤A00001', eventTime: null, submitTime: null, department: '', manager: '', customerName: null,
|
||||
},
|
||||
{
|
||||
id: '2', type: 'returned', typeLabel: '还车', date: '2026-08-12', truckId: '2',
|
||||
plateNumber: '粤A00002', eventTime: null, submitTime: null, department: '', manager: '', customerName: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
assert.deepEqual(
|
||||
selectFlowDetails(flowStats, { date: '2026-08-12', type: 'delivered' }).map((item) => item.id),
|
||||
['1'],
|
||||
);
|
||||
|
||||
const customers = [
|
||||
customer({ region: '广东', total: 2 }),
|
||||
customer({ customer: '客户乙', region: '浙江', total: 5 }),
|
||||
customer({ customer: '客户丙', region: '广东', total: 3 }),
|
||||
];
|
||||
assert.deepEqual(buildCustomerPieData(customers, 'region', []), [
|
||||
{ name: '广东', value: 5 },
|
||||
{ name: '浙江', value: 5 },
|
||||
]);
|
||||
const provinceData = [{ name: '广东省', value: 10 }];
|
||||
assert.equal(buildCustomerPieData(customers, 'province', provinceData), provinceData);
|
||||
});
|
||||
Reference in New Issue
Block a user