89 lines
3.0 KiB
TypeScript
89 lines
3.0 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
buildMileageAttribution,
|
|
filterAttributionVehicles,
|
|
reconcileMileageAttribution,
|
|
} from './attribution.js';
|
|
import type { TargetVehicle } from './types.js';
|
|
|
|
function vehicle(overrides: Partial<TargetVehicle>): TargetVehicle {
|
|
return {
|
|
plateNumber: '粤A00000',
|
|
todayMileage: 0,
|
|
totalMileage: 0,
|
|
completionRate: 0,
|
|
isQualified: false,
|
|
currentYearIsQualified: false,
|
|
dailyRequiredMileage: 100,
|
|
rentStatus: null,
|
|
department: null,
|
|
customer: null,
|
|
isOnline: false,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
test('groups vehicles and calculates additive attribution metrics', () => {
|
|
const result = buildMileageAttribution([
|
|
vehicle({ plateNumber: '粤A1', department: '业务一部', todayMileage: 120, totalMileage: 1000, completionRate: 0.8, isOnline: true }),
|
|
vehicle({ plateNumber: '粤A2', department: '业务一部', todayMileage: 40, totalMileage: 500, completionRate: 0.4 }),
|
|
vehicle({ plateNumber: '粤A3', department: '业务二部', todayMileage: 40, totalMileage: 400, completionRate: 0.2 }),
|
|
], 'department', 1000);
|
|
|
|
const first = result.find(group => group.key === '业务一部');
|
|
assert.deepEqual(first, {
|
|
key: '业务一部',
|
|
label: '业务一部',
|
|
vehicleCount: 2,
|
|
onlineCount: 1,
|
|
zeroMileageCount: 0,
|
|
behindCount: 1,
|
|
todayMileage: 160,
|
|
totalMileage: 1500,
|
|
targetMileage: 2000,
|
|
completedMileage: 1500,
|
|
shortfallMileage: 500,
|
|
completionRate: 75,
|
|
contributionRate: 80,
|
|
});
|
|
});
|
|
|
|
test('keeps unassigned vehicles visible and sorts largest shortfall first', () => {
|
|
const result = buildMileageAttribution([
|
|
vehicle({ plateNumber: '粤A1', department: null, todayMileage: 0 }),
|
|
vehicle({ plateNumber: '粤A2', department: '业务一部', todayMileage: 80, totalMileage: 100 }),
|
|
], 'department', 100);
|
|
|
|
assert.equal(result[0].label, '未分配部门');
|
|
assert.equal(result[0].shortfallMileage, 100);
|
|
assert.equal(result[0].zeroMileageCount, 1);
|
|
});
|
|
|
|
test('reconciles additive group shortfall with the target summary at display precision', () => {
|
|
const groups = buildMileageAttribution([
|
|
vehicle({ plateNumber: '粤A1', department: '业务一部', totalMileage: 1200 }),
|
|
vehicle({ plateNumber: '粤A2', department: '业务二部', totalMileage: 499.96 }),
|
|
], 'department', 1000);
|
|
|
|
assert.deepEqual(reconcileMileageAttribution(groups, 500), {
|
|
matches: true,
|
|
attributedShortfall: 500,
|
|
expectedShortfall: 500,
|
|
difference: 0,
|
|
});
|
|
assert.equal(reconcileMileageAttribution(groups, 450).difference, 50);
|
|
});
|
|
|
|
test('filters a group using the same empty-value semantics as aggregation', () => {
|
|
const vehicles = [
|
|
vehicle({ plateNumber: '粤A1', customer: null }),
|
|
vehicle({ plateNumber: '粤A2', customer: '客户甲' }),
|
|
];
|
|
|
|
assert.deepEqual(
|
|
filterAttributionVehicles(vehicles, 'customer', '未绑定客户').map(item => item.plateNumber),
|
|
['粤A1'],
|
|
);
|
|
});
|