İlk temizlik tamamlandı bir önceki projeden
This commit is contained in:
@@ -0,0 +1,517 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\RegisterCreator;
|
||||
|
||||
use App\Services\RegisterCreator\DocumentProcessors\DocumentProcessorFactory;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Exception;
|
||||
|
||||
class RegisterCreatorService
|
||||
{
|
||||
private ExcelHandler $excelHandler;
|
||||
private PdfConverter $pdfConverter;
|
||||
private PlaceholderReplacer $placeholderReplacer;
|
||||
private ProgressTracker $progressTracker;
|
||||
private array $settings;
|
||||
private array $statistics;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->excelHandler = new ExcelHandler();
|
||||
$this->pdfConverter = new PdfConverter();
|
||||
$this->placeholderReplacer = new PlaceholderReplacer();
|
||||
|
||||
// Initialize statistics
|
||||
$this->statistics = [
|
||||
'documents_processed' => 0,
|
||||
'documents_success' => 0,
|
||||
'documents_failed' => 0,
|
||||
'by_type' => []
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process single line with all documents
|
||||
*/
|
||||
public function processLine(array $lineData, array $documents, array $settings): array
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$this->settings = $settings;
|
||||
|
||||
// Initialize progress tracker
|
||||
$jobId = $settings['job_id'] ?? uniqid('rc_', true);
|
||||
$registerColumnBased = $settings['register_column_based'] ?? 'line_number';
|
||||
$lineIdentifier = $lineData[$registerColumnBased] ?? 'unknown';
|
||||
|
||||
// Set total to document count for proper progress tracking
|
||||
$totalDocuments = count($documents);
|
||||
$this->progressTracker = new ProgressTracker($jobId, $totalDocuments, 0, $lineIdentifier);
|
||||
|
||||
Log::info("Processing line started", [
|
||||
'job_id' => $jobId,
|
||||
'line' => $lineIdentifier,
|
||||
'documents_count' => $totalDocuments
|
||||
]);
|
||||
|
||||
$this->progressTracker->update("Loading template for {$lineIdentifier}", 0, 0);
|
||||
|
||||
try {
|
||||
// Get document template info
|
||||
$documentInfo = document_template("register");
|
||||
if (!$documentInfo) {
|
||||
throw new Exception("Register template not found!");
|
||||
}
|
||||
|
||||
// Prepare folder structure - matching blade structure
|
||||
$path = $settings['path'] ?? '';
|
||||
$basePath = "storage/documents/{$path}";
|
||||
$fullFolder = "{$basePath}/{$lineIdentifier}/";
|
||||
$fullFolder2 = str_replace("storage/documents/", "", $fullFolder);
|
||||
$justFolder = "{$path}/{$lineIdentifier}/";
|
||||
|
||||
// Clean target directory before processing
|
||||
if (Storage::exists($fullFolder2)) {
|
||||
Storage::deleteDirectory($fullFolder2);
|
||||
}
|
||||
Storage::makeDirectory($fullFolder2);
|
||||
|
||||
// Create log file using fullFolder2 (storage-relative path)
|
||||
$this->initializeLogFile($fullFolder2, $lineIdentifier);
|
||||
|
||||
// Create info.txt file with job and user information
|
||||
try {
|
||||
Log::info("About to create info.txt", ['folder' => $fullFolder2, 'line' => $lineIdentifier]);
|
||||
$this->createInfoFile($fullFolder2, $lineIdentifier, $settings, $totalDocuments, $lineData);
|
||||
Log::info("info.txt creation completed", ['folder' => $fullFolder2]);
|
||||
} catch (\Throwable $th) {
|
||||
Log::error("Failed to create info.txt", [
|
||||
'error' => $th->getMessage(),
|
||||
'file' => $th->getFile(),
|
||||
'line' => $th->getLine()
|
||||
]);
|
||||
}
|
||||
|
||||
// Load Excel template
|
||||
$this->excelHandler->loadTemplate($documentInfo->files);
|
||||
|
||||
// Prepare placeholders
|
||||
$replacements = $this->placeholderReplacer->prepareReplacements($lineData);
|
||||
|
||||
// Replace placeholders in Excel
|
||||
$this->excelHandler->replacePlaceholders($replacements);
|
||||
|
||||
// Get contractor information
|
||||
$contractor = $this->getContractorName($lineData);
|
||||
Cache::put("rc_contractor", $contractor);
|
||||
|
||||
// Get WPS data if available
|
||||
$wpsData = $this->getWpsData($lineData);
|
||||
|
||||
// Set template row
|
||||
$templateRow = setting("register_creator_start_row") ?: 16;
|
||||
Cache::put("rc_template_row", $templateRow);
|
||||
|
||||
$currentRow = $templateRow;
|
||||
$rowNo = 1; // Initialize global row number counter
|
||||
|
||||
// DEBUG: Log documents BEFORE sorting
|
||||
Log::info("📋 Documents BEFORE sorting", [
|
||||
'documents' => array_map(function($doc, $idx) {
|
||||
return [
|
||||
'index' => $idx,
|
||||
'type' => $doc['type'] ?? 'N/A',
|
||||
'order' => $doc['order'] ?? 'N/A',
|
||||
'title2' => $doc['title2'] ?? 'N/A'
|
||||
];
|
||||
}, $documents, array_keys($documents))
|
||||
]);
|
||||
|
||||
// Sort documents by order field before processing
|
||||
// This ensures Excel rows are created in the correct document type order
|
||||
usort($documents, function($a, $b) {
|
||||
$orderA = $a['order'] ?? 999;
|
||||
$orderB = $b['order'] ?? 999;
|
||||
|
||||
// If orders are equal, maintain original order by using type as secondary sort
|
||||
if ($orderA === $orderB) {
|
||||
$typeA = $a['type'] ?? '';
|
||||
$typeB = $b['type'] ?? '';
|
||||
return strcmp($typeA, $typeB);
|
||||
}
|
||||
|
||||
return $orderA <=> $orderB;
|
||||
});
|
||||
|
||||
// DEBUG: Log documents AFTER sorting
|
||||
Log::info("📋 Documents AFTER sorting", [
|
||||
'documents' => array_map(function($doc, $idx) {
|
||||
return [
|
||||
'index' => $idx,
|
||||
'type' => $doc['type'] ?? 'N/A',
|
||||
'order' => $doc['order'] ?? 'N/A',
|
||||
'title2' => $doc['title2'] ?? 'N/A'
|
||||
];
|
||||
}, $documents, array_keys($documents))
|
||||
]);
|
||||
|
||||
Log::debug("Documents sorted by order field", [
|
||||
'documents_count' => count($documents),
|
||||
'first_order' => $documents[0]['order'] ?? 'N/A',
|
||||
'last_order' => $documents[count($documents) - 1]['order'] ?? 'N/A'
|
||||
]);
|
||||
|
||||
// Process each document
|
||||
foreach ($documents as $index => $document) {
|
||||
$startDocTime = microtime(true);
|
||||
try {
|
||||
// Safe string conversion for title2
|
||||
$docTitle = $document['title2'] ?? 'unknown';
|
||||
if (is_array($docTitle)) {
|
||||
$docTitle = json_encode($docTitle);
|
||||
}
|
||||
|
||||
$currentDoc = $index + 1;
|
||||
$progressPercent = (int) (($currentDoc) / count($documents) * 100);
|
||||
$this->progressTracker->update(
|
||||
"Processing document {$currentDoc}/" . count($documents) . ": {$docTitle}",
|
||||
$progressPercent,
|
||||
$currentDoc // Update current to show which document we're on
|
||||
);
|
||||
|
||||
Log::debug("Starting document processing", [
|
||||
'job_id' => $this->progressTracker->getJobId(),
|
||||
'line' => $lineIdentifier,
|
||||
'document' => $docTitle,
|
||||
'index' => $index + 1,
|
||||
'total' => count($documents)
|
||||
]);
|
||||
|
||||
$oldRowNo = $rowNo;
|
||||
|
||||
$rowNo = $this->processDocument(
|
||||
$lineData,
|
||||
$document,
|
||||
$this->excelHandler->getSheet(),
|
||||
$currentRow,
|
||||
$wpsData,
|
||||
$rowNo,
|
||||
$documents // Pass all documents array for dynamic mapping
|
||||
);
|
||||
|
||||
$docDuration = round(microtime(true) - $startDocTime, 2);
|
||||
Log::info("📊 Document processed", [
|
||||
'job_id' => $this->progressTracker->getJobId(),
|
||||
'line' => $lineIdentifier,
|
||||
'document' => $docTitle,
|
||||
'document_type' => $document['type'] ?? 'N/A',
|
||||
'document_order' => $document['order'] ?? 'N/A',
|
||||
'rowNo_before' => $oldRowNo,
|
||||
'rowNo_after' => $rowNo,
|
||||
'rows_added' => ($rowNo - $oldRowNo),
|
||||
'duration' => $docDuration . 's'
|
||||
]);
|
||||
|
||||
$this->statistics['documents_success']++;
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
$this->statistics['documents_failed']++;
|
||||
|
||||
// Safe string conversion for error logging
|
||||
$docTitle = $document['title2'] ?? 'unknown';
|
||||
if (is_array($docTitle)) {
|
||||
$docTitle = json_encode($docTitle);
|
||||
}
|
||||
$docType = $document['type'] ?? 'unknown';
|
||||
if (is_array($docType)) {
|
||||
$docType = json_encode($docType);
|
||||
}
|
||||
$docPath = $document['path'] ?? 'unknown';
|
||||
if (is_array($docPath)) {
|
||||
$docPath = json_encode($docPath);
|
||||
}
|
||||
|
||||
// Detailed error logging for documents
|
||||
Log::error("Document processing error", [
|
||||
'job_id' => $this->progressTracker->getJobId(),
|
||||
'line' => $lineIdentifier,
|
||||
'document' => $docTitle,
|
||||
'document_type' => $docType,
|
||||
'document_path' => $docPath,
|
||||
'error_type' => get_class($th),
|
||||
'error_message' => $th->getMessage(),
|
||||
'file' => $th->getFile(),
|
||||
'line_number' => $th->getLine(),
|
||||
'trace' => $th->getTraceAsString()
|
||||
]);
|
||||
|
||||
// Write detailed error to log file
|
||||
$errorLog = "❌ Document error: {$docTitle}\n";
|
||||
$errorLog .= " Type: {$docType}\n";
|
||||
$errorLog .= " Path: {$docPath}\n";
|
||||
$errorLog .= " Error: " . get_class($th) . " - {$th->getMessage()}\n";
|
||||
$errorLog .= " File: {$th->getFile()}:{$th->getLine()}\n";
|
||||
|
||||
$this->writeLog($fullFolder2, $errorLog);
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->statistics['documents_processed']++;
|
||||
}
|
||||
|
||||
// Apply work permit replacements
|
||||
try {
|
||||
$spreadsheet = workPermitReplacerExcel2(
|
||||
$this->excelHandler->getSpreadsheet(),
|
||||
$lineData,
|
||||
$documentInfo,
|
||||
"replacer"
|
||||
);
|
||||
} catch (\Throwable $th) {
|
||||
Log::warning("Work permit replacer error", [
|
||||
'error' => $th->getMessage()
|
||||
]);
|
||||
}
|
||||
|
||||
// Remove template row
|
||||
$this->excelHandler->removeTemplateRow($templateRow);
|
||||
|
||||
// Save Excel file
|
||||
$registerFileName = $justFolder . "Register.xlsx";
|
||||
$this->excelHandler->save($registerFileName, $settings['override'] ?? true);
|
||||
|
||||
// Convert to PDF
|
||||
$pdfPath = $justFolder;
|
||||
$this->pdfConverter->convert($registerFileName, $pdfPath, $settings['override'] ?? true);
|
||||
|
||||
// Calculate duration
|
||||
$duration = round(microtime(true) - $startTime, 2);
|
||||
|
||||
// Write summary to log
|
||||
$this->writeSummary($justFolder, $lineIdentifier, $duration);
|
||||
|
||||
// Mark as complete - will show total/total (e.g., 23/23)
|
||||
$this->progressTracker->complete("Completed successfully");
|
||||
|
||||
Log::info("Line processing completed", [
|
||||
'job_id' => $this->progressTracker->getJobId(),
|
||||
'line' => $lineIdentifier,
|
||||
'duration' => $duration . 's',
|
||||
'documents_processed' => $this->statistics['documents_processed'],
|
||||
'documents_success' => $this->statistics['documents_success'],
|
||||
'documents_failed' => $this->statistics['documents_failed']
|
||||
]);
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'line' => $lineIdentifier,
|
||||
'duration' => $duration,
|
||||
'statistics' => $this->statistics
|
||||
];
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
$this->progressTracker->fail($th->getMessage());
|
||||
|
||||
Log::error("Line processing failed", [
|
||||
'job_id' => $this->progressTracker->getJobId(),
|
||||
'line' => $lineIdentifier,
|
||||
'error' => $th->getMessage(),
|
||||
'trace' => $th->getTraceAsString()
|
||||
]);
|
||||
|
||||
throw $th;
|
||||
|
||||
} finally {
|
||||
// Cleanup
|
||||
$this->excelHandler->cleanup();
|
||||
Cache::forget("rc_lastPage");
|
||||
Cache::forget("rc_firstPage");
|
||||
Cache::forget("rc_contractor");
|
||||
Cache::forget("rc_template_row");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process single document
|
||||
*/
|
||||
private function processDocument(
|
||||
array $lineData,
|
||||
array $document,
|
||||
$sheet,
|
||||
int &$currentRow,
|
||||
$wpsData,
|
||||
int $rowNo,
|
||||
array $allDocuments = [] // All documents array for dynamic type order mapping
|
||||
): int {
|
||||
// Validate document structure - ensure 'type' field exists
|
||||
if (!isset($document['type']) || empty($document['type'])) {
|
||||
// Log detailed document information for debugging
|
||||
Log::warning("⚠️ Document missing 'type' field, attempting to infer", [
|
||||
'document_keys' => array_keys($document),
|
||||
'document_id' => $document['id'] ?? 'unknown',
|
||||
'document_path' => $document['path'] ?? 'unknown'
|
||||
]);
|
||||
|
||||
// Try to infer type from document structure
|
||||
if (isset($document['is_dynamic']) && $document['is_dynamic']) {
|
||||
$document['type'] = 'dynamic';
|
||||
Log::info(" ✓ Inferred type as 'dynamic' based on is_dynamic flag");
|
||||
} else if (isset($document['sql_query'])) {
|
||||
// If has SQL query, it's likely a dynamic document
|
||||
$document['type'] = 'dynamic';
|
||||
Log::info(" ✓ Inferred type as 'dynamic' based on sql_query presence");
|
||||
} else if (isset($document['id']) && strpos($document['id'], 'template') !== false) {
|
||||
$document['type'] = 'template';
|
||||
Log::info(" ✓ Inferred type as 'template' based on id");
|
||||
} else {
|
||||
// Default fallback
|
||||
$document['type'] = 'qa';
|
||||
Log::info(" ✓ Using default type 'qa' as fallback");
|
||||
}
|
||||
}
|
||||
|
||||
$processor = DocumentProcessorFactory::make($document['type'], $document);
|
||||
|
||||
// Prepare settings for processor
|
||||
$processorSettings = array_merge($this->settings, [
|
||||
'wps_data' => $wpsData,
|
||||
'row_no' => $rowNo, // Pass global row number to processor
|
||||
'all_documents' => $allDocuments // Pass all documents for dynamic mapping
|
||||
]);
|
||||
|
||||
$newRow = $processor->process(
|
||||
$lineData,
|
||||
$document,
|
||||
$sheet,
|
||||
$currentRow,
|
||||
$processorSettings
|
||||
);
|
||||
|
||||
// Update current row if changed
|
||||
if ($newRow > $currentRow) {
|
||||
$currentRow = $newRow;
|
||||
}
|
||||
|
||||
// Update statistics
|
||||
$docType = $document['type'];
|
||||
if (!isset($this->statistics['by_type'][$docType])) {
|
||||
$this->statistics['by_type'][$docType] = 0;
|
||||
}
|
||||
$this->statistics['by_type'][$docType]++;
|
||||
|
||||
// Get number of documents added from processor and increment rowNo accordingly
|
||||
$documentsAdded = $processor->getDocumentsAdded();
|
||||
|
||||
Log::debug("Documents added by processor", [
|
||||
'processor' => get_class($processor),
|
||||
'documents_added' => $documentsAdded,
|
||||
'current_rowNo' => $rowNo,
|
||||
'next_rowNo' => $rowNo + $documentsAdded
|
||||
]);
|
||||
|
||||
return $rowNo + $documentsAdded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contractor name
|
||||
*/
|
||||
private function getContractorName(array $lineData): string
|
||||
{
|
||||
$subcontractors = Cache::get("subcontractors", []);
|
||||
$contractorKey = $lineData['contractor'] ?? '';
|
||||
|
||||
if (isset($subcontractors[$contractorKey])) {
|
||||
return $subcontractors[$contractorKey]->company_name_ru ?? $contractorKey;
|
||||
}
|
||||
|
||||
return $contractorKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get WPS data
|
||||
*/
|
||||
private function getWpsData(array $lineData)
|
||||
{
|
||||
$wpsNo = $lineData['wps_no'] ?? '';
|
||||
|
||||
if (empty($wpsNo)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return db("w_p_s")->where("details", $wpsNo)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize log file
|
||||
*/
|
||||
private function initializeLogFile(string $folder, string $lineIdentifier): void
|
||||
{
|
||||
$logPath = $folder . 'log.txt';
|
||||
|
||||
Storage::delete($logPath);
|
||||
|
||||
$header = str_repeat("🚀", 30) . "\n";
|
||||
$header .= "REGISTER CREATOR LOG FILE\n";
|
||||
$header .= "Line: {$lineIdentifier}\n";
|
||||
$header .= "Started: " . now()->toDateTimeString() . "\n";
|
||||
$header .= str_repeat("🚀", 30) . "\n\n";
|
||||
|
||||
Storage::put($logPath, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create info.txt file with user information (JSON format)
|
||||
*/
|
||||
private function createInfoFile(string $folder, string $lineIdentifier, array $settings, int $totalDocuments, array $lineData): void
|
||||
{
|
||||
$infoPath = $folder . 'info.txt';
|
||||
|
||||
// Delete old info file if exists
|
||||
Storage::delete($infoPath);
|
||||
|
||||
// Create simple JSON with user information
|
||||
$infoData = [
|
||||
'user_name' => $settings['user_name'] ?? 'Unknown',
|
||||
'user_id' => $settings['user_id'] ?? null,
|
||||
'created_at' => now()->format('Y-m-d H:i:s'),
|
||||
'line_identifier' => $lineIdentifier
|
||||
];
|
||||
|
||||
Storage::put($infoPath, json_encode($infoData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to log file
|
||||
*/
|
||||
private function writeLog(string $folder, string $message): void
|
||||
{
|
||||
$logPath = $folder . 'log.txt';
|
||||
Storage::append($logPath, $message . "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Write summary to log file
|
||||
*/
|
||||
private function writeSummary(string $folder, string $lineIdentifier, float $duration): void
|
||||
{
|
||||
$summary = "\n" . str_repeat("=", 80) . "\n";
|
||||
$summary .= "PROCESSING SUMMARY\n";
|
||||
$summary .= str_repeat("=", 80) . "\n";
|
||||
$summary .= "Line: {$lineIdentifier}\n";
|
||||
$summary .= "Duration: {$duration}s\n";
|
||||
$summary .= "Documents Processed: {$this->statistics['documents_processed']}\n";
|
||||
$summary .= "Successful: {$this->statistics['documents_success']}\n";
|
||||
$summary .= "Failed: {$this->statistics['documents_failed']}\n";
|
||||
$summary .= "\nBy Type:\n";
|
||||
|
||||
foreach ($this->statistics['by_type'] as $type => $count) {
|
||||
$summary .= " - {$type}: {$count}\n";
|
||||
}
|
||||
|
||||
$summary .= str_repeat("=", 80) . "\n";
|
||||
|
||||
$this->writeLog($folder, $summary);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user