fix(bom): batch large costing result persistence
This commit is contained in:
parent
8c56a3a164
commit
1e41401ec3
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user