Merge asset analysis exports into main and release v1.2.2
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import type { VehicleListItem } from './types';
|
||||
|
||||
export type ExportRow = Record<string, string | number | null | undefined>;
|
||||
|
||||
/** Use complete filtered groups, never the progressively rendered slice. Repeat parent labels for Excel filtering. */
|
||||
export function statisticsExportRows(labels: string[], groups: { values: string[]; count: number; extra?: ExportRow }[], total: number, unit = '台'): ExportRow[] {
|
||||
return groups.map(group => ({
|
||||
...Object.fromEntries(labels.length ? labels.map((label, index) => [label, group.values[index]]) : [['统计范围', '当前筛选结果']]),
|
||||
[`数量(${unit})`]: group.count,
|
||||
...group.extra,
|
||||
'占比(%)': total ? Number((group.count / total * 100).toFixed(1)) : 0,
|
||||
}));
|
||||
}
|
||||
|
||||
export function vehicleExportRows(vehicles: VehicleListItem[]): ExportRow[] {
|
||||
const status: Record<string, string> = { Abnormal: '异动', Pending: '待交车', Inventory: '在库', Operating: '运营中' };
|
||||
const operation: Record<string, string> = { '1': '租赁', '2': '自营', '3': '可运营', '4': '待运营' };
|
||||
const hasPending = vehicles.some(v => v.pendingDelivery);
|
||||
const hasMovement = vehicles.some(v => v.abnormalMove);
|
||||
return vehicles.map(v => ({
|
||||
车牌: v.plateNumber, VIN: v.vin, 品牌: v.brandLabel, 车型: v.type, 型号: v.model,
|
||||
省份: v.province, 城市: v.city || v.location,
|
||||
状态: status[v.status] || v.status, 运营状态: operation[v.operationStatus || ''] || v.operationStatus,
|
||||
客户: v.customerName, 签约公司: v.orgName, 部门: v.departmentName, 业务负责人: v.customerManager,
|
||||
所属公司: v.subjectOrg, 合同: v.contractNo,
|
||||
...(hasPending ? { 待交车合同: v.pendingDelivery?.contractNo, 待交车客户: v.pendingDelivery?.customerName } : {}),
|
||||
...(hasMovement ? {
|
||||
异动类型: v.abnormalMove?.type, 审批通过时间: v.abnormalMove?.approvalPassedAt,
|
||||
'已异动时长(天)': v.abnormalMove?.elapsedDays == null ? null : Number(v.abnormalMove.elapsedDays.toFixed(1)),
|
||||
具体工作: v.abnormalMove?.work, 申请人: v.abnormalMove?.applicant,
|
||||
预计完成时间: v.abnormalMove?.targetCompletionTime,
|
||||
实际完成时间: v.abnormalMove?.actualCompletionTime || (v.abnormalMove?.status === '待结束' ? '未完成' : null),
|
||||
} : {}),
|
||||
}));
|
||||
}
|
||||
|
||||
async function analysisSheets(statistics: ExportRow[], details: ExportRow[]) {
|
||||
const { buildJsonSheet } = await import('../../shared/xlsx');
|
||||
return ([['统计表', statistics], ['详情表', details]] as const).map(([name, rows]) => {
|
||||
const sheet = buildJsonSheet(rows.map(row => Object.fromEntries(Object.entries(row).map(([key, value]) => [key, value ?? '—']))));
|
||||
sheet['!cols'] = Object.keys(rows[0] || {}).map(label => ({ wch: /时间|合同|项目|工作/.test(label) ? 28 : 18 }));
|
||||
if (sheet['!ref']) sheet['!autofilter'] = { ref: sheet['!ref'] };
|
||||
return { name, sheet };
|
||||
});
|
||||
}
|
||||
|
||||
export async function createAnalysisWorkbook(statistics: ExportRow[], details: ExportRow[]) {
|
||||
const { createWorkbook } = await import('../../shared/xlsx');
|
||||
return createWorkbook(await analysisSheets(statistics, details));
|
||||
}
|
||||
|
||||
export async function downloadAnalysisWorkbook(statistics: ExportRow[], details: ExportRow[], title: string) {
|
||||
const { writeWorkbook } = await import('../../shared/xlsx');
|
||||
const safeTitle = title.replace(/[\\/:*?"<>|\x00-\x1f]/g, '-').slice(0, 120);
|
||||
writeWorkbook(await analysisSheets(statistics, details), `${safeTitle}.xlsx`, { compression: true });
|
||||
}
|
||||
Reference in New Issue
Block a user