feat(assets): align flow statistics and separate abnormal inventory
ci/woodpecker/push/woodpecker Pipeline failed

Use operations signing times and delivery completion statuses; include replacement deliveries in delivery totals with range drilldowns and exports. Separate abnormal vehicles from inventory, add a date range picker, and enable CI for feature/asset-statistics.
This commit is contained in:
shishengliang
2026-09-16 10:35:50 +08:00
parent 6ea1b3d5dd
commit 0c26b973ef
21 changed files with 371 additions and 119 deletions
+10 -5
View File
@@ -70,7 +70,7 @@ app.get('/summary', async (c) => {
hanging: 0,
},
inventory: {
total: vehicles.filter((v) => v.status === 'Inventory' || v.status === 'Abnormal').length,
total: vehicles.filter((v) => v.status === 'Inventory').length,
inStock: vehicles.filter((v) => v.status === 'Inventory').length,
abnormal: vehicles.filter((v) => v.status === 'Abnormal').length,
},
@@ -111,6 +111,7 @@ app.get('/by-type', async (c) => {
type: t.name,
totalAssets: typeVehicles.length,
totalInventory: typeStats.inventory,
totalAbnormal: typeStats.abnormal,
totalOperating: typeStats.operating,
inventoryRegions: typeStats.inventoryRegions,
pending: typeStats.pending,
@@ -273,6 +274,7 @@ app.get('/region-stats', async (c) => {
total: tv.length,
operating: tv.filter((v) => v.status === 'Operating').length,
inventory: tv.filter((v) => v.status === 'Inventory').length,
abnormal: tv.filter((v) => v.status === 'Abnormal').length,
pending: tv.filter((v) => v.status === 'Pending').length,
customers: Array.from(new Set(tv.map((v) => v.customerName).filter(Boolean))) as string[],
});
@@ -405,7 +407,9 @@ app.get('/list', async (c) => {
}
if (category) {
if (category === 'Inventory') {
filtered = filtered.filter((v) => v.status === 'Inventory' || v.status === 'Abnormal');
filtered = filtered.filter((v) => v.status === 'Inventory');
} else if (category === 'Abnormal') {
filtered = filtered.filter((v) => v.status === 'Abnormal');
} else if (category === 'Operating') {
filtered = filtered.filter((v) => v.status === 'Operating');
} else if (category === 'Pending') {
@@ -461,7 +465,7 @@ app.get('/list', async (c) => {
// GET /api/vehicles/inventory-stats — 库存统计,不设数据权限,对所有人开放
app.get('/inventory-stats', async (c) => {
const vehicles = applySubjectFilter(c, await vehicleRepository.getVehicles());
const inventory = vehicles.filter((v) => v.status === 'Inventory' || v.status === 'Abnormal');
const inventory = vehicles.filter((v) => v.status === 'Inventory');
const TYPE_NAME_MAP: Record<string, string> = {
t4_5: '4.5T普货',
@@ -525,7 +529,7 @@ app.get('/weekly-detail', async (c) => {
});
// GET /api/vehicles/flow-stats?start=YYYY-MM-DD&end=YYYY-MM-DD
// 资产流转日报:按提交时间(create_time)统计交车、还车、替换车,并返回可点击明细。
// 资产流转日报:按运维签章时间统计业务车次;交车包含关联替换单的记录,替换作为其中的子项,同车多笔业务分别累计。
app.get('/flow-stats', async (c) => {
const { start, end } = normalizeDateRange(c.req.query('start'), c.req.query('end'));
const allowedVehicles = await getVehiclesForUser(c);
@@ -555,6 +559,7 @@ app.get('/flow-stats', async (c) => {
const stat = dailyMap.get(item.date);
if (!stat) continue;
stat[item.type] += 1;
if (item.type === 'replaced') stat.delivered += 1;
stat.total += 1;
}
@@ -585,7 +590,7 @@ app.get('/subjects', async (c) => {
if (!map.has(name)) map.set(name, { total: 0, inventory: 0, operating: 0 });
const s = map.get(name)!;
s.total += 1;
if (v.status === 'Inventory' || v.status === 'Abnormal') s.inventory += 1;
if (v.status === 'Inventory') s.inventory += 1;
if (v.status === 'Operating') s.operating += 1;
}