İlk temizlik tamamlandı bir önceki projeden

This commit is contained in:
Ümit Tunç
2026-04-28 21:14:25 +03:00
commit f80443aec0
10000 changed files with 959965 additions and 0 deletions
@@ -0,0 +1,207 @@
<?php
namespace App\Services\RegisterCreator\DocumentProcessors;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use App\Services\RegisterCreator\ExcelRowHandler;
abstract class AbstractDocumentProcessor
{
protected array $weldLogData;
protected array $document;
protected Worksheet $sheet;
protected int $currentRow;
protected string $registerColumnBased;
protected array $settings;
protected string $logFilePath;
protected ExcelRowHandler $excelRowHandler;
protected int $documentsAdded = 0; // Counter for documents added to Excel
/**
* Process the document
*/
abstract public function process(
array $weldLogData,
array $document,
Worksheet $sheet,
int &$currentRow,
array $settings
): int;
/**
* Initialize processor
*/
protected function initialize(
array $weldLogData,
array $document,
Worksheet $sheet,
int $currentRow,
array $settings
): void {
$this->weldLogData = $weldLogData;
$this->document = $document;
$this->sheet = $sheet;
$this->currentRow = $currentRow;
$this->settings = $settings;
$this->registerColumnBased = $settings['register_column_based'] ?? 'line_number';
$this->excelRowHandler = new ExcelRowHandler();
$this->documentsAdded = 0; // Reset counter for each document type
// Set documents array for dynamic type order mapping
if (isset($settings['all_documents']) && is_array($settings['all_documents'])) {
$this->excelRowHandler->setDocuments($settings['all_documents']);
Log::debug("ExcelRowHandler initialized with documents", [
'processor' => class_basename($this),
'documents_count' => count($settings['all_documents'])
]);
}
// Setup log file path
$lineIdentifier = $weldLogData[$this->registerColumnBased] ?? 'unknown';
$basePath = $settings['path'] ?? '';
$this->logFilePath = "{$basePath}/{$lineIdentifier}/log.txt";
}
/**
* Normalize search term for file matching
*/
protected function normalizeSearchTerm(string $term): string
{
$term = str_replace("/", "*", $term);
$term = str_replace(" ", "*", $term);
$term = str_replace("\\", "*", $term);
return $term;
}
/**
* Search for files using glob pattern
*/
protected function searchFiles(string $pattern): array
{
Log::debug('Searching for files', ['pattern' => $pattern]);
$files = glob($pattern);
if ($files === false) {
$files = [];
}
Log::debug('Files found', ['count' => count($files)]);
return $files;
}
/**
* Get full folder path for current line
*/
protected function getFullFolder(): string
{
$path = $this->settings['path'] ?? '';
$lineIdentifier = $this->weldLogData[$this->registerColumnBased] ?? 'unknown';
$basePath = "storage/documents/{$path}";
return "{$basePath}/{$lineIdentifier}/";
}
/**
* Write to log file
* Note: This method is now silent to keep log files clean
* Only errors and NOT FOUND messages are logged
*/
protected function log(string $message, string $level = 'info'): void
{
// Don't write info messages to log file anymore - keep it clean
// Only errors will be logged via ExcelRowHandler
// Only log to Laravel debug for development purposes
Log::debug($message, [
'processor' => class_basename($this),
'line' => $this->weldLogData[$this->registerColumnBased] ?? 'unknown'
]);
}
/**
* Get contractor name
*/
protected function getContractor(): string
{
$subcontractors = \Cache::get("subcontractors", []);
$contractorKey = $this->weldLogData['contractor'] ?? '';
if (isset($subcontractors[$contractorKey])) {
return $subcontractors[$contractorKey]->company_name_ru ?? $contractorKey;
}
return $contractorKey;
}
/**
* Add row to Excel using ExcelRowHandler service
*
* @param array $search File paths to search for
* @param string $lineNumber Line number or identifier
* @param string $documentDate Document date
*
* @return int Next row position
*/
protected function addRowToExcel(
array $search,
string $lineNumber,
string $documentDate
): int {
$fullFolder = $this->getFullFolder();
$override = $this->settings['override'] ?? false;
// Calculate current row number based on starting row + documents added so far
$startRowNo = $this->settings['row_no'] ?? 1;
$currentRowNo = $startRowNo + $this->documentsAdded;
Log::info("🟢 AbstractDocumentProcessor->addRowToExcel() CALLED", [
'processor' => class_basename($this),
'row_no' => $currentRowNo,
'start_row_no' => $startRowNo,
'documents_added' => $this->documentsAdded,
'doc_type' => $this->document['type'] ?? 'N/A',
'doc_title2' => $this->document['title2'] ?? 'N/A',
'line' => $lineNumber,
'search_files' => count($search)
]);
$oldCurrentRow = $this->currentRow;
// Ensure the actual line identifier is available in the document array
// This allows processors (like TemplateProcessor) to override $lineNumber for Excel
// while still preserving the real line number for filename generation.
$this->document['real_line_identifier'] = $this->weldLogData[$this->registerColumnBased] ?? $lineNumber;
$newRow = $this->excelRowHandler->addRow(
$search,
$this->document,
$fullFolder,
$lineNumber,
$documentDate,
$currentRowNo,
$this->sheet,
$this->currentRow,
$override
);
// If a row was added, increment the documents counter and update current row
if ($newRow > $oldCurrentRow) {
$this->documentsAdded++;
$this->currentRow = $newRow; // ← UPDATE CURRENT ROW!
}
return $newRow;
}
/**
* Get the number of documents added during processing
*/
public function getDocumentsAdded(): int
{
return $this->documentsAdded;
}
}
@@ -0,0 +1,57 @@
<?php
namespace App\Services\RegisterCreator\DocumentProcessors;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class DocumentProcedureProcessor extends AbstractDocumentProcessor
{
public function process(array $weldLogData, array $document, Worksheet $sheet, int &$currentRow, array $settings): int
{
$this->initialize($weldLogData, $document, $sheet, $currentRow, $settings);
$procedures = [];
if (!empty($document['title2'])) {
$procedures = array_map('trim', explode(",", $document['title2']));
}
// Prepare procedures with dates for sorting
$proceduresWithDates = [];
foreach ($procedures as $procedureNo) {
$procedure = db("document_procedures")->where("document_no", $procedureNo)->first();
if (!$procedure) continue;
$proceduresWithDates[] = [
'procedure' => $procedure,
'date' => $procedure->publish_date ?? ''
];
}
// Sort by publish_date (oldest first) - reverse insertion order
usort($proceduresWithDates, function($a, $b) {
$timestampA = !empty($a['date']) ? strtotime($a['date']) : 0;
$timestampB = !empty($b['date']) ? strtotime($b['date']) : 0;
return $timestampA <=> $timestampB;
});
foreach ($proceduresWithDates as $procData) {
$procedure = $procData['procedure'];
$normalized = $this->normalizeSearchTerm($procedure->document_no);
$search = $this->searchFiles("storage/documents/{$document['path']}/*{$normalized}*.pdf");
$document['title2'] = $procedure->document_no;
$this->document = $document;
$documentDate = $procData['date'];
$newRow = $this->addRowToExcel($search, $procedure->document_no, $documentDate);
if ($newRow > $currentRow) { $currentRow = $newRow; }
}
return $currentRow;
}
}
@@ -0,0 +1,56 @@
<?php
namespace App\Services\RegisterCreator\DocumentProcessors;
use Exception;
class DocumentProcessorFactory
{
/**
* Create document processor instance based on type
*/
public static function make(string $type, ?array $document = null): AbstractDocumentProcessor
{
// Check if this is a dynamic document
if ($type === 'dynamic' || ($document && isset($document['is_dynamic']) && $document['is_dynamic'])) {
return new DynamicMappingProcessor();
}
return match($type) {
'qa' => new QaDocumentProcessor(),
'wdb' => new WdbDocumentProcessor(),
'wps_naks_technology' => new WpsNaksTechnologyProcessor(),
'naks_consumables_certificate' => new NaksConsumablesCertificateProcessor(),
'naks_consumables_inspection_test_report' => new NaksConsumablesInspectionProcessor(),
'drawings' => new DrawingsProcessor(),
'materials' => new MaterialsProcessor(),
'incoming_control_materials' => new IncomingControlProcessor(),
'template' => new TemplateProcessor(),
'prikaz' => new PrikazProcessor(),
'document-procedure' => new DocumentProcedureProcessor(),
'dynamic' => new DynamicMappingProcessor(),
default => new GenericDocumentProcessor(),
};
}
/**
* Check if processor exists for given type
*/
public static function exists(string $type): bool
{
try {
self::make($type);
return true;
} catch (\Throwable $th) {
return false;
}
}
}
@@ -0,0 +1,50 @@
<?php
namespace App\Services\RegisterCreator\DocumentProcessors;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class DrawingsProcessor extends AbstractDocumentProcessor
{
/**
* Process drawings documents
*/
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
);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
return $currentRow;
}
}
@@ -0,0 +1,233 @@
<?php
namespace App\Services\RegisterCreator\DocumentProcessors;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
class DynamicMappingProcessor 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 dynamic mapped document: {$document['title2']}");
// Check if this document has SQL query configuration
if (empty($document['sql_query'])) {
$this->log("No SQL query found for dynamic document", 'warning');
return $currentRow;
}
return $this->processSqlBasedMapping($currentRow);
}
private function processSqlBasedMapping(int &$currentRow): int
{
try {
// Execute SQL query with placeholder replacement
$results = $this->executeSqlQuery();
if (empty($results)) {
$this->log("SQL query returned no results", 'warning');
return $currentRow;
}
$this->log("Found " . count($results) . " records from SQL query");
// Process each result
foreach ($results as $result) {
try {
$identifier = $result['identifier'];
$recordData = $result['data'];
$documentDate = $result['document_date'];
// Generate row title using pattern
$rowTitle = $this->generateRowTitle($recordData);
// Generate file search pattern
$searchPattern = $this->generateFileSearchPattern($recordData);
$fullPath = "storage/documents/{$this->document['path']}/{$searchPattern}";
// Search for files
$files = $this->searchFiles($fullPath);
if (empty($files)) {
$this->log("No files found for: {$identifier} (pattern: {$searchPattern})", 'warning');
continue;
}
$this->log("✓ Found " . count($files) . " files for: {$identifier}");
// Update document titles
$this->document['title2'] = $identifier;
$this->document['title4'] = $rowTitle;
// Add row to Excel
$newRow = $this->addRowToExcel(
$files,
$identifier,
$documentDate
);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
$this->log("✓ Processed: {$identifier} (from SQL query)");
} catch (\Throwable $th) {
$this->log("Error processing SQL result: {$th->getMessage()}", 'error');
Log::error("Dynamic mapping result processing error", [
'identifier' => $result['identifier'] ?? 'unknown',
'error' => $th->getMessage(),
'trace' => $th->getTraceAsString()
]);
continue;
}
}
return $currentRow;
} catch (\Throwable $th) {
$this->log("SQL query execution error: {$th->getMessage()}", 'error');
Log::error("Dynamic mapping SQL execution error", [
'sql_query' => $this->document['sql_query'] ?? 'N/A',
'error' => $th->getMessage(),
'trace' => $th->getTraceAsString()
]);
throw $th;
}
}
/**
* Execute SQL query with placeholder replacement
*/
private function executeSqlQuery(): array
{
$sqlQuery = $this->document['sql_query'] ?? '';
if (empty($sqlQuery)) {
throw new \Exception("No SQL query defined for dynamic document");
}
// Replace placeholders
$executedQuery = $this->replacePlaceholders($sqlQuery);
Log::info("Executing dynamic SQL query", [
'original_query' => $sqlQuery,
'executed_query' => $executedQuery
]);
try {
$startTime = microtime(true);
$results = DB::select($executedQuery);
$executionTime = round((microtime(true) - $startTime) * 1000, 2);
Log::info("Dynamic SQL query executed successfully", [
'record_count' => count($results),
'execution_time' => $executionTime . 'ms'
]);
return $this->formatResults($results);
} catch (\Throwable $th) {
Log::error("Dynamic SQL query execution failed", [
'query' => $executedQuery,
'error' => $th->getMessage()
]);
throw $th;
}
}
/**
* Replace :placeholder with actual values from register data
*/
private function replacePlaceholders(string $query): string
{
$result = $query;
// Find all :placeholder patterns
preg_match_all('/:(\w+)/', $query, $matches);
foreach ($matches[1] as $placeholder) {
$value = $this->weldLogData[$placeholder] ?? null;
if ($value !== null) {
// Escape value for SQL
$escapedValue = DB::getPdo()->quote($value);
$result = str_replace(":{$placeholder}", $escapedValue, $result);
} else {
Log::warning("Placeholder value not found", [
'placeholder' => $placeholder,
'available_fields' => array_keys($this->weldLogData)
]);
}
}
return $result;
}
/**
* Format SQL results to standard structure
*/
private function formatResults(array $results): array
{
$identifierField = $this->document['identifier_field'] ?? 'identifier';
$dateField = $this->document['date_field'] ?? 'document_date';
$formatted = [];
foreach ($results as $result) {
$recordArray = (array) $result;
$identifier = $recordArray[$identifierField] ?? $recordArray['id'] ?? 'unknown';
$date = $recordArray[$dateField] ?? '';
$formatted[] = [
'identifier' => $identifier,
'document_date' => $date,
'data' => $recordArray
];
}
return $formatted;
}
/**
* Generate file search pattern with field replacements
*/
private function generateFileSearchPattern(array $recordData): string
{
$pattern = $this->document['file_search_pattern'] ?? '*{identifier}*.pdf';
// Replace {field_name} with actual values
foreach ($recordData as $key => $value) {
$pattern = str_replace("{{$key}}", $value, $pattern);
}
return $pattern;
}
/**
* Generate row title with pattern
*/
private function generateRowTitle(array $recordData): string
{
$pattern = $this->document['title4_pattern'] ?? '{identifier}';
// Replace {field_name} with actual values
foreach ($recordData as $key => $value) {
$pattern = str_replace("{{$key}}", $value, $pattern);
}
return $pattern;
}
}
@@ -0,0 +1,52 @@
<?php
namespace App\Services\RegisterCreator\DocumentProcessors;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
/**
* Generic processor for document types that don't have specific processors
*/
class GenericDocumentProcessor extends AbstractDocumentProcessor
{
/**
* Process generic document
*/
public function process(
array $weldLogData,
array $document,
Worksheet $sheet,
int &$currentRow,
array $settings
): int {
$this->initialize($weldLogData, $document, $sheet, $currentRow, $settings);
$this->log("Processing generic document: {$document['type']}");
$lineIdentifier = $weldLogData[$this->registerColumnBased];
$normalized = $this->normalizeSearchTerm($lineIdentifier);
$search = $this->searchFiles("storage/documents/{$document['path']}/*{$normalized}*.pdf");
$placeholderReplacer = new \App\Services\RegisterCreator\PlaceholderReplacer();
$documentDate = $placeholderReplacer->getLatestDate($weldLogData, $this->registerColumnBased);
$newRow = $this->addRowToExcel(
$search,
$lineIdentifier,
$documentDate
);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
return $currentRow;
}
}
@@ -0,0 +1,53 @@
<?php
namespace App\Services\RegisterCreator\DocumentProcessors;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Illuminate\Support\Facades\Log;
class IncomingControlProcessor extends AbstractDocumentProcessor
{
public function process(array $weldLogData, array $document, Worksheet $sheet, int &$currentRow, array $settings): int
{
$this->initialize($weldLogData, $document, $sheet, $currentRow, $settings);
$project = $weldLogData['line_number'] ?? '';
if (empty($project)) return $currentRow;
$incomingControls = db("incoming_controls")->where("project", $project)->groupBy("certificate_no", "description_ru")->get()->toArray();
$placeholderReplacer = new \App\Services\RegisterCreator\PlaceholderReplacer();
// Sort by certificate_date (oldest first) - reverse insertion order
usort($incomingControls, function($a, $b) use ($placeholderReplacer, $weldLogData) {
$a = (object) $a;
$b = (object) $b;
$dateA = $a->certificate_date ?? $a->created_at ?? $placeholderReplacer->getLatestDate($weldLogData, $this->registerColumnBased);
$dateB = $b->certificate_date ?? $b->created_at ?? $placeholderReplacer->getLatestDate($weldLogData, $this->registerColumnBased);
$timestampA = !empty($dateA) ? strtotime($dateA) : 0;
$timestampB = !empty($dateB) ? strtotime($dateB) : 0;
return $timestampA <=> $timestampB;
});
foreach ($incomingControls as $control) {
$control = (object) $control;
if (empty($control->certificate_no)) continue;
$normalized = $this->normalizeSearchTerm($control->certificate_no);
$search = $this->searchFiles("storage/documents/{$document['path']}/*{$normalized}*.pdf");
$document['title2'] = $control->description_ru;
$document['incoming_control_description'] = $control->description_ru;
$document['title3'] = $control->certificate_no;
$this->document = $document;
$documentDate = $control->certificate_date ?? $control->created_at ?? $placeholderReplacer->getLatestDate($weldLogData, $this->registerColumnBased);
$newRow = $this->addRowToExcel($search, $control->certificate_no, $documentDate);
if ($newRow > $currentRow) { $currentRow = $newRow; }
}
return $currentRow;
}
}
@@ -0,0 +1,113 @@
<?php
namespace App\Services\RegisterCreator\DocumentProcessors;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class MaterialsProcessor extends AbstractDocumentProcessor
{
/**
* Process materials documents
*/
public function process(
array $weldLogData,
array $document,
Worksheet $sheet,
int &$currentRow,
array $settings
): int {
$this->initialize($weldLogData, $document, $sheet, $currentRow, $settings);
$this->log("Processing Materials");
// Get all certificates from same line
$allRecords = db("weld_logs")
->where($this->registerColumnBased, $weldLogData['line_number'])
->get();
$uniqueCertificates = [];
foreach ($allRecords as $record) {
if (!empty($record->certificate_number_of_1) && !in_array($record->certificate_number_of_1, $uniqueCertificates)) {
$uniqueCertificates[] = $record->certificate_number_of_1;
}
if (!empty($record->certificate_number_of_2) && !in_array($record->certificate_number_of_2, $uniqueCertificates)) {
$uniqueCertificates[] = $record->certificate_number_of_2;
}
}
$this->log("Found " . count($uniqueCertificates) . " unique certificates");
$placeholderReplacer = new \App\Services\RegisterCreator\PlaceholderReplacer();
// Prepare certificates with dates for sorting
$certificatesWithDates = [];
foreach ($uniqueCertificates as $certificateNumber) {
$incomingControl = db("incoming_controls")
->where("certificate_no", $certificateNumber)
->first();
$documentDate = $incomingControl->certificate_date ?? $incomingControl->created_at ?? $placeholderReplacer->getLatestDate($weldLogData, $this->registerColumnBased);
$certificatesWithDates[] = [
'certificate_no' => $certificateNumber,
'incoming_control' => $incomingControl,
'date' => $documentDate
];
}
// Sort by date (oldest first) - reverse insertion order
usort($certificatesWithDates, function($a, $b) {
$timestampA = !empty($a['date']) ? strtotime($a['date']) : 0;
$timestampB = !empty($b['date']) ? strtotime($b['date']) : 0;
return $timestampA <=> $timestampB;
});
$this->log("Certificates sorted by date (oldest first for reverse insertion)");
foreach ($certificatesWithDates as $certData) {
$certificateNumber = $certData['certificate_no'];
$incomingControl = $certData['incoming_control'];
$documentDate = $certData['date'];
try {
if ($incomingControl) {
$document['title2'] = $incomingControl->description_ru;
$document['incoming_control_description'] = $incomingControl->description_ru;
$document['file_name'] = $certificateNumber;
} else {
$document['title2'] = "-";
$document['incoming_control_description'] = "-";
$document['file_name'] = $certificateNumber;
}
$normalized = $this->normalizeSearchTerm($certificateNumber);
$search = $this->searchFiles("{$document['path']}/*{$normalized}*.pdf");
// Update document reference for current iteration
$this->document = $document;
$newRow = $this->addRowToExcel(
$search,
$certificateNumber,
$documentDate
);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
} catch (\Throwable $th) {
$this->log("Error processing certificate: {$certificateNumber} - {$th->getMessage()}", 'error');
continue;
}
}
return $currentRow;
}
}
@@ -0,0 +1,40 @@
<?php
namespace App\Services\RegisterCreator\DocumentProcessors;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class NaksConsumablesCertificateProcessor extends AbstractDocumentProcessor
{
public function process(array $weldLogData, array $document, Worksheet $sheet, int &$currentRow, array $settings): int
{
$this->initialize($weldLogData, $document, $sheet, $currentRow, $settings);
$certificates = array_filter([
$weldLogData['welding_materials_1_certificate_no'] ?? '',
$weldLogData['welding_materials_2_certificate_no'] ?? '',
$weldLogData['welding_materials_3_certificate_no'] ?? ''
]);
$placeholderReplacer = new \App\Services\RegisterCreator\PlaceholderReplacer();
foreach ($certificates as $certNo) {
$normalized = $this->normalizeSearchTerm($certNo);
$search = $this->searchFiles("storage/documents/{$document['path']}/*{$normalized}*.pdf");
$document['title2'] = $certNo;
$this->document = $document;
$documentDate = $placeholderReplacer->getLatestDate($weldLogData, $this->registerColumnBased);
$newRow = $this->addRowToExcel($search, $certNo, $documentDate);
if ($newRow > $currentRow) { $currentRow = $newRow; }
}
return $currentRow;
}
}
@@ -0,0 +1,40 @@
<?php
namespace App\Services\RegisterCreator\DocumentProcessors;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class NaksConsumablesInspectionProcessor extends AbstractDocumentProcessor
{
public function process(array $weldLogData, array $document, Worksheet $sheet, int &$currentRow, array $settings): int
{
$this->initialize($weldLogData, $document, $sheet, $currentRow, $settings);
$lotNumbers = array_filter([
$weldLogData['welding_materials_1_lot_no'] ?? '',
$weldLogData['welding_materials_2_lot_no'] ?? '',
$weldLogData['welding_materials_3_lot_no'] ?? ''
]);
$placeholderReplacer = new \App\Services\RegisterCreator\PlaceholderReplacer();
foreach ($lotNumbers as $lotNo) {
$normalized = $this->normalizeSearchTerm($lotNo);
$search = $this->searchFiles("storage/documents/{$document['path']}/*{$normalized}*.pdf");
$document['title2'] = $lotNo;
$this->document = $document;
$documentDate = $placeholderReplacer->getLatestDate($weldLogData, $this->registerColumnBased);
$newRow = $this->addRowToExcel($search, $lotNo, $documentDate);
if ($newRow > $currentRow) { $currentRow = $newRow; }
}
return $currentRow;
}
}
@@ -0,0 +1,51 @@
<?php
namespace App\Services\RegisterCreator\DocumentProcessors;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class PrikazProcessor extends AbstractDocumentProcessor
{
public function process(array $weldLogData, array $document, Worksheet $sheet, int &$currentRow, array $settings): int
{
$this->initialize($weldLogData, $document, $sheet, $currentRow, $settings);
$project = $weldLogData['project'] ?? '';
if (empty($project)) return $currentRow;
$workPermitDocs = db("work_permit_documents")->where("zone", "like", "%" . $project . "%")->get()->toArray();
$placeholderReplacer = new \App\Services\RegisterCreator\PlaceholderReplacer();
// Sort by issue_date (oldest first) - reverse insertion order
usort($workPermitDocs, function($a, $b) use ($placeholderReplacer, $weldLogData) {
$a = (object) $a;
$b = (object) $b;
$dateA = $a->issue_date ?? $a->created_at ?? $placeholderReplacer->getLatestDate($weldLogData, $this->registerColumnBased);
$dateB = $b->issue_date ?? $b->created_at ?? $placeholderReplacer->getLatestDate($weldLogData, $this->registerColumnBased);
$timestampA = !empty($dateA) ? strtotime($dateA) : 0;
$timestampB = !empty($dateB) ? strtotime($dateB) : 0;
return $timestampA <=> $timestampB;
});
foreach ($workPermitDocs as $doc) {
$doc = (object) $doc;
$normalized = $this->normalizeSearchTerm($doc->document_number);
$search = $this->searchFiles("storage/documents/{$document['path']}/*{$normalized}*.pdf");
$document['title2'] = $doc->document_number;
$document['title3'] = $doc->title ?? $doc->document_number;
$this->document = $document;
$documentDate = $doc->issue_date ?? $doc->created_at ?? $placeholderReplacer->getLatestDate($weldLogData, $this->registerColumnBased);
$newRow = $this->addRowToExcel($search, $doc->document_number, $documentDate);
if ($newRow > $currentRow) { $currentRow = $newRow; }
}
return $currentRow;
}
}
@@ -0,0 +1,209 @@
<?php
namespace App\Services\RegisterCreator\DocumentProcessors;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Illuminate\Support\Facades\Log;
class QaDocumentProcessor extends AbstractDocumentProcessor
{
/**
* Process QA type documents (NDT reports, procedures, etc.)
*/
public function process(
array $weldLogData,
array $document,
Worksheet $sheet,
int &$currentRow,
array $settings
): int {
$this->initialize($weldLogData, $document, $sheet, $currentRow, $settings);
$this->log("Processing QA document: {$document['path']}");
// Get all joints with same line number
$allJoints = apply_welded_filter(
db("weld_logs")->where(
$this->registerColumnBased,
$this->weldLogData[$this->registerColumnBased]
)
)->get();
$this->log("Found {$allJoints->count()} joints for processing");
// Check if this is a procedure document
if (strpos($document['path'], "Procedure") !== false) {
return $this->processProcedureDocuments($currentRow);
}
// Process NDT reports
return $this->processNdtReports($allJoints, $currentRow);
}
/**
* Process NDT (Non-Destructive Testing) reports
*/
private function processNdtReports($allJoints, int &$currentRow): int
{
$allReports = [];
$uniqueReports = [];
$duplicateCount = 0;
// LAYER 1: Get reports from weld_logs (synced data)
$this->log("Layer 1: Extracting reports from weld_logs");
foreach ($allJoints as $joint) {
$jointArray = (array) $joint;
$logTypes = array_keys(log_test_types());
foreach ($logTypes as $logType) {
try {
if (strpos(strtolower($this->document['path']), $logType) !== false) {
$reportNoPrefix = $logType . "_report";
if ($logType == "pmi") {
$reportNoPrefix = "no_of_testing_report";
}
$reportNo = $jointArray[$reportNoPrefix] ?? '';
if (!empty($reportNo) && !in_array($reportNo, $uniqueReports)) {
$uniqueReports[] = $reportNo;
$allReports[] = [
'report_no' => $reportNo,
'document_date' => $jointArray[$logType . '_test_date'] ?? '',
'log_type' => $logType,
'weld_log_array' => $jointArray
];
$this->log("Layer 1 - Report added: {$reportNo} ({$logType})");
} else if (!empty($reportNo)) {
$duplicateCount++;
}
}
} catch (\Throwable $th) {
Log::error("Error processing test type: {$logType}", [
'error' => $th->getMessage()
]);
continue;
}
}
}
$this->log("Layer 1 complete: " . count($allReports) . " unique reports from weld_logs");
// Sort reports by joint number (A-Z)
// Note: Excel rows are inserted in reverse (insertNewRowBefore),
// so A-Z order becomes Z-A in Excel (correct order)
usort($allReports, function($a, $b) {
$jointNoA = $a['weld_log_array']['no_of_the_joint_as_per_as_built_survey'] ?? '';
$jointNoB = $b['weld_log_array']['no_of_the_joint_as_per_as_built_survey'] ?? '';
// Check if joint numbers are empty - throw error
if (empty($jointNoA)) {
throw new \Exception("Joint number is empty for report: {$a['report_no']}");
}
if (empty($jointNoB)) {
throw new \Exception("Joint number is empty for report: {$b['report_no']}");
}
// Sort by joint number (natural/numeric order: 1, 2, 3, 10, 11)
return strnatcmp($jointNoA, $jointNoB);
});
$this->log("Total " . count($allReports) . " unique reports sorted by joint number (A-Z)");
$this->log("Duplicate reports skipped: {$duplicateCount}");
// Process sorted reports
foreach ($allReports as $reportData) {
try {
$normalizedReportNo = $this->normalizeSearchTerm($reportData['report_no']);
$search = $this->searchFiles("storage/documents/{$this->document['path']}/*{$normalizedReportNo}*.pdf");
$this->document['title2'] = $reportData['report_no'];
// Get translation safely - handle array return
$translationKey = $reportData['log_type'] . "_register_title";
$translatedValue = e2($translationKey);
// Ensure we have a string, not an array
if (is_array($translatedValue)) {
$this->document['title4'] = $translationKey; // Use key as fallback
Log::warning("Translation returned array for key: {$translationKey}, using key as fallback");
} else {
$this->document['title4'] = (string) $translatedValue;
}
$lineNumber = $reportData['report_no'];
$newRow = $this->addRowToExcel(
$search,
$lineNumber,
$reportData['document_date']
);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
} catch (\Throwable $th) {
$this->log("Error processing report: {$reportData['report_no']} - {$th->getMessage()}", 'error');
continue;
}
}
return $currentRow;
}
/**
* Process procedure documents
*/
private function processProcedureDocuments(int &$currentRow): int
{
$this->log("Processing Document Procedure");
$documentProcedures = [];
if (!empty($this->document['title2'])) {
$customCertificates = array_map('trim', explode(",", $this->document['title2']));
$documentProcedures = $customCertificates;
}
foreach ($documentProcedures as $procedureNo) {
try {
$procedure = db("document_procedures")
->where("document_no", $procedureNo)
->first();
if ($procedure) {
$this->log("Procedure found: {$procedure->document_no}");
$normalizedDocNo = $this->normalizeSearchTerm($procedure->document_no);
$searchPath = "storage/documents/{$this->document['path']}/*{$normalizedDocNo}*.pdf";
$search = $this->searchFiles($searchPath);
$documentDate = $procedure->publish_date ?? '';
$lineNumber = $procedure->document_no;
$this->document['title2'] = $procedure->document_no;
$newRow = $this->addRowToExcel(
$search,
$lineNumber,
$documentDate
);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
} else {
$this->log("Procedure not found: {$procedureNo}", 'warning');
}
} catch (\Throwable $th) {
$this->log("Error processing procedure: {$procedureNo} - {$th->getMessage()}", 'error');
continue;
}
}
return $currentRow;
}
}
@@ -0,0 +1,152 @@
<?php
namespace App\Services\RegisterCreator\DocumentProcessors;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Illuminate\Support\Facades\Log;
use Carbon\Carbon;
class TemplateProcessor extends AbstractDocumentProcessor
{
/**
* Process template type documents
*/
public function process(
array $weldLogData,
array $document,
Worksheet $sheet,
int &$currentRow,
array $settings
): int {
$this->initialize($weldLogData, $document, $sheet, $currentRow, $settings);
$this->log("Processing Template document: {$document['path']}");
$lineIdentifier = $weldLogData[$this->registerColumnBased];
// Search by line number
$normalized1 = $this->normalizeSearchTerm($lineIdentifier);
$search1 = $this->searchFiles("storage/documents/{$document['path']}/*{$normalized1}*.pdf");
// Search by test package numbers
$testPackageNumbers = db("weld_logs")
->where($this->registerColumnBased, $lineIdentifier)
->whereNotNull("test_package_no")
->where("test_package_no", "!=", "")
->distinct()
->pluck("test_package_no")
->toArray();
$this->log("Found " . count($testPackageNumbers) . " test package numbers for search");
$search2 = [];
foreach ($testPackageNumbers as $testPackageNo) {
$normalized2 = $this->normalizeSearchTerm($testPackageNo);
$result = $this->searchFiles("storage/documents/{$document['path']}/*{$normalized2}*.pdf");
if (!empty($result)) {
$search2 = array_merge($search2, $result);
}
}
// Merge and remove duplicates
$allFiles = array_unique(array_merge($search1, $search2));
$this->log("Total files found: " . count($allFiles));
if (empty($allFiles)) {
$this->log("No template files found", 'warning');
return $currentRow;
}
// Sort files by date extracted from filename (oldest first)
// Note: Excel rows are inserted in reverse (insertNewRowBefore),
// so oldest first becomes newest last in Excel (correct order)
$self = $this;
usort($allFiles, function($a, $b) use ($self) {
$dateA = strtotime($self->extractDateFromFilename(basename($a))) ?: 0;
$dateB = strtotime($self->extractDateFromFilename(basename($b))) ?: 0;
// Sort ascending (oldest first)
return $dateA <=> $dateB;
});
$this->log("Files sorted by date (oldest first for reverse insertion)");
foreach ($allFiles as $filePath) {
try {
$fileName = basename($filePath);
$documentDate = $this->extractDateFromFilename($fileName);
// Process single file
$templateFileName = str_replace(".pdf", "", $fileName);
$lineNumber = $templateFileName;
$document['title2'] = $templateFileName;
// Remove date from filename
$templateFileName = preg_replace('/_\d{2}\.\d{2}\.\d{4}/', '', $templateFileName);
$templateFileName = preg_replace('/_\d{4}-\d{2}-\d{2}/', '', $templateFileName);
$document['templateFileName'] = $templateFileName;
// Special handling for Incoming_Control
if ($document['id'] == "Incoming_Control") {
$incomingControl = db("incoming_controls")
->where("certificate_no", $lineNumber)
->first();
if ($incomingControl) {
$document['title2'] = $incomingControl->description_ru;
$this->log("Incoming Control found: {$document['title2']}");
}
}
// Update document reference for current iteration
$this->document = $document;
$newRow = $this->addRowToExcel(
[$filePath],
$lineNumber,
$documentDate
);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
} catch (\Throwable $th) {
$this->log("Error processing template file: " . basename($filePath) . " - {$th->getMessage()}", 'error');
continue;
}
}
return $currentRow;
}
/**
* Extract date from filename
*/
private function extractDateFromFilename(string $fileName): string
{
// Try YYYY-MM-DD format
if (preg_match('/(\d{4}-\d{2}-\d{2})/', $fileName, $matches)) {
return $matches[1];
}
// Try DD.MM.YYYY format
if (preg_match('/(\d{2}\.\d{2}\.\d{4})/', $fileName, $matches)) {
try {
return Carbon::createFromFormat('d.m.Y', $matches[1])->format('Y-m-d');
} catch (\Throwable $th) {
Log::debug("Could not parse date: {$matches[1]}");
}
}
// Default to latest date
$placeholderReplacer = new \App\Services\RegisterCreator\PlaceholderReplacer();
return $placeholderReplacer->getLatestDate($this->weldLogData, $this->registerColumnBased);
}
}
@@ -0,0 +1,459 @@
<?php
namespace App\Services\RegisterCreator\DocumentProcessors;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Illuminate\Support\Facades\Log;
class WdbDocumentProcessor extends AbstractDocumentProcessor
{
/**
* Process WDB (Welding Database) type documents
*/
public function process(
array $weldLogData,
array $document,
Worksheet $sheet,
int &$currentRow,
array $settings
): int {
$this->initialize($weldLogData, $document, $sheet, $currentRow, $settings);
$this->log("Processing WDB document: {$document['path']}");
$path = $document['path'];
$wpsData = $settings['wps_data'] ?? null;
$startRowNo = $settings['row_no'] ?? 1;
// Process based on sub-type
if (strpos($path, "Naks Technology") !== false) {
return $this->processNaksTechnology($currentRow, $wpsData, $startRowNo);
}
if (strpos($path, "Naks_Welder") !== false) {
return $this->processNaksWelder($currentRow, $startRowNo);
}
if (strpos($path, "Naks_Equipments") !== false) {
return $this->processNaksEquipments($currentRow, $startRowNo);
}
if (strpos($path, "Naks_Consumables") !== false) {
return $this->processNaksConsumables($currentRow, $startRowNo);
}
if (strpos($path, "Welding Experts") !== false) {
return $this->processWeldingExperts($currentRow, $startRowNo);
}
if (strpos($path, "WPQ") !== false) {
return $this->processWpq($currentRow, $startRowNo);
}
if (strpos($path, "WPS") !== false) {
return $this->processWps($currentRow, $wpsData, $startRowNo);
}
if (strpos($path, "PQR") !== false) {
return $this->processPqr($currentRow, $wpsData, $startRowNo);
}
return $currentRow;
}
/**
* Process Naks Technology certificates
*/
private function processNaksTechnology(int &$currentRow, $wpsData, int $startRowNo = 1): int
{
if (!$wpsData) {
$this->log("WPS data not found, skipping Naks Technology", 'warning');
return $currentRow;
}
$naksCertificates = explode(" + ", $wpsData->naks_certificate_no);
$this->log("Processing " . count($naksCertificates) . " Naks Technology certificates");
foreach ($naksCertificates as $naksCertificate) {
try {
$normalized = $this->normalizeSearchTerm($naksCertificate);
$search = $this->searchFiles("storage/documents/{$this->document['path']}/*{$normalized}*.pdf");
$this->document['title3'] = $naksCertificate;
$newRow = $this->addRowToExcel(
$search,
$naksCertificate,
$wpsData->date ?? ''
);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
} catch (\Throwable $th) {
$this->log("Error processing Naks certificate: {$naksCertificate} - {$th->getMessage()}", 'error');
continue;
}
}
return $currentRow;
}
/**
* Process Naks Welder certificates
*/
private function processNaksWelder(int &$currentRow, int $startRowNo = 1): int
{
$this->log("Processing Naks Welders");
$weldersData = apply_welded_filter(
db("weld_logs")
->select("certificate_no_1", "certificate_no_2")
->where($this->registerColumnBased, $this->weldLogData[$this->registerColumnBased])
)->get();
$naksWelders = [];
foreach ($weldersData as $welderData) {
if (!empty($welderData->certificate_no_1) && !in_array($welderData->certificate_no_1, $naksWelders)) {
$naksWelders[] = $welderData->certificate_no_1;
}
if (!empty($welderData->certificate_no_2) && !in_array($welderData->certificate_no_2, $naksWelders)) {
$naksWelders[] = $welderData->certificate_no_2;
}
}
$this->log("Found " . count($naksWelders) . " welder certificates");
// Prepare welders with their dates for sorting
$weldersWithDates = [];
foreach ($naksWelders as $naksWelder) {
$welderInfo = db("naks_welders")->where("naks_certificate_no", $naksWelder)->first();
$weldersWithDates[] = [
'certificate_no' => $naksWelder,
'date' => $welderInfo->period_of_validity ?? ''
];
}
// Sort by date (oldest first) - reverse insertion order
usort($weldersWithDates, function($a, $b) {
$dateA = !empty($a['date']) ? strtotime($a['date']) : 0;
$dateB = !empty($b['date']) ? strtotime($b['date']) : 0;
return $dateA <=> $dateB;
});
$this->log("Welders sorted by date (oldest first for reverse insertion)");
foreach ($weldersWithDates as $welderData) {
try {
$naksWelder = $welderData['certificate_no'];
$normalized = $this->normalizeSearchTerm($naksWelder);
$search = $this->searchFiles("storage/documents/{$this->document['path']}/*{$normalized}*.pdf");
$this->document['title2'] = $naksWelder;
$newRow = $this->addRowToExcel(
$search,
$naksWelder,
$welderData['date']
);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
} catch (\Throwable $th) {
$this->log("Error processing welder: {$naksWelder} - {$th->getMessage()}", 'error');
continue;
}
}
return $currentRow;
}
/**
* Process Naks Equipment certificates
*/
private function processNaksEquipments(int &$currentRow, int $startRowNo = 1): int
{
$this->log("Processing Naks Equipments");
$certificates = [];
if (!empty($this->document['title2'])) {
$certificates = array_map('trim', explode(",", $this->document['title2']));
}
// Prepare equipments with dates for sorting
$equipmentsWithDates = [];
foreach ($certificates as $certificate) {
$equipmentInfo = db("welding_equipment")->where("attestation", $certificate)->first();
$equipmentsWithDates[] = [
'certificate' => $certificate,
'date' => $equipmentInfo->valid_until ?? ''
];
}
// Sort by valid_until date (oldest first) - reverse insertion order
usort($equipmentsWithDates, function($a, $b) {
$timestampA = !empty($a['date']) ? strtotime($a['date']) : 0;
$timestampB = !empty($b['date']) ? strtotime($b['date']) : 0;
return $timestampA <=> $timestampB;
});
$this->log("Equipments sorted by date (oldest first for reverse insertion)");
foreach ($equipmentsWithDates as $equipData) {
try {
$certificate = $equipData['certificate'];
$normalized = $this->normalizeSearchTerm($certificate);
$search = $this->searchFiles("storage/documents/{$this->document['path']}/*{$normalized}*.pdf");
$this->document['title2'] = $certificate;
$this->document['title3'] = $certificate;
$newRow = $this->addRowToExcel(
$search,
$certificate,
$equipData['date']
);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
} catch (\Throwable $th) {
$this->log("Error processing equipment: {$certificate} - {$th->getMessage()}", 'error');
continue;
}
}
return $currentRow;
}
/**
* Process Naks Consumables
*/
private function processNaksConsumables(int &$currentRow, int $startRowNo = 1): int
{
$this->log("Processing Naks Consumables");
$consumables = [];
if (!empty($this->document['title2'])) {
$consumables = array_map('trim', explode(",", $this->document['title2']));
}
// Prepare consumables with dates for sorting
$consumablesWithDates = [];
foreach ($consumables as $consumable) {
$consumableInfo = db("naks_consumables")->where("naks_certificate_no", $consumable)->first();
$consumablesWithDates[] = [
'consumable' => $consumable,
'date' => $consumableInfo->certificate_date ?? ''
];
}
// Sort by certificate_date (oldest first) - reverse insertion order
usort($consumablesWithDates, function($a, $b) {
$timestampA = !empty($a['date']) ? strtotime($a['date']) : 0;
$timestampB = !empty($b['date']) ? strtotime($b['date']) : 0;
return $timestampA <=> $timestampB;
});
$this->log("Consumables sorted by date (oldest first for reverse insertion)");
foreach ($consumablesWithDates as $consData) {
try {
$consumable = $consData['consumable'];
$normalized = $this->normalizeSearchTerm($consumable);
$search = $this->searchFiles("storage/documents/{$this->document['path']}/*{$normalized}*.pdf");
$this->document['title2'] = $consumable;
$newRow = $this->addRowToExcel(
$search,
$consumable,
$consData['date']
);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
} catch (\Throwable $th) {
$this->log("Error processing consumable: {$consumable} - {$th->getMessage()}", 'error');
continue;
}
}
return $currentRow;
}
/**
* Process Welding Experts
*/
private function processWeldingExperts(int &$currentRow, int $startRowNo = 1): int
{
$this->log("Processing Welding Experts");
$certificates = array_map('trim', explode(',', $this->document['title2'] ?? ''));
// Prepare experts with dates for sorting
$expertsWithDates = [];
foreach ($certificates as $certificate) {
$expertInfo = db("register_of_experts")->where("certificate_no", $certificate)->first();
$expertsWithDates[] = [
'certificate' => $certificate,
'date' => $expertInfo->expration_of_the_certificate ?? ''
];
}
// Sort by expiration date (oldest first) - reverse insertion order
usort($expertsWithDates, function($a, $b) {
$timestampA = !empty($a['date']) ? strtotime($a['date']) : 0;
$timestampB = !empty($b['date']) ? strtotime($b['date']) : 0;
return $timestampA <=> $timestampB;
});
$this->log("Experts sorted by date (oldest first for reverse insertion)");
foreach ($expertsWithDates as $expertData) {
try {
$certificate = $expertData['certificate'];
$normalized = $this->normalizeSearchTerm($certificate);
$search = $this->searchFiles("storage/documents/{$this->document['path']}/*{$normalized}*.pdf");
$this->document['title2'] = $certificate;
$newRow = $this->addRowToExcel(
$search,
$certificate,
$expertData['date']
);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
} catch (\Throwable $th) {
$this->log("Error processing expert: {$certificate} - {$th->getMessage()}", 'error');
continue;
}
}
return $currentRow;
}
/**
* Process WPQ documents
*/
private function processWpq(int &$currentRow, int $startRowNo = 1): int
{
$this->log("Processing WPQ");
$wpqs = db("welder_tests")->whereIn("wpq_document_no", [
$this->weldLogData['wpq_report_1'] ?? '',
$this->weldLogData['wpq_report_2'] ?? '',
])->get()->toArray();
$this->log("Found " . count($wpqs) . " WPQ documents");
// Sort by naks_validity date (oldest first) - reverse insertion order
usort($wpqs, function($a, $b) {
$a = (object) $a;
$b = (object) $b;
$timestampA = !empty($a->naks_validity) ? strtotime($a->naks_validity) : 0;
$timestampB = !empty($b->naks_validity) ? strtotime($b->naks_validity) : 0;
return $timestampA <=> $timestampB;
});
$this->log("WPQ documents sorted by date (oldest first for reverse insertion)");
foreach ($wpqs as $wpq) {
$wpq = (object) $wpq;
try {
$normalized = $this->normalizeSearchTerm($wpq->wpq_document_no);
$search = $this->searchFiles("storage/documents/{$this->document['path']}/*{$normalized}*.pdf");
$this->document['title3'] = $wpq->wpq_document_no;
$newRow = $this->addRowToExcel(
$search,
$wpq->wpq_document_no,
$wpq->naks_validity ?? ''
);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
} catch (\Throwable $th) {
$this->log("Error processing WPQ: {$wpq->wpq_document_no} - {$th->getMessage()}", 'error');
continue;
}
}
return $currentRow;
}
/**
* Process WPS documents
*/
private function processWps(int &$currentRow, $wpsData, int $startRowNo = 1): int
{
if (!$wpsData) {
$this->log("WPS data not found", 'warning');
return $currentRow;
}
$this->log("Processing WPS: {$wpsData->details}");
$normalized = $this->normalizeSearchTerm($wpsData->details);
$search = $this->searchFiles("storage/documents/{$this->document['path']}/*{$normalized}*.pdf");
$this->document['title3'] = $wpsData->details;
$newRow = $this->addRowToExcel(
$search,
$wpsData->details,
$wpsData->date ?? ''
);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
return $currentRow;
}
/**
* Process PQR documents
*/
private function processPqr(int &$currentRow, $wpsData, int $startRowNo = 1): int
{
if (!$wpsData) {
$this->log("WPS data not found, cannot process PQR", 'warning');
return $currentRow;
}
$this->log("Processing PQR");
$pqr = db("prosedure_qualification_records")->where("pqr_no", $wpsData->pqr_no)->first();
if ($pqr) {
$this->log("PQR found: {$pqr->pqr_no}");
$normalized = $this->normalizeSearchTerm($pqr->pqr_no);
$search = $this->searchFiles("storage/documents/{$this->document['path']}/*{$normalized}*.pdf");
$this->document['title2'] = $pqr->pqr_no;
$newRow = $this->addRowToExcel(
$search,
$pqr->pqr_no,
$pqr->approved_date ?? ''
);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
}
return $currentRow;
}
}
@@ -0,0 +1,229 @@
<?php
namespace App\Services\RegisterCreator\DocumentProcessors;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Illuminate\Support\Facades\Log;
class WpsNaksTechnologyProcessor extends AbstractDocumentProcessor
{
/**
* Search files using the same algorithm as pdf-db-naks-technology-sync
* This algorithm handles zero-prefix patterns and multiple search strategies
*/
private function searchFilesWithFallback(string $basePath, string $certificateNo): array
{
$files = [];
// Parse certificate number to extract short_number and cert_no parts
// Format examples: АЦСТ-20-01934, АЦСТ-161-00050
$certParts = explode('-', $certificateNo);
if (count($certParts) >= 2) {
$shortNumber = $certParts[0]; // e.g., АЦСТ
$certNumber = isset($certParts[1]) ? $certParts[1] : '';
// If there's a third part, combine with second
if (count($certParts) >= 3) {
$certNumber = $certParts[1]; // e.g., 20 or 161
$thirdPart = $certParts[2]; // e.g., 01934 or 00050
// Generate certificate patterns with different zero prefixes
$certPatterns = [$thirdPart];
// Clean leading zeros and generate additional search patterns
$trimmedCertNo = ltrim($thirdPart, '0');
if ($trimmedCertNo != $thirdPart && $trimmedCertNo != '') {
$certPatterns[] = $trimmedCertNo;
// Add versions with different numbers of leading zeros
for ($i = 1; $i <= 5; $i++) {
$certPatterns[] = str_pad($trimmedCertNo, $i, '0', STR_PAD_LEFT);
}
}
// Try each pattern until we find a match
foreach ($certPatterns as $certPattern) {
// Format: short_number-certNumber-certPattern
$searchData = "*{$shortNumber}-{$certNumber}-{$certPattern}*";
$searchData = str_replace("/", "*", $searchData);
$searchData = str_replace(" ", "*", $searchData);
$searchData = str_replace("\\", "*", $searchData);
$path = "{$basePath}/{$searchData}";
$searchPDF = glob($path);
if (!empty($searchPDF)) {
Log::debug("Found with pattern", [
'pattern' => $searchData,
'files' => count($searchPDF)
]);
return $searchPDF;
}
}
} else {
// Format with only 2 parts: АЦСТ-161
$certPatterns = [$certNumber];
$trimmedCertNo = ltrim($certNumber, '0');
if ($trimmedCertNo != $certNumber && $trimmedCertNo != '') {
$certPatterns[] = $trimmedCertNo;
for ($i = 1; $i <= 5; $i++) {
$certPatterns[] = str_pad($trimmedCertNo, $i, '0', STR_PAD_LEFT);
}
}
foreach ($certPatterns as $certPattern) {
$searchData = "*{$shortNumber}-{$certPattern}*";
$searchData = str_replace("/", "*", $searchData);
$searchData = str_replace(" ", "*", $searchData);
$searchData = str_replace("\\", "*", $searchData);
$path = "{$basePath}/{$searchData}";
$searchPDF = glob($path);
if (!empty($searchPDF)) {
Log::debug("Found with 2-part pattern", [
'pattern' => $searchData,
'files' => count($searchPDF)
]);
return $searchPDF;
}
}
}
// Fallback: Try just the short_number
$searchData = "*{$shortNumber}*";
$searchData = str_replace("/", "*", $searchData);
$searchData = str_replace(" ", "*", $searchData);
$searchData = str_replace("\\", "*", $searchData);
$path = "{$basePath}/{$searchData}";
$searchPDF = glob($path);
if (!empty($searchPDF)) {
Log::debug("Found with short_number fallback", [
'pattern' => $searchData,
'files' => count($searchPDF)
]);
return $searchPDF;
}
}
// Final fallback: Try exact certificate number
$searchData = "*{$certificateNo}*";
$searchData = str_replace("/", "*", $searchData);
$searchData = str_replace(" ", "*", $searchData);
$searchData = str_replace("\\", "*", $searchData);
$path = "{$basePath}/{$searchData}";
$searchPDF = glob($path);
if (!empty($searchPDF)) {
Log::debug("Found with exact match", [
'pattern' => $searchData,
'files' => count($searchPDF)
]);
return $searchPDF;
}
Log::warning("No files found for certificate", [
'certificate' => $certificateNo,
'base_path' => $basePath
]);
return $files;
}
public function process(
array $weldLogData,
array $document,
Worksheet $sheet,
int &$currentRow,
array $settings
): int {
$this->initialize($weldLogData, $document, $sheet, $currentRow, $settings);
$wpsData = $settings['wps_data'] ?? null;
if (!$wpsData || empty($wpsData->naks_certificate_no)) {
$this->log("WPS Naks certificate not found", 'warning');
return $currentRow;
}
$naksCertificates = explode(" + ", $wpsData->naks_certificate_no);
// Prepare certificates with dates for sorting
$certificatesWithDates = [];
foreach ($naksCertificates as $naksCertificate) {
$certInfo = db("naks_certificates")->where("certificate_no", $naksCertificate)->first();
$certificatesWithDates[] = [
'certificate_no' => $naksCertificate,
'date' => $certInfo->valid_from ?? ''
];
}
// Sort by valid_from date (oldest first) - reverse insertion order
usort($certificatesWithDates, function($a, $b) {
$timestampA = !empty($a['date']) ? strtotime($a['date']) : 0;
$timestampB = !empty($b['date']) ? strtotime($b['date']) : 0;
return $timestampA <=> $timestampB;
});
$this->log("Certificates sorted by date (oldest first for reverse insertion)");
foreach ($certificatesWithDates as $certData) {
$naksCertificate = $certData['certificate_no'];
try {
Log::debug("Processing Naks certificate: " . $naksCertificate);
// Try multiple search strategies for Cyrillic characters and URL encoding
$search = [];
$basePath = "storage/documents/{$document['path']}";
// Strategy 1: Try with URL decoded path
$decodedPath = urldecode($basePath);
$files = $this->searchFilesWithFallback($decodedPath, $naksCertificate);
if (!empty($files)) {
$search = $files;
Log::debug("Found files with decoded path", ['count' => count($files), 'path' => $decodedPath]);
}
// Strategy 2: Try with original path if first strategy failed
if (empty($search) && $decodedPath !== $basePath) {
$files = $this->searchFilesWithFallback($basePath, $naksCertificate);
if (!empty($files)) {
$search = $files;
Log::debug("Found files with original path", ['count' => count($files), 'path' => $basePath]);
}
}
$document['title2'] = $naksCertificate;
$this->document = $document;
$newRow = $this->addRowToExcel(
$search,
$naksCertificate,
$certData['date']
);
if ($newRow > $currentRow) {
$currentRow = $newRow;
}
} catch (\Throwable $th) {
$this->log("Error: {$th->getMessage()}", 'error');
continue;
}
}
return $currentRow;
}
}