refactor(cleaner): improve order number tracking and error reporting

- Add QueryResultRow interface to represent query results with order numbers
- Add collectQueryResultRows() method to extract order numbers upfront before processing
- Add extractOrderNumberFromQueryRow() helper to parse order numbers from query result cells
- Process rows with order number context instead of just row indexes
- Use actual order numbers in error details instead of BATCH_ROW_X placeholders
- Pass expected order number to detail processing for better validation
- Fix retry success handling to properly update statistics when retries succeed

This change provides better error context by associating each processed row with its actual order number from the query results, improving traceability and debugging.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
test
2026-03-17 21:53:13 +08:00
parent 3854c0f048
commit b8163d7d4f

View File

@@ -23,6 +23,11 @@ interface ProgressState {
totalOrders: number totalOrders: number
} }
interface QueryResultRow {
rowIndex: number
orderNumber: string
}
class AsyncMutex { class AsyncMutex {
private queue: Promise<void> = Promise.resolve() private queue: Promise<void> = Promise.resolve()
@@ -232,13 +237,11 @@ export class CleanerService {
await this.queryOrders(workFrame, batchOrders) await this.queryOrders(workFrame, batchOrders)
await this.waitForLoading(workFrame) await this.waitForLoading(workFrame)
const rows = workFrame.locator('tbody tr') const queriedRows = await this.collectQueryResultRows(workFrame)
const rowCount = await rows.count() const queriedOrderNumbersInBatch = new Set(queriedRows.map((row) => row.orderNumber))
const rowIndexes = Array.from({ length: rowCount }, (_, i) => i)
const processedOrderNumbersInBatch = new Set<string>() await runWithConcurrency(queriedRows, processConcurrency, async (row) => {
const { rowIndex, orderNumber } = row
await runWithConcurrency(rowIndexes, processConcurrency, async (rowIndex) => {
const openedDetailPage = await popupMutex.runExclusive(async () => { const openedDetailPage = await popupMutex.runExclusive(async () => {
return await this.openDetailPageFromRow(workFrame, popupPage!, rowIndex) return await this.openDetailPageFromRow(workFrame, popupPage!, rowIndex)
}) })
@@ -249,12 +252,13 @@ export class CleanerService {
detailPage: openedDetailPage, detailPage: openedDetailPage,
deleteSet, deleteSet,
dryRun, dryRun,
expectedOrderNumber: orderNumber,
progressState, progressState,
onProgress: input.onProgress onProgress: input.onProgress
}) })
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error' const message = error instanceof Error ? error.message : 'Unknown error'
detail = this.createErrorDetail(`BATCH_ROW_${rowIndex + 1}`, message) detail = this.createErrorDetail(orderNumber, message)
} finally { } finally {
progressState.completedOrders += 1 progressState.completedOrders += 1
} }
@@ -269,13 +273,9 @@ export class CleanerService {
result.ordersProcessed += 1 result.ordersProcessed += 1
result.materialsDeleted += detail.materialsDeleted result.materialsDeleted += detail.materialsDeleted
result.materialsSkipped += detail.materialsSkipped result.materialsSkipped += detail.materialsSkipped
if (this.isOrderNumber(detail.orderNumber)) {
processedOrderNumbersInBatch.add(detail.orderNumber)
}
}) })
const missingOrders = getMissingOrders(batchOrders, processedOrderNumbersInBatch) const missingOrders = getMissingOrders(batchOrders, queriedOrderNumbersInBatch)
for (const missingOrder of missingOrders) { for (const missingOrder of missingOrders) {
const missingMessage = '订单未出现在查询结果中' const missingMessage = '订单未出现在查询结果中'
result.errors.push(`Order ${missingOrder}: ${missingMessage}`) result.errors.push(`Order ${missingOrder}: ${missingMessage}`)
@@ -300,6 +300,12 @@ export class CleanerService {
retryResult.updatedDetails.forEach((updatedDetail) => { retryResult.updatedDetails.forEach((updatedDetail) => {
const index = result.details.findIndex((d) => d.orderNumber === updatedDetail.orderNumber) const index = result.details.findIndex((d) => d.orderNumber === updatedDetail.orderNumber)
if (index !== -1) { if (index !== -1) {
const previousDetail = result.details[index]
if (updatedDetail.retrySuccess && previousDetail.errors.length > 0) {
result.ordersProcessed += 1
result.materialsDeleted += updatedDetail.materialsDeleted
result.materialsSkipped += updatedDetail.materialsSkipped
}
result.details[index] = updatedDetail result.details[index] = updatedDetail
} }
}) })
@@ -373,6 +379,36 @@ export class CleanerService {
await workFrame.locator('.search-component-searchBtn').click() await workFrame.locator('.search-component-searchBtn').click()
} }
private async collectQueryResultRows(workFrame: FrameLocator): Promise<QueryResultRow[]> {
const rows = workFrame.locator('tbody tr')
const rowCount = await rows.count()
const result: QueryResultRow[] = []
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
const row = rows.nth(rowIndex)
const orderNumber = await this.extractOrderNumberFromQueryRow(row)
if (!this.isOrderNumber(orderNumber)) {
continue
}
result.push({ rowIndex, orderNumber })
}
return result
}
private async extractOrderNumberFromQueryRow(row: Locator): Promise<string> {
try {
const cell = row.locator('td[colkey="vbillcode"]')
const codeLink = cell.locator('.code-detail-link').first()
const rawValue = (await codeLink.count()) > 0 ? await codeLink.innerText() : await cell.innerText()
const value = rawValue.trim()
const match = value.match(/SC\d{14}/)
return match ? match[0] : value
} catch {
return ''
}
}
private async openDetailPageFromRow( private async openDetailPageFromRow(
workFrame: FrameLocator, workFrame: FrameLocator,
popupPage: Page, popupPage: Page,