fix(extractor): track per-order RecordCount in operation history

Previously updateBatchStatus wrote the batch-level total recordCount to
every row, causing the detail view to show misleading identical counts.
Now mergeFiles collects per-order material counts, the handler writes
each order's count individually via updateRecordStatus, and batch
aggregation uses SUM instead of MAX for accurate totals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-03-31 20:42:22 +08:00
parent 17fbd7d251
commit 6e04f21b10
4 changed files with 66 additions and 42 deletions

View File

@@ -256,7 +256,14 @@ export function registerExtractorHandlers(): void {
: result.errors.length > 0
? 'failed'
: 'success'
await historyDao.updateBatchStatus(batchId, status, result.recordCount)
// Write per-order record counts
for (const { orderNumber, recordCount } of result.orderRecordCounts) {
await historyDao.updateRecordStatus(batchId, orderNumber, status, undefined, recordCount)
}
// Update batch status without recordCount (per-order counts are set individually)
await historyDao.updateBatchStatus(batchId, status)
log.info('Operation history batch status updated', { batchId, status })
}

View File

@@ -160,43 +160,27 @@ export class ExtractorOperationHistoryDAO {
* Update the status of all records in a batch
* @param batchId - Batch identifier
* @param status - New status (success, failed, partial)
* @param recordCount - Total record count for the batch
* @returns Update result
*/
async updateBatchStatus(
batchId: string,
status: string,
recordCount: number | null
status: string
): Promise<UpdateBatchStatusResult> {
try {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const isSqlServer = dbService.type === 'sqlserver'
const placeholder = isSqlServer ? '@p0' : '?'
let sqlString: string
let params: (string | number | null)[]
if (recordCount !== null) {
sqlString = `
const sqlString = `
UPDATE ${tableName}
SET Status = ${isSqlServer ? '@p0' : '?'},
RecordCount = ${isSqlServer ? '@p1' : '?'}
WHERE BatchId = ${isSqlServer ? '@p2' : '?'}
`
params = isSqlServer ? [status, recordCount, batchId] : [status, recordCount, batchId]
} else {
sqlString = `
UPDATE ${tableName}
SET Status = ${placeholder}
SET Status = ${isSqlServer ? '@p0' : '?'}
WHERE BatchId = ${isSqlServer ? '@p1' : '?'}
`
params = isSqlServer ? [status, batchId] : [status, batchId]
}
const params = [status, batchId]
await dbService.query(sqlString, params)
log.info('Batch status updated', { batchId, status, recordCount })
log.info('Batch status updated', { batchId, status })
return { success: true, updatedCount: 1 }
} catch (error) {
log.error('Update batch status error', {
@@ -208,33 +192,51 @@ export class ExtractorOperationHistoryDAO {
}
/**
* Update a single record's status and error message
* Update a single record's status, error message, and optional record count
* @param batchId - Batch identifier
* @param orderNumber - Order number
* @param status - New status
* @param errorMessage - Optional error message
* @param recordCount - Optional per-order record count
* @returns True if successful
*/
async updateRecordStatus(
batchId: string,
orderNumber: string,
status: string,
errorMessage?: string
errorMessage?: string,
recordCount?: number
): Promise<boolean> {
try {
const dbService = await this.getDatabaseService()
const tableName = this.getTableName()
const isSqlServer = dbService.type === 'sqlserver'
const sqlString = `
let sqlString: string
let params: (string | number | null)[]
if (recordCount !== undefined) {
sqlString = `
UPDATE ${tableName}
SET Status = ${isSqlServer ? '@p0' : '?'},
ErrorMessage = ${isSqlServer ? '@p1' : '?'},
RecordCount = ${isSqlServer ? '@p2' : '?'}
WHERE BatchId = ${isSqlServer ? '@p3' : '?'}
AND OrderNumber = ${isSqlServer ? '@p4' : '?'}
`
params = [status, errorMessage || null, recordCount, batchId, orderNumber]
} else {
sqlString = `
UPDATE ${tableName}
SET Status = ${isSqlServer ? '@p0' : '?'},
ErrorMessage = ${isSqlServer ? '@p1' : '?'}
WHERE BatchId = ${isSqlServer ? '@p2' : '?'}
AND OrderNumber = ${isSqlServer ? '@p3' : '?'}
`
params = [status, errorMessage || null, batchId, orderNumber]
}
await dbService.query(sqlString, [status, errorMessage || null, batchId, orderNumber])
await dbService.query(sqlString, params)
return true
} catch (error) {

View File

@@ -46,7 +46,8 @@ export class ExtractorService {
downloadedFiles: [],
mergedFile: null,
recordCount: 0,
errors: []
errors: [],
orderRecordCounts: []
}
try {
@@ -79,6 +80,7 @@ export class ExtractorService {
const mergeResult = await this.mergeFiles(result.downloadedFiles)
result.mergedFile = mergeResult.mergedFile
result.recordCount = mergeResult.recordCount
result.orderRecordCounts = mergeResult.orderRecordCounts
// Add merge error to result if any
if (mergeResult.error) {
@@ -123,9 +125,14 @@ export class ExtractorService {
*/
private async mergeFiles(
filePaths: string[]
): Promise<{ mergedFile: string | null; recordCount: number; error?: string }> {
): Promise<{
mergedFile: string | null
recordCount: number
error?: string
orderRecordCounts: Array<{ orderNumber: string; recordCount: number }>
}> {
if (filePaths.length === 0) {
return { mergedFile: null, recordCount: 0 }
return { mergedFile: null, recordCount: 0, orderRecordCounts: [] }
}
log.info('Starting merge', { fileCount: filePaths.length })
@@ -154,15 +161,21 @@ export class ExtractorService {
// Calculate total record count (total material rows)
let recordCount = 0
const orderRecordCounts: Array<{ orderNumber: string; recordCount: number }> = []
for (const order of allOrders) {
recordCount += order.materials.length
const count = order.materials.length
recordCount += count
orderRecordCounts.push({
orderNumber: order.orderInfo.productionOrder || '',
recordCount: count
})
}
log.info('Merge summary', { orderCount: allOrders.length, recordCount })
if (recordCount === 0) {
log.warn('No records found in any downloaded files')
return { mergedFile: null, recordCount: 0 }
return { mergedFile: null, recordCount: 0, orderRecordCounts }
}
// Generate output filename with timestamp
@@ -178,13 +191,13 @@ export class ExtractorService {
log.info('Saving merged file', { outputPath })
await this.saveMergedOrders(allOrders, outputPath)
log.info('Merged file saved successfully', { recordCount })
return { mergedFile: outputPath, recordCount }
return { mergedFile: outputPath, recordCount, orderRecordCounts }
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error)
const errorStack = error instanceof Error ? error.stack : ''
log.error('Failed to save merged file', { error: errorMsg, stack: errorStack })
// Return parsed record count and error info even if save fails
return { mergedFile: null, recordCount, error: `保存合并文件失败:${errorMsg}` }
return { mergedFile: null, recordCount, orderRecordCounts, error: `保存合并文件失败:${errorMsg}` }
}
}

View File

@@ -43,6 +43,8 @@ export interface ExtractorResult {
errors: string[]
/** Database import result (only populated if mergedFile was created) */
importResult?: ImportResult
/** Per-order material row counts */
orderRecordCounts: Array<{ orderNumber: string; recordCount: number }>
}
export interface OrderInfo {