# ExcelRowHandler Service Guide ## Overview The `ExcelRowHandler` service is a modern, object-oriented replacement for the `addRowInTable` helper function. It provides a clean and maintainable way to add formatted rows to Excel sheets in the Register Creator system. ## Location - **Service Class**: `App\Services\RegisterCreator\ExcelRowHandler` - **Integration**: `App\Services\RegisterCreator\DocumentProcessors\AbstractDocumentProcessor` ## Features - ✅ **Duplicate Detection**: Automatically prevents duplicate report entries - ✅ **File Management**: Handles PDF and XLSX file copying with smart overwrite logic - ✅ **Page Counting**: Automatically counts PDF pages using pdftk - ✅ **Excel Formatting**: Preserves styles, formulas, merged cells, and row heights - ✅ **Comprehensive Logging**: Provides detailed logging with emoji indicators - ✅ **Statistics Tracking**: Tracks file types and successful operations - ✅ **Type Safety**: Full PHP type hints for better IDE support and error prevention ## Architecture ### Service Class: ExcelRowHandler The main service class handles all row addition operations: ```php use App\Services\RegisterCreator\ExcelRowHandler; $handler = new ExcelRowHandler(); $newRow = $handler->addRow( $search, // Array of file paths to search $selectDocument, // Document information array $fullFolder, // Target folder path $lineNumber, // Line identifier $documentDate, // Document date $rowNo, // Row sequence number $sheet, // PhpSpreadsheet Worksheet object $currentRow, // Current row position $override // Whether to overwrite existing files ); ``` ### Integration with AbstractDocumentProcessor The service is integrated into `AbstractDocumentProcessor` for easy use in all document processors: ```php protected function addRowToExcel( array $search, string $lineNumber, string $documentDate, int $rowNo ): int ``` ## Usage in Document Processors ### Basic Usage Example Here's how to use the service in a document processor: ```php class MyDocumentProcessor extends AbstractDocumentProcessor { public function process( array $weldLogData, array $document, Worksheet $sheet, int &$currentRow, array $settings ): int { // Initialize processor (this creates the ExcelRowHandler instance) $this->initialize($weldLogData, $document, $sheet, $currentRow, $settings); // Get line identifier $lineIdentifier = $weldLogData[$this->registerColumnBased]; // Search for files $search = $this->searchFiles("{$document['path']}/*{$lineIdentifier}*.pdf"); // Get document date $placeholderReplacer = new \App\Services\RegisterCreator\PlaceholderReplacer(); $documentDate = $placeholderReplacer->getLatestDate($weldLogData, $this->registerColumnBased); // Add row to Excel using the service $newRow = $this->addRowToExcel( $search, $lineIdentifier, $documentDate, 1 // Row number in sequence ); if ($newRow > $currentRow) { $currentRow = $newRow; } return $currentRow; } } ``` ### Real Example: DrawingsProcessor ```php class DrawingsProcessor extends AbstractDocumentProcessor { public function process( array $weldLogData, array $document, Worksheet $sheet, int &$currentRow, array $settings ): int { $this->initialize($weldLogData, $document, $sheet, $currentRow, $settings); $this->log("Processing Drawings"); $lineIdentifier = $weldLogData[$this->registerColumnBased]; $normalized = $this->normalizeSearchTerm($lineIdentifier); $search = $this->searchFiles("{$document['path']}/*{$normalized}*.pdf"); $placeholderReplacer = new \App\Services\RegisterCreator\PlaceholderReplacer(); $documentDate = $placeholderReplacer->getLatestDate($weldLogData, $this->registerColumnBased); // Use the new ExcelRowHandler service method $newRow = $this->addRowToExcel( $search, $lineIdentifier, $documentDate, 1 ); if ($newRow > $currentRow) { $currentRow = $newRow; } return $currentRow; } } ``` ## Method Parameters ### addRow() Method | Parameter | Type | Description | |-----------|------|-------------| | `$search` | `array` | Array of file paths to search for | | `$selectDocument` | `array` | Document information containing title, path, type, order, etc. | | `$fullFolder` | `string` | Target folder path (e.g., "storage/documents/project/line/") | | `$lineNumber` | `string` | Line number or identifier | | `$documentDate` | `string` | Document date | | `$rowNo` | `int` | Row sequence number | | `$sheet` | `Worksheet` | PhpSpreadsheet Worksheet object (passed by reference) | | `$currentRow` | `int` | Current row position in Excel | | `$override` | `bool` | Whether to overwrite existing files (default: false) | **Returns**: `int` - Next row position ### addRowToExcel() Helper Method | Parameter | Type | Description | |-----------|------|-------------| | `$search` | `array` | Array of file paths to search for | | `$lineNumber` | `string` | Line number or identifier | | `$documentDate` | `string` | Document date | | `$rowNo` | `int` | Row sequence number | **Returns**: `int` - Next row position ## Document Information Structure The `$selectDocument` array should contain: ```php [ 'title1' => 'Main title', 'title2' => 'NDT Report Number', 'title3' => 'PDF Document Title', 'title4' => 'NDT Register Title', 'path' => 'storage/documents/...', 'type' => 'drawing|template|...', 'order' => 0, // Display order 'file_name' => 'Custom file name (optional)', 'incoming_control_description' => 'Description (optional)' ] ``` ## Excel Template Placeholders The service replaces the following placeholders in the Excel template: | Placeholder | Description | |-------------|-------------| | `{rowNo}` | Row sequence number | | `{type_order_no}` | Document type order number (e.g., 1 for QA, 2 for WDB) | | `{sub_number}` | Sub-number within document type (1, 2, 3...) | | `{full_number}` | Combined number in format "type_order_no.sub_number" (e.g., "1.1", "1.2", "3.1") | | `{rowTitle}` | Document title (processed) | | `{documentReportNumber}` | Line number/identifier | | `{documentDate}` | Formatted document date | | `{constructor}` | Contractor name | | `{pageCount}` | Number of pages in PDF | | `{pageIndex}` | Page range (e.g., "1-3") | | `{title1}` | Document title 1 | | `{ndt_report_no}` | NDT report number (title2) | | `{pdf_document_title}` | PDF document title (title3) | | `{ndt_register_title}` | NDT register title (title4) | ## Log Format The service generates structured logs with emoji indicators: ``` 📋 1. Document Title - LINE001 | 1 file(s) -- filename.pdf 📁 File: copied 📋 2. Another Document - LINE002 | 2 file(s) -- document1.pdf -- document2.pdf 📁 File: updated 📊 XLSX: copied ❌ 3. Missing Document - LINE003 | NO FILES FOUND Search paths: -- expected_filename.pdf 🔄 DUPLICATE: Report Title - LINE001 ``` ### Log Indicators - 📋 = Document processed - ✅ = Success - ❌ = Error or not found - 🔄 = Duplicate (skipped) - 📁 = PDF file operation - 📊 = XLSX file operation - ❓ = Warning or missing optional file ### File Copy Status - `copied` = New file copied - `updated` = File content changed, updated - `overwritten` = File forcefully overwritten (override=true) - `same content, skipped` = File exists with same content, no action needed ## File Management ### PDF Files All PDF files are automatically: 1. Validated for existence 2. Page counted using pdftk 3. Copied to target location 4. Named according to order and title ### XLSX Files (Templates) For documents with type="template": 1. Searches for corresponding .xlsx file 2. Copies alongside PDF if found 3. Logs result (found/not found) ### Smart Copy Logic The service uses MD5 hash comparison to avoid unnecessary file operations: - If file exists and content is identical → skip - If file exists and content differs → update - If override flag is true → always overwrite ## Statistics The service tracks statistics that can be accessed statically: ```php // Get file type statistics $stats = ExcelRowHandler::getFileTypeStats(); // Returns: ['pdf' => 150, 'xlsx' => 25, ...] // Get successful operations count $count = ExcelRowHandler::getSuccessfulOperations(); // Returns: 150 // Reset statistics ExcelRowHandler::resetStatistics(); ``` ## Duplicate Prevention The service automatically prevents duplicate entries based on: - Document title (title2) - Line number If a duplicate is detected: 1. Row is skipped 2. Duplicate log is written 3. Original row position is returned (no increment) ## Error Handling The service handles errors gracefully: ```php try { $newRow = $this->addRowToExcel($search, $lineNumber, $documentDate, 1); } catch (\Throwable $th) { // Error is logged automatically // Exception is re-thrown for upstream handling } ``` ### Error Logging All errors are logged to: 1. Laravel log (with context) 2. Project-specific log.txt file 3. Console output ## Cache Dependencies The service relies on cached values: | Cache Key | Description | |-----------|-------------| | `rc_contractor` | Contractor name | | `rc_firstPage` | First page number | | `rc_lastPage` | Last page number | | `rc_template_row` | Template row number | These are managed by `RegisterCreatorService`. ## Migration from addRowInTable Helper ### Old Way (Helper Function) ```php $newRow = addRowInTable( $search, $document, $this->getFullFolder(), $lineIdentifier, $documentDate, 1, $this->sheet, $currentRow, $this->settings['override'] ?? true ); ``` ### New Way (Service Method) ```php $newRow = $this->addRowToExcel( $search, $lineIdentifier, $documentDate, 1 ); ``` ### Benefits of Migration 1. **Cleaner Code**: Less parameters, more intuitive 2. **Better Testability**: Service can be mocked and tested 3. **Type Safety**: Full type hints 4. **Object-Oriented**: Follows SOLID principles 5. **Easier Maintenance**: All logic in one place 6. **Better IDE Support**: Autocomplete and hints ## Best Practices ### 1. Always Initialize ```php $this->initialize($weldLogData, $document, $sheet, $currentRow, $settings); ``` This creates the ExcelRowHandler instance and sets up necessary properties. ### 2. Update Current Row ```php $newRow = $this->addRowToExcel(...); if ($newRow > $currentRow) { $currentRow = $newRow; } ``` Always check if row was actually added before updating position. ### 3. Handle Search Results ```php $search = $this->searchFiles("{$document['path']}/*{$normalized}*.pdf"); if (empty($search)) { $this->log("No files found for pattern", "warning"); return $currentRow; } ``` Check if files exist before calling addRowToExcel. ### 4. Use Normalized Search Terms ```php $normalized = $this->normalizeSearchTerm($lineIdentifier); $search = $this->searchFiles("{$document['path']}/*{$normalized}*.pdf"); ``` Normalize search terms for better file matching. ### 5. Log Processing Steps ```php $this->log("Processing Drawings"); $newRow = $this->addRowToExcel(...); $this->log("Drawings processed successfully"); ``` Use the log() method for consistent logging. ## Troubleshooting ### Files Not Found 1. Check file paths in search array 2. Verify storage permissions 3. Check if files exist in expected location 4. Review log.txt for exact search paths ### Duplicate Detection Issues 1. Check title2 field consistency 2. Verify line number format 3. Reset statistics if testing: `ExcelRowHandler::resetStatistics()` ### Page Count Issues 1. Ensure pdftk is installed: `which pdftk` 2. Check LANG environment variable 3. Verify PDF file is not corrupted ### Excel Formatting Issues 1. Verify template row is set correctly 2. Check placeholder names match exactly 3. Ensure template has proper merged cells ## Performance Considerations 1. **File Operations**: Uses Laravel Storage for efficiency 2. **MD5 Comparison**: Only calculates when necessary 3. **Static Properties**: Shared across instances for statistics 4. **Lazy Loading**: PDFs only opened when needed ## Testing Example test structure: ```php use Tests\TestCase; use App\Services\RegisterCreator\ExcelRowHandler; class ExcelRowHandlerTest extends TestCase { public function test_adds_row_successfully() { $handler = new ExcelRowHandler(); // Setup test data $search = ['storage/documents/test.pdf']; $document = ['title2' => 'Test', 'order' => 0, ...]; // Execute $newRow = $handler->addRow( $search, $document, 'storage/documents/test/', 'LINE001', '2024-01-01', 1, $this->mockSheet, 10, false ); // Assert $this->assertEquals(11, $newRow); } } ``` ## Related Documentation - [Register Creator Guide](./register-creator-guide.md) - [Save Trigger System Guide](./save-trigger-system-guide.md) - [Document Processors](./document-processors-guide.md) ## Version History - **v1.0** (2024-10): Initial release - Migrated from addRowInTable helper function - Added full type hints - Improved error handling - Added statistics tracking