feat: support WPS report material workbooks

This commit is contained in:
hectorzhao
2026-09-04 17:10:04 +08:00
parent 48d0363920
commit fb39c8b606
29 changed files with 2737 additions and 734 deletions
@@ -1,45 +1,12 @@
import { BadRequestException, ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { BadRequestException, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import ExcelJS from 'exceljs';
import { createHash, randomUUID } from 'node:crypto';
import { extname } from 'node:path';
import { FilesService } from '../files/files.service';
import { PrismaService } from '../prisma/prisma.service';
import { SmsConfigService } from '../sms-config/sms-config.service';
import type {
AnalyzeImportOptions,
CreateImportProfileDto,
CreateReportBatchDto,
EmbeddedImage,
ImportCommitDto,
ImportMapping,
PagedQuery,
ReportBatchInspection,
ReportBatchTarget,
ReviewImportItemsDto,
SingleReportMaterialDto,
} from './report-materials.contracts';
import type { SingleReportMaterialDto } from './report-materials.contracts';
import {
profileData,
validateProfile,
loadWorkbook,
assertSafeWorkbook,
safeSpreadsheetText,
readEmbeddedImages,
suggestMappings,
remapProfileColumns,
signatureCoreMapping,
drainageCoreMapping,
normalizeHeader,
normalizeFieldCode,
clamp,
normalizePage,
normalizePageSize,
dateRange,
cellText,
transformValue,
mappedCoreValue,
dynamicValues,
jsonRecord,
hasValue,
isFileRef,
@@ -47,12 +14,9 @@ import {
applyExportTransform,
styleHeader,
normalizeImageExtension,
imageContentType,
safeFileName,
normalizeBatchIdempotencyKey,
jsonStringArray,
jsonSafe,
} from './report-materials.helpers';
import { convertWorkbookOutput } from './workbook-compatibility';
import type { ReportBatchGenerationService } from './batch-generation.service';
/** R4 report-materials domain service composed behind ReportMaterialsService. */
@@ -97,7 +61,7 @@ export class ReportChannelExportService {
);
const smsContentField = fields.find((field) => field.name.trim() === '短信内容');
const smsContentValue = smsContentField
? resolveExportValue(item.snapshot, smsContentField.code, smsContentField.name) ?? ''
? (resolveExportValue(item.snapshot, smsContentField.code, smsContentField.name) ?? '')
: '';
const smsContent = isFileRef(smsContentValue) ? '' : String(smsContentValue ?? '');
const missing = fields.filter((field, index) => field.required && !hasValue(values[index]));
@@ -292,6 +256,11 @@ export class ReportChannelExportService {
});
const configuredCodes = new Set(fields.map((field) => field.code));
const values = jsonRecord(snapshot.values);
const historicalCodes = Object.keys(values).filter((code) => !configuredCodes.has(code));
const historicalDefinitions = historicalCodes.length
? await this.prisma.drainageField.findMany({ where: { code: { in: historicalCodes } } })
: [];
const historicalDefinitionByCode = new Map(historicalDefinitions.map((field) => [field.code, field]));
const materialFields = fields.map((field) => {
const submittedValue = resolveExportValue(snapshot, field.code, field.name);
const value = hasValue(submittedValue) ? submittedValue : field.defaultValue;
@@ -312,9 +281,9 @@ export class ReportChannelExportService {
};
});
const historicalFields = Object.entries(values)
.filter(([code, value]) => !configuredCodes.has(code) && hasValue(value))
.filter(([code]) => !configuredCodes.has(code) && historicalDefinitionByCode.get(code)?.status !== 'deleted')
.sort(([left], [right]) => left.localeCompare(right, 'zh-CN'))
.map(([code, value]) => ({ code, name: code, value }));
.map(([code, value]) => ({ code, name: historicalDefinitionByCode.get(code)?.name ?? code, value }));
return {
reportType,
signatureId: signature.id,
@@ -380,7 +349,7 @@ export class ReportChannelExportService {
}
row.height = targetHeight;
const fileName = `${safeFileName(detail.channel.name)}-${safeFileName(detail.signatureName)}-V${detail.materialVersion}.xlsx`;
const content = Buffer.from(await workbook.xlsx.writeBuffer());
const content = await convertWorkbookOutput(Buffer.from(await workbook.xlsx.writeBuffer()), data.outputFormat);
await this.prisma.operationLog.create({
data: {
tenantId: detail.tenant.id,