İlk temizlik tamamlandı bir önceki projeden

This commit is contained in:
Ümit Tunç
2026-04-28 21:14:25 +03:00
commit f80443aec0
10000 changed files with 959965 additions and 0 deletions
+80
View File
@@ -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;
}
}
+267
View File
@@ -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);
}
}
+34
View File
@@ -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());
}
}
}
}
+412
View File
@@ -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;
}
}