feat(assets): refine compact inventory drilldowns and exact subtotal scopes
This commit is contained in:
@@ -1,11 +1,126 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { buildVehicleModalRequest, groupInventoryByRegion } from '../../model';
|
||||
import { createElement } from 'react';
|
||||
import { renderToStaticMarkup } from 'react-dom/server';
|
||||
import { InventoryStatusCounts } from './InventoryStatusCounts';
|
||||
import { buildVehicleModalRequest, deriveInventoryView, groupInventoryByRegion, type VehicleModalSelection } from '../../model';
|
||||
import { selectInventoryGroup, selectInventoryRow } from './InventoryDesktopTable';
|
||||
import type { RegionalInventoryStats } from '../../types';
|
||||
import { inventoryBrandGroups, inventoryGroups, inventoryModelTags, inventorySelection } from './inventory-presentation';
|
||||
import { inventoryBrandGroups, inventoryGroups, inventoryHierarchy, inventoryMobileDrilldown, inventoryMobileGroups, inventoryMobileRows, inventoryModelTags, inventoryPage, inventoryRegionRows, inventorySelection, inventorySubtotalSelection } from './inventory-presentation';
|
||||
import { inventoryKey } from '../../../../shared/assets/inventory';
|
||||
import { fetchVehicleList } from '../../api';
|
||||
|
||||
const filters = { region: '华东', city: '上海', brand: '品牌甲', type: '18T', model: '批次一' };
|
||||
|
||||
test('mobile region navigation shows only cities then types, preserving totals and exact leaf scope', () => {
|
||||
const rows = [
|
||||
{ ...filters, batch: '', quantity: 9 },
|
||||
{ ...filters, brand: '品牌乙', batch: '', quantity: 8 },
|
||||
{ ...filters, type: '49T', batch: '', quantity: 3 },
|
||||
{ ...filters, city: '苏州', batch: '', quantity: 2 },
|
||||
];
|
||||
const cities = inventoryMobileDrilldown(rows, 'region', []);
|
||||
assert.equal(cities.dimension, 'city');
|
||||
assert.deepEqual(cities.groups.map(group => [group.label, group.quantity]), [['上海', 20], ['苏州', 2]]);
|
||||
const types = inventoryMobileDrilldown(rows, 'region', ['上海']);
|
||||
assert.equal(types.dimension, 'type');
|
||||
assert.deepEqual(types.groups.map(group => [group.label, group.quantity]), [['18T', 17], ['49T', 3]]);
|
||||
const variants = inventoryMobileDrilldown(rows, 'region', ['上海', '18T']);
|
||||
assert.equal(variants.dimension, 'variant');
|
||||
assert.deepEqual(variants.groups.map(group => group.quantity), [9, 8]);
|
||||
assert.deepEqual(variants.groups.flatMap(group => inventorySubtotalSelection(group.rows, group.label).inventoryKeys), rows.slice(0, 2).map(inventoryKey));
|
||||
assert.deepEqual(inventoryMobileDrilldown(rows, 'region', ['上海']).groups, types.groups, 'back navigation restores the whole parent');
|
||||
});
|
||||
|
||||
test('mobile model navigation isolates same-named cities by region and recovers invalid paths', () => {
|
||||
const rows = [
|
||||
{ ...filters, batch: '', quantity: 4 },
|
||||
{ ...filters, region: '华南', batch: '', quantity: 7 },
|
||||
{ ...filters, region: '华南', city: '', batch: '', quantity: 1 },
|
||||
];
|
||||
assert.equal(inventoryMobileDrilldown(rows, 'model', []).dimension, 'region');
|
||||
const cities = inventoryMobileDrilldown(rows, 'model', ['华南']);
|
||||
assert.deepEqual(cities.groups.map(group => [group.label, group.quantity]), [['上海', 7], ['未分配城市', 1]]);
|
||||
assert.deepEqual(inventoryMobileDrilldown(rows, 'model', ['华南', '上海']).rows, [rows[1]]);
|
||||
assert.deepEqual(inventoryMobileDrilldown(rows, 'model', ['华南', '']).rows, [rows[2]]);
|
||||
assert.equal(inventoryMobileDrilldown(rows, 'model', ['华南', '已移除城市']).dimension, 'city');
|
||||
assert.equal(inventoryMobileDrilldown(rows, 'model', ['已移除区域']).dimension, 'region');
|
||||
assert.deepEqual(inventoryMobileDrilldown([], 'model', []).groups, []);
|
||||
});
|
||||
|
||||
test('mobile type totals combine brands and other types without merging distinct variants', () => {
|
||||
const rows = [
|
||||
{ ...filters, type: '挂车', brand: '帕力安牌', batch: '一批', quantity: 3 },
|
||||
{ ...filters, type: '其他', brand: '帕力安', batch: '二批', quantity: 4 },
|
||||
];
|
||||
assert.deepEqual(inventoryMobileGroups(rows, 'type').map(group => [group.label, group.quantity]), [['其他', 7]]);
|
||||
const view = inventoryMobileDrilldown(rows, 'region', ['上海', '其他']);
|
||||
assert.equal(view.groups.length, 2);
|
||||
assert.deepEqual(view.groups.flatMap(group => group.rows), rows);
|
||||
});
|
||||
|
||||
test('subtotal drilldown sends the exact filtered records, including merged brand aliases and vehicle types', async (t) => {
|
||||
const rows = [
|
||||
{ ...filters, brand: '帕力安牌', type: '挂车', batch: '', quantity: 2 },
|
||||
{ ...filters, brand: '帕力安', type: '其他', model: '其他型号', batch: '', quantity: 1 },
|
||||
];
|
||||
const selection = inventorySubtotalSelection(rows, '其他');
|
||||
const request = buildVehicleModalRequest(selection, '公司甲');
|
||||
assert.equal(request.kind, 'vehicles');
|
||||
if (request.kind !== 'vehicles') throw new Error('wrong endpoint');
|
||||
assert.deepEqual(request.params.inventoryKeys, rows.map(inventoryKey));
|
||||
assert.equal(request.params.subject, '公司甲');
|
||||
assert.equal(request.params.category, 'Inventory');
|
||||
assert.equal(request.params.brand, undefined, 'a merged alias must not restrict the scope to one spelling');
|
||||
t.mock.method(globalThis, 'fetch', async (url: string, init?: RequestInit) => {
|
||||
assert.equal(init?.method, 'POST');
|
||||
assert.deepEqual(JSON.parse(String(init?.body)).inventoryKeys, rows.map(inventoryKey));
|
||||
assert.equal(new URL(url, 'http://localhost').searchParams.get('subject'), '公司甲');
|
||||
return new Response('[]', { status: 200 });
|
||||
});
|
||||
await fetchVehicleList(request.params);
|
||||
});
|
||||
|
||||
test('hierarchy subtotals stay inside their parent and include records beyond the current page', () => {
|
||||
const base = { ...filters, batch: '', quantity: 9 };
|
||||
const rows = [base, { ...base, model: '批次二', quantity: 8 }, { ...base, type: '49T', quantity: 5 }, { ...base, brand: '品牌乙', quantity: 3 }, { ...base, city: '苏州', quantity: 2 }];
|
||||
assert.deepEqual(inventoryHierarchy(base, rows, 'region').map(level => level.quantity), [25, 22, 17]);
|
||||
const modelRows = rows.filter(row => row.type === '18T');
|
||||
assert.deepEqual(inventoryHierarchy(base, modelRows, 'model').map(level => level.quantity), [19, 11, 11]);
|
||||
assert.deepEqual(inventoryHierarchy(base, [base], 'region').map(level => level.quantity), [9, 9, 9]);
|
||||
});
|
||||
|
||||
test('region merging brings separated cities together without losing their quantities or scope', () => {
|
||||
const rows = [
|
||||
{ ...filters, region: '华南', city: '广州', batch: '', quantity: 25 },
|
||||
{ ...filters, batch: '', quantity: 9 },
|
||||
{ ...filters, region: '华南', city: '佛山', batch: '', quantity: 1 },
|
||||
];
|
||||
const merged = inventoryRegionRows(rows);
|
||||
assert.deepEqual(merged.map(({ row, regionSpan }) => [row.city, row.quantity, regionSpan]), [['广州', 25, 2], ['佛山', 1, 0], ['上海', 9, 1]]);
|
||||
assert.deepEqual(rows.map(row => row.city), ['广州', '上海', '佛山']);
|
||||
assert.equal(merged[1].row, rows[2]);
|
||||
});
|
||||
|
||||
test('mobile pages cover every record once and clamp after a filter reduces the rows', () => {
|
||||
const rows = Array.from({ length: 19 }, (_, index) => ({ ...filters, model: `型号${index}`, batch: '', quantity: index + 1 }));
|
||||
const group = inventoryGroups(groupInventoryByRegion(rows), 'default')[0];
|
||||
const ordered = inventoryMobileRows(group, 'region');
|
||||
const pages = Array.from({ length: 4 }, (_, index) => inventoryPage(ordered, index));
|
||||
assert.deepEqual(pages.map(page => page.rows.length), [6, 6, 6, 1]);
|
||||
assert.deepEqual(pages.flatMap(page => page.rows), rows);
|
||||
assert.equal(inventoryPage(rows.slice(0, 2), 3).page, 0);
|
||||
assert.deepEqual(inventoryPage([], 1), { page: 0, pageCount: 1, start: 0, rows: [] });
|
||||
});
|
||||
|
||||
test('inventory status hints show nonzero subset counts and hide zeros', () => {
|
||||
const row = { ...filters, batch: '', quantity: 9, pending: 2, abnormal: 1 };
|
||||
const html = renderToStaticMarkup(createElement(InventoryStatusCounts, { rows: [row] }));
|
||||
assert.match(html, /其中待交车 <strong>2<\/strong> 台/);
|
||||
assert.match(html, /其中异动 <strong>1<\/strong> 台/);
|
||||
assert.equal(renderToStaticMarkup(createElement(InventoryStatusCounts, { rows: [{ ...row, pending: 0, abnormal: 0 }] })), '');
|
||||
});
|
||||
|
||||
test('filtered inventory totals retain all filters in the vehicle request', () => {
|
||||
const request = buildVehicleModalRequest(inventorySelection(filters, '筛选结果'), '公司甲');
|
||||
assert.deepEqual(request, { kind: 'vehicles', params: {
|
||||
@@ -44,7 +159,7 @@ test('model tags separate type and qualifiers without inventing a batch', () =>
|
||||
{ kind: 'detail', label: '型号说明', value: '交投' },
|
||||
]);
|
||||
assert.deepEqual(inventoryModelTags({ type: '18T', model: '飞驰18T(白车)', batch: '2026-01' }).map(tag => tag.value), ['18T', '白车', '2026-01']);
|
||||
assert.deepEqual(inventoryModelTags({ type: '挂车', model: '挂车', batch: '挂车' }).map(tag => tag.value), ['挂车']);
|
||||
assert.deepEqual(inventoryModelTags({ type: '挂车', model: '挂车', batch: '挂车' }).map(tag => tag.value), ['其他', '挂车']);
|
||||
assert.deepEqual(inventoryModelTags({ type: '', model: '未知型号(未闭合', batch: '' }).map(tag => tag.value), ['未知型号(未闭合']);
|
||||
});
|
||||
|
||||
@@ -107,3 +222,29 @@ test('model grouping merges locations but keeps different brands, colors and bat
|
||||
assert.equal(inventorySelection(row, '明细').location, row.city);
|
||||
}
|
||||
});
|
||||
|
||||
test('inventory other includes trailers in groups, filters and drilldown while retaining exact row types', () => {
|
||||
const trailers = { ...filters, brand: '明威', type: '挂车', model: '挂车', batch: '挂车', quantity: 3, pending: 1 };
|
||||
const other = { ...filters, brand: '其他', type: '其他', model: '氢能叉车', batch: '', quantity: 2, abnormal: 1 };
|
||||
const standard = { ...filters, type: '18T', batch: '', quantity: 10 };
|
||||
const filter = { region: '', city: '', brand: '', type: '其他', model: '' };
|
||||
const view = deriveInventoryView([trailers, other, standard], filter, '其他');
|
||||
assert.deepEqual(view.filtered, [trailers, other]);
|
||||
assert.deepEqual(view.types, ['18T', '其他']);
|
||||
assert.deepEqual(view.modelsForType, ['挂车', '氢能叉车']);
|
||||
assert.deepEqual(Object.keys(view.byModel), ['其他']);
|
||||
const group = inventoryGroups(view.byModel, 'default', 'model')[0];
|
||||
assert.equal(group.quantity, 5);
|
||||
assert.equal(group.rows.reduce((sum, row) => sum + (row.pending || 0), 0), 1);
|
||||
assert.equal(group.rows.reduce((sum, row) => sum + (row.abnormal || 0), 0), 1);
|
||||
let selected: VehicleModalSelection | null = null;
|
||||
const select = (value: VehicleModalSelection | null | ((previous: VehicleModalSelection | null) => VehicleModalSelection | null)) => { selected = typeof value === 'function' ? value(selected) : value; };
|
||||
selectInventoryGroup(group, { tab: 'model', filters: filter, select });
|
||||
assert.equal((selected as VehicleModalSelection | null)?.vehicleType, '库存其他');
|
||||
assert.equal(inventorySelection(filter, '筛选后库存').vehicleType, '库存其他');
|
||||
selectInventoryRow(trailers, select);
|
||||
assert.equal((selected as VehicleModalSelection | null)?.vehicleType, '挂车');
|
||||
assert.equal((selected as VehicleModalSelection | null)?.model, '挂车');
|
||||
selectInventoryRow(other, select);
|
||||
assert.equal((selected as VehicleModalSelection | null)?.vehicleType, '其他');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user