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 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-03-01 13:08:04 +08:00
parent d9e0091602
commit 65bf79fa44
2 changed files with 94 additions and 3 deletions

View File

@@ -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,
};
}
}

View File

@@ -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;
}