Untitled
unknown
plain_text
9 months ago
13 kB
12
Indexable
async listWeighedProductions(): Promise<GetWeighedProductionsResponseDto> {
this.logger.log('Starting listWeighedProductions')
// statuses
const weighedStatusId = await this.list.getListUUID(
'batch_process_productions',
'statuses',
'weighed',
)
const currentBatchStatusId = await this.list.getListUUID('batches', 'statuses', 'current')
const checkedBatchStatusId = await this.list.getListUUID('batches', 'statuses', 'checked')
const forApprovalBatchStatusId = await this.list.getListUUID(
'batches',
'statuses',
'for_approval',
)
// Use pagination and streaming approach
const BATCH_SIZE = 50 // Reduced from 100 to be more conservative
const items: WeighedProductionDto[] = []
let skip = 0
let hasMore = true
let processedCount = 0
while (hasMore) {
this.logger.debug(`Processing batch ${skip / BATCH_SIZE + 1}, skip: ${skip}`)
const productions = await this.prisma.batchProcessProduction.findMany({
skip,
take: BATCH_SIZE,
where: {
listId_status: weighedStatusId,
batchProcess: {
batch: {
listId_statuses: {
in: [currentBatchStatusId, forApprovalBatchStatusId, checkedBatchStatusId],
},
},
},
},
select: {
id: true,
batchProcessId: true,
dtproduction: true,
material: {
select: {
id: true,
description: true,
quantityUnit: { select: { value: { select: { name: true } } } },
weightUnit: { select: { value: { select: { name: true } } } },
},
},
location: { select: { id: true, name: true } },
quantity: true,
weight: true,
batchProcess: {
select: {
id: true,
subinstallation: { select: { id: true, name: true } },
processId: true,
batchId: true,
batch: {
select: {
batchNumber: true,
processtypeId: true,
installationId: true,
processtype: { select: { id: true } },
},
},
},
},
},
orderBy: { dtproduction: 'asc' },
})
if (productions.length === 0) {
hasMore = false
break
}
// Batch load all batch template materials to avoid N+1 queries
const batchTemplateMaterials = await this._getBatchTemplateMaterialsBatch(
productions.map((p) => ({
batchProcessId: p.batchProcess.id,
materialId: p.material.id,
subinstallationId: p.batchProcess.subinstallation.id,
installationId: p.batchProcess.batch.installationId,
processtypeId: p.batchProcess.batch.processtypeId,
})),
)
// Get production IDs that have weighed materials
const weighedProductionIds = productions
.filter((p) => {
const btmKey = `${p.batchProcess.id}-${p.material.id}`
const batchTemplateMaterial = batchTemplateMaterials.get(btmKey)
return batchTemplateMaterial?.weight?.value?.name === 'weighed'
})
.map((p) => p.id)
// Batch load analysis headers for weighed productions only
const analysisHeadersMap = await this._loadAnalysisHeadersBatch(weighedProductionIds)
// Process this batch
for (const p of productions) {
const btmKey = `${p.batchProcess.id}-${p.material.id}`
const batchTemplateMaterial = batchTemplateMaterials.get(btmKey)
// Filter: only include productions whose batch template material weight type is 'weighed'
if (batchTemplateMaterial?.weight?.value?.name !== 'weighed') {
continue // skip non-weighed materials
}
const analysisHeaders = analysisHeadersMap.get(p.id)
items.push({
productionId: p.id,
batchProcessId: p.batchProcessId,
batchId: p.batchProcess.batchId,
batchNumber: (p.batchProcess.batch as any)?.batchNumber ?? null,
subinstallationName: p.batchProcess.subinstallation.name,
dtproduction: p.dtproduction,
materialId: p.material.id,
materialDescription: p.material.description,
locationId: p.location.id,
locationName: p.location.name,
quantity: p.quantity?.toNumber() ?? null,
weight: p.weight.toNumber(),
weightUnit: p.material.weightUnit?.value?.name ?? '',
quantityUnit: p.material.quantityUnit?.value?.name ?? '',
analysisHeaders,
})
processedCount++
}
skip += BATCH_SIZE
hasMore = productions.length === BATCH_SIZE
this.logger.debug(`Processed ${processedCount} weighed productions so far`)
}
this.logger.log(`Completed listWeighedProductions with ${items.length} items`)
return { items }
}
/**
* Batch load batch template materials to avoid N+1 queries
*/
private async _getBatchTemplateMaterialsBatch(
requests: Array<{
batchProcessId: string
materialId: string
subinstallationId: string
installationId: string
processtypeId: string
}>,
): Promise<Map<string, { weight?: { value: { name: string } } | null }>> {
if (requests.length === 0) return new Map()
const productionTypeId = await this.list.getListUUID(
'batchtemplate_materials',
'types',
'production',
)
// Create unique pairs without string manipulation
const uniquePairs = Array.from(
new Map(
requests.map((r) => [
`${r.installationId}|${r.subinstallationId}`, // Use | separator to avoid UUID conflicts
{ installationId: r.installationId, subinstallationId: r.subinstallationId },
]),
).values(),
)
const installationSubinstallations = await this.prisma.installationSubinstallation.findMany({
where: {
OR: uniquePairs,
},
select: {
id: true,
subinstallationId: true,
installationId: true,
},
})
const isMap = new Map(
installationSubinstallations.map((is) => [
`${is.installationId}|${is.subinstallationId}`,
is.id,
]),
)
// Get unique processtype and material combinations
const uniqueProcesstypes = Array.from(new Set(requests.map((r) => r.processtypeId)))
const uniqueMaterials = Array.from(new Set(requests.map((r) => r.materialId)))
// Get all batch template materials in one query
const isps = await this.prisma.installationSubinstallationProcesstype.findMany({
where: {
processtypeId: { in: uniqueProcesstypes },
installationSubinstallationId: { in: Array.from(isMap.values()) },
},
include: {
batchtemplate: {
include: {
batchTemplateMaterials: {
where: {
listId_types: productionTypeId,
materialId: { in: uniqueMaterials },
},
include: { weight: { include: { value: true } } },
},
},
},
},
})
// Build result map
const result = new Map<string, { weight?: { value: { name: string } } | null }>()
for (const request of requests) {
const key = `${request.batchProcessId}-${request.materialId}`
const isId = isMap.get(`${request.installationId}|${request.subinstallationId}`)
const isp = isps.find(
(i) =>
i.processtypeId === request.processtypeId && i.installationSubinstallationId === isId,
)
const btm = isp?.batchtemplate?.batchTemplateMaterials.find(
(btm) => btm.materialId === request.materialId,
)
result.set(key, btm || {})
}
return result
}
/**
* Batch load analysis headers for multiple productions
*/
private async _loadAnalysisHeadersBatch(
productionIds: string[],
): Promise<Map<string, InstallationAnalysisHeader[]>> {
if (productionIds.length === 0) return new Map()
// Load productions with their analysis headers
const productionsWithAnalysis = await this.prisma.batchProcessProduction.findMany({
where: { id: { in: productionIds } },
select: {
id: true,
batchProcess: {
select: {
processId: true,
subinstallation: { select: { id: true } },
batch: { select: { processtypeId: true } },
},
},
batchProcessAnalyseHeader: {
select: {
id: true,
dtanalysis: true,
batchProcessSamplerequest: {
select: { kind: { select: { value: { select: { name: true } } } } },
},
batchProcessAnalyseDetails: {
select: {
id: true,
analyseContent: true,
componentId: true,
component: {
select: {
id: true,
name: true,
sampletypeComponents: {
select: {
processId: true,
processtypeId: true,
componentId: true,
mincontent: true,
maxcontent: true,
sequence: true,
unit: { select: { value: { select: { name: true } } } },
},
},
},
},
unit: { select: { value: { select: { name: true } } } },
},
},
batchProcessProductions: { select: { id: true } },
batchProcessAnalyseAttribute: {
select: {
value: true,
type: { select: { value: { select: { name: true } } } },
},
},
},
},
},
})
const result = new Map<string, InstallationAnalysisHeader[]>()
for (const p of productionsWithAnalysis) {
if (!p.batchProcessAnalyseHeader) {
result.set(p.id, [])
continue
}
try {
const srKind = p.batchProcessAnalyseHeader?.batchProcessSamplerequest?.kind?.value?.name
const ratioDefs = await this.analysisService.getRatioDefinitions(
p.batchProcess.subinstallation.id,
p.batchProcess.processId,
srKind ?? '',
)
const ratios = await this.analysisService.populateRatiosForAnalysisHeader(
ratioDefs,
p.batchProcessAnalyseHeader as any,
)
const details = p.batchProcessAnalyseHeader.batchProcessAnalyseDetails
.map((d) => {
const stc = d.component.sampletypeComponents?.find(
(stc) =>
stc.processId === p.batchProcess.processId &&
stc.processtypeId === p.batchProcess.batch?.processtypeId,
)
if (!stc) return null // hide details without SOPSOC
const sopsocSpec = this.sopsoc.extractSpecFromRecord(stc)
const analysisValue = d.analyseContent?.toNumber?.() ?? d.analyseContent
const valueUnit = d.unit.value.name as any
const evalResult = this.sopsoc.evaluate(analysisValue, valueUnit, sopsocSpec as any)
return {
id: d.id,
name: d.component.name,
value: d.analyseContent,
unit: d.unit.value.name,
minContent: stc?.mincontent ?? 0,
maxContent: stc?.maxcontent ?? 0,
sopsocUnit: sopsocSpec?.unit,
outOfBounds: evalResult ? evalResult.outOfBounds : undefined,
sequence: stc?.sequence ?? Number.MAX_SAFE_INTEGER,
}
})
.filter((x): x is any => x !== null)
.sort((a, b) => a.sequence - b.sequence)
const analysisHeaders: InstallationAnalysisHeader[] = [
{
id: p.batchProcessAnalyseHeader.id,
dtAnalysis: p.batchProcessAnalyseHeader.dtanalysis,
ratios,
batchProcessProductions: p.batchProcessAnalyseHeader.batchProcessProductions.length,
sampleRequestAttributes: p.batchProcessAnalyseHeader.batchProcessAnalyseAttribute.map(
(attr) => ({ value: attr.value, type: attr.type.value.name }),
),
details,
},
]
result.set(p.id, analysisHeaders)
} catch (e) {
this.logger.warn(
`Failed to compute analysis for production ${p.id}: ${(e as Error).message}`,
p.id,
)
result.set(p.id, [])
}
}
return result
}
Editor is loading...
Leave a Comment