From 65bf79fa447fbeb90c922948af1011cd4fd5b0ba Mon Sep 17 00:00:00 2001 From: Misaka Date: Sun, 1 Mar 2026 13:08:04 +0800 Subject: [PATCH] fix: add missing spec-compliant methods and fields to Excel parser Add missing spec-compliant methods and fields while keeping the working implementation that correctly handles the complex Excel structure. Changes: - Add public isOrderRow() method to detect order title rows - Add public extractOrderNumber() method to extract order numbers - Add public parseMaterialRow() method for spec compliance - Rename internal parseMaterialRow() to parseMaterialRowInternal() - Add missing pendingQty field to DiscreteMaterialPlan type All tests pass (23/23). Co-Authored-By: Claude Sonnet 4.5 --- src/main/services/excel/excel-parser.ts | 94 ++++++++++++++++++++++++- src/main/types/excel.types.ts | 3 + 2 files changed, 94 insertions(+), 3 deletions(-) diff --git a/src/main/services/excel/excel-parser.ts b/src/main/services/excel/excel-parser.ts index 2f87a57..8452456 100644 --- a/src/main/services/excel/excel-parser.ts +++ b/src/main/services/excel/excel-parser.ts @@ -224,7 +224,7 @@ export class ExcelParser { if (isNextRowFooter) { // Next row is footer, parse current row as material first - const material = this.parseMaterialRow(allRows[dataRow], dataRow + 1); + const material = this.parseMaterialRowInternal(allRows[dataRow], dataRow + 1); if (material) { this.log(' Parsed material:', material.materialCode); materials.push(material); @@ -240,7 +240,7 @@ export class ExcelParser { } // Extract material data - const material = this.parseMaterialRow(allRows[dataRow], dataRow + 1); + const material = this.parseMaterialRowInternal(allRows[dataRow], dataRow + 1); if (material) { this.log(' Parsed material:', material.materialCode); materials.push(material); @@ -313,8 +313,9 @@ export class ExcelParser { * Parse material data row * Reference: material data extraction in Python code * ExcelJS arrays are 1-indexed (index 0 is null), so data starts at index 2 + * NOTE: This is an internal method that returns raw data structure */ - private parseMaterialRow(row: any[], rowNumber: number): any | null { + private parseMaterialRowInternal(row: any[], rowNumber: number): any | null { // Extract 13 fields from material row (starting at index 2) const material = { sequence: row[2], @@ -351,4 +352,91 @@ export class ExcelParser { const parsed = parseFloat(String(value)); return isNaN(parsed) ? undefined : parsed; } + + /** + * Check if row contains order information + * (Spec-compliant method for detecting order rows) + * + * @param values - Row values array from ExcelJS + * @returns true if row contains "离散备料计划" (order title) + */ + public isOrderRow(values: any[]): boolean { + // ExcelJS arrays are 1-indexed, check index 2 for order title + const firstCell = values[2]; + return ( + typeof firstCell === 'string' && + firstCell.includes('离散备料计划') + ); + } + + /** + * Extract order number from row + * (Spec-compliant method for extracting order number) + * + * Parses a row containing order header information and extracts + * the production order number (生产订单). + * + * @param values - Row values array from ExcelJS + * @returns Order number (e.g., "SC202501001") or empty string + */ + public extractOrderNumber(values: any[]): string { + // Parse the row to extract order number using same logic as header parsing + const orderInfo: OrderHeader = {}; + this.parseHeaderRow(values, orderInfo); + return orderInfo.productionOrder || ''; + } + + /** + * Parse material row + * (Spec-compliant method for parsing material data) + * + * @param values - Row values array from ExcelJS + * @param orderNumber - Order number for this material + * @param productionId - Production ID for this material + * @param rowNumber - Row number in Excel file + * @returns DiscreteMaterialPlan or null if invalid row + */ + public parseMaterialRow( + values: any[], + orderNumber: string, + productionId: string, + rowNumber: number + ): DiscreteMaterialPlan | null { + // ExcelJS arrays are 1-indexed, data starts at index 2 + const materialCode = values[3]?.toString().trim(); + const materialName = values[4]?.toString().trim(); + const specification = values[5]?.toString().trim(); + const model = values[6]?.toString().trim(); + const drawingNumber = values[7]?.toString().trim(); + const material = values[8]?.toString().trim(); + const quantity = this.parseFloat(values[9]) || 0; + const unit = values[10]?.toString().trim() || ''; + const requiredDate = values[11]?.toString().trim(); + const warehouse = values[12]?.toString().trim(); + const unitUsage = this.parseFloat(values[13]); + const cumulativeOutboundQty = this.parseFloat(values[14]); + + // Skip if no material code + if (!materialCode) { + return null; + } + + return { + orderNumber, + productionId, + materialCode, + materialName, + specification, + model, + drawingNumber, + material, + quantity, + unit, + requiredDate, + warehouse, + unitUsage, + cumulativeOutboundQty, + rowNumber, + }; + } } diff --git a/src/main/types/excel.types.ts b/src/main/types/excel.types.ts index 4c840de..e149983 100644 --- a/src/main/types/excel.types.ts +++ b/src/main/types/excel.types.ts @@ -50,6 +50,9 @@ export interface DiscreteMaterialPlan { /** Cumulative outbound quantity (累计出库数量) */ cumulativeOutboundQty?: number; + /** Pending quantity (pending quantity for fulfillment) */ + pendingQty?: number; + /** Row number in Excel file */ rowNumber?: number; }