Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 16s
Gemini PR Review / Gemini PR Review (pull_request) Failing after 17s
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (pull_request) Successful in 16s
Laravel Pint / pint (pull_request) Successful in 3m47s
Laravel Larastan / larastan (pull_request) Failing after 5m15s
590 lines
19 KiB
PHP
590 lines
19 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\Machine;
|
|
use App\Models\Plant;
|
|
use App\Models\ProductionTemp;
|
|
use Illuminate\Console\Command;
|
|
use App\Services\SftpFileService;
|
|
use Filament\Facades\Filament;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Facades\File;
|
|
|
|
class ProcessSftpFiles extends Command
|
|
{
|
|
protected $signature = 'sftp:process-files';
|
|
protected $description = 'Read SFTP files, insert into DB, and move to processed folder';
|
|
|
|
protected $sftpService;
|
|
|
|
public function __construct(SftpFileService $sftpService)
|
|
{
|
|
parent::__construct(); // calls parent class constructor
|
|
$this->sftpService = $sftpService;
|
|
}
|
|
|
|
// public function handle()
|
|
// {
|
|
|
|
// // $files = Storage::disk('ftp')->allFiles();
|
|
|
|
// $folders = Storage::disk('ftp')->directories();
|
|
|
|
// $allFiles = [];
|
|
|
|
// foreach ($folders as $folder) {
|
|
|
|
// Log::info("Checking folder: $folder");
|
|
|
|
// // Get files inside the current folder
|
|
// $files = Storage::disk('ftp')->files($folder);
|
|
|
|
// foreach ($files as $file) {
|
|
|
|
// $allFiles[] = $file;
|
|
// }
|
|
// }
|
|
|
|
// if (empty($allFiles)) {
|
|
// $this->info('No files to process.');
|
|
// return;
|
|
// }
|
|
|
|
// $logMessages = [];
|
|
|
|
// foreach ($allFiles as $file)
|
|
// {
|
|
// $this->info("Processing file: $file");
|
|
|
|
// $folderName = explode('/', $file)[0];
|
|
|
|
// $plant = Plant::where('code', $folderName)->first();
|
|
|
|
// if (!$plant) {
|
|
// $this->warn("No plant found for folder: $folderName. Skipping file.");
|
|
// $logMessages[] = $folderName;
|
|
// continue;
|
|
// }
|
|
|
|
// $plantId = $plant->id;
|
|
|
|
// $plantCode = $plant->code ?? null;
|
|
|
|
// $fileName = basename($file); // GMGLAS03-1818199-1.txt
|
|
// $workCenterCode = explode('-', $fileName)[0]; // GMGLAS03
|
|
|
|
// $machine = Machine::where('work_center', $workCenterCode)->first();
|
|
// if (!$machine) {
|
|
// $message = "Machine '$workCenterCode' not found and file name '$file'";
|
|
// $logMessages[] = $message;
|
|
// continue;
|
|
// }
|
|
|
|
// $machineAgaPlant = Machine::where('plant_id', $plantId)->where('work_center', $workCenterCode)->first();
|
|
// if (!$machineAgaPlant) {
|
|
// $message = "Machine '$workCenterCode' not found for plant code '$plantCode' and file name '$file'";
|
|
// $logMessages[] = $message;
|
|
// continue;
|
|
// }
|
|
|
|
// $machineId = $machineAgaPlant->id;
|
|
|
|
// $content = $this->sftpService->getFileContent($file);
|
|
|
|
// $lines = array_filter(preg_split("/\r\n|\n|\r/", $content));
|
|
|
|
// $headers = array_map(fn($header) => strtolower(trim($header)), str_getcsv(array_shift($lines)));
|
|
|
|
// $dbColumns = Schema::getColumnListing('production_temps');
|
|
|
|
// $missingColumns = array_diff($headers, $dbColumns);
|
|
|
|
// if (!empty($missingColumns)) {
|
|
|
|
// $message = "Skipping file: $file. The following columns do not exist in the database. Please add them to the database: " . implode(', ', $missingColumns);
|
|
|
|
// $logMessages[] = $message;
|
|
|
|
// $this->warn($message);
|
|
|
|
// continue;
|
|
// }
|
|
|
|
// $expectedColumns = count($headers);
|
|
// $isValidFile = true;
|
|
// $isserialNotEmptyFile = true;
|
|
|
|
// foreach ($lines as $lineNumber => $line) {
|
|
// $data = str_getcsv($line);
|
|
|
|
// if (!empty($data[0])) {
|
|
// if (count($data) != $expectedColumns) {
|
|
// $message = "Column mismatch in file: $file at line " . ($lineNumber + 2) .
|
|
// ". Expected $expectedColumns, got " . count($data);
|
|
|
|
// $logMessages[] = $message;
|
|
|
|
// $isValidFile = false;
|
|
// break;
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// if (!$isValidFile) {
|
|
// $message = "Skipping entire file due to invalid column mismatch: $file";
|
|
// $logMessages[] = $message;
|
|
// continue;
|
|
// }
|
|
// else if (!$isserialNotEmptyFile) {
|
|
// $message = "serial number not found in file name $file";
|
|
// $logMessages[] = $message;
|
|
// continue;
|
|
// }
|
|
|
|
// $rows = [];
|
|
|
|
// // foreach ($lines as $line) {
|
|
// // $data = str_getcsv($line);
|
|
|
|
// // if (!empty($data[0])) {
|
|
// // $row = [];
|
|
// // foreach ($headers as $index => $column) {
|
|
// // $row[$column] = isset($data[$index]) ? trim($data[$index]) : null;
|
|
// // }
|
|
|
|
// // $row['plant_id'] = $plantId;
|
|
// // $row['machine_id'] = $machineId;
|
|
// // $row['created_at'] = now();
|
|
// // $row['updated_at'] = now();
|
|
|
|
// // // $rows[] = $row;
|
|
// // if (!empty($row['gernr'])) {
|
|
// // $rows[] = $row;
|
|
// // }
|
|
// // }
|
|
// // }
|
|
|
|
// foreach ($lines as $line) {
|
|
|
|
// $data = str_getcsv($line);
|
|
|
|
// if (!empty($data[0])) {
|
|
|
|
// $row = [];
|
|
|
|
// foreach ($headers as $index => $column) {
|
|
// $row[$column] = isset($data[$index])
|
|
// ? trim($data[$index])
|
|
// : null;
|
|
// }
|
|
|
|
// $row['plant_id'] = $plantId;
|
|
// $row['machine_id'] = $machineId;
|
|
// $row['created_at'] = now();
|
|
// $row['updated_at'] = now();
|
|
|
|
// if (empty($row['gernr'])) {
|
|
// $message = "serial number not found in file name $file";
|
|
// $logMessages[] = $message;
|
|
// continue;
|
|
// }
|
|
|
|
// $gamng = (float) ($row['gamng'] ?? 0);
|
|
// $lmnga = (float) ($row['lmnga'] ?? 0);
|
|
|
|
// $existingRecords = ProductionTemp::where('plant_id', $plantId)
|
|
// ->where('aufnr', ($row['aufnr']))
|
|
// ->get();
|
|
|
|
|
|
// $existingRecord = $existingRecords->where('gernr', $row['gernr'])->first();
|
|
|
|
// $lastStatus = $existingRecords->max('pending_released_status');
|
|
|
|
// if ($gamng == $lmnga) {
|
|
|
|
// $row['pending_released_status'] = 0;
|
|
|
|
// $this->info("Production order {$row['aufnr']} completed. " ."Serial: {$row['gernr']}. " ."GAMNG: {$row['gamng']}, LMNGA: {$row['lmnga']}. " ."pending_released_status = 0" );
|
|
|
|
// } elseif ($existingRecord) {
|
|
|
|
// $row['pending_released_status'] = (int) $existingRecord->pending_released_status;
|
|
|
|
// $this->info("Same serial already exists. " ."Production order: {$row['aufnr']}. " ."Serial: {$row['gernr']}. " ."Keeping pending_released_status = {$row['pending_released_status']}");
|
|
|
|
// } else {
|
|
|
|
// if ($existingRecords->isEmpty()) {
|
|
|
|
// // First release
|
|
// $row['pending_released_status'] = 0;
|
|
|
|
// } else {
|
|
// $row['pending_released_status'] = ((int) $lastStatus) + 1;
|
|
// }
|
|
// $this->info("Production order {$row['aufnr']} pending. " ."New serial: {$row['gernr']}. " . "GAMNG: {$row['gamng']}, LMNGA: {$row['lmnga']}. " ."pending_released_status = {$row['pending_released_status']}");
|
|
// }
|
|
|
|
// $rows[] = $row;
|
|
// }
|
|
// }
|
|
|
|
// if (!empty($rows)) {
|
|
// ProductionTemp::upsert($rows, ['plant_id', 'gernr'], array_keys($rows[0])); //(update + insert)
|
|
// }
|
|
// }
|
|
|
|
// if (!empty($logMessages)) {
|
|
|
|
// $content = implode(PHP_EOL, $logMessages);
|
|
|
|
// Mail::raw('Please find attached machine error logs.', function ($message) use ($content) {
|
|
// $message->to(env('ERROR_LOG_MAIL'))
|
|
// ->subject('Machine Error Logs')
|
|
// ->attachData($content, 'machine_errors.txt', [
|
|
// 'mime' => 'text/plain',
|
|
// ]);
|
|
// });
|
|
// }
|
|
// }
|
|
|
|
public function handle()
|
|
{
|
|
$basePath = '/var/www/ftpdata';
|
|
|
|
// $basePath = '/home/iot-dev/projects/pds/ftpdata';
|
|
|
|
$allFiles = [];
|
|
$logMessages = [];
|
|
|
|
// Check whether data2 exists
|
|
if (!File::isDirectory($basePath)) {
|
|
$this->error("Directory not found: {$basePath}");
|
|
return;
|
|
}
|
|
|
|
$folders = Plant::pluck('code');
|
|
|
|
foreach ($folders as $folderName) {
|
|
|
|
$folderPath = $basePath . '/' . $folderName;
|
|
|
|
if (!File::isDirectory($folderPath)) {
|
|
|
|
File::makeDirectory($folderPath, 0755, true);
|
|
|
|
// Set ownership
|
|
exec("chown www-data:www-data " . escapeshellarg($folderPath));
|
|
|
|
$this->info("Created missing folder: {$folderName}");
|
|
}
|
|
}
|
|
|
|
// Get plant folders: 1000, 1010, etc.
|
|
$folders = File::directories($basePath);
|
|
|
|
if (empty($folders)) {
|
|
$this->info('No plant folders found.');
|
|
return;
|
|
}
|
|
|
|
// Collect TXT files from each plant folder
|
|
foreach ($folders as $folderPath) {
|
|
|
|
$folderName = basename($folderPath);
|
|
|
|
Log::info("Checking plant folder: {$folderName}");
|
|
|
|
// Get files inside the plant folder
|
|
$files = File::files($folderPath);
|
|
|
|
foreach ($files as $file) {
|
|
|
|
if (strtolower($file->getExtension()) !== 'txt') {
|
|
continue;
|
|
}
|
|
|
|
$allFiles[] = [
|
|
'path' => $file->getPathname(),
|
|
'folder' => $folderName,
|
|
];
|
|
}
|
|
}
|
|
|
|
if (empty($allFiles)) {
|
|
$this->info('No TXT files to process.');
|
|
return;
|
|
}
|
|
|
|
$dbColumns = Schema::getColumnListing('production_temps');
|
|
|
|
// Process each TXT file
|
|
foreach ($allFiles as $fileDetails) {
|
|
|
|
$file = $fileDetails['path'];
|
|
$folderName = $fileDetails['folder'];
|
|
|
|
$this->info("Processing file: {$file}");
|
|
|
|
// Find plant using folder name
|
|
$plant = Plant::where('code', $folderName)->first();
|
|
|
|
if (!$plant) {
|
|
|
|
$message = "No plant found for folder: {$folderName}. File: {$file}";
|
|
|
|
$this->warn($message);
|
|
$logMessages[] = $message;
|
|
|
|
continue;
|
|
}
|
|
|
|
$plantId = $plant->id;
|
|
$plantCode = $plant->code;
|
|
|
|
// Get filename and work center
|
|
// Example: GMGLAS03-1818199-1.txt
|
|
$fileName = basename($file);
|
|
|
|
$workCenterCode = explode('-', $fileName)[0];
|
|
|
|
// Find machine belonging to this plant
|
|
$machine = Machine::where('plant_id', $plantId)
|
|
->where('work_center', $workCenterCode)
|
|
->first();
|
|
|
|
if (!$machine) {
|
|
|
|
$message = "Machine '{$workCenterCode}' not found for plant '{$plantCode}'. File: {$file}";
|
|
|
|
$this->warn($message);
|
|
$logMessages[] = $message;
|
|
|
|
continue;
|
|
}
|
|
|
|
$machineId = $machine->id;
|
|
|
|
// Read local TXT file
|
|
$content = File::get($file);
|
|
|
|
// Split file into lines
|
|
$lines = preg_split("/\r\n|\n|\r/", $content);
|
|
|
|
// Remove empty lines and reindex
|
|
$lines = array_values(array_filter($lines, fn($line) => trim($line) !== ''));
|
|
|
|
if (empty($lines)) {
|
|
|
|
$message = "File is empty: {$file}";
|
|
|
|
$this->warn($message);
|
|
$logMessages[] = $message;
|
|
|
|
continue;
|
|
}
|
|
|
|
// Read CSV headers
|
|
$headers = array_map(
|
|
fn($header) => strtolower(trim($header)),
|
|
str_getcsv(array_shift($lines))
|
|
);
|
|
|
|
if (empty($headers)) {
|
|
|
|
$message = "Headers not found in file: {$file}";
|
|
|
|
$this->warn($message);
|
|
$logMessages[] = $message;
|
|
|
|
continue;
|
|
}
|
|
|
|
// Validate CSV columns against database columns
|
|
$missingColumns = array_diff($headers, $dbColumns);
|
|
|
|
if (!empty($missingColumns)) {
|
|
|
|
$message = "Skipping file: {$file}. The following columns do not exist in the database: "
|
|
. implode(', ', $missingColumns);
|
|
|
|
$this->warn($message);
|
|
$logMessages[] = $message;
|
|
|
|
continue;
|
|
}
|
|
|
|
$expectedColumns = count($headers);
|
|
$isValidFile = true;
|
|
|
|
// Validate column count in every row
|
|
foreach ($lines as $lineNumber => $line) {
|
|
|
|
$data = str_getcsv($line);
|
|
|
|
if (!empty($data[0]) && count($data) != $expectedColumns) {
|
|
|
|
$message = "Column mismatch in file: {$file} at line "
|
|
. ($lineNumber + 2)
|
|
. ". Expected {$expectedColumns}, got "
|
|
. count($data);
|
|
|
|
$logMessages[] = $message;
|
|
|
|
$this->warn($message);
|
|
|
|
$isValidFile = false;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$isValidFile) {
|
|
|
|
$message = "Skipping entire file due to invalid column mismatch: {$file}";
|
|
|
|
$this->warn($message);
|
|
$logMessages[] = $message;
|
|
|
|
continue;
|
|
}
|
|
|
|
$rows = [];
|
|
|
|
// Process each CSV row
|
|
foreach ($lines as $line) {
|
|
|
|
$data = str_getcsv($line);
|
|
|
|
if (empty($data[0])) {
|
|
continue;
|
|
}
|
|
|
|
$row = [];
|
|
|
|
foreach ($headers as $index => $column) {
|
|
|
|
$row[$column] = isset($data[$index])
|
|
? trim($data[$index])
|
|
: null;
|
|
}
|
|
|
|
$row['plant_id'] = $plantId;
|
|
$row['machine_id'] = $machineId;
|
|
$row['created_at'] = now();
|
|
$row['updated_at'] = now();
|
|
|
|
// Validate serial number
|
|
if (empty($row['gernr'])) {
|
|
|
|
$message = "Serial number not found in file {$file}";
|
|
|
|
$logMessages[] = $message;
|
|
|
|
continue;
|
|
}
|
|
|
|
$gamng = (float) ($row['gamng'] ?? 0);
|
|
$lmnga = (float) ($row['lmnga'] ?? 0);
|
|
|
|
// Get existing production order records
|
|
$existingRecords = ProductionTemp::where('plant_id', $plantId)
|
|
->where('aufnr', $row['aufnr'] ?? null)
|
|
->get();
|
|
|
|
// Check whether this serial already exists
|
|
$existingRecord = $existingRecords
|
|
->where('gernr', $row['gernr'])
|
|
->first();
|
|
|
|
$lastStatus = $existingRecords->max('pending_released_status');
|
|
|
|
if ($gamng == $lmnga) {
|
|
|
|
// Production order completed
|
|
$row['pending_released_status'] = 0;
|
|
|
|
$this->info(
|
|
"Production order {$row['aufnr']} completed. "
|
|
. "Serial: {$row['gernr']}. "
|
|
. "GAMNG: {$gamng}, LMNGA: {$lmnga}. "
|
|
. "pending_released_status = 0"
|
|
);
|
|
|
|
} elseif ($existingRecord) {
|
|
|
|
// Keep existing serial status
|
|
$row['pending_released_status'] =
|
|
(int) $existingRecord->pending_released_status;
|
|
|
|
$this->info(
|
|
"Same serial already exists. "
|
|
. "Production order: {$row['aufnr']}. "
|
|
. "Serial: {$row['gernr']}. "
|
|
. "Keeping pending_released_status = {$row['pending_released_status']}"
|
|
);
|
|
|
|
} else {
|
|
|
|
if ($existingRecords->isEmpty()) {
|
|
|
|
// First release
|
|
$row['pending_released_status'] = 0;
|
|
|
|
} else {
|
|
|
|
// Increment pending release status
|
|
$row['pending_released_status'] = ((int) $lastStatus) + 1;
|
|
}
|
|
|
|
$this->info(
|
|
"Production order {$row['aufnr']} pending. "
|
|
. "New serial: {$row['gernr']}. "
|
|
. "GAMNG: {$gamng}, LMNGA: {$lmnga}. "
|
|
. "pending_released_status = {$row['pending_released_status']}"
|
|
);
|
|
}
|
|
|
|
$rows[] = $row;
|
|
}
|
|
|
|
// Insert or update records
|
|
if (!empty($rows)) {
|
|
|
|
ProductionTemp::upsert(
|
|
$rows,
|
|
['plant_id', 'gernr'],
|
|
array_keys($rows[0])
|
|
);
|
|
|
|
$this->info(
|
|
count($rows) . " records inserted/updated from {$file}"
|
|
);
|
|
}
|
|
}
|
|
|
|
// Send error logs through email
|
|
if (!empty($logMessages)) {
|
|
|
|
$emailContent = implode(PHP_EOL, $logMessages);
|
|
|
|
Mail::raw('Please find attached machine error logs.', function ($message) use ($emailContent) {
|
|
|
|
$message->to(env('ERROR_LOG_MAIL'))
|
|
->subject('Machine Error Logs')
|
|
->attachData($emailContent, 'machine_errors.txt', [
|
|
'mime' => 'text/plain',
|
|
]);
|
|
});
|
|
}
|
|
|
|
$this->info('Production TXT file processing completed.');
|
|
}
|
|
}
|