Files
citrus-cms/resources/views/guide/excel-row-handler-guide.md
T
2026-04-28 21:15:09 +03:00

514 lines
14 KiB
Markdown

# 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