İlk temizlik tamamlandı bir önceki projeden
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CacheBladeViews extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'cache:blade-views';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Dispatch jobs to cache specified blade views';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Starting to dispatch cache blade views...');
|
||||
|
||||
$views = [
|
||||
[
|
||||
'view' => 'admin-ajax.register-no-cache',
|
||||
'cache' => 'register-creator'
|
||||
],
|
||||
[
|
||||
'view' => 'admin-ajax.request-ndt-no-cache',
|
||||
'cache' => 'request-ndt'
|
||||
],
|
||||
[
|
||||
'view' => 'admin-ajax.repair-log-no-cache',
|
||||
'cache' => 'repair-log'
|
||||
],
|
||||
[
|
||||
'view' => 'admin-ajax.ndt-order.order-list-no-cache',
|
||||
'cache' => 'ndt-order-list'
|
||||
],
|
||||
[
|
||||
'view' => 'admin-ajax.spool-area-release-no-cache',
|
||||
'cache' => 'spool-area-release'
|
||||
],
|
||||
[
|
||||
'view' => 'admin-ajax.spool-list-no-cache',
|
||||
'cache' => 'spool-list'
|
||||
],
|
||||
[
|
||||
'view' => 'admin.type.spool-release.spool-list-chart-no-cache',
|
||||
'cache' => 'spool-list-chart'
|
||||
],
|
||||
];
|
||||
|
||||
dispatchCacheBladeViews($views);
|
||||
|
||||
$this->info('Cache blade views dispatched successfully.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class CheckNDTReminders extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-ndt';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check NDT tests and send reminder notifications for overdue or pending tests';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
* Performance: Optimized with chunking and bulk operations
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Starting NDT reminder checks...');
|
||||
|
||||
$totalNotifications = 0;
|
||||
|
||||
// Check overdue NDT tests
|
||||
$totalNotifications += $this->checkOverdueTests();
|
||||
|
||||
// Check missing test reports
|
||||
$totalNotifications += $this->checkMissingReports();
|
||||
|
||||
// Check deadline approaching tests (within 3 days)
|
||||
$totalNotifications += $this->checkDeadlineApproaching();
|
||||
|
||||
// Check pending results
|
||||
$totalNotifications += $this->checkPendingResults();
|
||||
|
||||
$this->info("NDT reminder check completed. Sent {$totalNotifications} notifications.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for overdue NDT tests
|
||||
* Performance: Single query with multiple conditions
|
||||
*/
|
||||
private function checkOverdueTests()
|
||||
{
|
||||
$overdueThreshold = Carbon::now()->subDays(7); // 7 days overdue
|
||||
$nullableColumns = $this->availableColumns('weld_logs', [
|
||||
'rt_testing_date',
|
||||
'ut_testing_date',
|
||||
'pt_testing_date',
|
||||
'mt_testing_date'
|
||||
]);
|
||||
|
||||
if (empty($nullableColumns)) {
|
||||
$this->info('Skipping overdue check: weld_logs table lacks test date columns.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$ids = DB::table('weld_logs')
|
||||
->where('welding_date', '<=', $overdueThreshold)
|
||||
->where(function($query) use ($nullableColumns) {
|
||||
foreach ($nullableColumns as $column) {
|
||||
$query->orWhereNull($column);
|
||||
}
|
||||
})
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
return $this->sendWeldLogBatch(
|
||||
'notification_ndt_test_overdue',
|
||||
'NDT Test Overdue',
|
||||
'%d weld log(s) have welding dates older than 7 days without NDT testing.',
|
||||
$ids
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for missing test reports
|
||||
* Performance: Checks only records with test dates but no results
|
||||
*/
|
||||
private function checkMissingReports()
|
||||
{
|
||||
$checkDate = Carbon::now()->subDays(3); // 3 days after test date
|
||||
|
||||
$rtIds = [];
|
||||
if ($this->columnsAvailable('weld_logs', ['rt_testing_date', 'rt_result'])) {
|
||||
$rtIds = DB::table('weld_logs')
|
||||
->whereNotNull('rt_testing_date')
|
||||
->where('rt_testing_date', '<=', $checkDate)
|
||||
->where(function($query) {
|
||||
$query->whereNull('rt_result')
|
||||
->orWhere('rt_result', '');
|
||||
})
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
$utIds = [];
|
||||
if ($this->columnsAvailable('weld_logs', ['ut_testing_date', 'ut_result'])) {
|
||||
$utIds = DB::table('weld_logs')
|
||||
->whereNotNull('ut_testing_date')
|
||||
->where('ut_testing_date', '<=', $checkDate)
|
||||
->where(function($query) {
|
||||
$query->whereNull('ut_result')
|
||||
->orWhere('ut_result', '');
|
||||
})
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
$notifications = 0;
|
||||
|
||||
$notifications += $this->sendWeldLogBatch(
|
||||
'notification_ndt_report_missing',
|
||||
'RT Report Missing',
|
||||
'%d RT test(s) have results pending more than 3 days.',
|
||||
$rtIds
|
||||
);
|
||||
|
||||
$notifications += $this->sendWeldLogBatch(
|
||||
'notification_ndt_report_missing',
|
||||
'UT Report Missing',
|
||||
'%d UT test(s) have results pending more than 3 days.',
|
||||
$utIds
|
||||
);
|
||||
|
||||
return $notifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for tests with approaching deadlines
|
||||
* Performance: Single query with date calculation
|
||||
*/
|
||||
private function checkDeadlineApproaching()
|
||||
{
|
||||
$warnDate = Carbon::now()->subDays(4); // Welded 4 days ago, should be tested within 7 days
|
||||
$graceDate = Carbon::now()->subDays(7);
|
||||
|
||||
$dateColumns = array_filter($this->availableColumns('weld_logs', ['rt_testing_date', 'ut_testing_date']));
|
||||
if (empty($dateColumns)) {
|
||||
$this->info('Skipping deadline approaching check: weld_logs table lacks RT/UT testing date columns.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$ids = DB::table('weld_logs')
|
||||
->whereBetween('welding_date', [$graceDate, $warnDate]) // Fixed: older date first, newer date second
|
||||
->where(function($query) use ($dateColumns) {
|
||||
foreach ($dateColumns as $column) {
|
||||
$query->orWhereNull($column);
|
||||
}
|
||||
})
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
return $this->sendWeldLogBatch(
|
||||
'notification_test_deadline_approaching',
|
||||
'Test Deadline Approaching',
|
||||
'%d weld log(s) are approaching the 7-day NDT testing deadline.',
|
||||
$ids
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for pending test results
|
||||
* Performance: Optimized with specific conditions
|
||||
*/
|
||||
private function checkPendingResults()
|
||||
{
|
||||
$pendingThreshold = Carbon::now()->subDays(2);
|
||||
|
||||
$ids = [];
|
||||
$rtColumnsAvailable = $this->columnsAvailable('weld_logs', ['rt_testing_date', 'rt_result']);
|
||||
$utColumnsAvailable = $this->columnsAvailable('weld_logs', ['ut_testing_date', 'ut_result']);
|
||||
|
||||
if (!$rtColumnsAvailable && !$utColumnsAvailable) {
|
||||
$this->info('Skipping pending results check: weld_logs table lacks RT/UT result columns.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$query = DB::table('weld_logs')
|
||||
->where(function($query) use ($pendingThreshold) {
|
||||
if ($this->columnsAvailable('weld_logs', ['rt_testing_date', 'rt_result'])) {
|
||||
$query->orWhere(function($q) use ($pendingThreshold) {
|
||||
$q->whereNotNull('rt_testing_date')
|
||||
->where('rt_testing_date', '<=', $pendingThreshold)
|
||||
->where(function($sq) {
|
||||
$sq->whereNull('rt_result')
|
||||
->orWhere('rt_result', '');
|
||||
});
|
||||
});
|
||||
}
|
||||
if ($this->columnsAvailable('weld_logs', ['ut_testing_date', 'ut_result'])) {
|
||||
$query->orWhere(function($q) use ($pendingThreshold) {
|
||||
$q->whereNotNull('ut_testing_date')
|
||||
->where('ut_testing_date', '<=', $pendingThreshold)
|
||||
->where(function($sq) {
|
||||
$sq->whereNull('ut_result')
|
||||
->orWhere('ut_result', '');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
return $this->sendWeldLogBatch(
|
||||
'notification_ndt_result_pending',
|
||||
'NDT Result Pending',
|
||||
'%d weld log(s) have completed NDT tests but results are pending more than 2 days.',
|
||||
$ids
|
||||
);
|
||||
}
|
||||
|
||||
private function sendWeldLogBatch(string $notificationCode, string $title, string $messageTemplate, array $ids): int
|
||||
{
|
||||
$ids = array_values(array_unique(array_filter($ids)));
|
||||
if (empty($ids)) {
|
||||
$this->info("No {$title} notifications to send.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'weld_logs',
|
||||
'conditions' => [
|
||||
'id' => $ids
|
||||
]
|
||||
];
|
||||
|
||||
sendBatchNotificationWithFilter(
|
||||
$notificationCode,
|
||||
$title,
|
||||
$messageTemplate,
|
||||
$filterParams,
|
||||
count($ids)
|
||||
);
|
||||
|
||||
$this->info("Sent batch notification for {$title} (" . count($ids) . " joints).");
|
||||
return 1;
|
||||
}
|
||||
|
||||
private function availableColumns(string $table, array $columns): array
|
||||
{
|
||||
return array_values(array_filter($columns, function($column) use ($table) {
|
||||
return Schema::hasColumn($table, $column);
|
||||
}));
|
||||
}
|
||||
|
||||
private function columnsAvailable(string $table, array $columns): bool
|
||||
{
|
||||
return count($this->availableColumns($table, $columns)) === count($columns);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\DocumentManager\FolderCatalog;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class DocumentSyncFolders extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*/
|
||||
protected $signature = 'document:sync-folders {--dry-run : Show operations without creating directories} {--root= : Override the documents root path}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*/
|
||||
protected $description = 'Synchronise the Document Manager folder structure using the standard catalog definition.';
|
||||
|
||||
public function handle(FolderCatalog $catalog): int
|
||||
{
|
||||
$rootPath = $this->option('root') ?: storage_path('documents');
|
||||
$dryRun = (bool) $this->option('dry-run');
|
||||
|
||||
$this->info('Document Manager folder synchronisation');
|
||||
$this->line('Root path: ' . $rootPath);
|
||||
$this->line($dryRun ? 'Mode: Dry run (no directories will be created)' : 'Mode: Live');
|
||||
$this->newLine();
|
||||
|
||||
if (! File::isDirectory($rootPath)) {
|
||||
if ($dryRun) {
|
||||
$this->warn('Documents directory does not exist. (dry-run)');
|
||||
} else {
|
||||
File::ensureDirectoryExists($rootPath);
|
||||
$this->info('Created documents root directory.');
|
||||
}
|
||||
}
|
||||
|
||||
$results = $catalog->sync($rootPath, $dryRun);
|
||||
|
||||
if (! empty($results['created'])) {
|
||||
$title = $dryRun ? 'Directories that would be created' : 'Directories created';
|
||||
$this->section($title);
|
||||
$this->displayList($results['created']);
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
if (! empty($results['existing'])) {
|
||||
$this->section('Directories already present');
|
||||
$this->displayList($results['existing']);
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
// Detect directories that are not part of the catalog
|
||||
$extras = $catalog->detectExtra($rootPath);
|
||||
if ($extras->isNotEmpty()) {
|
||||
$this->section('Directories not defined in the catalog');
|
||||
$sorted = $extras->sort()->values();
|
||||
$this->displayList($sorted->all());
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
$this->info('Document Manager folder synchronisation completed.');
|
||||
|
||||
if ($dryRun) {
|
||||
$this->comment('Dry run mode - no changes were applied.');
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
protected function section(string $title): void
|
||||
{
|
||||
$this->line('<options=bold>' . $title . '</>');
|
||||
}
|
||||
|
||||
protected function displayList(array $items, int $limit = 20): void
|
||||
{
|
||||
$items = array_values(array_unique($items));
|
||||
$total = count($items);
|
||||
$display = array_slice($items, 0, $limit);
|
||||
|
||||
foreach ($display as $item) {
|
||||
$this->line(' - ' . $item);
|
||||
}
|
||||
|
||||
if ($total > $limit) {
|
||||
$this->line(' ... ' . ($total - $limit) . ' more');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class GenerateEndpointsDocs extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'docs:endpoints
|
||||
{--append : Append to existing OpenAPI spec}
|
||||
{--output= : Output file path}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generate API documentation for admin-ajax endpoints and append to Scribe OpenAPI spec';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Scanning admin-ajax endpoints...');
|
||||
|
||||
$endpoints = $this->scanEndpoints();
|
||||
$this->info("Found " . count($endpoints) . " endpoints");
|
||||
|
||||
if ($this->option('append')) {
|
||||
$this->appendToOpenApiSpec($endpoints);
|
||||
} else {
|
||||
$this->generateStandaloneSpec($endpoints);
|
||||
}
|
||||
|
||||
$this->info('✅ Endpoints documentation generated successfully!');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan admin-ajax directory for blade files
|
||||
*/
|
||||
protected function scanEndpoints(): array
|
||||
{
|
||||
$endpoints = [];
|
||||
$path = resource_path('views/admin-ajax');
|
||||
|
||||
if (!File::isDirectory($path)) {
|
||||
$this->error('admin-ajax directory not found');
|
||||
return $endpoints;
|
||||
}
|
||||
|
||||
// Scan files recursively
|
||||
$this->scanDirectory($path, '', $endpoints);
|
||||
|
||||
// Sort alphabetically
|
||||
usort($endpoints, fn($a, $b) => strcmp($a['name'], $b['name']));
|
||||
|
||||
return $endpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively scan directory for blade files
|
||||
* SECURITY: Only includes endpoints marked with @api-readonly annotation
|
||||
*/
|
||||
protected function scanDirectory(string $basePath, string $prefix, array &$endpoints): void
|
||||
{
|
||||
$items = File::files($basePath);
|
||||
|
||||
foreach ($items as $file) {
|
||||
$filename = $file->getFilename();
|
||||
|
||||
// Only process .blade.php files
|
||||
if (!Str::endsWith($filename, '.blade.php')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract endpoint name
|
||||
$name = str_replace('.blade.php', '', $filename);
|
||||
$fullName = $prefix ? "{$prefix}/{$name}" : $name;
|
||||
|
||||
// Analyze file for response type hints
|
||||
$content = File::get($file->getPathname());
|
||||
|
||||
// SECURITY: Only include endpoints marked as @api-readonly
|
||||
if (!Str::contains($content, '@api-readonly')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$responseType = $this->detectResponseType($content);
|
||||
$description = $this->extractDescription($content, $name);
|
||||
$category = $this->categorizeEndpoint($fullName);
|
||||
|
||||
$endpoints[] = [
|
||||
'name' => $fullName,
|
||||
'path' => str_replace(base_path(), '', $file->getPathname()),
|
||||
'response_type' => $responseType,
|
||||
'description' => $description,
|
||||
'category' => $category,
|
||||
];
|
||||
}
|
||||
|
||||
// Scan subdirectories
|
||||
$directories = File::directories($basePath);
|
||||
foreach ($directories as $dir) {
|
||||
$dirName = basename($dir);
|
||||
$newPrefix = $prefix ? "{$prefix}/{$dirName}" : $dirName;
|
||||
$this->scanDirectory($dir, $newPrefix, $endpoints);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect response type from blade content
|
||||
*/
|
||||
protected function detectResponseType(string $content): string
|
||||
{
|
||||
// Check for JSON indicators
|
||||
if (
|
||||
Str::contains($content, 'json_encode') ||
|
||||
Str::contains($content, 'json_encode_tr') ||
|
||||
Str::contains($content, 'response()->json') ||
|
||||
Str::contains($content, "'status' =>")
|
||||
) {
|
||||
return 'json';
|
||||
}
|
||||
|
||||
// Check for HTML indicators
|
||||
if (
|
||||
Str::contains($content, '<html') ||
|
||||
Str::contains($content, '<div') ||
|
||||
Str::contains($content, '@extends') ||
|
||||
Str::contains($content, '@include')
|
||||
) {
|
||||
return 'html';
|
||||
}
|
||||
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract description from blade file comments
|
||||
*/
|
||||
protected function extractDescription(string $content, string $name): string
|
||||
{
|
||||
// Try to find PHPDoc style comment at the start
|
||||
if (preg_match('/^<\?php\s*\/\*\*?\s*\n?\s*\*?\s*(.+?)(?:\n|\*\/)/s', $content, $matches)) {
|
||||
return trim($matches[1]);
|
||||
}
|
||||
|
||||
// Try to find HTML comment
|
||||
if (preg_match('/<!--\s*(.+?)\s*-->/s', $content, $matches)) {
|
||||
return trim($matches[1]);
|
||||
}
|
||||
|
||||
// Generate description from name
|
||||
return 'Execute ' . Str::title(str_replace(['-', '_'], ' ', $name)) . ' endpoint';
|
||||
}
|
||||
|
||||
/**
|
||||
* Categorize endpoint based on name
|
||||
*/
|
||||
protected function categorizeEndpoint(string $name): string
|
||||
{
|
||||
$prefixes = [
|
||||
'ndt-' => 'NDT Operations',
|
||||
'welder-' => 'Welder Management',
|
||||
'weld' => 'Welding',
|
||||
'register-' => 'Register Creator',
|
||||
'test-package' => 'Test Packages',
|
||||
'tp-' => 'Test Packages',
|
||||
'document-' => 'Document Management',
|
||||
'report-' => 'Reports',
|
||||
'summary' => 'Summaries',
|
||||
'excel-' => 'Excel Operations',
|
||||
'pdf' => 'PDF Operations',
|
||||
'user' => 'User Management',
|
||||
'permission' => 'Permissions',
|
||||
'content-' => 'Content Management',
|
||||
'spool-' => 'Spool Operations',
|
||||
'material-' => 'Materials',
|
||||
'paint-' => 'Paint Operations',
|
||||
'repair-' => 'Repair Operations',
|
||||
'rfi-' => 'RFI Operations',
|
||||
'punch-' => 'Punch Lists',
|
||||
'mto' => 'MTO Operations',
|
||||
'ai-' => 'AI Features',
|
||||
'cron-' => 'Cron Jobs',
|
||||
'si-' => 'System Info',
|
||||
];
|
||||
|
||||
$baseName = Str::contains($name, '/') ? Str::afterLast($name, '/') : $name;
|
||||
|
||||
foreach ($prefixes as $prefix => $category) {
|
||||
if (Str::startsWith($baseName, $prefix)) {
|
||||
return $category;
|
||||
}
|
||||
}
|
||||
|
||||
return 'General';
|
||||
}
|
||||
|
||||
/**
|
||||
* Append endpoints to existing OpenAPI spec
|
||||
*/
|
||||
protected function appendToOpenApiSpec(array $endpoints): void
|
||||
{
|
||||
$specPath = storage_path('app/scribe/openapi.yaml');
|
||||
|
||||
if (!File::exists($specPath)) {
|
||||
$this->warn('OpenAPI spec not found at ' . $specPath);
|
||||
$this->info('Run "php artisan scribe:generate" first, then run this command with --append');
|
||||
return;
|
||||
}
|
||||
|
||||
// Load existing spec
|
||||
$spec = yaml_parse_file($specPath);
|
||||
|
||||
if (!$spec) {
|
||||
$this->error('Failed to parse OpenAPI spec');
|
||||
return;
|
||||
}
|
||||
|
||||
// Add endpoint paths
|
||||
foreach ($endpoints as $endpoint) {
|
||||
$pathKey = '/api/endpoints/' . $endpoint['name'];
|
||||
|
||||
$spec['paths'][$pathKey] = $this->generatePathSpec($endpoint);
|
||||
}
|
||||
|
||||
// Add Endpoints tag if not exists
|
||||
$hasEndpointsTag = false;
|
||||
foreach ($spec['tags'] ?? [] as $tag) {
|
||||
if ($tag['name'] === 'Endpoints') {
|
||||
$hasEndpointsTag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasEndpointsTag) {
|
||||
$spec['tags'][] = [
|
||||
'name' => 'Endpoints',
|
||||
'description' => 'Admin-ajax endpoints served via API'
|
||||
];
|
||||
}
|
||||
|
||||
// Save updated spec
|
||||
$yaml = yaml_emit($spec, YAML_UTF8_ENCODING);
|
||||
File::put($specPath, $yaml);
|
||||
|
||||
$this->info('Updated OpenAPI spec at ' . $specPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate standalone endpoints spec file
|
||||
*/
|
||||
protected function generateStandaloneSpec(array $endpoints): void
|
||||
{
|
||||
$outputPath = $this->option('output') ?: storage_path('app/scribe/endpoints.json');
|
||||
|
||||
// Group endpoints by category
|
||||
$grouped = [];
|
||||
foreach ($endpoints as $endpoint) {
|
||||
$category = $endpoint['category'];
|
||||
if (!isset($grouped[$category])) {
|
||||
$grouped[$category] = [];
|
||||
}
|
||||
$grouped[$category][] = $endpoint;
|
||||
}
|
||||
|
||||
$output = [
|
||||
'generated_at' => now()->toIso8601String(),
|
||||
'total_endpoints' => count($endpoints),
|
||||
'categories' => array_keys($grouped),
|
||||
'endpoints' => $endpoints,
|
||||
'grouped' => $grouped,
|
||||
];
|
||||
|
||||
File::ensureDirectoryExists(dirname($outputPath));
|
||||
File::put($outputPath, json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
$this->info('Generated endpoints spec at ' . $outputPath);
|
||||
|
||||
// Generate Scribe response file for dynamic documentation
|
||||
$this->generateScribeResponseFile($endpoints);
|
||||
|
||||
// Also generate markdown documentation
|
||||
$this->generateMarkdownDocs($grouped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate response file for Scribe documentation
|
||||
*/
|
||||
protected function generateScribeResponseFile(array $endpoints): void
|
||||
{
|
||||
$responsePath = storage_path('app/scribe/endpoints-response.json');
|
||||
|
||||
// Format endpoints with url and methods for API response
|
||||
$formattedEndpoints = array_map(function ($endpoint) {
|
||||
return [
|
||||
'name' => $endpoint['name'],
|
||||
'url' => '/api/endpoints/' . $endpoint['name'],
|
||||
'methods' => ['GET', 'POST'],
|
||||
'response_type' => $endpoint['response_type'],
|
||||
'description' => $endpoint['description'],
|
||||
'category' => $endpoint['category'],
|
||||
];
|
||||
}, $endpoints);
|
||||
|
||||
$response = [
|
||||
'status' => 'success',
|
||||
'data' => [
|
||||
'endpoints' => $formattedEndpoints,
|
||||
'total' => count($formattedEndpoints)
|
||||
]
|
||||
];
|
||||
|
||||
File::ensureDirectoryExists(dirname($responsePath));
|
||||
File::put($responsePath, json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
$this->info('Generated Scribe response file at ' . $responsePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate OpenAPI path specification for an endpoint
|
||||
*/
|
||||
protected function generatePathSpec(array $endpoint): array
|
||||
{
|
||||
$responseSchema = $endpoint['response_type'] === 'json'
|
||||
? ['type' => 'object', 'properties' => ['status' => ['type' => 'string'], 'data' => ['type' => 'object']]]
|
||||
: ['type' => 'object', 'properties' => ['status' => ['type' => 'string'], 'html' => ['type' => 'string']]];
|
||||
|
||||
return [
|
||||
'get' => [
|
||||
'tags' => ['Endpoints'],
|
||||
'summary' => $endpoint['description'],
|
||||
'description' => "Endpoint: {$endpoint['name']}\nResponse Type: {$endpoint['response_type']}\nCategory: {$endpoint['category']}",
|
||||
'operationId' => 'endpoint_' . Str::slug($endpoint['name'], '_'),
|
||||
'security' => [['bearerAuth' => []]],
|
||||
'responses' => [
|
||||
'200' => [
|
||||
'description' => 'Successful response',
|
||||
'content' => [
|
||||
'application/json' => [
|
||||
'schema' => $responseSchema
|
||||
]
|
||||
]
|
||||
],
|
||||
'401' => [
|
||||
'description' => 'Unauthorized'
|
||||
],
|
||||
'404' => [
|
||||
'description' => 'Endpoint not found'
|
||||
]
|
||||
]
|
||||
],
|
||||
'post' => [
|
||||
'tags' => ['Endpoints'],
|
||||
'summary' => $endpoint['description'],
|
||||
'description' => "Endpoint: {$endpoint['name']}\nResponse Type: {$endpoint['response_type']}\nCategory: {$endpoint['category']}",
|
||||
'operationId' => 'endpoint_' . Str::slug($endpoint['name'], '_') . '_post',
|
||||
'security' => [['bearerAuth' => []]],
|
||||
'requestBody' => [
|
||||
'content' => [
|
||||
'application/json' => [
|
||||
'schema' => [
|
||||
'type' => 'object',
|
||||
'additionalProperties' => true
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'responses' => [
|
||||
'200' => [
|
||||
'description' => 'Successful response',
|
||||
'content' => [
|
||||
'application/json' => [
|
||||
'schema' => $responseSchema
|
||||
]
|
||||
]
|
||||
],
|
||||
'401' => [
|
||||
'description' => 'Unauthorized'
|
||||
],
|
||||
'404' => [
|
||||
'description' => 'Endpoint not found'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate markdown documentation
|
||||
*/
|
||||
protected function generateMarkdownDocs(array $grouped): void
|
||||
{
|
||||
$markdown = "# Available Endpoints\n\n";
|
||||
$markdown .= "This document lists all available admin-ajax endpoints accessible via the API.\n\n";
|
||||
$markdown .= "**Base URL:** `/api/endpoints/{endpoint}`\n\n";
|
||||
$markdown .= "**Authentication:** Bearer Token required\n\n";
|
||||
$markdown .= "---\n\n";
|
||||
|
||||
foreach ($grouped as $category => $endpoints) {
|
||||
$markdown .= "## {$category}\n\n";
|
||||
$markdown .= "| Endpoint | Description | Response Type |\n";
|
||||
$markdown .= "|----------|-------------|---------------|\n";
|
||||
|
||||
foreach ($endpoints as $endpoint) {
|
||||
$markdown .= "| `{$endpoint['name']}` | {$endpoint['description']} | {$endpoint['response_type']} |\n";
|
||||
}
|
||||
|
||||
$markdown .= "\n";
|
||||
}
|
||||
|
||||
$markdown .= "---\n\n";
|
||||
$markdown .= "## Usage Examples\n\n";
|
||||
$markdown .= "### cURL\n\n";
|
||||
$markdown .= "```bash\n";
|
||||
$markdown .= "curl -X GET \\\n";
|
||||
$markdown .= " '{base_url}/api/endpoints/users' \\\n";
|
||||
$markdown .= " -H 'Authorization: Bearer {your_token}' \\\n";
|
||||
$markdown .= " -H 'Accept: application/json'\n";
|
||||
$markdown .= "```\n\n";
|
||||
$markdown .= "### JavaScript\n\n";
|
||||
$markdown .= "```javascript\n";
|
||||
$markdown .= "const response = await fetch('/api/endpoints/users', {\n";
|
||||
$markdown .= " headers: {\n";
|
||||
$markdown .= " 'Authorization': 'Bearer ' + token,\n";
|
||||
$markdown .= " 'Accept': 'application/json'\n";
|
||||
$markdown .= " }\n";
|
||||
$markdown .= "});\n";
|
||||
$markdown .= "const data = await response.json();\n";
|
||||
$markdown .= "```\n";
|
||||
|
||||
$docsPath = resource_path('views/guide/api-endpoints.md');
|
||||
File::ensureDirectoryExists(dirname($docsPath));
|
||||
File::put($docsPath, $markdown);
|
||||
|
||||
$this->info('Generated markdown docs at ' . $docsPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class MigrateFromDate extends Command
|
||||
{
|
||||
protected $signature = 'migrate:from-date {date}';
|
||||
protected $description = 'Migrate migrations from a specific date';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$date = $this->argument('date');
|
||||
$migrationsPath = database_path('migrations');
|
||||
$migrations = File::files($migrationsPath);
|
||||
|
||||
foreach ($migrations as $migration) {
|
||||
$migrationDate = substr($migration->getFilename(), 0, 10); // YYYY_MM_DD kısmını alır
|
||||
|
||||
if ($migrationDate > $date) {
|
||||
Artisan::call('migrate', ['--path' => 'database/migrations/' . $migration->getFilename()]);
|
||||
$this->info('Migrated: ' . $migration->getFilename());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\NaksSyncService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* NAKS Multi-Module Synchronization Command
|
||||
*
|
||||
* Synchronizes NAKS data across all project sites for multiple modules:
|
||||
* - technology: NAKS Technology (naks_certificates)
|
||||
* - welder: NAKS Welder (naks_welders)
|
||||
* - consumables: NAKS Consumables (naks_consumables)
|
||||
* - expert: NAKS Expert (register_of_experts)
|
||||
* - equipment: NAKS Equipment (welding_equipment)
|
||||
*
|
||||
* Usage:
|
||||
* php artisan naks:sync # Sync all modules from all projects
|
||||
* php artisan naks:sync --module=welder # Sync only welder module
|
||||
* php artisan naks:sync --project=101 # Sync specific project only
|
||||
* php artisan naks:sync --dry-run # Test mode (no changes)
|
||||
* php artisan naks:sync --force # Ignore last sync ID, sync all
|
||||
* php artisan naks:sync --status # Show sync status only
|
||||
* php artisan naks:sync --reset # Reset sync state
|
||||
*/
|
||||
class NaksSync extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'naks:sync
|
||||
{--module=all : Module to sync (all, technology, welder, consumables, expert, equipment)}
|
||||
{--project= : Filter to sync only a specific project (by name or URL fragment)}
|
||||
{--dry-run : Run in test mode without making any changes}
|
||||
{--force : Ignore last synced ID and sync all records}
|
||||
{--status : Show sync status only, do not sync}
|
||||
{--reset : Reset sync state for all or specific module/project}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Synchronize NAKS data (certificates, welders, consumables, experts, equipment) from other project sites';
|
||||
|
||||
/**
|
||||
* The sync service instance.
|
||||
*/
|
||||
protected NaksSyncService $syncService;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*/
|
||||
public function __construct(NaksSyncService $syncService)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->syncService = $syncService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$module = $this->option('module') ?? 'all';
|
||||
$project = $this->option('project');
|
||||
$dryRun = (bool) $this->option('dry-run');
|
||||
$force = (bool) $this->option('force');
|
||||
$statusOnly = (bool) $this->option('status');
|
||||
$reset = (bool) $this->option('reset');
|
||||
|
||||
$this->info('╔════════════════════════════════════════════════════════════╗');
|
||||
$this->info('║ NAKS Multi-Module Cross-Site Synchronization ║');
|
||||
$this->info('╚════════════════════════════════════════════════════════════╝');
|
||||
$this->newLine();
|
||||
|
||||
// Show available modules
|
||||
$this->showAvailableModules();
|
||||
|
||||
// Handle status display
|
||||
if ($statusOnly) {
|
||||
return $this->showStatus($module);
|
||||
}
|
||||
|
||||
// Handle reset
|
||||
if ($reset) {
|
||||
return $this->resetSyncState($module, $project);
|
||||
}
|
||||
|
||||
// Validate module
|
||||
if ($module !== 'all' && !$this->syncService->getModuleConfig($module)) {
|
||||
$this->error("❌ Invalid module: {$module}");
|
||||
$this->line("Available modules: all, technology, welder, consumables, expert, equipment");
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
// Show mode information
|
||||
if ($dryRun) {
|
||||
$this->warn('⚠️ DRY RUN MODE - No changes will be made');
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
if ($force) {
|
||||
$this->warn('⚠️ FORCE MODE - Ignoring last sync ID, syncing all records');
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
$this->info("📦 Module(s): " . ($module === 'all' ? 'All modules' : $module));
|
||||
|
||||
if ($project) {
|
||||
$this->info("📌 Project filter: {$project}");
|
||||
}
|
||||
$this->newLine();
|
||||
|
||||
// Check credentials
|
||||
if (!env('SYNC_ADMIN_EMAIL') || !env('SYNC_ADMIN_PASSWORD')) {
|
||||
$this->error('❌ SYNC_ADMIN_EMAIL and SYNC_ADMIN_PASSWORD must be set in .env');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
// Show available projects
|
||||
$this->showAvailableProjects();
|
||||
|
||||
// Run sync
|
||||
$this->info('🔄 Starting synchronization...');
|
||||
$this->newLine();
|
||||
|
||||
$startTime = microtime(true);
|
||||
|
||||
try {
|
||||
$stats = $this->syncService->sync($module, $project, $dryRun, $force);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('❌ Sync failed: ' . $e->getMessage());
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$duration = round(microtime(true) - $startTime, 2);
|
||||
|
||||
// Display results
|
||||
$this->displayResults($stats, $duration, $dryRun);
|
||||
|
||||
// Return appropriate exit code
|
||||
if (!empty($stats['errors']) || !empty($stats['failed_projects'])) {
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show available modules
|
||||
*/
|
||||
protected function showAvailableModules(): void
|
||||
{
|
||||
$modules = $this->syncService->getModules();
|
||||
|
||||
$this->info('📦 Available Modules:');
|
||||
$tableData = [];
|
||||
foreach ($modules as $key => $config) {
|
||||
$tableData[] = [
|
||||
$key,
|
||||
$config['name'],
|
||||
$config['table'],
|
||||
implode(' + ', $config['unique_keys']),
|
||||
];
|
||||
}
|
||||
|
||||
$this->table(
|
||||
['Key', 'Name', 'Table', 'Unique Keys'],
|
||||
$tableData
|
||||
);
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show available projects
|
||||
*/
|
||||
protected function showAvailableProjects(): void
|
||||
{
|
||||
try {
|
||||
$projects = $this->syncService->getProjectUrls();
|
||||
|
||||
$this->info('📋 Available Projects:');
|
||||
$this->table(
|
||||
['Project Name', 'URL'],
|
||||
array_map(function ($p) {
|
||||
return [$p['project_name'] ?? 'Unknown', $p['url'] ?? ''];
|
||||
}, $projects)
|
||||
);
|
||||
$this->newLine();
|
||||
} catch (\Exception $e) {
|
||||
$this->warn('Could not fetch project list: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display sync results
|
||||
*/
|
||||
protected function displayResults(array $stats, float $duration, bool $dryRun): void
|
||||
{
|
||||
$this->newLine();
|
||||
$this->info('════════════════════════════════════════════════════════════');
|
||||
$this->info(' SYNC RESULTS');
|
||||
$this->info('════════════════════════════════════════════════════════════');
|
||||
$this->newLine();
|
||||
|
||||
// Project summary
|
||||
$this->table(
|
||||
['Metric', 'Value'],
|
||||
[
|
||||
['Total Projects', $stats['total_projects']],
|
||||
['Successful Projects', $stats['successful_projects']],
|
||||
['Failed Projects', count($stats['failed_projects'])],
|
||||
['Duration', "{$duration}s"],
|
||||
]
|
||||
);
|
||||
|
||||
// Module-specific results
|
||||
if (!empty($stats['modules'])) {
|
||||
$this->newLine();
|
||||
$this->info('📦 Module Results:');
|
||||
|
||||
$moduleData = [];
|
||||
$totalSynced = 0;
|
||||
$totalInserted = 0;
|
||||
$totalUpdated = 0;
|
||||
$totalSkipped = 0;
|
||||
$totalPdfs = 0;
|
||||
|
||||
foreach ($stats['modules'] as $key => $moduleStats) {
|
||||
$moduleData[] = [
|
||||
$moduleStats['name'],
|
||||
$moduleStats['total_synced'],
|
||||
$moduleStats['total_inserted'],
|
||||
$moduleStats['total_updated'],
|
||||
$moduleStats['total_skipped'],
|
||||
$moduleStats['total_pdfs_downloaded'],
|
||||
];
|
||||
|
||||
$totalSynced += $moduleStats['total_synced'];
|
||||
$totalInserted += $moduleStats['total_inserted'];
|
||||
$totalUpdated += $moduleStats['total_updated'];
|
||||
$totalSkipped += $moduleStats['total_skipped'];
|
||||
$totalPdfs += $moduleStats['total_pdfs_downloaded'];
|
||||
}
|
||||
|
||||
// Add totals row
|
||||
$moduleData[] = [
|
||||
'─────────',
|
||||
'─────',
|
||||
'────────',
|
||||
'───────',
|
||||
'───────',
|
||||
'────',
|
||||
];
|
||||
$moduleData[] = [
|
||||
'TOTAL',
|
||||
$totalSynced,
|
||||
$totalInserted,
|
||||
$totalUpdated,
|
||||
$totalSkipped,
|
||||
$totalPdfs,
|
||||
];
|
||||
|
||||
$this->table(
|
||||
['Module', 'Synced', 'Inserted', 'Updated', 'Skipped', 'PDFs'],
|
||||
$moduleData
|
||||
);
|
||||
}
|
||||
|
||||
// Show failed projects
|
||||
if (!empty($stats['failed_projects'])) {
|
||||
$this->newLine();
|
||||
$this->error('❌ Failed Projects:');
|
||||
foreach ($stats['failed_projects'] as $failed) {
|
||||
$this->line(" • {$failed['name']}: {$failed['error']}");
|
||||
}
|
||||
}
|
||||
|
||||
// Show errors
|
||||
if (!empty($stats['errors'])) {
|
||||
$this->newLine();
|
||||
$this->error('❌ Errors:');
|
||||
foreach ($stats['errors'] as $error) {
|
||||
$this->line(" • {$error}");
|
||||
}
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
|
||||
if ($dryRun) {
|
||||
$this->warn('ℹ️ This was a DRY RUN - no actual changes were made');
|
||||
} else {
|
||||
$hasSyncedRecords = false;
|
||||
foreach ($stats['modules'] as $moduleStats) {
|
||||
if ($moduleStats['total_synced'] > 0) {
|
||||
$hasSyncedRecords = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasSyncedRecords) {
|
||||
$this->info('✅ Synchronization completed successfully!');
|
||||
} else {
|
||||
$this->info('ℹ️ No new records to sync');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show sync status
|
||||
*/
|
||||
protected function showStatus(string $module): int
|
||||
{
|
||||
$this->info('📊 Sync Status:');
|
||||
$this->newLine();
|
||||
|
||||
$tableName = null;
|
||||
if ($module !== 'all') {
|
||||
$config = $this->syncService->getModuleConfig($module);
|
||||
if ($config) {
|
||||
$tableName = $config['table'];
|
||||
}
|
||||
}
|
||||
|
||||
$states = $this->syncService->getSyncStates($tableName);
|
||||
|
||||
if (empty($states)) {
|
||||
$this->warn('No sync history found. Run "php artisan naks:sync" to start syncing.');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$tableData = array_map(function ($state) {
|
||||
return [
|
||||
$state->table_name ?? 'Unknown',
|
||||
$state->source_url ?? 'Unknown',
|
||||
$state->last_synced_id ?? 0,
|
||||
$state->synced_count ?? 0,
|
||||
$state->last_synced_at ?? 'Never',
|
||||
];
|
||||
}, $states);
|
||||
|
||||
$this->table(
|
||||
['Table', 'Source URL', 'Last ID', 'Total Synced', 'Last Sync'],
|
||||
$tableData
|
||||
);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset sync state
|
||||
*/
|
||||
protected function resetSyncState(string $module, ?string $project): int
|
||||
{
|
||||
$tableName = null;
|
||||
if ($module !== 'all') {
|
||||
$config = $this->syncService->getModuleConfig($module);
|
||||
if ($config) {
|
||||
$tableName = $config['table'];
|
||||
} else {
|
||||
$this->error("Invalid module: {$module}");
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
$message = 'Reset sync state for ';
|
||||
$message .= $module === 'all' ? 'ALL modules' : "module: {$module}";
|
||||
if ($project) {
|
||||
$message .= " and project: {$project}";
|
||||
}
|
||||
$message .= '?';
|
||||
|
||||
if (!$this->confirm($message)) {
|
||||
$this->info('Operation cancelled.');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$sourceUrl = null;
|
||||
|
||||
if ($project) {
|
||||
// Find matching project URL
|
||||
try {
|
||||
$projects = $this->syncService->getProjectUrls();
|
||||
foreach ($projects as $p) {
|
||||
if (str_contains(strtolower($p['url']), strtolower($project)) ||
|
||||
str_contains(strtolower($p['project_name'] ?? ''), strtolower($project))) {
|
||||
$sourceUrl = $p['url'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$sourceUrl) {
|
||||
$this->error("Project not found: {$project}");
|
||||
return self::FAILURE;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->error('Could not fetch project list: ' . $e->getMessage());
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
$deleted = $this->syncService->resetSyncState($tableName, $sourceUrl);
|
||||
|
||||
$this->info("✅ Reset {$deleted} sync state record(s)");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class NotificationCheckAll extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-all';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Run all notification check commands at once (for initial trigger)';
|
||||
|
||||
/**
|
||||
* List of all notification commands to run
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $commands = [
|
||||
'notifications:check-deleted-joints',
|
||||
'notifications:check-repair-log-new-joint',
|
||||
'notifications:check-daily-repair-rate',
|
||||
'notifications:check-line-list-missing-data',
|
||||
'notifications:check-pdf-documents',
|
||||
'notifications:check-weldlog-certificate',
|
||||
'notifications:check-incoming-control-certificate',
|
||||
'notifications:check-nde-matrix-discrepancies',
|
||||
'notifications:check-ndt-calculation',
|
||||
'notifications:check-manage-ndt-unnecessary',
|
||||
'notifications:check-support-log-weldlog',
|
||||
'notifications:check-paint-system-values',
|
||||
'notifications:check-weldlog-test-dates',
|
||||
'notifications:check-ndt-request-overdue',
|
||||
'notifications:check-test-log-pdf',
|
||||
];
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Running all notification checks...');
|
||||
$this->info('This will trigger initial scan for all notification types.');
|
||||
$this->newLine();
|
||||
|
||||
$totalCommands = count($this->commands);
|
||||
$successCount = 0;
|
||||
$skipCount = 0;
|
||||
$errorCount = 0;
|
||||
|
||||
$bar = $this->output->createProgressBar($totalCommands);
|
||||
$bar->start();
|
||||
|
||||
foreach ($this->commands as $index => $command) {
|
||||
$bar->advance();
|
||||
|
||||
try {
|
||||
$exitCode = $this->call($command);
|
||||
|
||||
if ($exitCode === 0) {
|
||||
$successCount++;
|
||||
} else {
|
||||
$errorCount++;
|
||||
$this->newLine();
|
||||
$this->warn("Command '{$command}' returned exit code: {$exitCode}");
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$errorCount++;
|
||||
$this->newLine();
|
||||
$this->error("Error running '{$command}': " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
$this->newLine(2);
|
||||
|
||||
// Summary
|
||||
$this->info('=== Summary ===');
|
||||
$this->line("Total commands: {$totalCommands}");
|
||||
$this->line("Successful: {$successCount}");
|
||||
$this->line("Skipped (5 min check): {$skipCount}");
|
||||
$this->line("Errors: {$errorCount}");
|
||||
$this->newLine();
|
||||
|
||||
if ($errorCount > 0) {
|
||||
$this->warn('Some commands encountered errors. Check logs for details.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->info('All notification checks completed successfully!');
|
||||
$this->info('Scheduler will continue running checks every 5 minutes automatically.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckCalibrationDue extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-calibration-due';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check calibration logs for equipment with calibration due within 20 days or overdue and send notifications.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
logNotificationStart($commandName);
|
||||
$this->info('Checking Calibration Log for upcoming/overdue calibrations...');
|
||||
|
||||
$notificationCode = 'notification_calibration_due_soon';
|
||||
|
||||
try {
|
||||
// Run at most every 5 minutes
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: last calibration notification was sent recently.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$today = now()->startOfDay();
|
||||
$limitDate = $today->copy()->addDays(20);
|
||||
|
||||
$query = DB::table('calibration_logs')
|
||||
->whereNotNull('calibration_due_date')
|
||||
->whereDate('calibration_due_date', '<=', $limitDate->toDateString());
|
||||
|
||||
// Optional: skip records explicitly marked as completed
|
||||
$query->where(function ($q) {
|
||||
$q->whereNull('status')
|
||||
->orWhereNotIn('status', ['Completed']);
|
||||
});
|
||||
|
||||
if ($lastCheck !== null) {
|
||||
$query->where(function ($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->info('No new calibration records found for notification.');
|
||||
logNotificationComplete($commandName, 0, 0, 0, microtime(true) - $startTime);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'calibration_logs',
|
||||
'conditions' => [],
|
||||
];
|
||||
|
||||
if ($lastCheck !== null) {
|
||||
$existingIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($existingIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($existingIds, $ids)));
|
||||
$newIds = array_diff($ids, $existingIds);
|
||||
|
||||
if (empty($newIds)) {
|
||||
$this->info('Skipping: no new calibration records since last notification.');
|
||||
logNotificationComplete($commandName, 0, count($allIds), 0, microtime(true) - $startTime);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: duplicate calibration notification with same filter params.');
|
||||
logNotificationComplete($commandName, 0, count($filterParams['conditions']['id']), 0, microtime(true) - $startTime);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$recordCount = count($filterParams['conditions']['id']);
|
||||
$newCount = $lastCheck !== null
|
||||
? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode)))
|
||||
: $recordCount;
|
||||
|
||||
$messageTemplate = '%d equipment item(s) have calibration due within 20 days or are already overdue. Please review the Calibration Log module.';
|
||||
|
||||
$sent = sendBatchNotificationWithFilter(
|
||||
$notificationCode,
|
||||
'Calibration Due Soon',
|
||||
$messageTemplate,
|
||||
$filterParams,
|
||||
$recordCount
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, $sent, $recordCount, $newCount, $duration);
|
||||
|
||||
$this->info("Calibration notifications sent: {$sent} (records: {$recordCount}, new: {$newCount}).");
|
||||
|
||||
return 0;
|
||||
} catch (\Throwable $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error('Error: ' . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckDeletedJoints extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-deleted-joints';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check deleted joints for missing comments and send notifications';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking deleted joints for missing comments...');
|
||||
|
||||
$notificationCode = 'notification_deleted_joint_missing_comment';
|
||||
|
||||
try {
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
// Build query
|
||||
$query = DB::table('deleted_joints')
|
||||
->where(function($q) {
|
||||
$q->whereNull('comment')
|
||||
->orWhere('comment', '');
|
||||
});
|
||||
|
||||
// If not first run, only check new records
|
||||
if ($lastCheck !== null) {
|
||||
$query->where('created_at', '>', $lastCheck);
|
||||
|
||||
// Get already notified IDs to prevent duplicates
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
// If first run (lastCheck === null), check ALL records (no where clause added)
|
||||
|
||||
// Get IDs of records that need notification
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->info('No new deleted joints with missing comments found.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare filter parameters
|
||||
$filterParams = [
|
||||
'table' => 'deleted_joints',
|
||||
'conditions' => [
|
||||
'or' => [
|
||||
['comment' => null],
|
||||
['comment' => '']
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
// If not first run, merge with existing IDs (additive approach)
|
||||
if ($lastCheck !== null) {
|
||||
$existingIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($existingIds)) {
|
||||
// Merge new IDs with existing ones
|
||||
$allIds = array_values(array_unique(array_merge($existingIds, $ids)));
|
||||
|
||||
// Check if there are actually new IDs
|
||||
$newIds = array_diff($ids, $existingIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info('Skipping: No new records since last notification.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'No new records since last notification');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
} else {
|
||||
// First run: use all IDs
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
|
||||
// Check for duplicate (same issue already notified and no new records)
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: Same issue already notified and no new records.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Same issue already notified and no new records');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$title = 'Deleted Joints Missing Comments';
|
||||
$newCount = $lastCheck !== null ? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode))) : count($ids);
|
||||
$message = sprintf(
|
||||
'%d deleted joint(s) have no comment. Please add deletion reason for each joint.',
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
// Send notification with all IDs (existing + new)
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null, // Link will be generated automatically
|
||||
$title,
|
||||
$filterParams,
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$totalRecords = count($filterParams['conditions']['id']);
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $sentCount, $totalRecords, $newCount, $duration);
|
||||
|
||||
$this->info("Sent {$sentCount} notification(s) for {$totalRecords} deleted joints with missing comments ({$newCount} new).");
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class NotificationCheckIncomingControlCertificate extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-incoming-control-certificate';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check incoming control records for missing certificate numbers';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking incoming control records for missing certificates...');
|
||||
|
||||
try {
|
||||
if (!Schema::hasTable('incoming_controls')) {
|
||||
$this->info('incoming_controls table not found; skipping check.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'incoming_controls table not found');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$notificationCode = 'notification_incoming_control_cert_missing';
|
||||
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$query = DB::table('incoming_controls')
|
||||
->whereNotNull('created_at')
|
||||
->where(function($q) {
|
||||
$q->whereNull('certificate_no')
|
||||
->orWhere('certificate_no', '');
|
||||
});
|
||||
|
||||
// If not first run, only check new records
|
||||
if ($lastCheck !== null) {
|
||||
$query->where('created_at', '>', $lastCheck);
|
||||
|
||||
// Get already notified IDs
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->info('No new missing certificates found in incoming control.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare filter params
|
||||
$filterParams = [
|
||||
'table' => 'incoming_controls',
|
||||
'conditions' => []
|
||||
];
|
||||
|
||||
// If not first run, merge with existing IDs
|
||||
if ($lastCheck !== null) {
|
||||
$existingIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($existingIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($existingIds, $ids)));
|
||||
$newIds = array_diff($ids, $existingIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info('Skipping: No new records since last notification.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'No new records since last notification');
|
||||
return 0;
|
||||
}
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: Same issue already notified and no new records.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Same issue already notified and no new records');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode))) : count($ids);
|
||||
$title = 'Incoming Control Certificate Missing';
|
||||
$message = sprintf(
|
||||
'%d incoming control record(s) have empty certificate numbers. Please update the certificate information.',
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$sentCount = sendBatchNotificationWithFilter(
|
||||
$notificationCode,
|
||||
$title,
|
||||
$message,
|
||||
$filterParams,
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$totalRecords = count($filterParams['conditions']['id']);
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $sentCount, $totalRecords, $newCount, $duration);
|
||||
|
||||
$this->info("Sent batch notification for incoming control certificates ({$totalRecords} records, {$newCount} new).");
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckLineList extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-line-list-missing-data';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check line list for missing required data (category, NDT ratio, pressure, temperature, test type, test media) where weld logs exist';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking line list for missing required data...');
|
||||
|
||||
$notificationCode = 'notification_line_list_missing_data';
|
||||
|
||||
try {
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
// Get unique line numbers from weld_logs that have records
|
||||
$usedLinesQuery = DB::table('weld_logs')
|
||||
->select('line_number')
|
||||
->whereNotNull('line_number')
|
||||
->where('line_number', '!=', '')
|
||||
->distinct();
|
||||
|
||||
// If not first run, only check lines from new weld_logs
|
||||
if ($lastCheck !== null) {
|
||||
$usedLinesQuery->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
}
|
||||
|
||||
$usedLines = $usedLinesQuery->limit(100)->pluck('line_number');
|
||||
|
||||
if ($usedLines->isEmpty()) {
|
||||
$this->info('No weld log records found with line numbers.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->info("Found {$usedLines->count()} unique lines in weld logs. Checking line list...");
|
||||
|
||||
// Get already notified line numbers
|
||||
$alreadyNotifiedLines = [];
|
||||
if ($lastCheck !== null) {
|
||||
$lastNotification = \App\Models\Notification::where('notification_code', $notificationCode)
|
||||
->whereNotNull('filter_params')
|
||||
->orderBy('created_at', 'DESC')
|
||||
->first();
|
||||
|
||||
if ($lastNotification && $lastNotification->filter_params) {
|
||||
$lastParams = is_array($lastNotification->filter_params)
|
||||
? $lastNotification->filter_params
|
||||
: json_decode($lastNotification->filter_params, true);
|
||||
if (isset($lastParams['conditions']['line_no'])) {
|
||||
$alreadyNotifiedLines = is_array($lastParams['conditions']['line_no'])
|
||||
? $lastParams['conditions']['line_no']
|
||||
: [$lastParams['conditions']['line_no']];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$linesWithMissingData = [];
|
||||
|
||||
foreach ($usedLines as $lineNumber) {
|
||||
// Skip if already notified (unless first run)
|
||||
if ($lastCheck !== null && in_array($lineNumber, $alreadyNotifiedLines)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$linelist = DB::table('line_lists')
|
||||
->where('line_no', $lineNumber)
|
||||
->first();
|
||||
|
||||
if (!$linelist) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isMissing = empty($linelist->category) ||
|
||||
empty($linelist->ndt) ||
|
||||
(empty($linelist->working_pressure_mpa) && empty($linelist->design_pressure_mpa)) ||
|
||||
(empty($linelist->working_temperature) && empty($linelist->design_temperature)) ||
|
||||
empty($linelist->test_type) ||
|
||||
empty($linelist->test_media);
|
||||
|
||||
if ($isMissing) {
|
||||
$linesWithMissingData[] = $lineNumber;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($linesWithMissingData)) {
|
||||
$this->info('No new lines with missing data were found.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Merge with existing lines if not first run
|
||||
if ($lastCheck !== null && !empty($alreadyNotifiedLines)) {
|
||||
$linesWithMissingData = array_values(array_unique(array_merge($alreadyNotifiedLines, $linesWithMissingData)));
|
||||
} else {
|
||||
$linesWithMissingData = array_values(array_unique($linesWithMissingData));
|
||||
}
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'line_lists',
|
||||
'conditions' => [
|
||||
'line_no' => $linesWithMissingData
|
||||
]
|
||||
];
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: Same issue already notified and no new records.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Same issue already notified and no new records');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($linesWithMissingData, $alreadyNotifiedLines)) : count($linesWithMissingData);
|
||||
$message = sprintf(
|
||||
'%d line(s) referenced in weld logs have missing required data (category, NDT ratio, pressure, temperature, test type, or test media). Please update the Line List entries.',
|
||||
count($linesWithMissingData)
|
||||
);
|
||||
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null,
|
||||
'Line List Missing Required Data',
|
||||
$filterParams,
|
||||
count($linesWithMissingData)
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$totalRecords = count($linesWithMissingData);
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $sentCount, $totalRecords, $newCount, $duration);
|
||||
|
||||
$this->info("Sent {$sentCount} notification(s) for {$totalRecords} line(s) with missing data ({$newCount} new).");
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckManageNDT extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-manage-ndt-unnecessary';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check for unnecessary manual NDT requests where NDE Matrix has 0% ratio';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking for unnecessary manual NDT requests...');
|
||||
|
||||
$totalCount = 0;
|
||||
$totalSent = 0;
|
||||
$totalRecords = 0;
|
||||
$totalNew = 0;
|
||||
|
||||
try {
|
||||
// Check each NDT type
|
||||
$result1 = $this->checkTestType('RT', 'rt', 'rt_request_no');
|
||||
$totalCount += $result1;
|
||||
$result2 = $this->checkTestType('UT', 'ut', 'ut_request_no');
|
||||
$totalCount += $result2;
|
||||
$result3 = $this->checkTestType('PT', 'pt', 'pt_request_no');
|
||||
$totalCount += $result3;
|
||||
$result4 = $this->checkTestType('MT', 'mt', 'mt_request_no');
|
||||
$totalCount += $result4;
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, $totalSent, $totalRecords, $totalNew, $duration);
|
||||
|
||||
$this->info("Total notifications sent: {$totalCount}");
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for unnecessary requests for a specific test type
|
||||
*
|
||||
* @param string $testName Display name (RT, UT, PT, MT)
|
||||
* @param string $ndeField NDE Matrix field name (rt, ut, pt, mt)
|
||||
* @param string $requestField Weldlog request field name (rt_request_no, ut_request_no, etc.)
|
||||
* @return int
|
||||
*/
|
||||
private function checkTestType($testName, $ndeField, $requestField)
|
||||
{
|
||||
$this->info("Checking {$testName} unnecessary requests...");
|
||||
|
||||
$ids = apply_welded_filter(DB::table('weld_logs'))
|
||||
->leftJoin('nde_matrices', function($join) {
|
||||
$join->on('weld_logs.line_number', '=', 'nde_matrices.line')
|
||||
->on('weld_logs.type_of_joint', '=', 'nde_matrices.type_of_joint');
|
||||
})
|
||||
->whereNotNull('weld_logs.welding_date')
|
||||
->where('weld_logs.welding_date', '!=', '')
|
||||
->whereNotNull("weld_logs.{$requestField}")
|
||||
->where("weld_logs.{$requestField}", '!=', '')
|
||||
->where(function($query) use ($ndeField) {
|
||||
$query->whereNull("nde_matrices.{$ndeField}")
|
||||
->orWhere("nde_matrices.{$ndeField}", 0)
|
||||
->orWhere("nde_matrices.{$ndeField}", '0');
|
||||
})
|
||||
->pluck('weld_logs.id')
|
||||
->toArray();
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->info("No unnecessary {$testName} requests found.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$notificationCode = 'notification_manage_ndt_unnecessary';
|
||||
|
||||
// Skip if checked recently
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info("Skipping {$testName}: Last notification sent less than 5 minutes ago.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
// Filter IDs: only keep new ones if not first run
|
||||
if ($lastCheck !== null) {
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$newIds = array_diff($ids, $alreadyNotifiedIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info("Skipping {$testName}: No new records since last notification.");
|
||||
return 0;
|
||||
}
|
||||
$ids = array_values(array_unique(array_merge($alreadyNotifiedIds, $ids)));
|
||||
}
|
||||
}
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'weld_logs',
|
||||
'conditions' => [
|
||||
'id' => array_values(array_unique($ids))
|
||||
]
|
||||
];
|
||||
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info("Skipping {$testName}: Same issue already notified and no new records.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode))) : count($ids);
|
||||
$sentCount = sendBatchNotificationWithFilter(
|
||||
$notificationCode,
|
||||
"Unnecessary {$testName} Request",
|
||||
'%d joint(s) have unnecessary ' . $testName . ' test requests (NDE ratio 0% or missing).',
|
||||
$filterParams,
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$this->info("Sent batch notification for unnecessary {$testName} requests (" . count($filterParams['conditions']['id']) . " joints, {$newCount} new).");
|
||||
return $sentCount > 0 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,427 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckNDEMatrixDiscrepancies extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-nde-matrix-discrepancies';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check for discrepancies between NDE Matrix, Line List, and Weldlog';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking NDE Matrix discrepancies...');
|
||||
|
||||
$totalCount = 0;
|
||||
$totalSent = 0;
|
||||
$totalRecords = 0;
|
||||
$totalNew = 0;
|
||||
|
||||
try {
|
||||
// Check 1: NDE Matrix lines not in Weldlog
|
||||
$result1 = $this->checkNDEMatrixNotInWeldlog($totalSent, $totalRecords, $totalNew);
|
||||
if ($result1 > 0) {
|
||||
$totalCount += 1;
|
||||
}
|
||||
|
||||
// Check 2: Weldlog lines not in Line List
|
||||
$result2 = $this->checkWeldlogNotInLineList($totalSent, $totalRecords, $totalNew);
|
||||
if ($result2 > 0) {
|
||||
$totalCount += 1;
|
||||
}
|
||||
|
||||
// Check 3: Line List entries with missing NDT ratio
|
||||
$result3 = $this->checkLineListMissingNDT($totalSent, $totalRecords, $totalNew);
|
||||
if ($result3 > 0) {
|
||||
$totalCount += 1;
|
||||
}
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, $totalSent, $totalRecords, $totalNew, $duration);
|
||||
|
||||
$this->info("Total notifications sent: {$totalCount}");
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 1: NDE Matrix rows with empty NDT values (all 6 columns empty)
|
||||
*/
|
||||
private function checkNDEMatrixNotInWeldlog(&$totalSent = null, &$totalRecords = null, &$totalNew = null)
|
||||
{
|
||||
$this->info('Check 1: NDE Matrix rows with empty NDT values...');
|
||||
|
||||
$notificationCode = 'notification_nde_matrix_not_in_weldlog';
|
||||
|
||||
// Skip if checked recently
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping Check 1: Last notification sent less than 5 minutes ago.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$query = DB::table('nde_matrices')
|
||||
->select('id', 'line', 'project', 'type_of_joint')
|
||||
->whereNotNull('line')
|
||||
->where('line', '!=', '')
|
||||
->where(function($q) {
|
||||
$q->where(function($sq) {
|
||||
$sq->whereNull('rt')->orWhere('rt', '')->orWhere('rt', 0)->orWhere('rt', '0');
|
||||
})
|
||||
->where(function($sq) {
|
||||
$sq->whereNull('ut')->orWhere('ut', '')->orWhere('ut', 0)->orWhere('ut', '0');
|
||||
})
|
||||
->where(function($sq) {
|
||||
$sq->whereNull('pt')->orWhere('pt', '')->orWhere('pt', 0)->orWhere('pt', '0');
|
||||
})
|
||||
->where(function($sq) {
|
||||
$sq->whereNull('mt')->orWhere('mt', '')->orWhere('mt', 0)->orWhere('mt', '0');
|
||||
})
|
||||
->where(function($sq) {
|
||||
$sq->whereNull('pmi')->orWhere('pmi', '')->orWhere('pmi', 0)->orWhere('pmi', '0');
|
||||
})
|
||||
->where(function($sq) {
|
||||
$sq->whereNull('ht')->orWhere('ht', '')->orWhere('ht', 0)->orWhere('ht', '0');
|
||||
});
|
||||
});
|
||||
|
||||
if ($lastCheck !== null) {
|
||||
$query->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$emptyRows = $query->limit(50)->get();
|
||||
|
||||
if ($emptyRows->isEmpty()) {
|
||||
$this->info('No new empty NDE Matrix rows found.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$emptyIds = array_values(array_unique($emptyRows->pluck('id')->toArray()));
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'nde_matrices',
|
||||
'conditions' => []
|
||||
];
|
||||
|
||||
if ($lastCheck !== null) {
|
||||
$existingIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($existingIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($existingIds, $emptyIds)));
|
||||
$newIds = array_diff($emptyIds, $existingIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info('Skipping Check 1: No new records since last notification.');
|
||||
return 0;
|
||||
}
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $emptyIds;
|
||||
}
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $emptyIds;
|
||||
}
|
||||
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping Check 1: Same issue already notified and no new records.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($emptyIds, getAlreadyNotifiedIds($notificationCode))) : count($emptyIds);
|
||||
$totalRecords = count($filterParams['conditions']['id']);
|
||||
$message = sprintf(
|
||||
'%d NDE Matrix row(s) have empty NDT values (RT, UT, PT, MT, PMI, HT). Please assign NDT requirements.',
|
||||
$totalRecords
|
||||
);
|
||||
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null,
|
||||
'NDE Matrix Empty Row - NDT Assignment Required',
|
||||
$filterParams,
|
||||
$totalRecords
|
||||
);
|
||||
|
||||
// Update totals for handle method
|
||||
if ($totalSent !== null) {
|
||||
$totalSent += $sentCount;
|
||||
$totalRecords += $totalRecords;
|
||||
$totalNew += $newCount;
|
||||
}
|
||||
|
||||
$this->info("Sent {$sentCount} batch notification(s) for empty NDE Matrix rows ({$totalRecords} records, {$newCount} new).");
|
||||
return $sentCount > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 2: Lines in Weldlog but not in Line List
|
||||
*/
|
||||
private function checkWeldlogNotInLineList(&$totalSent = null, &$totalRecords = null, &$totalNew = null)
|
||||
{
|
||||
$this->info('Check 2: Weldlog lines not found in Line List...');
|
||||
|
||||
$notificationCode = 'notification_weldlog_not_in_line_list';
|
||||
|
||||
// Skip if checked recently
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping Check 2: Last notification sent less than 5 minutes ago.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$weldlogLinesQuery = DB::table('weld_logs')
|
||||
->select('line_number', 'project', 'iso_number')
|
||||
->whereNotNull('line_number')
|
||||
->where('line_number', '!=', '')
|
||||
->whereNotNull('welding_date')
|
||||
->distinct();
|
||||
|
||||
if ($lastCheck !== null) {
|
||||
$weldlogLinesQuery->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
}
|
||||
|
||||
$weldlogLines = $weldlogLinesQuery->limit(50)->get();
|
||||
|
||||
// Get already notified line numbers
|
||||
$alreadyNotifiedLines = [];
|
||||
if ($lastCheck !== null) {
|
||||
$lastNotification = \App\Models\Notification::where('notification_code', $notificationCode)
|
||||
->whereNotNull('filter_params')
|
||||
->orderBy('created_at', 'DESC')
|
||||
->first();
|
||||
|
||||
if ($lastNotification && $lastNotification->filter_params) {
|
||||
$lastParams = is_array($lastNotification->filter_params)
|
||||
? $lastNotification->filter_params
|
||||
: json_decode($lastNotification->filter_params, true);
|
||||
if (isset($lastParams['conditions']['line_number'])) {
|
||||
$alreadyNotifiedLines = is_array($lastParams['conditions']['line_number'])
|
||||
? $lastParams['conditions']['line_number']
|
||||
: [$lastParams['conditions']['line_number']];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$missingLines = [];
|
||||
|
||||
foreach ($weldlogLines as $weldLine) {
|
||||
if ($lastCheck !== null && in_array($weldLine->line_number, $alreadyNotifiedLines)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$existsInLineList = DB::table('line_lists')
|
||||
->where('line_no', $weldLine->line_number)
|
||||
->exists();
|
||||
|
||||
if (!$existsInLineList) {
|
||||
$missingLines[] = $weldLine->line_number;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($missingLines)) {
|
||||
$this->info('No new discrepancies found for Check 2.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($lastCheck !== null && !empty($alreadyNotifiedLines)) {
|
||||
$missingLines = array_values(array_unique(array_merge($alreadyNotifiedLines, $missingLines)));
|
||||
} else {
|
||||
$missingLines = array_values(array_unique($missingLines));
|
||||
}
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'weld_logs',
|
||||
'conditions' => [
|
||||
'line_number' => $missingLines
|
||||
]
|
||||
];
|
||||
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping Check 2: Same issue already notified and no new records.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($missingLines, $alreadyNotifiedLines)) : count($missingLines);
|
||||
$totalRecords = count($missingLines);
|
||||
$message = sprintf(
|
||||
'%d weld log line(s) could not be matched in the Line List. Please review and add the missing line records.',
|
||||
$totalRecords
|
||||
);
|
||||
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null,
|
||||
'Weldlog Not in Line List',
|
||||
$filterParams,
|
||||
$totalRecords
|
||||
);
|
||||
|
||||
// Update totals for handle method
|
||||
if ($totalSent !== null) {
|
||||
$totalSent += $sentCount;
|
||||
$totalRecords += $totalRecords;
|
||||
$totalNew += $newCount;
|
||||
}
|
||||
|
||||
$this->info("Sent {$sentCount} batch notification(s) for Check 2 ({$totalRecords} lines, {$newCount} new).");
|
||||
return $sentCount > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 3: Line List entries with missing NDT ratio
|
||||
*/
|
||||
private function checkLineListMissingNDT(&$totalSent = null, &$totalRecords = null, &$totalNew = null)
|
||||
{
|
||||
$this->info('Check 3: Line List entries missing NDT ratio...');
|
||||
|
||||
$notificationCode = 'notification_line_list_ndt_missing';
|
||||
|
||||
// Skip if checked recently
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping Check 3: Last notification sent less than 5 minutes ago.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$query = DB::table('line_lists')
|
||||
->select('line_lists.id', 'line_lists.line_no', 'line_lists.ndt')
|
||||
->join('weld_logs', 'weld_logs.line_number', '=', 'line_lists.line_no')
|
||||
->where(function($q) {
|
||||
$q->whereNull('line_lists.ndt')
|
||||
->orWhere('line_lists.ndt', '')
|
||||
->orWhere('line_lists.ndt', '0');
|
||||
})
|
||||
->whereNotNull('weld_logs.welding_date')
|
||||
->distinct();
|
||||
|
||||
if ($lastCheck !== null) {
|
||||
$query->where(function($q) use ($lastCheck) {
|
||||
$q->where('line_lists.created_at', '>', $lastCheck)
|
||||
->orWhere('line_lists.updated_at', '>', $lastCheck)
|
||||
->orWhere('weld_logs.created_at', '>', $lastCheck)
|
||||
->orWhere('weld_logs.updated_at', '>', $lastCheck);
|
||||
});
|
||||
}
|
||||
|
||||
$missingNDT = $query->limit(50)->get();
|
||||
|
||||
if ($missingNDT->isEmpty()) {
|
||||
$this->info('No new discrepancies found for Check 3.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lineNumbers = array_values(array_unique($missingNDT->pluck('line_no')->toArray()));
|
||||
|
||||
// Get already notified line numbers
|
||||
$alreadyNotifiedLines = [];
|
||||
if ($lastCheck !== null) {
|
||||
$lastNotification = \App\Models\Notification::where('notification_code', $notificationCode)
|
||||
->whereNotNull('filter_params')
|
||||
->orderBy('created_at', 'DESC')
|
||||
->first();
|
||||
|
||||
if ($lastNotification && $lastNotification->filter_params) {
|
||||
$lastParams = is_array($lastNotification->filter_params)
|
||||
? $lastNotification->filter_params
|
||||
: json_decode($lastNotification->filter_params, true);
|
||||
if (isset($lastParams['conditions']['line_no'])) {
|
||||
$alreadyNotifiedLines = is_array($lastParams['conditions']['line_no'])
|
||||
? $lastParams['conditions']['line_no']
|
||||
: [$lastParams['conditions']['line_no']];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($lastCheck !== null && !empty($alreadyNotifiedLines)) {
|
||||
$lineNumbers = array_values(array_unique(array_merge($alreadyNotifiedLines, $lineNumbers)));
|
||||
}
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'line_lists',
|
||||
'conditions' => [
|
||||
'line_no' => $lineNumbers
|
||||
]
|
||||
];
|
||||
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping Check 3: Same issue already notified and no new records.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($lineNumbers, $alreadyNotifiedLines)) : count($lineNumbers);
|
||||
$totalRecords = count($lineNumbers);
|
||||
$message = sprintf(
|
||||
'%d Line List entry/entries used in Weldlog are missing NDT ratio. Please update the Line List records.',
|
||||
$totalRecords
|
||||
);
|
||||
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null,
|
||||
'Line List NDT Missing',
|
||||
$filterParams,
|
||||
$totalRecords
|
||||
);
|
||||
|
||||
// Update totals for handle method
|
||||
if ($totalSent !== null) {
|
||||
$totalSent += $sentCount;
|
||||
$totalRecords += $totalRecords;
|
||||
$totalNew += $newCount;
|
||||
}
|
||||
|
||||
$this->info("Sent {$sentCount} batch notification(s) for Check 3 ({$totalRecords} lines, {$newCount} new).");
|
||||
return $sentCount > 0 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckNDTCalculation extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-ndt-calculation';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check NDT calculation warnings based on material grades, thicknesses, and joint types';
|
||||
|
||||
/**
|
||||
* Stainless steel material groups
|
||||
*/
|
||||
private $ssGroups = ['11', '8', '9', 'M11', 'M111', 'M9'];
|
||||
|
||||
/**
|
||||
* Carbon steel material group
|
||||
*/
|
||||
private $csGroup = ['1', 'M01'];
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking NDT calculation warnings...');
|
||||
|
||||
$totalCount = 0;
|
||||
$totalSent = 0;
|
||||
$totalRecords = 0;
|
||||
$totalNew = 0;
|
||||
|
||||
try {
|
||||
// Check 1: PMI test missing for SS or CS+SS
|
||||
$result1 = $this->checkPMIMissing($totalSent, $totalRecords, $totalNew);
|
||||
if ($result1 > 0) {
|
||||
$totalCount += 1;
|
||||
}
|
||||
|
||||
// Check 2: FN test missing for SS with temperature > 350
|
||||
$result2 = $this->checkFNMissing($totalSent, $totalRecords, $totalNew);
|
||||
if ($result2 > 0) {
|
||||
$totalCount += 1;
|
||||
}
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, $totalSent, $totalRecords, $totalNew, $duration);
|
||||
|
||||
$this->info("Total notifications sent: {$totalCount}");
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 1: PMI test missing for SS or CS+SS materials
|
||||
*/
|
||||
private function checkPMIMissing(&$totalSent = null, &$totalRecords = null, &$totalNew = null)
|
||||
{
|
||||
$this->info('Check 1: PMI test missing for SS or CS+SS materials...');
|
||||
|
||||
$ids = [];
|
||||
|
||||
$weldLogs = DB::table('weld_logs')
|
||||
->whereNotNull('welding_date')
|
||||
->where('welding_date', '!=', '')
|
||||
->where(function($query) {
|
||||
$query->whereIn('ru_material_group_1', array_merge($this->ssGroups, $this->csGroup))
|
||||
->orWhereIn('ru_material_group_2', array_merge($this->ssGroups, $this->csGroup));
|
||||
})
|
||||
->where(function($query) {
|
||||
$query->whereNull('pmi_request_no')
|
||||
->orWhere('pmi_request_no', '');
|
||||
})
|
||||
->select('id', 'ru_material_group_1', 'ru_material_group_2')
|
||||
->get();
|
||||
|
||||
foreach ($weldLogs as $weld) {
|
||||
$mat1 = $weld->ru_material_group_1 ?? '';
|
||||
$mat2 = $weld->ru_material_group_2 ?? '';
|
||||
|
||||
$isSS = in_array($mat1, $this->ssGroups) || in_array($mat2, $this->ssGroups);
|
||||
$isCS_SS = (in_array($mat1, $this->csGroup) && in_array($mat2, $this->ssGroups)) ||
|
||||
(in_array($mat1, $this->ssGroups) && in_array($mat2, $this->csGroup));
|
||||
|
||||
if ($isSS || $isCS_SS) {
|
||||
$ids[] = $weld->id;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->sendWeldLogBatch(
|
||||
'notification_ndt_pmi_missing',
|
||||
'PMI Test Missing',
|
||||
'%d joint(s) have SS/CS+SS materials but PMI test requests are missing.',
|
||||
$ids,
|
||||
$totalSent,
|
||||
$totalRecords,
|
||||
$totalNew
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 2: FN (Ferrite) test missing for SS materials with operating temperature > 350°C
|
||||
*/
|
||||
private function checkFNMissing(&$totalSent = null, &$totalRecords = null, &$totalNew = null)
|
||||
{
|
||||
$this->info('Check 2: FN test missing for SS materials with temperature > 350...');
|
||||
|
||||
$ids = DB::table('weld_logs')
|
||||
->whereNotNull('welding_date')
|
||||
->where('welding_date', '!=', '')
|
||||
->where(function($query) {
|
||||
$query->whereIn('ru_material_group_1', $this->ssGroups)
|
||||
->orWhereIn('ru_material_group_2', $this->ssGroups);
|
||||
})
|
||||
->where(function($query) {
|
||||
// Temperature control: operating_temperature_s > 350
|
||||
$query->whereRaw('CAST(operating_temperature_s AS DECIMAL(10,2)) > 350');
|
||||
})
|
||||
->where(function($query) {
|
||||
$query->whereNull('ferrite_request_no')
|
||||
->orWhere('ferrite_request_no', '');
|
||||
})
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
return $this->sendWeldLogBatch(
|
||||
'notification_ndt_fn_missing',
|
||||
'FN Test Missing',
|
||||
'%d joint(s) with SS materials and operating temperature > 350°C are missing FN (Ferrite) test requests.',
|
||||
$ids,
|
||||
$totalSent,
|
||||
$totalRecords,
|
||||
$totalNew
|
||||
);
|
||||
}
|
||||
|
||||
private function sendWeldLogBatch(string $notificationCode, string $title, string $messageTemplate, array $ids, &$totalSent = null, &$totalRecords = null, &$totalNew = null): int
|
||||
{
|
||||
$ids = array_values(array_unique(array_filter($ids)));
|
||||
if (empty($ids)) {
|
||||
$this->info("No {$title} records found.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Skip if checked recently
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info("Skipping {$title}: Last notification sent less than 5 minutes ago.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
// Filter IDs: only keep new ones if not first run
|
||||
if ($lastCheck !== null) {
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$newIds = array_diff($ids, $alreadyNotifiedIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info("Skipping {$title}: No new records since last notification.");
|
||||
return 0;
|
||||
}
|
||||
$ids = array_values(array_unique(array_merge($alreadyNotifiedIds, $ids)));
|
||||
}
|
||||
}
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'weld_logs',
|
||||
'conditions' => [
|
||||
'id' => $ids
|
||||
]
|
||||
];
|
||||
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info("Skipping {$title}: Same issue already notified and no new records.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode))) : count($ids);
|
||||
$totalRecords = count($ids);
|
||||
$sentCount = sendBatchNotificationWithFilter(
|
||||
$notificationCode,
|
||||
$title,
|
||||
$messageTemplate,
|
||||
$filterParams,
|
||||
$totalRecords
|
||||
);
|
||||
|
||||
// Update totals for handle method
|
||||
if ($totalSent !== null) {
|
||||
$totalSent += $sentCount;
|
||||
$totalRecords += $totalRecords;
|
||||
$totalNew += $newCount;
|
||||
}
|
||||
|
||||
$this->info("Sent batch notification for {$title} ({$totalRecords} joints, {$newCount} new).");
|
||||
return $sentCount > 0 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class NotificationCheckNDTRequestOverdue extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-ndt-request-overdue';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check if NDT request dates are older than 10 days without test results';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking NDT request dates older than 10 days...');
|
||||
|
||||
$notificationCode = 'notification_ndt_request_overdue';
|
||||
|
||||
try {
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$overdueThreshold = Carbon::now()->subDays(10)->toDateString();
|
||||
|
||||
$invalidIds = [];
|
||||
|
||||
// Get already notified IDs
|
||||
$alreadyNotifiedIds = $lastCheck !== null ? getAlreadyNotifiedIds($notificationCode) : [];
|
||||
|
||||
// Request date and result fields to check
|
||||
$requestChecks = [
|
||||
['request' => 'rt_request_date', 'result' => 'rt_result', 'name' => 'RT'],
|
||||
['request' => 'vt_request_date', 'result' => 'vt_result', 'name' => 'VT'],
|
||||
['request' => 'ut_request_date', 'result' => 'ut_result', 'name' => 'UT'],
|
||||
['request' => 'pt_request_date', 'result' => 'pt_result', 'name' => 'PT'],
|
||||
['request' => 'mt_request_date', 'result' => 'mt_result', 'name' => 'MT'],
|
||||
['request' => 'ht_request_date', 'result' => 'ht_result', 'name' => 'HT'],
|
||||
['request' => 'pmi_request_date', 'result' => 'pmi_result', 'name' => 'PMI'],
|
||||
['request' => 'pwht_request_date', 'result' => 'pwht_result', 'name' => 'PWHT'],
|
||||
['request' => 'ferrite_request_date', 'result' => 'ferrite_result', 'name' => 'Ferrite'],
|
||||
];
|
||||
|
||||
foreach ($requestChecks as $check) {
|
||||
$this->info("Checking {$check['name']} requests...");
|
||||
|
||||
$query = DB::table('weld_logs')
|
||||
->whereNotNull($check['request'])
|
||||
->where($check['request'], '!=', '')
|
||||
->where($check['request'], '!=', '0000-00-00')
|
||||
->where($check['request'], '<=', $overdueThreshold)
|
||||
->where(function($q) use ($check) {
|
||||
$q->whereNull($check['result'])
|
||||
->orWhere($check['result'], '')
|
||||
->orWhere($check['result'], 'Pending');
|
||||
});
|
||||
|
||||
// If not first run, only check records that might have changed
|
||||
// (request date is still overdue, but result might have been updated)
|
||||
if ($lastCheck !== null) {
|
||||
$query->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (!empty($ids)) {
|
||||
$this->warn("Found " . count($ids) . " overdue {$check['name']} requests (10+ days)");
|
||||
$invalidIds = array_merge($invalidIds, $ids);
|
||||
}
|
||||
}
|
||||
|
||||
$invalidIds = array_values(array_unique($invalidIds));
|
||||
|
||||
if (empty($invalidIds)) {
|
||||
$this->info('No new overdue NDT requests found. All requests within 10 days or have results.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare filter params
|
||||
$filterParams = [
|
||||
'table' => 'weld_logs',
|
||||
'conditions' => []
|
||||
];
|
||||
|
||||
// If not first run, merge with existing IDs
|
||||
if ($lastCheck !== null && !empty($alreadyNotifiedIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($alreadyNotifiedIds, $invalidIds)));
|
||||
$newIds = array_diff($invalidIds, $alreadyNotifiedIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info('Skipping: No new records since last notification.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'No new records since last notification');
|
||||
return 0;
|
||||
}
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $invalidIds;
|
||||
}
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: Same issue already notified and no new records.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Same issue already notified and no new records');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($invalidIds, $alreadyNotifiedIds)) : count($invalidIds);
|
||||
$message = sprintf(
|
||||
'%d weld log record(s) have NDT request dates older than 10 days without test results (RT, VT, UT, PT, MT, HT, PWHT). Please follow up with testing laboratory.',
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null,
|
||||
'NDT Request Overdue (10+ Days)',
|
||||
$filterParams,
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$totalRecords = count($filterParams['conditions']['id']);
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $sentCount, $totalRecords, $newCount, $duration);
|
||||
|
||||
$this->warn("Sent notification for {$totalRecords} overdue NDT request(s) ({$newCount} new).");
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckPDFDocuments extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-pdf-documents';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check for missing PDF documents in WPS, NAKS Welder, Welding Equipment, and WPQR tables';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking for missing PDF documents...');
|
||||
|
||||
$totalNotifications = 0;
|
||||
$totalSent = 0;
|
||||
$totalRecords = 0;
|
||||
$totalNew = 0;
|
||||
|
||||
try {
|
||||
$pdfNotificationCodes = [
|
||||
'notification_wps_pdf_missing',
|
||||
'notification_naks_welder_cert_missing',
|
||||
'notification_welding_equipment_pdf_missing',
|
||||
'notification_wpq_followup_pdf_missing',
|
||||
];
|
||||
$result1 = $this->sendBatchNotification(
|
||||
'w_p_s',
|
||||
'download',
|
||||
'notification_wps_pdf_missing',
|
||||
'WPS PDF Document Missing',
|
||||
'download',
|
||||
[
|
||||
'message' => '%d WPS document(s) have no PDF file uploaded. Please add the missing PDF files.'
|
||||
],
|
||||
$totalSent,
|
||||
$totalRecords,
|
||||
$totalNew
|
||||
);
|
||||
if ($result1 > 0) {
|
||||
$totalNotifications += 1;
|
||||
}
|
||||
|
||||
$result2 = $this->sendBatchNotification(
|
||||
'naks_welders',
|
||||
'naks_certificate_no',
|
||||
'notification_naks_welder_cert_missing',
|
||||
'NAKS Welder Certificate Missing',
|
||||
'naks_certificate_no',
|
||||
[
|
||||
'message' => '%d NAKS welder(s) have no certificate number assigned. Please update each welder record.'
|
||||
],
|
||||
$totalSent,
|
||||
$totalRecords,
|
||||
$totalNew
|
||||
);
|
||||
if ($result2 > 0) {
|
||||
$totalNotifications += 1;
|
||||
}
|
||||
|
||||
$result3 = $this->sendBatchNotification(
|
||||
'welding_equipment',
|
||||
'download',
|
||||
'notification_welding_equipment_pdf_missing',
|
||||
'Welding Equipment PDF Missing',
|
||||
'download',
|
||||
[
|
||||
'message' => '%d welding equipment record(s) have no PDF document uploaded. Please add the missing files.',
|
||||
],
|
||||
$totalSent,
|
||||
$totalRecords,
|
||||
$totalNew
|
||||
);
|
||||
if ($result3 > 0) {
|
||||
$totalNotifications += 1;
|
||||
}
|
||||
|
||||
$result4 = $this->sendBatchNotification(
|
||||
'welder_tests',
|
||||
'download',
|
||||
'notification_wpq_followup_pdf_missing',
|
||||
'WPQR PDF Missing',
|
||||
'download',
|
||||
[
|
||||
'message' => '%d WPQR record(s) have no PDF document uploaded. Please add the missing files.',
|
||||
],
|
||||
$totalSent,
|
||||
$totalRecords,
|
||||
$totalNew
|
||||
);
|
||||
if ($result4 > 0) {
|
||||
$totalNotifications += 1;
|
||||
}
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $totalSent, $totalRecords, $totalNew, $duration);
|
||||
|
||||
$this->info("Total batch notifications sent: {$totalNotifications}");
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a batch notification for a table/column combination
|
||||
*/
|
||||
private function sendBatchNotification(
|
||||
string $table,
|
||||
string $column,
|
||||
string $notificationCode,
|
||||
string $title,
|
||||
string $columnKeyForFilter,
|
||||
array $options = [],
|
||||
&$totalSent = null,
|
||||
&$totalRecords = null,
|
||||
&$totalNew = null
|
||||
): int {
|
||||
$this->info("Checking {$title}...");
|
||||
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info("Skipping {$title}: Last notification sent less than 5 minutes ago.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$query = DB::table($table)->where(function($q) use ($column) {
|
||||
$q->whereNull($column)
|
||||
->orWhere($column, '');
|
||||
});
|
||||
|
||||
// If not first run, only check new/updated records
|
||||
if ($lastCheck !== null) {
|
||||
$query->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
|
||||
// Get already notified IDs
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->info("No new missing records found for {$title}.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare filter params
|
||||
$filterParams = [
|
||||
'table' => $table,
|
||||
'conditions' => [
|
||||
'or' => [
|
||||
[$columnKeyForFilter => null],
|
||||
[$columnKeyForFilter => '']
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
// If not first run, merge with existing IDs
|
||||
if ($lastCheck !== null) {
|
||||
$existingIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($existingIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($existingIds, $ids)));
|
||||
$newIds = array_diff($ids, $existingIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info("Skipping {$title}: No new records since last notification.");
|
||||
return 0;
|
||||
}
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info("Skipping {$title}: Same issue already notified and no new records.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode))) : count($ids);
|
||||
$totalRecords = count($filterParams['conditions']['id'] ?? $ids);
|
||||
$message = $options['message'] ?? '%d record(s) have missing data. Please review the filtered list.';
|
||||
$message = sprintf($message, $totalRecords);
|
||||
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null,
|
||||
$title,
|
||||
$filterParams,
|
||||
$totalRecords
|
||||
);
|
||||
|
||||
// Update totals if references provided
|
||||
if ($totalSent !== null) {
|
||||
$totalSent += $sentCount;
|
||||
$totalRecords += $totalRecords;
|
||||
$totalNew += $newCount;
|
||||
}
|
||||
|
||||
$this->info("Sent batch notification for {$title} ({$totalRecords} records, {$newCount} new).");
|
||||
return $sentCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckPaintSystem extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-paint-system-values';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check for Paint Systems with incomplete customer-agreed paint values';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking Paint Systems for incomplete customer-agreed values...');
|
||||
|
||||
$notificationCode = 'notification_paint_system_incomplete';
|
||||
|
||||
try {
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$query = DB::table('paint_systems')
|
||||
->where(function($q) {
|
||||
$q->whereNull('primer_coat_name_1')
|
||||
->orWhere('primer_coat_name_1', '')
|
||||
->orWhereNull('brand_name_1')
|
||||
->orWhere('brand_name_1', '')
|
||||
->orWhereNull('thickness_1')
|
||||
->orWhere('thickness_1', 0);
|
||||
})
|
||||
->whereExists(function($q) {
|
||||
$q->select(DB::raw(1))
|
||||
->from('line_lists')
|
||||
->whereColumn('line_lists.painting_cycle', 'paint_systems.paint_cycle');
|
||||
});
|
||||
|
||||
// If not first run, only check new/updated records
|
||||
if ($lastCheck !== null) {
|
||||
$query->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
|
||||
// Get already notified IDs
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->info('No new incomplete Paint Systems detected.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare filter params
|
||||
$filterParams = [
|
||||
'table' => 'paint_systems',
|
||||
'conditions' => []
|
||||
];
|
||||
|
||||
// If not first run, merge with existing IDs
|
||||
if ($lastCheck !== null) {
|
||||
$existingIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($existingIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($existingIds, $ids)));
|
||||
$newIds = array_diff($ids, $existingIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info('Skipping: No new records since last notification.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'No new records since last notification');
|
||||
return 0;
|
||||
}
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: Same issue already notified and no new records.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Same issue already notified and no new records');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode))) : count($ids);
|
||||
$title = 'Paint System Missing';
|
||||
$message = sprintf(
|
||||
'%d paint system(s) used by Line Lists have missing customer-agreed values. Please complete the paint system details.',
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$sentCount = sendBatchNotificationWithFilter(
|
||||
$notificationCode,
|
||||
$title,
|
||||
$message,
|
||||
$filterParams,
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$totalRecords = count($filterParams['conditions']['id']);
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $sentCount, $totalRecords, $newCount, $duration);
|
||||
|
||||
$this->info("Sent batch notification for incomplete Paint Systems ({$totalRecords} records, {$newCount} new).");
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckRepairLog extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-repair-log-new-joint';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check repair logs for missing new joint numbers and send notifications';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking repair logs for missing new joint numbers...');
|
||||
|
||||
$notificationCode = 'notification_repair_log_missing_new_joint';
|
||||
|
||||
try {
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
// Build query
|
||||
$query = DB::table('repair_logs')
|
||||
->where(function($q) {
|
||||
$q->whereNull('new_joint_no')
|
||||
->orWhere('new_joint_no', '');
|
||||
});
|
||||
|
||||
// If not first run, only check new records
|
||||
if ($lastCheck !== null) {
|
||||
$query->where('created_at', '>', $lastCheck);
|
||||
|
||||
// Get already notified IDs to prevent duplicates
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
// Get IDs of records that need notification
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->info('No new repair logs with missing new joint numbers found.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare filter parameters
|
||||
$filterParams = [
|
||||
'table' => 'repair_logs',
|
||||
'conditions' => [
|
||||
'or' => [
|
||||
['new_joint_no' => null],
|
||||
['new_joint_no' => '']
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
// If not first run, merge with existing IDs (additive approach)
|
||||
if ($lastCheck !== null) {
|
||||
$existingIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($existingIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($existingIds, $ids)));
|
||||
$newIds = array_diff($ids, $existingIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info('Skipping: No new records since last notification.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'No new records since last notification');
|
||||
return 0;
|
||||
}
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: Same issue already notified and no new records.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Same issue already notified and no new records');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$title = 'Repair Logs Missing New Joint Number';
|
||||
$newCount = $lastCheck !== null ? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode))) : count($ids);
|
||||
$message = sprintf(
|
||||
'%d repair log(s) have no new joint number assigned. Please update each record with the new joint number.',
|
||||
count($filterParams['conditions']['id'] ?? $ids)
|
||||
);
|
||||
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null,
|
||||
$title,
|
||||
$filterParams,
|
||||
count($filterParams['conditions']['id'] ?? $ids)
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$totalRecords = count($filterParams['conditions']['id'] ?? $ids);
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $sentCount, $totalRecords, $newCount, $duration);
|
||||
|
||||
$this->info("Sent {$sentCount} notification(s) covering {$totalRecords} repair logs ({$newCount} new).");
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class NotificationCheckRepairRate extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-daily-repair-rate';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check if daily repair rate exceeds 12% AND overall repair rate exceeds 5%';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking daily repair rate...');
|
||||
|
||||
$notificationCode = 'notification_daily_repair_rate_high';
|
||||
|
||||
try {
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$today = Carbon::today()->toDateString();
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
// If not first run, check if new repairs/welds since last check
|
||||
if ($lastCheck !== null) {
|
||||
$newRepairs = DB::table('repair_logs')
|
||||
->whereDate('repair_date', $today)
|
||||
->where('created_at', '>', $lastCheck)
|
||||
->count();
|
||||
|
||||
$newWelds = DB::table('weld_logs')
|
||||
->whereDate('welding_date', $today)
|
||||
->where('created_at', '>', $lastCheck)
|
||||
->count();
|
||||
|
||||
if ($newRepairs == 0 && $newWelds == 0) {
|
||||
$this->info('No new repairs or welds since last check. Skipping.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'No new repairs or welds since last check');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Count today's repairs
|
||||
$repairCount = DB::table('repair_logs')
|
||||
->whereDate('repair_date', $today)
|
||||
->count();
|
||||
|
||||
// Count today's welds
|
||||
$weldCount = DB::table('weld_logs')
|
||||
->whereDate('welding_date', $today)
|
||||
->count();
|
||||
|
||||
if ($weldCount == 0) {
|
||||
$this->info("No welding records for today. Skipping repair rate check.");
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'No welding records for today');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$repairRate = ($repairCount / $weldCount) * 100;
|
||||
$threshold = 12;
|
||||
|
||||
$this->info("Today's stats: {$repairCount} repairs / {$weldCount} welds = " . number_format($repairRate, 2) . "%");
|
||||
|
||||
if ($repairRate > $threshold) {
|
||||
// Check for duplicate (same day already notified)
|
||||
if ($lastCheck !== null && $lastCheck->isToday()) {
|
||||
$this->info('Skipping: Already notified for today.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Already notified for today');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$title = 'Daily Repair Rate Exceeds 12%';
|
||||
$message = sprintf(
|
||||
'Daily repair rate is %.2f%% (%d repairs out of %d welds), which exceeds the %d%% threshold. QC team attention required.',
|
||||
$repairRate,
|
||||
$repairCount,
|
||||
$weldCount,
|
||||
$threshold
|
||||
);
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'repair_logs',
|
||||
'conditions' => [
|
||||
'repair_date' => $today
|
||||
]
|
||||
];
|
||||
|
||||
$sentCount = sendNotification($notificationCode, $message, null, $title, $filterParams);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, $sentCount, 1, 1, $duration);
|
||||
|
||||
$this->warn("⚠️ HIGH REPAIR RATE: Notification sent to QC teams.");
|
||||
|
||||
// Check overall (project-wide) repair rate
|
||||
$this->checkOverallRepairRate();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->info("✓ Daily repair rate is within acceptable limits.");
|
||||
|
||||
// Check overall (project-wide) repair rate
|
||||
$this->checkOverallRepairRate();
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check overall repair rate for entire project
|
||||
*/
|
||||
private function checkOverallRepairRate()
|
||||
{
|
||||
$this->info('Checking overall project repair rate...');
|
||||
|
||||
$notificationCode = 'notification_overall_repair_rate_high';
|
||||
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping overall rate: Last notification sent less than 5 minutes ago.');
|
||||
return;
|
||||
}
|
||||
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
// If not first run, check if new repairs/welds since last check
|
||||
if ($lastCheck !== null) {
|
||||
$newRepairs = DB::table('repair_logs')
|
||||
->where('created_at', '>', $lastCheck)
|
||||
->count();
|
||||
|
||||
$newWelds = DB::table('weld_logs')
|
||||
->whereNotNull('welding_date')
|
||||
->where('welding_date', '!=', '')
|
||||
->where('welding_date', '!=', '0000-00-00')
|
||||
->where('created_at', '>', $lastCheck)
|
||||
->count();
|
||||
|
||||
if ($newRepairs == 0 && $newWelds == 0) {
|
||||
$this->info('No new repairs or welds since last check. Skipping overall rate.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Count total repairs (all time)
|
||||
$totalRepairs = DB::table('repair_logs')->count();
|
||||
|
||||
// Count total welds (all time)
|
||||
$totalWelds = DB::table('weld_logs')
|
||||
->whereNotNull('welding_date')
|
||||
->where('welding_date', '!=', '')
|
||||
->where('welding_date', '!=', '0000-00-00')
|
||||
->count();
|
||||
|
||||
if ($totalWelds == 0) {
|
||||
$this->info("No welding records found. Skipping overall repair rate check.");
|
||||
return;
|
||||
}
|
||||
|
||||
$overallRepairRate = ($totalRepairs / $totalWelds) * 100;
|
||||
$threshold = 5;
|
||||
|
||||
$this->info("Overall stats: {$totalRepairs} repairs / {$totalWelds} welds = " . number_format($overallRepairRate, 2) . "%");
|
||||
|
||||
if ($overallRepairRate > $threshold) {
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, ['table' => 'repair_logs', 'conditions' => []])) {
|
||||
$this->info('Skipping: Already notified for overall rate.');
|
||||
return;
|
||||
}
|
||||
|
||||
$title = 'Overall Repair Rate Exceeds 5%';
|
||||
$message = sprintf(
|
||||
'Overall project repair rate is %.2f%% (%d repairs out of %d welds), which exceeds the %d%% threshold. Quality improvement measures recommended.',
|
||||
$overallRepairRate,
|
||||
$totalRepairs,
|
||||
$totalWelds,
|
||||
$threshold
|
||||
);
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'repair_logs',
|
||||
'conditions' => []
|
||||
];
|
||||
|
||||
sendNotification($notificationCode, $message, null, $title, $filterParams);
|
||||
|
||||
$this->warn("⚠️ HIGH OVERALL REPAIR RATE: Notification sent.");
|
||||
} else {
|
||||
$this->info("✓ Overall repair rate is within acceptable limits.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckSupportLogWeldlog extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-support-log-weldlog';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check if Support Log weld count matches Weldlog count by line number (Process Piping Support only)';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking Support Log vs Weldlog count discrepancies by line...');
|
||||
|
||||
$notificationCode = 'notification_support_log_missing_in_weldlog';
|
||||
|
||||
try {
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
// Get line_number based weld count from supports (erection_type = 'weld')
|
||||
$supportWeldCountsQuery = DB::table('supports')
|
||||
->select('line_number', DB::raw('COUNT(*) as support_count'))
|
||||
->where('erection_type', 'weld')
|
||||
->whereNotNull('line_number')
|
||||
->where('line_number', '!=', '')
|
||||
->groupBy('line_number');
|
||||
|
||||
// If not first run, only check lines with new/updated supports or welds
|
||||
if ($lastCheck !== null) {
|
||||
$supportWeldCountsQuery->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
}
|
||||
|
||||
$supportWeldCounts = $supportWeldCountsQuery->get()->keyBy('line_number');
|
||||
|
||||
if ($supportWeldCounts->isEmpty()) {
|
||||
$this->info('No new welded supports found in Support Log.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->info("Found {$supportWeldCounts->count()} unique lines with welded supports");
|
||||
|
||||
// Get already notified line numbers
|
||||
$alreadyNotifiedLines = [];
|
||||
if ($lastCheck !== null) {
|
||||
$lastNotification = \App\Models\Notification::where('notification_code', $notificationCode)
|
||||
->whereNotNull('filter_params')
|
||||
->orderBy('created_at', 'DESC')
|
||||
->first();
|
||||
|
||||
if ($lastNotification && $lastNotification->filter_params) {
|
||||
$lastParams = is_array($lastNotification->filter_params)
|
||||
? $lastNotification->filter_params
|
||||
: json_decode($lastNotification->filter_params, true);
|
||||
if (isset($lastParams['conditions']['line_number'])) {
|
||||
$alreadyNotifiedLines = is_array($lastParams['conditions']['line_number'])
|
||||
? $lastParams['conditions']['line_number']
|
||||
: [$lastParams['conditions']['line_number']];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$discrepantLines = [];
|
||||
|
||||
foreach ($supportWeldCounts as $lineNum => $supportData) {
|
||||
// Skip if already notified (unless first run)
|
||||
if ($lastCheck !== null && in_array($lineNum, $alreadyNotifiedLines)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Count matching records in weld_logs (piping_type = 'Process Piping Support')
|
||||
$weldlogCount = DB::table('weld_logs')
|
||||
->where('line_number', $lineNum)
|
||||
->where('piping_type', 'Process Piping Support')
|
||||
->count();
|
||||
|
||||
// If counts don't match, add to discrepancy list
|
||||
if ($supportData->support_count != $weldlogCount) {
|
||||
$discrepantLines[] = [
|
||||
'line' => $lineNum,
|
||||
'support_count' => $supportData->support_count,
|
||||
'weldlog_count' => $weldlogCount
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($discrepantLines)) {
|
||||
$this->info('No new mismatched counts found.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lineNumbers = array_column($discrepantLines, 'line');
|
||||
|
||||
// Merge with existing lines if not first run
|
||||
if ($lastCheck !== null && !empty($alreadyNotifiedLines)) {
|
||||
$lineNumbers = array_values(array_unique(array_merge($alreadyNotifiedLines, $lineNumbers)));
|
||||
} else {
|
||||
$lineNumbers = array_values(array_unique($lineNumbers));
|
||||
}
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'supports',
|
||||
'conditions' => [
|
||||
'line_number' => $lineNumbers
|
||||
]
|
||||
];
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: Same issue already notified and no new records.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Same issue already notified and no new records');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($lineNumbers, $alreadyNotifiedLines)) : count($lineNumbers);
|
||||
$message = sprintf(
|
||||
'%d line(s) have mismatched support weld counts between Support Log (erection_type=weld) and Weldlog (piping_type=Process Piping Support). Please review and reconcile.',
|
||||
count($lineNumbers)
|
||||
);
|
||||
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null,
|
||||
'Support Log - Weldlog Count Mismatch',
|
||||
$filterParams,
|
||||
count($lineNumbers)
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$totalRecords = count($lineNumbers);
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $sentCount, $totalRecords, $newCount, $duration);
|
||||
|
||||
$this->info("Sent {$sentCount} batch notification(s) for {$totalRecords} line(s) with mismatched counts ({$newCount} new).");
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckTestLogPDF extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-test-log-pdf';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check NDT test logs with results but missing PDF reports (VT, RT, UT, PT, MT, HT, PMI, PWHT, Ferrite)';
|
||||
|
||||
/**
|
||||
* Test log tables with their result field names
|
||||
*/
|
||||
private $testLogs = [
|
||||
'v_t_logs' => ['result' => 'vt_result', 'name' => 'VT'],
|
||||
'radiographic_tests' => ['result' => 'rt_result', 'name' => 'RT'],
|
||||
'ultrasonic_tests' => ['result' => 'ut_result', 'name' => 'UT'],
|
||||
'p_t_logs' => ['result' => 'pt_result', 'name' => 'PT'],
|
||||
'magnetic_tests' => ['result' => 'mt_result', 'name' => 'MT'],
|
||||
'hardness_tests' => ['result' => 'ht_result', 'name' => 'HT'],
|
||||
'p_m_i_tests' => ['result' => 'pmi_result', 'name' => 'PMI'],
|
||||
'p_w_h_t_s' => ['result' => 'pwht_result', 'name' => 'PWHT'],
|
||||
'ferrits' => ['result' => 'ferrite_result', 'name' => 'Ferrite'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking test logs for missing PDF reports...');
|
||||
|
||||
$totalNotifications = 0;
|
||||
$totalSent = 0;
|
||||
$totalRecords = 0;
|
||||
$totalNew = 0;
|
||||
|
||||
try {
|
||||
foreach ($this->testLogs as $table => $config) {
|
||||
$result = $this->checkTestLog($table, $config['result'], $config['name'], $totalSent, $totalRecords, $totalNew);
|
||||
if ($result > 0) {
|
||||
$totalNotifications += 1; // Count as notification sent
|
||||
}
|
||||
}
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, $totalSent, $totalRecords, $totalNew, $duration);
|
||||
|
||||
$this->info("Total notifications sent: {$totalNotifications}");
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a specific test log table for missing PDFs
|
||||
*/
|
||||
private function checkTestLog(string $table, string $resultField, string $testName, &$totalSent, &$totalRecords, &$totalNew): int
|
||||
{
|
||||
$this->info("Checking {$testName} Log...");
|
||||
|
||||
$notificationCode = 'notification_test_log_pdf_missing_' . strtolower($testName);
|
||||
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info("Skipping {$testName}: Last notification sent less than 5 minutes ago.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$query = DB::table($table)
|
||||
->whereNotNull($resultField)
|
||||
->where($resultField, '!=', '')
|
||||
->where($resultField, '!=', 'Cancel')
|
||||
->where(function($q) {
|
||||
$q->whereNull('report_file')
|
||||
->orWhere('report_file', '');
|
||||
});
|
||||
|
||||
// If not first run, only check new/updated records
|
||||
if ($lastCheck !== null) {
|
||||
$query->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
|
||||
// Get already notified IDs
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->info("No new missing PDFs found in {$testName} Log.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare filter params
|
||||
$filterParams = [
|
||||
'table' => $table,
|
||||
'conditions' => []
|
||||
];
|
||||
|
||||
// If not first run, merge with existing IDs
|
||||
if ($lastCheck !== null) {
|
||||
$existingIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($existingIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($existingIds, $ids)));
|
||||
$newIds = array_diff($ids, $existingIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info("Skipping {$testName}: No new records since last notification.");
|
||||
return 0;
|
||||
}
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info("Skipping {$testName}: Same issue already notified and no new records.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode))) : count($ids);
|
||||
$recordCount = count($filterParams['conditions']['id']);
|
||||
$message = sprintf(
|
||||
'%d %s test record(s) have results (non-Cancel) but missing PDF report files. Please upload the missing reports.',
|
||||
$recordCount,
|
||||
$testName
|
||||
);
|
||||
|
||||
$sentCount = sendBatchNotificationWithFilter(
|
||||
$notificationCode,
|
||||
"{$testName} Test - PDF Report Missing",
|
||||
$message,
|
||||
$filterParams,
|
||||
$recordCount
|
||||
);
|
||||
|
||||
// Update totals for handle method
|
||||
$totalSent += $sentCount;
|
||||
$totalRecords += $recordCount;
|
||||
$totalNew += $newCount;
|
||||
|
||||
$this->info("Sent batch notification for {$testName} Log ({$recordCount} records, {$newCount} new).");
|
||||
return $sentCount > 0 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckWeldlogCertificate extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-weldlog-certificate';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check weldlog records for missing certificate PDFs after welding date is entered';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking weldlog records for missing certificates...');
|
||||
|
||||
$notificationCode = 'notification_weldlog_certificate_missing';
|
||||
|
||||
try {
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$query = DB::table('weld_logs')
|
||||
->whereNotNull('welding_date')
|
||||
->where('welding_date', '!=', '')
|
||||
->where(function($q) {
|
||||
$q->whereNull('certificate_number_of_1')
|
||||
->orWhere('certificate_number_of_1', '')
|
||||
->orWhereNull('certificate_number_of_2')
|
||||
->orWhere('certificate_number_of_2', '');
|
||||
});
|
||||
|
||||
// If not first run, only check new/updated records
|
||||
if ($lastCheck !== null) {
|
||||
$query->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
|
||||
// Get already notified IDs
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->info('No new missing certificates found in weldlog.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare filter params
|
||||
$filterParams = [
|
||||
'table' => 'weld_logs',
|
||||
'conditions' => []
|
||||
];
|
||||
|
||||
// If not first run, merge with existing IDs
|
||||
if ($lastCheck !== null) {
|
||||
$existingIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($existingIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($existingIds, $ids)));
|
||||
$newIds = array_diff($ids, $existingIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info('Skipping: No new records since last notification.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'No new records since last notification');
|
||||
return 0;
|
||||
}
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: Same issue already notified and no new records.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Same issue already notified and no new records');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode))) : count($ids);
|
||||
$title = 'Certificate Number 1-2 Missing';
|
||||
$message = sprintf(
|
||||
'%d weld log record(s) have missing certificate numbers. Please update certificate_number_of_1/2.',
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$sentCount = sendBatchNotificationWithFilter(
|
||||
$notificationCode,
|
||||
$title,
|
||||
$message,
|
||||
$filterParams,
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$totalRecords = count($filterParams['conditions']['id']);
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $sentCount, $totalRecords, $newCount, $duration);
|
||||
|
||||
$this->info("Sent batch notification for weldlog certificates ({$totalRecords} records, {$newCount} new).");
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckWeldlogTestDates extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-weldlog-test-dates';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check if NDT test dates are before welding_date in weld_logs';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking weldlog test dates vs welding dates...');
|
||||
|
||||
$notificationCode = 'notification_weldlog_test_date_invalid';
|
||||
|
||||
try {
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$invalidIds = [];
|
||||
|
||||
// Test date fields to check
|
||||
$testDateFields = [
|
||||
'rt_test_date' => 'RT',
|
||||
'date_of_vt' => 'VT',
|
||||
'ut_test_date' => 'UT',
|
||||
'pt_test_date' => 'PT',
|
||||
'mt_test_date' => 'MT',
|
||||
'ht_test_date' => 'HT',
|
||||
'pmi_test_date' => 'PMI',
|
||||
'pwht_date' => 'PWHT',
|
||||
'date_of_ferrite_check' => 'Ferrite',
|
||||
];
|
||||
|
||||
// Get already notified IDs
|
||||
$alreadyNotifiedIds = $lastCheck !== null ? getAlreadyNotifiedIds($notificationCode) : [];
|
||||
|
||||
foreach ($testDateFields as $testField => $testName) {
|
||||
$this->info("Checking {$testName} test dates...");
|
||||
|
||||
$query = DB::table('weld_logs')
|
||||
->whereNotNull('welding_date')
|
||||
->where('welding_date', '!=', '')
|
||||
->where('welding_date', '!=', '0000-00-00')
|
||||
->whereNotNull($testField)
|
||||
->where($testField, '!=', '')
|
||||
->where($testField, '!=', '0000-00-00')
|
||||
->whereColumn($testField, '<', 'welding_date');
|
||||
|
||||
// If not first run, only check new/updated records
|
||||
if ($lastCheck !== null) {
|
||||
$query->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (!empty($ids)) {
|
||||
$this->warn("Found " . count($ids) . " records with {$testName} test date before welding date");
|
||||
$invalidIds = array_merge($invalidIds, $ids);
|
||||
}
|
||||
}
|
||||
|
||||
$invalidIds = array_values(array_unique($invalidIds));
|
||||
|
||||
if (empty($invalidIds)) {
|
||||
$this->info('No new invalid test dates found. All test dates are >= welding_date.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare filter params
|
||||
$filterParams = [
|
||||
'table' => 'weld_logs',
|
||||
'conditions' => []
|
||||
];
|
||||
|
||||
// If not first run, merge with existing IDs
|
||||
if ($lastCheck !== null && !empty($alreadyNotifiedIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($alreadyNotifiedIds, $invalidIds)));
|
||||
$newIds = array_diff($invalidIds, $alreadyNotifiedIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info('Skipping: No new records since last notification.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'No new records since last notification');
|
||||
return 0;
|
||||
}
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $invalidIds;
|
||||
}
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: Same issue already notified and no new records.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Same issue already notified and no new records');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($invalidIds, $alreadyNotifiedIds)) : count($invalidIds);
|
||||
$message = sprintf(
|
||||
'%d weld log record(s) have NDT test dates that are before welding_date. Please review and correct (RT, VT, UT, PT, MT, HT, PWHT).',
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null,
|
||||
'Weldlog Test Date Before Welding Date',
|
||||
$filterParams,
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$totalRecords = count($filterParams['conditions']['id']);
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $sentCount, $totalRecords, $newCount, $duration);
|
||||
|
||||
$this->warn("Sent notification for {$totalRecords} record(s) with invalid test dates ({$newCount} new).");
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SummaryCalculation extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'summary:calculation';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Dispatch blade view caching for summary calculation';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Starting summary calculation cache dispatch...');
|
||||
|
||||
$cacheViews = [
|
||||
[
|
||||
'view' => 'admin.type.summary-nocache',
|
||||
'cache' => 'summary-dashboard'
|
||||
],
|
||||
[
|
||||
'view' => 'admin.dashboard.module.dashboard',
|
||||
'cache' => 'dashboard'
|
||||
],
|
||||
[
|
||||
'view' => 'admin-ajax.ndt-calculation-no-cache',
|
||||
'cache' => 'ndt-calculation'
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
if (function_exists('dispatchCacheBladeViews')) {
|
||||
dispatchCacheBladeViews($cacheViews);
|
||||
$this->info('Dispatched cache jobs successfully.');
|
||||
} else {
|
||||
$this->error('Helper function dispatchCacheBladeViews not found.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\WeldLog;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class VerifyMechanicalSummaries extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'mechanical:verify-summaries
|
||||
{--columns=nps_1,nps_2,outside_diameter_1,outside_diameter_2 : Comma separated numeric columns to inspect}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Compare SUM values for welded vs mechanical joints across selected columns';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$columns = collect(explode(',', $this->option('columns')))
|
||||
->map(fn ($column) => trim($column))
|
||||
->filter()
|
||||
->unique()
|
||||
->values();
|
||||
|
||||
if ($columns->isEmpty()) {
|
||||
$this->error('Please provide at least one column via --columns option.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
|
||||
foreach ($columns as $column) {
|
||||
if (!Schema::hasColumn('weld_logs', $column)) {
|
||||
$this->warn("Column '{$column}' does not exist on weld_logs table. Skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
$weldedSum = apply_welded_filter(WeldLog::query())->sum($column);
|
||||
$mechanicalSum = apply_mechanical_filter(WeldLog::query())->sum($column);
|
||||
$total = $weldedSum + $mechanicalSum;
|
||||
|
||||
$rows[] = [
|
||||
'Column' => $column,
|
||||
'SUM (Welded)' => $weldedSum,
|
||||
'SUM (Mechanical)' => $mechanicalSum,
|
||||
'SUM (Total)' => $total,
|
||||
];
|
||||
}
|
||||
|
||||
if (empty($rows)) {
|
||||
$this->warn('No valid columns were processed.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->table(
|
||||
['Column', 'SUM (Welded)', 'SUM (Mechanical)', 'SUM (Total)'],
|
||||
$rows
|
||||
);
|
||||
|
||||
$this->info('Verification completed. Ensure mechanical sums are zero to confirm filtering.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user