Rapport-Laravel/app/Console/Commands/ProcessNeblogisticImport.php

450 lines
16 KiB
PHP

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Maatwebsite\Excel\Facades\Excel;
use League\Csv\Reader;
use Carbon\Carbon;
class ProcessNeblogisticImport extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'neblogistic:process-import {--id= : Process specific import ID}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Process pending neblogistic import files';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$importId = $this->option('id');
if ($importId) {
$import = DB::table('neblogistic_import')->where('id', $importId)->first();
if (!$import) {
$this->error("Import record with ID {$importId} not found.");
return 1;
}
$this->processImport($import);
} else {
// Process all pending imports
$imports = DB::table('neblogistic_import')
->where('status', 'pending')
->orderBy('created_at', 'asc')
->get();
if ($imports->isEmpty()) {
$this->info('No pending imports found.');
return 0;
}
$this->info("Found {$imports->count()} pending import(s).");
foreach ($imports as $import) {
$this->processImport($import);
}
}
return 0;
}
private function processImport($import)
{
$this->info("Processing import ID: {$import->id} - {$import->file_name}");
// Update status to processing
DB::table('neblogistic_import')
->where('id', $import->id)
->update([
'status' => 'processing',
'updated_at' => now()
]);
try {
$filePath = $import->file_path;
$extension = $import->file_extension;
// Download file from S3 to temporary location
$tempPath = $this->downloadFileFromS3($filePath);
if (!$tempPath) {
throw new \Exception("Failed to download file from S3: {$filePath}");
}
$updatedCount = 0;
$notFoundCount = 0;
$errorCount = 0;
$errors = [];
$processedRows = 0;
if (in_array($extension, ['xlsx', 'xls'])) {
$result = $this->processExcelFile($tempPath, $errors);
$updatedCount = $result['updated'];
$notFoundCount = $result['not_found'];
$errorCount = $result['errors'];
$processedRows = $result['processed'];
} elseif ($extension === 'csv') {
$result = $this->processCsvFile($tempPath, $errors);
$updatedCount = $result['updated'];
$notFoundCount = $result['not_found'];
$errorCount = $result['errors'];
$processedRows = $result['processed'];
}
// Clean up temporary file
if (file_exists($tempPath)) {
unlink($tempPath);
}
// Update import record
$errorLog = !empty($errors) ? implode("\n", array_slice($errors, 0, 100)) : null;
DB::table('neblogistic_import')
->where('id', $import->id)
->update([
'status' => 'completed',
'processed_rows' => $processedRows,
'updated_count' => $updatedCount,
'not_found_count' => $notFoundCount,
'error_count' => $errorCount,
'error_log' => $errorLog,
'processed_at' => now(),
'updated_at' => now()
]);
$this->info("Import completed. Updated: {$updatedCount}, Not Found: {$notFoundCount}, Errors: {$errorCount}");
} catch (\Exception $e) {
Log::error('Import processing error', [
'import_id' => $import->id,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
DB::table('neblogistic_import')
->where('id', $import->id)
->update([
'status' => 'failed',
'error_log' => $e->getMessage(),
'updated_at' => now()
]);
$this->error("Failed to process import ID {$import->id}: " . $e->getMessage());
}
}
private function downloadFileFromS3($filePath)
{
try {
// Extract the S3 key from the full URL
$urlParts = parse_url($filePath);
$s3Key = ltrim($urlParts['path'] ?? '', '/');
// Remove AWS_URL prefix if present
$awsUrl = env('AWS_URL');
if ($awsUrl && strpos($filePath, $awsUrl) === 0) {
$s3Key = str_replace($awsUrl . '/', '', $filePath);
}
// Download from S3
$contents = Storage::disk('s3')->get($s3Key);
if (!$contents) {
return null;
}
// Save to temporary file
$tempPath = sys_get_temp_dir() . '/' . uniqid('neblogistic_import_') . '.tmp';
file_put_contents($tempPath, $contents);
return $tempPath;
} catch (\Exception $e) {
Log::error('Error downloading file from S3', [
'file_path' => $filePath,
'error' => $e->getMessage()
]);
return null;
}
}
private function processExcelFile($filePath, &$errors)
{
$updatedCount = 0;
$notFoundCount = 0;
$errorCount = 0;
$processedRows = 0;
try {
$data = Excel::toArray([], $filePath);
if (empty($data) || empty($data[0])) {
throw new \Exception('The file is empty or invalid.');
}
$rows = $data[0];
$headers = array_shift($rows);
// Find the index of "Connect od Order" column
$connectOrderIndex = null;
foreach ($headers as $index => $header) {
if (trim(strtolower($header)) === 'connect od order') {
$connectOrderIndex = $index;
break;
}
}
if ($connectOrderIndex === null) {
throw new \Exception('Column "Connect od Order" not found in the file.');
}
// Map all other columns for update
$columnMapping = [];
foreach ($headers as $index => $header) {
$columnMapping[$index] = trim($header);
}
// Process each row
foreach ($rows as $rowIndex => $row) {
try {
$connectOrderId = trim($row[$connectOrderIndex] ?? '');
if (empty($connectOrderId)) {
continue;
}
$processedRows++;
// Check if record exists
$record = DB::table('neblogistic')->where('id', $connectOrderId)->first();
if (!$record) {
$notFoundCount++;
$errors[] = "Row " . ($rowIndex + 2) . ": Record with ID {$connectOrderId} not found.";
continue;
}
// Build update data from the row
$updateData = [];
foreach ($row as $colIndex => $value) {
if ($colIndex === $connectOrderIndex) {
continue;
}
$columnName = $columnMapping[$colIndex] ?? null;
if ($columnName && !empty(trim($value))) {
$dbField = $this->mapColumnToDbField($columnName);
if ($dbField) {
$trimmedValue = trim($value);
// Convert date format for lr_date field from d.m.Y to Y-m-d
if ($dbField === 'lr_date') {
try {
$date = \DateTime::createFromFormat('d.m.Y', $trimmedValue);
if ($date) {
$trimmedValue = $date->format('Y-m-d');
}
} catch (\Exception $e) {
// If parsing fails, keep original value
}
}
$updateData[$dbField] = $trimmedValue;
}
}
}
// Update the record if there's data to update
if (!empty($updateData)) {
// Get old data before update for audit trail
$oldRecord = DB::table('neblogistic')->where('id', $connectOrderId)->first();
$oldDataArray = $oldRecord ? json_decode(json_encode($oldRecord), true) : null;
DB::table('neblogistic')
->where('id', $connectOrderId)
->update($updateData);
// Log UPDATE operation (console command - no request object)
\App\Services\NeblogisticAuditService::logUpdate(
$connectOrderId,
$oldDataArray,
$updateData,
null,
null // No request object in console command
);
$updatedCount++;
}
} catch (\Exception $e) {
$errorCount++;
$errors[] = "Row " . ($rowIndex + 2) . ": " . $e->getMessage();
Log::error('Import row error', [
'row' => $rowIndex + 2,
'error' => $e->getMessage()
]);
}
}
} catch (\Exception $e) {
throw new \Exception('Error processing Excel file: ' . $e->getMessage());
}
return [
'updated' => $updatedCount,
'not_found' => $notFoundCount,
'errors' => $errorCount,
'processed' => $processedRows
];
}
private function processCsvFile($filePath, &$errors)
{
$updatedCount = 0;
$notFoundCount = 0;
$errorCount = 0;
$processedRows = 0;
try {
$csv = Reader::createFromPath($filePath, 'r');
$csv->setHeaderOffset(0);
$headers = $csv->getHeader();
$records = $csv->getRecords();
// Find "Connect od Order" in headers
$connectOrderKey = null;
foreach ($headers as $header) {
if (trim(strtolower($header)) === 'connect od order') {
$connectOrderKey = $header;
break;
}
}
if (!$connectOrderKey) {
throw new \Exception('Column "Connect od Order" not found in the file.');
}
// Process each record
$rowIndex = 0;
foreach ($records as $row) {
$rowIndex++;
try {
$connectOrderId = trim($row[$connectOrderKey] ?? '');
if (empty($connectOrderId)) {
continue;
}
$processedRows++;
// Check if record exists
$record = DB::table('neblogistic')->where('id', $connectOrderId)->first();
if (!$record) {
$notFoundCount++;
$errors[] = "Row " . ($rowIndex + 1) . ": Record with ID {$connectOrderId} not found.";
continue;
}
// Build update data
$updateData = [];
foreach ($row as $columnName => $value) {
if ($columnName === $connectOrderKey) {
continue;
}
if (!empty(trim($value))) {
$dbField = $this->mapColumnToDbField($columnName);
if ($dbField) {
$trimmedValue = trim($value);
// Convert date format for lr_date field from d.m.Y to Y-m-d
if ($dbField === 'lr_date') {
try {
$date = \DateTime::createFromFormat('d.m.Y', $trimmedValue);
if ($date) {
$trimmedValue = $date->format('Y-m-d');
}
} catch (\Exception $e) {
// If parsing fails, keep original value
}
}
$updateData[$dbField] = $trimmedValue;
}
}
}
// Update the record if there's data to update
if (!empty($updateData)) {
DB::table('neblogistic')
->where('id', $connectOrderId)
->update($updateData);
$updatedCount++;
}
} catch (\Exception $e) {
$errorCount++;
$errors[] = "Row " . ($rowIndex + 1) . ": " . $e->getMessage();
Log::error('Import row error', [
'row' => $rowIndex + 1,
'error' => $e->getMessage()
]);
}
}
} catch (\Exception $e) {
throw new \Exception('Error processing CSV file: ' . $e->getMessage());
}
return [
'updated' => $updatedCount,
'not_found' => $notFoundCount,
'errors' => $errorCount,
'processed' => $processedRows
];
}
private function mapColumnToDbField($columnName)
{
$mapping = [
'Connect od Order' => 'id',
'ORDER NO' => 'order_no',
'INVOICE NO' => 'invoice_no',
'COURIER NAME' => 'courier_name',
'LR NO' => 'lr_no',
'LR DATE' => 'lr_date',
];
$normalizedColumn = trim($columnName);
return $mapping[$normalizedColumn] ?? null;
}
}