fix(bom): batch large costing result persistence

This commit is contained in:
WuYongJie 2026-08-05 03:07:18 +08:00
parent 8c56a3a164
commit 1e41401ec3

View File

@ -219,8 +219,14 @@ function readSystemName(metadata: Prisma.JsonValue | null) {
return typeof value === 'string' ? value.trim() : ''
}
function buildSkuCreateData(skus: BomCostingPreparedSku[]) {
const BOM_COSTING_PERSIST_BATCH_SIZE = 250
function buildSkuCreateData(
caseId: string,
skus: BomCostingPreparedSku[],
): Prisma.BomCostingSkuCreateManyInput[] {
return skus.map((sku) => ({
caseId,
externalSkuId: sku.externalSkuId,
name: sku.name,
structureGroupKey: sku.structureGroupKey,
@ -237,8 +243,21 @@ function buildSkuCreateData(skus: BomCostingPreparedSku[]) {
estimatedCost: sku.estimatedCost,
costCalculatedAt: sku.costCalculatedAt,
sortOrder: sku.sortOrder,
bomLines: {
create: sku.bomLines.map((line) => ({
}))
}
function buildLineCreateData(
skus: BomCostingPreparedSku[],
skuIdByExternalId: Map<string, string>,
): Prisma.BomCostingLineCreateManyInput[] {
return skus.flatMap((sku) => {
const skuId = skuIdByExternalId.get(sku.externalSkuId)
if (!skuId) {
throw new Error(`BOM 核算规格写入失败:未找到规格 ${sku.externalSkuId}`)
}
return sku.bomLines.map((line) => ({
skuId,
externalLineId: line.externalLineId,
category: line.category,
partName: line.partName,
@ -265,9 +284,31 @@ function buildSkuCreateData(skus: BomCostingPreparedSku[]) {
calculatedQuantity: line.calculatedQuantity,
subtotal: line.subtotal,
sortOrder: line.sortOrder,
priceSnapshots: line.priceSnapshot
? {
create: {
}))
})
}
function buildPriceSnapshotCreateData(
skus: BomCostingPreparedSku[],
skuIdByExternalId: Map<string, string>,
lineIdBySkuAndExternalLineId: Map<string, string>,
): Prisma.BomCostingPriceSnapshotCreateManyInput[] {
return skus.flatMap((sku) => {
const skuId = skuIdByExternalId.get(sku.externalSkuId)
if (!skuId) {
throw new Error(`BOM 核算价格快照写入失败:未找到规格 ${sku.externalSkuId}`)
}
return sku.bomLines.flatMap((line) => {
if (!line.priceSnapshot) return []
const lineId = lineIdBySkuAndExternalLineId.get(
`${skuId}:${line.externalLineId}`,
)
if (!lineId) {
throw new Error(`BOM 核算价格快照写入失败:未找到明细 ${line.externalLineId}`)
}
return [{
lineId,
sourceType: line.priceSnapshot.sourceType,
sourceRecordId: line.priceSnapshot.sourceRecordId,
sourceName: line.priceSnapshot.sourceName,
@ -275,12 +316,18 @@ function buildSkuCreateData(skus: BomCostingPreparedSku[]) {
unitPrice: line.priceSnapshot.unitPrice,
unit: line.priceSnapshot.unit,
lossFactor: line.priceSnapshot.lossFactor,
},
}]
})
})
}
async function createManyInBatches<T>(
values: T[],
create: (batch: T[]) => Promise<unknown>,
) {
for (let index = 0; index < values.length; index += BOM_COSTING_PERSIST_BATCH_SIZE) {
await create(values.slice(index, index + BOM_COSTING_PERSIST_BATCH_SIZE))
}
: undefined,
})),
},
}))
}
export class BomCostingRepository {
@ -1495,6 +1542,52 @@ export class BomCostingRepository {
await tx.bomCostingSku.deleteMany({
where: { caseId: input.caseId },
})
const skuRows = buildSkuCreateData(input.caseId, input.skus)
await createManyInBatches(skuRows, (data) =>
tx.bomCostingSku.createMany({ data }),
)
const persistedSkus = await tx.bomCostingSku.findMany({
where: { caseId: input.caseId },
select: {
id: true,
externalSkuId: true,
},
})
const skuIdByExternalId = new Map(
persistedSkus.map((sku) => [sku.externalSkuId, sku.id]),
)
const lineRows = buildLineCreateData(input.skus, skuIdByExternalId)
await createManyInBatches(lineRows, (data) =>
tx.bomCostingLine.createMany({ data }),
)
const persistedLines = await tx.bomCostingLine.findMany({
where: {
skuId: {
in: persistedSkus.map((sku) => sku.id),
},
},
select: {
id: true,
skuId: true,
externalLineId: true,
},
})
const lineIdBySkuAndExternalLineId = new Map(
persistedLines.map((line) => [
`${line.skuId}:${line.externalLineId}`,
line.id,
]),
)
const priceSnapshotRows = buildPriceSnapshotCreateData(
input.skus,
skuIdByExternalId,
lineIdBySkuAndExternalLineId,
)
await createManyInBatches(priceSnapshotRows, (data) =>
tx.bomCostingPriceSnapshot.createMany({ data }),
)
await tx.bomCostingAnalysisRun.update({
where: { id: input.analysisRunId },
data: {
@ -1510,7 +1603,7 @@ export class BomCostingRepository {
completedAt: new Date(),
},
})
return tx.bomCostingCase.update({
await tx.bomCostingCase.update({
where: { id: input.caseId },
data: {
title: input.title,
@ -1519,9 +1612,6 @@ export class BomCostingRepository {
statusMessage: `核算完成,共 ${input.skus.length} 个 SKU`,
rawAnalysisSnapshot: input.analysis,
processingFinishedAt: new Date(),
skus: {
create: buildSkuCreateData(input.skus),
},
taskEvents: {
create: {
eventType: 'RESULT_SAVED',
@ -1532,8 +1622,10 @@ export class BomCostingRepository {
},
},
},
include: caseDetailInclude,
})
}, {
maxWait: 10_000,
timeout: 120_000,
})
}