450 lines
16 KiB
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;
|
|
}
|
|
}
|
|
|