İlk temizlik tamamlandı bir önceki projeden
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CacheBladeViews extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'cache:blade-views';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Dispatch jobs to cache specified blade views';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Starting to dispatch cache blade views...');
|
||||
|
||||
$views = [
|
||||
[
|
||||
'view' => 'admin-ajax.register-no-cache',
|
||||
'cache' => 'register-creator'
|
||||
],
|
||||
[
|
||||
'view' => 'admin-ajax.request-ndt-no-cache',
|
||||
'cache' => 'request-ndt'
|
||||
],
|
||||
[
|
||||
'view' => 'admin-ajax.repair-log-no-cache',
|
||||
'cache' => 'repair-log'
|
||||
],
|
||||
[
|
||||
'view' => 'admin-ajax.ndt-order.order-list-no-cache',
|
||||
'cache' => 'ndt-order-list'
|
||||
],
|
||||
[
|
||||
'view' => 'admin-ajax.spool-area-release-no-cache',
|
||||
'cache' => 'spool-area-release'
|
||||
],
|
||||
[
|
||||
'view' => 'admin-ajax.spool-list-no-cache',
|
||||
'cache' => 'spool-list'
|
||||
],
|
||||
[
|
||||
'view' => 'admin.type.spool-release.spool-list-chart-no-cache',
|
||||
'cache' => 'spool-list-chart'
|
||||
],
|
||||
];
|
||||
|
||||
dispatchCacheBladeViews($views);
|
||||
|
||||
$this->info('Cache blade views dispatched successfully.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class CheckNDTReminders extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-ndt';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check NDT tests and send reminder notifications for overdue or pending tests';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
* Performance: Optimized with chunking and bulk operations
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Starting NDT reminder checks...');
|
||||
|
||||
$totalNotifications = 0;
|
||||
|
||||
// Check overdue NDT tests
|
||||
$totalNotifications += $this->checkOverdueTests();
|
||||
|
||||
// Check missing test reports
|
||||
$totalNotifications += $this->checkMissingReports();
|
||||
|
||||
// Check deadline approaching tests (within 3 days)
|
||||
$totalNotifications += $this->checkDeadlineApproaching();
|
||||
|
||||
// Check pending results
|
||||
$totalNotifications += $this->checkPendingResults();
|
||||
|
||||
$this->info("NDT reminder check completed. Sent {$totalNotifications} notifications.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for overdue NDT tests
|
||||
* Performance: Single query with multiple conditions
|
||||
*/
|
||||
private function checkOverdueTests()
|
||||
{
|
||||
$overdueThreshold = Carbon::now()->subDays(7); // 7 days overdue
|
||||
$nullableColumns = $this->availableColumns('weld_logs', [
|
||||
'rt_testing_date',
|
||||
'ut_testing_date',
|
||||
'pt_testing_date',
|
||||
'mt_testing_date'
|
||||
]);
|
||||
|
||||
if (empty($nullableColumns)) {
|
||||
$this->info('Skipping overdue check: weld_logs table lacks test date columns.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$ids = DB::table('weld_logs')
|
||||
->where('welding_date', '<=', $overdueThreshold)
|
||||
->where(function($query) use ($nullableColumns) {
|
||||
foreach ($nullableColumns as $column) {
|
||||
$query->orWhereNull($column);
|
||||
}
|
||||
})
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
return $this->sendWeldLogBatch(
|
||||
'notification_ndt_test_overdue',
|
||||
'NDT Test Overdue',
|
||||
'%d weld log(s) have welding dates older than 7 days without NDT testing.',
|
||||
$ids
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for missing test reports
|
||||
* Performance: Checks only records with test dates but no results
|
||||
*/
|
||||
private function checkMissingReports()
|
||||
{
|
||||
$checkDate = Carbon::now()->subDays(3); // 3 days after test date
|
||||
|
||||
$rtIds = [];
|
||||
if ($this->columnsAvailable('weld_logs', ['rt_testing_date', 'rt_result'])) {
|
||||
$rtIds = DB::table('weld_logs')
|
||||
->whereNotNull('rt_testing_date')
|
||||
->where('rt_testing_date', '<=', $checkDate)
|
||||
->where(function($query) {
|
||||
$query->whereNull('rt_result')
|
||||
->orWhere('rt_result', '');
|
||||
})
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
$utIds = [];
|
||||
if ($this->columnsAvailable('weld_logs', ['ut_testing_date', 'ut_result'])) {
|
||||
$utIds = DB::table('weld_logs')
|
||||
->whereNotNull('ut_testing_date')
|
||||
->where('ut_testing_date', '<=', $checkDate)
|
||||
->where(function($query) {
|
||||
$query->whereNull('ut_result')
|
||||
->orWhere('ut_result', '');
|
||||
})
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
$notifications = 0;
|
||||
|
||||
$notifications += $this->sendWeldLogBatch(
|
||||
'notification_ndt_report_missing',
|
||||
'RT Report Missing',
|
||||
'%d RT test(s) have results pending more than 3 days.',
|
||||
$rtIds
|
||||
);
|
||||
|
||||
$notifications += $this->sendWeldLogBatch(
|
||||
'notification_ndt_report_missing',
|
||||
'UT Report Missing',
|
||||
'%d UT test(s) have results pending more than 3 days.',
|
||||
$utIds
|
||||
);
|
||||
|
||||
return $notifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for tests with approaching deadlines
|
||||
* Performance: Single query with date calculation
|
||||
*/
|
||||
private function checkDeadlineApproaching()
|
||||
{
|
||||
$warnDate = Carbon::now()->subDays(4); // Welded 4 days ago, should be tested within 7 days
|
||||
$graceDate = Carbon::now()->subDays(7);
|
||||
|
||||
$dateColumns = array_filter($this->availableColumns('weld_logs', ['rt_testing_date', 'ut_testing_date']));
|
||||
if (empty($dateColumns)) {
|
||||
$this->info('Skipping deadline approaching check: weld_logs table lacks RT/UT testing date columns.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$ids = DB::table('weld_logs')
|
||||
->whereBetween('welding_date', [$graceDate, $warnDate]) // Fixed: older date first, newer date second
|
||||
->where(function($query) use ($dateColumns) {
|
||||
foreach ($dateColumns as $column) {
|
||||
$query->orWhereNull($column);
|
||||
}
|
||||
})
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
return $this->sendWeldLogBatch(
|
||||
'notification_test_deadline_approaching',
|
||||
'Test Deadline Approaching',
|
||||
'%d weld log(s) are approaching the 7-day NDT testing deadline.',
|
||||
$ids
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for pending test results
|
||||
* Performance: Optimized with specific conditions
|
||||
*/
|
||||
private function checkPendingResults()
|
||||
{
|
||||
$pendingThreshold = Carbon::now()->subDays(2);
|
||||
|
||||
$ids = [];
|
||||
$rtColumnsAvailable = $this->columnsAvailable('weld_logs', ['rt_testing_date', 'rt_result']);
|
||||
$utColumnsAvailable = $this->columnsAvailable('weld_logs', ['ut_testing_date', 'ut_result']);
|
||||
|
||||
if (!$rtColumnsAvailable && !$utColumnsAvailable) {
|
||||
$this->info('Skipping pending results check: weld_logs table lacks RT/UT result columns.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$query = DB::table('weld_logs')
|
||||
->where(function($query) use ($pendingThreshold) {
|
||||
if ($this->columnsAvailable('weld_logs', ['rt_testing_date', 'rt_result'])) {
|
||||
$query->orWhere(function($q) use ($pendingThreshold) {
|
||||
$q->whereNotNull('rt_testing_date')
|
||||
->where('rt_testing_date', '<=', $pendingThreshold)
|
||||
->where(function($sq) {
|
||||
$sq->whereNull('rt_result')
|
||||
->orWhere('rt_result', '');
|
||||
});
|
||||
});
|
||||
}
|
||||
if ($this->columnsAvailable('weld_logs', ['ut_testing_date', 'ut_result'])) {
|
||||
$query->orWhere(function($q) use ($pendingThreshold) {
|
||||
$q->whereNotNull('ut_testing_date')
|
||||
->where('ut_testing_date', '<=', $pendingThreshold)
|
||||
->where(function($sq) {
|
||||
$sq->whereNull('ut_result')
|
||||
->orWhere('ut_result', '');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
return $this->sendWeldLogBatch(
|
||||
'notification_ndt_result_pending',
|
||||
'NDT Result Pending',
|
||||
'%d weld log(s) have completed NDT tests but results are pending more than 2 days.',
|
||||
$ids
|
||||
);
|
||||
}
|
||||
|
||||
private function sendWeldLogBatch(string $notificationCode, string $title, string $messageTemplate, array $ids): int
|
||||
{
|
||||
$ids = array_values(array_unique(array_filter($ids)));
|
||||
if (empty($ids)) {
|
||||
$this->info("No {$title} notifications to send.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'weld_logs',
|
||||
'conditions' => [
|
||||
'id' => $ids
|
||||
]
|
||||
];
|
||||
|
||||
sendBatchNotificationWithFilter(
|
||||
$notificationCode,
|
||||
$title,
|
||||
$messageTemplate,
|
||||
$filterParams,
|
||||
count($ids)
|
||||
);
|
||||
|
||||
$this->info("Sent batch notification for {$title} (" . count($ids) . " joints).");
|
||||
return 1;
|
||||
}
|
||||
|
||||
private function availableColumns(string $table, array $columns): array
|
||||
{
|
||||
return array_values(array_filter($columns, function($column) use ($table) {
|
||||
return Schema::hasColumn($table, $column);
|
||||
}));
|
||||
}
|
||||
|
||||
private function columnsAvailable(string $table, array $columns): bool
|
||||
{
|
||||
return count($this->availableColumns($table, $columns)) === count($columns);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\DocumentManager\FolderCatalog;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class DocumentSyncFolders extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*/
|
||||
protected $signature = 'document:sync-folders {--dry-run : Show operations without creating directories} {--root= : Override the documents root path}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*/
|
||||
protected $description = 'Synchronise the Document Manager folder structure using the standard catalog definition.';
|
||||
|
||||
public function handle(FolderCatalog $catalog): int
|
||||
{
|
||||
$rootPath = $this->option('root') ?: storage_path('documents');
|
||||
$dryRun = (bool) $this->option('dry-run');
|
||||
|
||||
$this->info('Document Manager folder synchronisation');
|
||||
$this->line('Root path: ' . $rootPath);
|
||||
$this->line($dryRun ? 'Mode: Dry run (no directories will be created)' : 'Mode: Live');
|
||||
$this->newLine();
|
||||
|
||||
if (! File::isDirectory($rootPath)) {
|
||||
if ($dryRun) {
|
||||
$this->warn('Documents directory does not exist. (dry-run)');
|
||||
} else {
|
||||
File::ensureDirectoryExists($rootPath);
|
||||
$this->info('Created documents root directory.');
|
||||
}
|
||||
}
|
||||
|
||||
$results = $catalog->sync($rootPath, $dryRun);
|
||||
|
||||
if (! empty($results['created'])) {
|
||||
$title = $dryRun ? 'Directories that would be created' : 'Directories created';
|
||||
$this->section($title);
|
||||
$this->displayList($results['created']);
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
if (! empty($results['existing'])) {
|
||||
$this->section('Directories already present');
|
||||
$this->displayList($results['existing']);
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
// Detect directories that are not part of the catalog
|
||||
$extras = $catalog->detectExtra($rootPath);
|
||||
if ($extras->isNotEmpty()) {
|
||||
$this->section('Directories not defined in the catalog');
|
||||
$sorted = $extras->sort()->values();
|
||||
$this->displayList($sorted->all());
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
$this->info('Document Manager folder synchronisation completed.');
|
||||
|
||||
if ($dryRun) {
|
||||
$this->comment('Dry run mode - no changes were applied.');
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
protected function section(string $title): void
|
||||
{
|
||||
$this->line('<options=bold>' . $title . '</>');
|
||||
}
|
||||
|
||||
protected function displayList(array $items, int $limit = 20): void
|
||||
{
|
||||
$items = array_values(array_unique($items));
|
||||
$total = count($items);
|
||||
$display = array_slice($items, 0, $limit);
|
||||
|
||||
foreach ($display as $item) {
|
||||
$this->line(' - ' . $item);
|
||||
}
|
||||
|
||||
if ($total > $limit) {
|
||||
$this->line(' ... ' . ($total - $limit) . ' more');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class GenerateEndpointsDocs extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'docs:endpoints
|
||||
{--append : Append to existing OpenAPI spec}
|
||||
{--output= : Output file path}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generate API documentation for admin-ajax endpoints and append to Scribe OpenAPI spec';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Scanning admin-ajax endpoints...');
|
||||
|
||||
$endpoints = $this->scanEndpoints();
|
||||
$this->info("Found " . count($endpoints) . " endpoints");
|
||||
|
||||
if ($this->option('append')) {
|
||||
$this->appendToOpenApiSpec($endpoints);
|
||||
} else {
|
||||
$this->generateStandaloneSpec($endpoints);
|
||||
}
|
||||
|
||||
$this->info('✅ Endpoints documentation generated successfully!');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan admin-ajax directory for blade files
|
||||
*/
|
||||
protected function scanEndpoints(): array
|
||||
{
|
||||
$endpoints = [];
|
||||
$path = resource_path('views/admin-ajax');
|
||||
|
||||
if (!File::isDirectory($path)) {
|
||||
$this->error('admin-ajax directory not found');
|
||||
return $endpoints;
|
||||
}
|
||||
|
||||
// Scan files recursively
|
||||
$this->scanDirectory($path, '', $endpoints);
|
||||
|
||||
// Sort alphabetically
|
||||
usort($endpoints, fn($a, $b) => strcmp($a['name'], $b['name']));
|
||||
|
||||
return $endpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively scan directory for blade files
|
||||
* SECURITY: Only includes endpoints marked with @api-readonly annotation
|
||||
*/
|
||||
protected function scanDirectory(string $basePath, string $prefix, array &$endpoints): void
|
||||
{
|
||||
$items = File::files($basePath);
|
||||
|
||||
foreach ($items as $file) {
|
||||
$filename = $file->getFilename();
|
||||
|
||||
// Only process .blade.php files
|
||||
if (!Str::endsWith($filename, '.blade.php')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract endpoint name
|
||||
$name = str_replace('.blade.php', '', $filename);
|
||||
$fullName = $prefix ? "{$prefix}/{$name}" : $name;
|
||||
|
||||
// Analyze file for response type hints
|
||||
$content = File::get($file->getPathname());
|
||||
|
||||
// SECURITY: Only include endpoints marked as @api-readonly
|
||||
if (!Str::contains($content, '@api-readonly')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$responseType = $this->detectResponseType($content);
|
||||
$description = $this->extractDescription($content, $name);
|
||||
$category = $this->categorizeEndpoint($fullName);
|
||||
|
||||
$endpoints[] = [
|
||||
'name' => $fullName,
|
||||
'path' => str_replace(base_path(), '', $file->getPathname()),
|
||||
'response_type' => $responseType,
|
||||
'description' => $description,
|
||||
'category' => $category,
|
||||
];
|
||||
}
|
||||
|
||||
// Scan subdirectories
|
||||
$directories = File::directories($basePath);
|
||||
foreach ($directories as $dir) {
|
||||
$dirName = basename($dir);
|
||||
$newPrefix = $prefix ? "{$prefix}/{$dirName}" : $dirName;
|
||||
$this->scanDirectory($dir, $newPrefix, $endpoints);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect response type from blade content
|
||||
*/
|
||||
protected function detectResponseType(string $content): string
|
||||
{
|
||||
// Check for JSON indicators
|
||||
if (
|
||||
Str::contains($content, 'json_encode') ||
|
||||
Str::contains($content, 'json_encode_tr') ||
|
||||
Str::contains($content, 'response()->json') ||
|
||||
Str::contains($content, "'status' =>")
|
||||
) {
|
||||
return 'json';
|
||||
}
|
||||
|
||||
// Check for HTML indicators
|
||||
if (
|
||||
Str::contains($content, '<html') ||
|
||||
Str::contains($content, '<div') ||
|
||||
Str::contains($content, '@extends') ||
|
||||
Str::contains($content, '@include')
|
||||
) {
|
||||
return 'html';
|
||||
}
|
||||
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract description from blade file comments
|
||||
*/
|
||||
protected function extractDescription(string $content, string $name): string
|
||||
{
|
||||
// Try to find PHPDoc style comment at the start
|
||||
if (preg_match('/^<\?php\s*\/\*\*?\s*\n?\s*\*?\s*(.+?)(?:\n|\*\/)/s', $content, $matches)) {
|
||||
return trim($matches[1]);
|
||||
}
|
||||
|
||||
// Try to find HTML comment
|
||||
if (preg_match('/<!--\s*(.+?)\s*-->/s', $content, $matches)) {
|
||||
return trim($matches[1]);
|
||||
}
|
||||
|
||||
// Generate description from name
|
||||
return 'Execute ' . Str::title(str_replace(['-', '_'], ' ', $name)) . ' endpoint';
|
||||
}
|
||||
|
||||
/**
|
||||
* Categorize endpoint based on name
|
||||
*/
|
||||
protected function categorizeEndpoint(string $name): string
|
||||
{
|
||||
$prefixes = [
|
||||
'ndt-' => 'NDT Operations',
|
||||
'welder-' => 'Welder Management',
|
||||
'weld' => 'Welding',
|
||||
'register-' => 'Register Creator',
|
||||
'test-package' => 'Test Packages',
|
||||
'tp-' => 'Test Packages',
|
||||
'document-' => 'Document Management',
|
||||
'report-' => 'Reports',
|
||||
'summary' => 'Summaries',
|
||||
'excel-' => 'Excel Operations',
|
||||
'pdf' => 'PDF Operations',
|
||||
'user' => 'User Management',
|
||||
'permission' => 'Permissions',
|
||||
'content-' => 'Content Management',
|
||||
'spool-' => 'Spool Operations',
|
||||
'material-' => 'Materials',
|
||||
'paint-' => 'Paint Operations',
|
||||
'repair-' => 'Repair Operations',
|
||||
'rfi-' => 'RFI Operations',
|
||||
'punch-' => 'Punch Lists',
|
||||
'mto' => 'MTO Operations',
|
||||
'ai-' => 'AI Features',
|
||||
'cron-' => 'Cron Jobs',
|
||||
'si-' => 'System Info',
|
||||
];
|
||||
|
||||
$baseName = Str::contains($name, '/') ? Str::afterLast($name, '/') : $name;
|
||||
|
||||
foreach ($prefixes as $prefix => $category) {
|
||||
if (Str::startsWith($baseName, $prefix)) {
|
||||
return $category;
|
||||
}
|
||||
}
|
||||
|
||||
return 'General';
|
||||
}
|
||||
|
||||
/**
|
||||
* Append endpoints to existing OpenAPI spec
|
||||
*/
|
||||
protected function appendToOpenApiSpec(array $endpoints): void
|
||||
{
|
||||
$specPath = storage_path('app/scribe/openapi.yaml');
|
||||
|
||||
if (!File::exists($specPath)) {
|
||||
$this->warn('OpenAPI spec not found at ' . $specPath);
|
||||
$this->info('Run "php artisan scribe:generate" first, then run this command with --append');
|
||||
return;
|
||||
}
|
||||
|
||||
// Load existing spec
|
||||
$spec = yaml_parse_file($specPath);
|
||||
|
||||
if (!$spec) {
|
||||
$this->error('Failed to parse OpenAPI spec');
|
||||
return;
|
||||
}
|
||||
|
||||
// Add endpoint paths
|
||||
foreach ($endpoints as $endpoint) {
|
||||
$pathKey = '/api/endpoints/' . $endpoint['name'];
|
||||
|
||||
$spec['paths'][$pathKey] = $this->generatePathSpec($endpoint);
|
||||
}
|
||||
|
||||
// Add Endpoints tag if not exists
|
||||
$hasEndpointsTag = false;
|
||||
foreach ($spec['tags'] ?? [] as $tag) {
|
||||
if ($tag['name'] === 'Endpoints') {
|
||||
$hasEndpointsTag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasEndpointsTag) {
|
||||
$spec['tags'][] = [
|
||||
'name' => 'Endpoints',
|
||||
'description' => 'Admin-ajax endpoints served via API'
|
||||
];
|
||||
}
|
||||
|
||||
// Save updated spec
|
||||
$yaml = yaml_emit($spec, YAML_UTF8_ENCODING);
|
||||
File::put($specPath, $yaml);
|
||||
|
||||
$this->info('Updated OpenAPI spec at ' . $specPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate standalone endpoints spec file
|
||||
*/
|
||||
protected function generateStandaloneSpec(array $endpoints): void
|
||||
{
|
||||
$outputPath = $this->option('output') ?: storage_path('app/scribe/endpoints.json');
|
||||
|
||||
// Group endpoints by category
|
||||
$grouped = [];
|
||||
foreach ($endpoints as $endpoint) {
|
||||
$category = $endpoint['category'];
|
||||
if (!isset($grouped[$category])) {
|
||||
$grouped[$category] = [];
|
||||
}
|
||||
$grouped[$category][] = $endpoint;
|
||||
}
|
||||
|
||||
$output = [
|
||||
'generated_at' => now()->toIso8601String(),
|
||||
'total_endpoints' => count($endpoints),
|
||||
'categories' => array_keys($grouped),
|
||||
'endpoints' => $endpoints,
|
||||
'grouped' => $grouped,
|
||||
];
|
||||
|
||||
File::ensureDirectoryExists(dirname($outputPath));
|
||||
File::put($outputPath, json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
$this->info('Generated endpoints spec at ' . $outputPath);
|
||||
|
||||
// Generate Scribe response file for dynamic documentation
|
||||
$this->generateScribeResponseFile($endpoints);
|
||||
|
||||
// Also generate markdown documentation
|
||||
$this->generateMarkdownDocs($grouped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate response file for Scribe documentation
|
||||
*/
|
||||
protected function generateScribeResponseFile(array $endpoints): void
|
||||
{
|
||||
$responsePath = storage_path('app/scribe/endpoints-response.json');
|
||||
|
||||
// Format endpoints with url and methods for API response
|
||||
$formattedEndpoints = array_map(function ($endpoint) {
|
||||
return [
|
||||
'name' => $endpoint['name'],
|
||||
'url' => '/api/endpoints/' . $endpoint['name'],
|
||||
'methods' => ['GET', 'POST'],
|
||||
'response_type' => $endpoint['response_type'],
|
||||
'description' => $endpoint['description'],
|
||||
'category' => $endpoint['category'],
|
||||
];
|
||||
}, $endpoints);
|
||||
|
||||
$response = [
|
||||
'status' => 'success',
|
||||
'data' => [
|
||||
'endpoints' => $formattedEndpoints,
|
||||
'total' => count($formattedEndpoints)
|
||||
]
|
||||
];
|
||||
|
||||
File::ensureDirectoryExists(dirname($responsePath));
|
||||
File::put($responsePath, json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
$this->info('Generated Scribe response file at ' . $responsePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate OpenAPI path specification for an endpoint
|
||||
*/
|
||||
protected function generatePathSpec(array $endpoint): array
|
||||
{
|
||||
$responseSchema = $endpoint['response_type'] === 'json'
|
||||
? ['type' => 'object', 'properties' => ['status' => ['type' => 'string'], 'data' => ['type' => 'object']]]
|
||||
: ['type' => 'object', 'properties' => ['status' => ['type' => 'string'], 'html' => ['type' => 'string']]];
|
||||
|
||||
return [
|
||||
'get' => [
|
||||
'tags' => ['Endpoints'],
|
||||
'summary' => $endpoint['description'],
|
||||
'description' => "Endpoint: {$endpoint['name']}\nResponse Type: {$endpoint['response_type']}\nCategory: {$endpoint['category']}",
|
||||
'operationId' => 'endpoint_' . Str::slug($endpoint['name'], '_'),
|
||||
'security' => [['bearerAuth' => []]],
|
||||
'responses' => [
|
||||
'200' => [
|
||||
'description' => 'Successful response',
|
||||
'content' => [
|
||||
'application/json' => [
|
||||
'schema' => $responseSchema
|
||||
]
|
||||
]
|
||||
],
|
||||
'401' => [
|
||||
'description' => 'Unauthorized'
|
||||
],
|
||||
'404' => [
|
||||
'description' => 'Endpoint not found'
|
||||
]
|
||||
]
|
||||
],
|
||||
'post' => [
|
||||
'tags' => ['Endpoints'],
|
||||
'summary' => $endpoint['description'],
|
||||
'description' => "Endpoint: {$endpoint['name']}\nResponse Type: {$endpoint['response_type']}\nCategory: {$endpoint['category']}",
|
||||
'operationId' => 'endpoint_' . Str::slug($endpoint['name'], '_') . '_post',
|
||||
'security' => [['bearerAuth' => []]],
|
||||
'requestBody' => [
|
||||
'content' => [
|
||||
'application/json' => [
|
||||
'schema' => [
|
||||
'type' => 'object',
|
||||
'additionalProperties' => true
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'responses' => [
|
||||
'200' => [
|
||||
'description' => 'Successful response',
|
||||
'content' => [
|
||||
'application/json' => [
|
||||
'schema' => $responseSchema
|
||||
]
|
||||
]
|
||||
],
|
||||
'401' => [
|
||||
'description' => 'Unauthorized'
|
||||
],
|
||||
'404' => [
|
||||
'description' => 'Endpoint not found'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate markdown documentation
|
||||
*/
|
||||
protected function generateMarkdownDocs(array $grouped): void
|
||||
{
|
||||
$markdown = "# Available Endpoints\n\n";
|
||||
$markdown .= "This document lists all available admin-ajax endpoints accessible via the API.\n\n";
|
||||
$markdown .= "**Base URL:** `/api/endpoints/{endpoint}`\n\n";
|
||||
$markdown .= "**Authentication:** Bearer Token required\n\n";
|
||||
$markdown .= "---\n\n";
|
||||
|
||||
foreach ($grouped as $category => $endpoints) {
|
||||
$markdown .= "## {$category}\n\n";
|
||||
$markdown .= "| Endpoint | Description | Response Type |\n";
|
||||
$markdown .= "|----------|-------------|---------------|\n";
|
||||
|
||||
foreach ($endpoints as $endpoint) {
|
||||
$markdown .= "| `{$endpoint['name']}` | {$endpoint['description']} | {$endpoint['response_type']} |\n";
|
||||
}
|
||||
|
||||
$markdown .= "\n";
|
||||
}
|
||||
|
||||
$markdown .= "---\n\n";
|
||||
$markdown .= "## Usage Examples\n\n";
|
||||
$markdown .= "### cURL\n\n";
|
||||
$markdown .= "```bash\n";
|
||||
$markdown .= "curl -X GET \\\n";
|
||||
$markdown .= " '{base_url}/api/endpoints/users' \\\n";
|
||||
$markdown .= " -H 'Authorization: Bearer {your_token}' \\\n";
|
||||
$markdown .= " -H 'Accept: application/json'\n";
|
||||
$markdown .= "```\n\n";
|
||||
$markdown .= "### JavaScript\n\n";
|
||||
$markdown .= "```javascript\n";
|
||||
$markdown .= "const response = await fetch('/api/endpoints/users', {\n";
|
||||
$markdown .= " headers: {\n";
|
||||
$markdown .= " 'Authorization': 'Bearer ' + token,\n";
|
||||
$markdown .= " 'Accept': 'application/json'\n";
|
||||
$markdown .= " }\n";
|
||||
$markdown .= "});\n";
|
||||
$markdown .= "const data = await response.json();\n";
|
||||
$markdown .= "```\n";
|
||||
|
||||
$docsPath = resource_path('views/guide/api-endpoints.md');
|
||||
File::ensureDirectoryExists(dirname($docsPath));
|
||||
File::put($docsPath, $markdown);
|
||||
|
||||
$this->info('Generated markdown docs at ' . $docsPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class MigrateFromDate extends Command
|
||||
{
|
||||
protected $signature = 'migrate:from-date {date}';
|
||||
protected $description = 'Migrate migrations from a specific date';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$date = $this->argument('date');
|
||||
$migrationsPath = database_path('migrations');
|
||||
$migrations = File::files($migrationsPath);
|
||||
|
||||
foreach ($migrations as $migration) {
|
||||
$migrationDate = substr($migration->getFilename(), 0, 10); // YYYY_MM_DD kısmını alır
|
||||
|
||||
if ($migrationDate > $date) {
|
||||
Artisan::call('migrate', ['--path' => 'database/migrations/' . $migration->getFilename()]);
|
||||
$this->info('Migrated: ' . $migration->getFilename());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\NaksSyncService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* NAKS Multi-Module Synchronization Command
|
||||
*
|
||||
* Synchronizes NAKS data across all project sites for multiple modules:
|
||||
* - technology: NAKS Technology (naks_certificates)
|
||||
* - welder: NAKS Welder (naks_welders)
|
||||
* - consumables: NAKS Consumables (naks_consumables)
|
||||
* - expert: NAKS Expert (register_of_experts)
|
||||
* - equipment: NAKS Equipment (welding_equipment)
|
||||
*
|
||||
* Usage:
|
||||
* php artisan naks:sync # Sync all modules from all projects
|
||||
* php artisan naks:sync --module=welder # Sync only welder module
|
||||
* php artisan naks:sync --project=101 # Sync specific project only
|
||||
* php artisan naks:sync --dry-run # Test mode (no changes)
|
||||
* php artisan naks:sync --force # Ignore last sync ID, sync all
|
||||
* php artisan naks:sync --status # Show sync status only
|
||||
* php artisan naks:sync --reset # Reset sync state
|
||||
*/
|
||||
class NaksSync extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'naks:sync
|
||||
{--module=all : Module to sync (all, technology, welder, consumables, expert, equipment)}
|
||||
{--project= : Filter to sync only a specific project (by name or URL fragment)}
|
||||
{--dry-run : Run in test mode without making any changes}
|
||||
{--force : Ignore last synced ID and sync all records}
|
||||
{--status : Show sync status only, do not sync}
|
||||
{--reset : Reset sync state for all or specific module/project}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Synchronize NAKS data (certificates, welders, consumables, experts, equipment) from other project sites';
|
||||
|
||||
/**
|
||||
* The sync service instance.
|
||||
*/
|
||||
protected NaksSyncService $syncService;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*/
|
||||
public function __construct(NaksSyncService $syncService)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->syncService = $syncService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$module = $this->option('module') ?? 'all';
|
||||
$project = $this->option('project');
|
||||
$dryRun = (bool) $this->option('dry-run');
|
||||
$force = (bool) $this->option('force');
|
||||
$statusOnly = (bool) $this->option('status');
|
||||
$reset = (bool) $this->option('reset');
|
||||
|
||||
$this->info('╔════════════════════════════════════════════════════════════╗');
|
||||
$this->info('║ NAKS Multi-Module Cross-Site Synchronization ║');
|
||||
$this->info('╚════════════════════════════════════════════════════════════╝');
|
||||
$this->newLine();
|
||||
|
||||
// Show available modules
|
||||
$this->showAvailableModules();
|
||||
|
||||
// Handle status display
|
||||
if ($statusOnly) {
|
||||
return $this->showStatus($module);
|
||||
}
|
||||
|
||||
// Handle reset
|
||||
if ($reset) {
|
||||
return $this->resetSyncState($module, $project);
|
||||
}
|
||||
|
||||
// Validate module
|
||||
if ($module !== 'all' && !$this->syncService->getModuleConfig($module)) {
|
||||
$this->error("❌ Invalid module: {$module}");
|
||||
$this->line("Available modules: all, technology, welder, consumables, expert, equipment");
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
// Show mode information
|
||||
if ($dryRun) {
|
||||
$this->warn('⚠️ DRY RUN MODE - No changes will be made');
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
if ($force) {
|
||||
$this->warn('⚠️ FORCE MODE - Ignoring last sync ID, syncing all records');
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
$this->info("📦 Module(s): " . ($module === 'all' ? 'All modules' : $module));
|
||||
|
||||
if ($project) {
|
||||
$this->info("📌 Project filter: {$project}");
|
||||
}
|
||||
$this->newLine();
|
||||
|
||||
// Check credentials
|
||||
if (!env('SYNC_ADMIN_EMAIL') || !env('SYNC_ADMIN_PASSWORD')) {
|
||||
$this->error('❌ SYNC_ADMIN_EMAIL and SYNC_ADMIN_PASSWORD must be set in .env');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
// Show available projects
|
||||
$this->showAvailableProjects();
|
||||
|
||||
// Run sync
|
||||
$this->info('🔄 Starting synchronization...');
|
||||
$this->newLine();
|
||||
|
||||
$startTime = microtime(true);
|
||||
|
||||
try {
|
||||
$stats = $this->syncService->sync($module, $project, $dryRun, $force);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('❌ Sync failed: ' . $e->getMessage());
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$duration = round(microtime(true) - $startTime, 2);
|
||||
|
||||
// Display results
|
||||
$this->displayResults($stats, $duration, $dryRun);
|
||||
|
||||
// Return appropriate exit code
|
||||
if (!empty($stats['errors']) || !empty($stats['failed_projects'])) {
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show available modules
|
||||
*/
|
||||
protected function showAvailableModules(): void
|
||||
{
|
||||
$modules = $this->syncService->getModules();
|
||||
|
||||
$this->info('📦 Available Modules:');
|
||||
$tableData = [];
|
||||
foreach ($modules as $key => $config) {
|
||||
$tableData[] = [
|
||||
$key,
|
||||
$config['name'],
|
||||
$config['table'],
|
||||
implode(' + ', $config['unique_keys']),
|
||||
];
|
||||
}
|
||||
|
||||
$this->table(
|
||||
['Key', 'Name', 'Table', 'Unique Keys'],
|
||||
$tableData
|
||||
);
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show available projects
|
||||
*/
|
||||
protected function showAvailableProjects(): void
|
||||
{
|
||||
try {
|
||||
$projects = $this->syncService->getProjectUrls();
|
||||
|
||||
$this->info('📋 Available Projects:');
|
||||
$this->table(
|
||||
['Project Name', 'URL'],
|
||||
array_map(function ($p) {
|
||||
return [$p['project_name'] ?? 'Unknown', $p['url'] ?? ''];
|
||||
}, $projects)
|
||||
);
|
||||
$this->newLine();
|
||||
} catch (\Exception $e) {
|
||||
$this->warn('Could not fetch project list: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display sync results
|
||||
*/
|
||||
protected function displayResults(array $stats, float $duration, bool $dryRun): void
|
||||
{
|
||||
$this->newLine();
|
||||
$this->info('════════════════════════════════════════════════════════════');
|
||||
$this->info(' SYNC RESULTS');
|
||||
$this->info('════════════════════════════════════════════════════════════');
|
||||
$this->newLine();
|
||||
|
||||
// Project summary
|
||||
$this->table(
|
||||
['Metric', 'Value'],
|
||||
[
|
||||
['Total Projects', $stats['total_projects']],
|
||||
['Successful Projects', $stats['successful_projects']],
|
||||
['Failed Projects', count($stats['failed_projects'])],
|
||||
['Duration', "{$duration}s"],
|
||||
]
|
||||
);
|
||||
|
||||
// Module-specific results
|
||||
if (!empty($stats['modules'])) {
|
||||
$this->newLine();
|
||||
$this->info('📦 Module Results:');
|
||||
|
||||
$moduleData = [];
|
||||
$totalSynced = 0;
|
||||
$totalInserted = 0;
|
||||
$totalUpdated = 0;
|
||||
$totalSkipped = 0;
|
||||
$totalPdfs = 0;
|
||||
|
||||
foreach ($stats['modules'] as $key => $moduleStats) {
|
||||
$moduleData[] = [
|
||||
$moduleStats['name'],
|
||||
$moduleStats['total_synced'],
|
||||
$moduleStats['total_inserted'],
|
||||
$moduleStats['total_updated'],
|
||||
$moduleStats['total_skipped'],
|
||||
$moduleStats['total_pdfs_downloaded'],
|
||||
];
|
||||
|
||||
$totalSynced += $moduleStats['total_synced'];
|
||||
$totalInserted += $moduleStats['total_inserted'];
|
||||
$totalUpdated += $moduleStats['total_updated'];
|
||||
$totalSkipped += $moduleStats['total_skipped'];
|
||||
$totalPdfs += $moduleStats['total_pdfs_downloaded'];
|
||||
}
|
||||
|
||||
// Add totals row
|
||||
$moduleData[] = [
|
||||
'─────────',
|
||||
'─────',
|
||||
'────────',
|
||||
'───────',
|
||||
'───────',
|
||||
'────',
|
||||
];
|
||||
$moduleData[] = [
|
||||
'TOTAL',
|
||||
$totalSynced,
|
||||
$totalInserted,
|
||||
$totalUpdated,
|
||||
$totalSkipped,
|
||||
$totalPdfs,
|
||||
];
|
||||
|
||||
$this->table(
|
||||
['Module', 'Synced', 'Inserted', 'Updated', 'Skipped', 'PDFs'],
|
||||
$moduleData
|
||||
);
|
||||
}
|
||||
|
||||
// Show failed projects
|
||||
if (!empty($stats['failed_projects'])) {
|
||||
$this->newLine();
|
||||
$this->error('❌ Failed Projects:');
|
||||
foreach ($stats['failed_projects'] as $failed) {
|
||||
$this->line(" • {$failed['name']}: {$failed['error']}");
|
||||
}
|
||||
}
|
||||
|
||||
// Show errors
|
||||
if (!empty($stats['errors'])) {
|
||||
$this->newLine();
|
||||
$this->error('❌ Errors:');
|
||||
foreach ($stats['errors'] as $error) {
|
||||
$this->line(" • {$error}");
|
||||
}
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
|
||||
if ($dryRun) {
|
||||
$this->warn('ℹ️ This was a DRY RUN - no actual changes were made');
|
||||
} else {
|
||||
$hasSyncedRecords = false;
|
||||
foreach ($stats['modules'] as $moduleStats) {
|
||||
if ($moduleStats['total_synced'] > 0) {
|
||||
$hasSyncedRecords = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasSyncedRecords) {
|
||||
$this->info('✅ Synchronization completed successfully!');
|
||||
} else {
|
||||
$this->info('ℹ️ No new records to sync');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show sync status
|
||||
*/
|
||||
protected function showStatus(string $module): int
|
||||
{
|
||||
$this->info('📊 Sync Status:');
|
||||
$this->newLine();
|
||||
|
||||
$tableName = null;
|
||||
if ($module !== 'all') {
|
||||
$config = $this->syncService->getModuleConfig($module);
|
||||
if ($config) {
|
||||
$tableName = $config['table'];
|
||||
}
|
||||
}
|
||||
|
||||
$states = $this->syncService->getSyncStates($tableName);
|
||||
|
||||
if (empty($states)) {
|
||||
$this->warn('No sync history found. Run "php artisan naks:sync" to start syncing.');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$tableData = array_map(function ($state) {
|
||||
return [
|
||||
$state->table_name ?? 'Unknown',
|
||||
$state->source_url ?? 'Unknown',
|
||||
$state->last_synced_id ?? 0,
|
||||
$state->synced_count ?? 0,
|
||||
$state->last_synced_at ?? 'Never',
|
||||
];
|
||||
}, $states);
|
||||
|
||||
$this->table(
|
||||
['Table', 'Source URL', 'Last ID', 'Total Synced', 'Last Sync'],
|
||||
$tableData
|
||||
);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset sync state
|
||||
*/
|
||||
protected function resetSyncState(string $module, ?string $project): int
|
||||
{
|
||||
$tableName = null;
|
||||
if ($module !== 'all') {
|
||||
$config = $this->syncService->getModuleConfig($module);
|
||||
if ($config) {
|
||||
$tableName = $config['table'];
|
||||
} else {
|
||||
$this->error("Invalid module: {$module}");
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
$message = 'Reset sync state for ';
|
||||
$message .= $module === 'all' ? 'ALL modules' : "module: {$module}";
|
||||
if ($project) {
|
||||
$message .= " and project: {$project}";
|
||||
}
|
||||
$message .= '?';
|
||||
|
||||
if (!$this->confirm($message)) {
|
||||
$this->info('Operation cancelled.');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$sourceUrl = null;
|
||||
|
||||
if ($project) {
|
||||
// Find matching project URL
|
||||
try {
|
||||
$projects = $this->syncService->getProjectUrls();
|
||||
foreach ($projects as $p) {
|
||||
if (str_contains(strtolower($p['url']), strtolower($project)) ||
|
||||
str_contains(strtolower($p['project_name'] ?? ''), strtolower($project))) {
|
||||
$sourceUrl = $p['url'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$sourceUrl) {
|
||||
$this->error("Project not found: {$project}");
|
||||
return self::FAILURE;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->error('Could not fetch project list: ' . $e->getMessage());
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
$deleted = $this->syncService->resetSyncState($tableName, $sourceUrl);
|
||||
|
||||
$this->info("✅ Reset {$deleted} sync state record(s)");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class NotificationCheckAll extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-all';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Run all notification check commands at once (for initial trigger)';
|
||||
|
||||
/**
|
||||
* List of all notification commands to run
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $commands = [
|
||||
'notifications:check-deleted-joints',
|
||||
'notifications:check-repair-log-new-joint',
|
||||
'notifications:check-daily-repair-rate',
|
||||
'notifications:check-line-list-missing-data',
|
||||
'notifications:check-pdf-documents',
|
||||
'notifications:check-weldlog-certificate',
|
||||
'notifications:check-incoming-control-certificate',
|
||||
'notifications:check-nde-matrix-discrepancies',
|
||||
'notifications:check-ndt-calculation',
|
||||
'notifications:check-manage-ndt-unnecessary',
|
||||
'notifications:check-support-log-weldlog',
|
||||
'notifications:check-paint-system-values',
|
||||
'notifications:check-weldlog-test-dates',
|
||||
'notifications:check-ndt-request-overdue',
|
||||
'notifications:check-test-log-pdf',
|
||||
];
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Running all notification checks...');
|
||||
$this->info('This will trigger initial scan for all notification types.');
|
||||
$this->newLine();
|
||||
|
||||
$totalCommands = count($this->commands);
|
||||
$successCount = 0;
|
||||
$skipCount = 0;
|
||||
$errorCount = 0;
|
||||
|
||||
$bar = $this->output->createProgressBar($totalCommands);
|
||||
$bar->start();
|
||||
|
||||
foreach ($this->commands as $index => $command) {
|
||||
$bar->advance();
|
||||
|
||||
try {
|
||||
$exitCode = $this->call($command);
|
||||
|
||||
if ($exitCode === 0) {
|
||||
$successCount++;
|
||||
} else {
|
||||
$errorCount++;
|
||||
$this->newLine();
|
||||
$this->warn("Command '{$command}' returned exit code: {$exitCode}");
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$errorCount++;
|
||||
$this->newLine();
|
||||
$this->error("Error running '{$command}': " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
$this->newLine(2);
|
||||
|
||||
// Summary
|
||||
$this->info('=== Summary ===');
|
||||
$this->line("Total commands: {$totalCommands}");
|
||||
$this->line("Successful: {$successCount}");
|
||||
$this->line("Skipped (5 min check): {$skipCount}");
|
||||
$this->line("Errors: {$errorCount}");
|
||||
$this->newLine();
|
||||
|
||||
if ($errorCount > 0) {
|
||||
$this->warn('Some commands encountered errors. Check logs for details.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->info('All notification checks completed successfully!');
|
||||
$this->info('Scheduler will continue running checks every 5 minutes automatically.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckCalibrationDue extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-calibration-due';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check calibration logs for equipment with calibration due within 20 days or overdue and send notifications.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
logNotificationStart($commandName);
|
||||
$this->info('Checking Calibration Log for upcoming/overdue calibrations...');
|
||||
|
||||
$notificationCode = 'notification_calibration_due_soon';
|
||||
|
||||
try {
|
||||
// Run at most every 5 minutes
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: last calibration notification was sent recently.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$today = now()->startOfDay();
|
||||
$limitDate = $today->copy()->addDays(20);
|
||||
|
||||
$query = DB::table('calibration_logs')
|
||||
->whereNotNull('calibration_due_date')
|
||||
->whereDate('calibration_due_date', '<=', $limitDate->toDateString());
|
||||
|
||||
// Optional: skip records explicitly marked as completed
|
||||
$query->where(function ($q) {
|
||||
$q->whereNull('status')
|
||||
->orWhereNotIn('status', ['Completed']);
|
||||
});
|
||||
|
||||
if ($lastCheck !== null) {
|
||||
$query->where(function ($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->info('No new calibration records found for notification.');
|
||||
logNotificationComplete($commandName, 0, 0, 0, microtime(true) - $startTime);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'calibration_logs',
|
||||
'conditions' => [],
|
||||
];
|
||||
|
||||
if ($lastCheck !== null) {
|
||||
$existingIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($existingIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($existingIds, $ids)));
|
||||
$newIds = array_diff($ids, $existingIds);
|
||||
|
||||
if (empty($newIds)) {
|
||||
$this->info('Skipping: no new calibration records since last notification.');
|
||||
logNotificationComplete($commandName, 0, count($allIds), 0, microtime(true) - $startTime);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: duplicate calibration notification with same filter params.');
|
||||
logNotificationComplete($commandName, 0, count($filterParams['conditions']['id']), 0, microtime(true) - $startTime);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$recordCount = count($filterParams['conditions']['id']);
|
||||
$newCount = $lastCheck !== null
|
||||
? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode)))
|
||||
: $recordCount;
|
||||
|
||||
$messageTemplate = '%d equipment item(s) have calibration due within 20 days or are already overdue. Please review the Calibration Log module.';
|
||||
|
||||
$sent = sendBatchNotificationWithFilter(
|
||||
$notificationCode,
|
||||
'Calibration Due Soon',
|
||||
$messageTemplate,
|
||||
$filterParams,
|
||||
$recordCount
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, $sent, $recordCount, $newCount, $duration);
|
||||
|
||||
$this->info("Calibration notifications sent: {$sent} (records: {$recordCount}, new: {$newCount}).");
|
||||
|
||||
return 0;
|
||||
} catch (\Throwable $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error('Error: ' . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckDeletedJoints extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-deleted-joints';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check deleted joints for missing comments and send notifications';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking deleted joints for missing comments...');
|
||||
|
||||
$notificationCode = 'notification_deleted_joint_missing_comment';
|
||||
|
||||
try {
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
// Build query
|
||||
$query = DB::table('deleted_joints')
|
||||
->where(function($q) {
|
||||
$q->whereNull('comment')
|
||||
->orWhere('comment', '');
|
||||
});
|
||||
|
||||
// If not first run, only check new records
|
||||
if ($lastCheck !== null) {
|
||||
$query->where('created_at', '>', $lastCheck);
|
||||
|
||||
// Get already notified IDs to prevent duplicates
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
// If first run (lastCheck === null), check ALL records (no where clause added)
|
||||
|
||||
// Get IDs of records that need notification
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->info('No new deleted joints with missing comments found.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare filter parameters
|
||||
$filterParams = [
|
||||
'table' => 'deleted_joints',
|
||||
'conditions' => [
|
||||
'or' => [
|
||||
['comment' => null],
|
||||
['comment' => '']
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
// If not first run, merge with existing IDs (additive approach)
|
||||
if ($lastCheck !== null) {
|
||||
$existingIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($existingIds)) {
|
||||
// Merge new IDs with existing ones
|
||||
$allIds = array_values(array_unique(array_merge($existingIds, $ids)));
|
||||
|
||||
// Check if there are actually new IDs
|
||||
$newIds = array_diff($ids, $existingIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info('Skipping: No new records since last notification.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'No new records since last notification');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
} else {
|
||||
// First run: use all IDs
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
|
||||
// Check for duplicate (same issue already notified and no new records)
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: Same issue already notified and no new records.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Same issue already notified and no new records');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$title = 'Deleted Joints Missing Comments';
|
||||
$newCount = $lastCheck !== null ? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode))) : count($ids);
|
||||
$message = sprintf(
|
||||
'%d deleted joint(s) have no comment. Please add deletion reason for each joint.',
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
// Send notification with all IDs (existing + new)
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null, // Link will be generated automatically
|
||||
$title,
|
||||
$filterParams,
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$totalRecords = count($filterParams['conditions']['id']);
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $sentCount, $totalRecords, $newCount, $duration);
|
||||
|
||||
$this->info("Sent {$sentCount} notification(s) for {$totalRecords} deleted joints with missing comments ({$newCount} new).");
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class NotificationCheckIncomingControlCertificate extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-incoming-control-certificate';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check incoming control records for missing certificate numbers';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking incoming control records for missing certificates...');
|
||||
|
||||
try {
|
||||
if (!Schema::hasTable('incoming_controls')) {
|
||||
$this->info('incoming_controls table not found; skipping check.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'incoming_controls table not found');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$notificationCode = 'notification_incoming_control_cert_missing';
|
||||
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$query = DB::table('incoming_controls')
|
||||
->whereNotNull('created_at')
|
||||
->where(function($q) {
|
||||
$q->whereNull('certificate_no')
|
||||
->orWhere('certificate_no', '');
|
||||
});
|
||||
|
||||
// If not first run, only check new records
|
||||
if ($lastCheck !== null) {
|
||||
$query->where('created_at', '>', $lastCheck);
|
||||
|
||||
// Get already notified IDs
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->info('No new missing certificates found in incoming control.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare filter params
|
||||
$filterParams = [
|
||||
'table' => 'incoming_controls',
|
||||
'conditions' => []
|
||||
];
|
||||
|
||||
// If not first run, merge with existing IDs
|
||||
if ($lastCheck !== null) {
|
||||
$existingIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($existingIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($existingIds, $ids)));
|
||||
$newIds = array_diff($ids, $existingIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info('Skipping: No new records since last notification.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'No new records since last notification');
|
||||
return 0;
|
||||
}
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: Same issue already notified and no new records.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Same issue already notified and no new records');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode))) : count($ids);
|
||||
$title = 'Incoming Control Certificate Missing';
|
||||
$message = sprintf(
|
||||
'%d incoming control record(s) have empty certificate numbers. Please update the certificate information.',
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$sentCount = sendBatchNotificationWithFilter(
|
||||
$notificationCode,
|
||||
$title,
|
||||
$message,
|
||||
$filterParams,
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$totalRecords = count($filterParams['conditions']['id']);
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $sentCount, $totalRecords, $newCount, $duration);
|
||||
|
||||
$this->info("Sent batch notification for incoming control certificates ({$totalRecords} records, {$newCount} new).");
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckLineList extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-line-list-missing-data';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check line list for missing required data (category, NDT ratio, pressure, temperature, test type, test media) where weld logs exist';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking line list for missing required data...');
|
||||
|
||||
$notificationCode = 'notification_line_list_missing_data';
|
||||
|
||||
try {
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
// Get unique line numbers from weld_logs that have records
|
||||
$usedLinesQuery = DB::table('weld_logs')
|
||||
->select('line_number')
|
||||
->whereNotNull('line_number')
|
||||
->where('line_number', '!=', '')
|
||||
->distinct();
|
||||
|
||||
// If not first run, only check lines from new weld_logs
|
||||
if ($lastCheck !== null) {
|
||||
$usedLinesQuery->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
}
|
||||
|
||||
$usedLines = $usedLinesQuery->limit(100)->pluck('line_number');
|
||||
|
||||
if ($usedLines->isEmpty()) {
|
||||
$this->info('No weld log records found with line numbers.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->info("Found {$usedLines->count()} unique lines in weld logs. Checking line list...");
|
||||
|
||||
// Get already notified line numbers
|
||||
$alreadyNotifiedLines = [];
|
||||
if ($lastCheck !== null) {
|
||||
$lastNotification = \App\Models\Notification::where('notification_code', $notificationCode)
|
||||
->whereNotNull('filter_params')
|
||||
->orderBy('created_at', 'DESC')
|
||||
->first();
|
||||
|
||||
if ($lastNotification && $lastNotification->filter_params) {
|
||||
$lastParams = is_array($lastNotification->filter_params)
|
||||
? $lastNotification->filter_params
|
||||
: json_decode($lastNotification->filter_params, true);
|
||||
if (isset($lastParams['conditions']['line_no'])) {
|
||||
$alreadyNotifiedLines = is_array($lastParams['conditions']['line_no'])
|
||||
? $lastParams['conditions']['line_no']
|
||||
: [$lastParams['conditions']['line_no']];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$linesWithMissingData = [];
|
||||
|
||||
foreach ($usedLines as $lineNumber) {
|
||||
// Skip if already notified (unless first run)
|
||||
if ($lastCheck !== null && in_array($lineNumber, $alreadyNotifiedLines)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$linelist = DB::table('line_lists')
|
||||
->where('line_no', $lineNumber)
|
||||
->first();
|
||||
|
||||
if (!$linelist) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isMissing = empty($linelist->category) ||
|
||||
empty($linelist->ndt) ||
|
||||
(empty($linelist->working_pressure_mpa) && empty($linelist->design_pressure_mpa)) ||
|
||||
(empty($linelist->working_temperature) && empty($linelist->design_temperature)) ||
|
||||
empty($linelist->test_type) ||
|
||||
empty($linelist->test_media);
|
||||
|
||||
if ($isMissing) {
|
||||
$linesWithMissingData[] = $lineNumber;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($linesWithMissingData)) {
|
||||
$this->info('No new lines with missing data were found.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Merge with existing lines if not first run
|
||||
if ($lastCheck !== null && !empty($alreadyNotifiedLines)) {
|
||||
$linesWithMissingData = array_values(array_unique(array_merge($alreadyNotifiedLines, $linesWithMissingData)));
|
||||
} else {
|
||||
$linesWithMissingData = array_values(array_unique($linesWithMissingData));
|
||||
}
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'line_lists',
|
||||
'conditions' => [
|
||||
'line_no' => $linesWithMissingData
|
||||
]
|
||||
];
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: Same issue already notified and no new records.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Same issue already notified and no new records');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($linesWithMissingData, $alreadyNotifiedLines)) : count($linesWithMissingData);
|
||||
$message = sprintf(
|
||||
'%d line(s) referenced in weld logs have missing required data (category, NDT ratio, pressure, temperature, test type, or test media). Please update the Line List entries.',
|
||||
count($linesWithMissingData)
|
||||
);
|
||||
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null,
|
||||
'Line List Missing Required Data',
|
||||
$filterParams,
|
||||
count($linesWithMissingData)
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$totalRecords = count($linesWithMissingData);
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $sentCount, $totalRecords, $newCount, $duration);
|
||||
|
||||
$this->info("Sent {$sentCount} notification(s) for {$totalRecords} line(s) with missing data ({$newCount} new).");
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckManageNDT extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-manage-ndt-unnecessary';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check for unnecessary manual NDT requests where NDE Matrix has 0% ratio';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking for unnecessary manual NDT requests...');
|
||||
|
||||
$totalCount = 0;
|
||||
$totalSent = 0;
|
||||
$totalRecords = 0;
|
||||
$totalNew = 0;
|
||||
|
||||
try {
|
||||
// Check each NDT type
|
||||
$result1 = $this->checkTestType('RT', 'rt', 'rt_request_no');
|
||||
$totalCount += $result1;
|
||||
$result2 = $this->checkTestType('UT', 'ut', 'ut_request_no');
|
||||
$totalCount += $result2;
|
||||
$result3 = $this->checkTestType('PT', 'pt', 'pt_request_no');
|
||||
$totalCount += $result3;
|
||||
$result4 = $this->checkTestType('MT', 'mt', 'mt_request_no');
|
||||
$totalCount += $result4;
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, $totalSent, $totalRecords, $totalNew, $duration);
|
||||
|
||||
$this->info("Total notifications sent: {$totalCount}");
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for unnecessary requests for a specific test type
|
||||
*
|
||||
* @param string $testName Display name (RT, UT, PT, MT)
|
||||
* @param string $ndeField NDE Matrix field name (rt, ut, pt, mt)
|
||||
* @param string $requestField Weldlog request field name (rt_request_no, ut_request_no, etc.)
|
||||
* @return int
|
||||
*/
|
||||
private function checkTestType($testName, $ndeField, $requestField)
|
||||
{
|
||||
$this->info("Checking {$testName} unnecessary requests...");
|
||||
|
||||
$ids = apply_welded_filter(DB::table('weld_logs'))
|
||||
->leftJoin('nde_matrices', function($join) {
|
||||
$join->on('weld_logs.line_number', '=', 'nde_matrices.line')
|
||||
->on('weld_logs.type_of_joint', '=', 'nde_matrices.type_of_joint');
|
||||
})
|
||||
->whereNotNull('weld_logs.welding_date')
|
||||
->where('weld_logs.welding_date', '!=', '')
|
||||
->whereNotNull("weld_logs.{$requestField}")
|
||||
->where("weld_logs.{$requestField}", '!=', '')
|
||||
->where(function($query) use ($ndeField) {
|
||||
$query->whereNull("nde_matrices.{$ndeField}")
|
||||
->orWhere("nde_matrices.{$ndeField}", 0)
|
||||
->orWhere("nde_matrices.{$ndeField}", '0');
|
||||
})
|
||||
->pluck('weld_logs.id')
|
||||
->toArray();
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->info("No unnecessary {$testName} requests found.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$notificationCode = 'notification_manage_ndt_unnecessary';
|
||||
|
||||
// Skip if checked recently
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info("Skipping {$testName}: Last notification sent less than 5 minutes ago.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
// Filter IDs: only keep new ones if not first run
|
||||
if ($lastCheck !== null) {
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$newIds = array_diff($ids, $alreadyNotifiedIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info("Skipping {$testName}: No new records since last notification.");
|
||||
return 0;
|
||||
}
|
||||
$ids = array_values(array_unique(array_merge($alreadyNotifiedIds, $ids)));
|
||||
}
|
||||
}
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'weld_logs',
|
||||
'conditions' => [
|
||||
'id' => array_values(array_unique($ids))
|
||||
]
|
||||
];
|
||||
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info("Skipping {$testName}: Same issue already notified and no new records.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode))) : count($ids);
|
||||
$sentCount = sendBatchNotificationWithFilter(
|
||||
$notificationCode,
|
||||
"Unnecessary {$testName} Request",
|
||||
'%d joint(s) have unnecessary ' . $testName . ' test requests (NDE ratio 0% or missing).',
|
||||
$filterParams,
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$this->info("Sent batch notification for unnecessary {$testName} requests (" . count($filterParams['conditions']['id']) . " joints, {$newCount} new).");
|
||||
return $sentCount > 0 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,427 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckNDEMatrixDiscrepancies extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-nde-matrix-discrepancies';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check for discrepancies between NDE Matrix, Line List, and Weldlog';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking NDE Matrix discrepancies...');
|
||||
|
||||
$totalCount = 0;
|
||||
$totalSent = 0;
|
||||
$totalRecords = 0;
|
||||
$totalNew = 0;
|
||||
|
||||
try {
|
||||
// Check 1: NDE Matrix lines not in Weldlog
|
||||
$result1 = $this->checkNDEMatrixNotInWeldlog($totalSent, $totalRecords, $totalNew);
|
||||
if ($result1 > 0) {
|
||||
$totalCount += 1;
|
||||
}
|
||||
|
||||
// Check 2: Weldlog lines not in Line List
|
||||
$result2 = $this->checkWeldlogNotInLineList($totalSent, $totalRecords, $totalNew);
|
||||
if ($result2 > 0) {
|
||||
$totalCount += 1;
|
||||
}
|
||||
|
||||
// Check 3: Line List entries with missing NDT ratio
|
||||
$result3 = $this->checkLineListMissingNDT($totalSent, $totalRecords, $totalNew);
|
||||
if ($result3 > 0) {
|
||||
$totalCount += 1;
|
||||
}
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, $totalSent, $totalRecords, $totalNew, $duration);
|
||||
|
||||
$this->info("Total notifications sent: {$totalCount}");
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 1: NDE Matrix rows with empty NDT values (all 6 columns empty)
|
||||
*/
|
||||
private function checkNDEMatrixNotInWeldlog(&$totalSent = null, &$totalRecords = null, &$totalNew = null)
|
||||
{
|
||||
$this->info('Check 1: NDE Matrix rows with empty NDT values...');
|
||||
|
||||
$notificationCode = 'notification_nde_matrix_not_in_weldlog';
|
||||
|
||||
// Skip if checked recently
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping Check 1: Last notification sent less than 5 minutes ago.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$query = DB::table('nde_matrices')
|
||||
->select('id', 'line', 'project', 'type_of_joint')
|
||||
->whereNotNull('line')
|
||||
->where('line', '!=', '')
|
||||
->where(function($q) {
|
||||
$q->where(function($sq) {
|
||||
$sq->whereNull('rt')->orWhere('rt', '')->orWhere('rt', 0)->orWhere('rt', '0');
|
||||
})
|
||||
->where(function($sq) {
|
||||
$sq->whereNull('ut')->orWhere('ut', '')->orWhere('ut', 0)->orWhere('ut', '0');
|
||||
})
|
||||
->where(function($sq) {
|
||||
$sq->whereNull('pt')->orWhere('pt', '')->orWhere('pt', 0)->orWhere('pt', '0');
|
||||
})
|
||||
->where(function($sq) {
|
||||
$sq->whereNull('mt')->orWhere('mt', '')->orWhere('mt', 0)->orWhere('mt', '0');
|
||||
})
|
||||
->where(function($sq) {
|
||||
$sq->whereNull('pmi')->orWhere('pmi', '')->orWhere('pmi', 0)->orWhere('pmi', '0');
|
||||
})
|
||||
->where(function($sq) {
|
||||
$sq->whereNull('ht')->orWhere('ht', '')->orWhere('ht', 0)->orWhere('ht', '0');
|
||||
});
|
||||
});
|
||||
|
||||
if ($lastCheck !== null) {
|
||||
$query->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$emptyRows = $query->limit(50)->get();
|
||||
|
||||
if ($emptyRows->isEmpty()) {
|
||||
$this->info('No new empty NDE Matrix rows found.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$emptyIds = array_values(array_unique($emptyRows->pluck('id')->toArray()));
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'nde_matrices',
|
||||
'conditions' => []
|
||||
];
|
||||
|
||||
if ($lastCheck !== null) {
|
||||
$existingIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($existingIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($existingIds, $emptyIds)));
|
||||
$newIds = array_diff($emptyIds, $existingIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info('Skipping Check 1: No new records since last notification.');
|
||||
return 0;
|
||||
}
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $emptyIds;
|
||||
}
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $emptyIds;
|
||||
}
|
||||
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping Check 1: Same issue already notified and no new records.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($emptyIds, getAlreadyNotifiedIds($notificationCode))) : count($emptyIds);
|
||||
$totalRecords = count($filterParams['conditions']['id']);
|
||||
$message = sprintf(
|
||||
'%d NDE Matrix row(s) have empty NDT values (RT, UT, PT, MT, PMI, HT). Please assign NDT requirements.',
|
||||
$totalRecords
|
||||
);
|
||||
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null,
|
||||
'NDE Matrix Empty Row - NDT Assignment Required',
|
||||
$filterParams,
|
||||
$totalRecords
|
||||
);
|
||||
|
||||
// Update totals for handle method
|
||||
if ($totalSent !== null) {
|
||||
$totalSent += $sentCount;
|
||||
$totalRecords += $totalRecords;
|
||||
$totalNew += $newCount;
|
||||
}
|
||||
|
||||
$this->info("Sent {$sentCount} batch notification(s) for empty NDE Matrix rows ({$totalRecords} records, {$newCount} new).");
|
||||
return $sentCount > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 2: Lines in Weldlog but not in Line List
|
||||
*/
|
||||
private function checkWeldlogNotInLineList(&$totalSent = null, &$totalRecords = null, &$totalNew = null)
|
||||
{
|
||||
$this->info('Check 2: Weldlog lines not found in Line List...');
|
||||
|
||||
$notificationCode = 'notification_weldlog_not_in_line_list';
|
||||
|
||||
// Skip if checked recently
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping Check 2: Last notification sent less than 5 minutes ago.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$weldlogLinesQuery = DB::table('weld_logs')
|
||||
->select('line_number', 'project', 'iso_number')
|
||||
->whereNotNull('line_number')
|
||||
->where('line_number', '!=', '')
|
||||
->whereNotNull('welding_date')
|
||||
->distinct();
|
||||
|
||||
if ($lastCheck !== null) {
|
||||
$weldlogLinesQuery->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
}
|
||||
|
||||
$weldlogLines = $weldlogLinesQuery->limit(50)->get();
|
||||
|
||||
// Get already notified line numbers
|
||||
$alreadyNotifiedLines = [];
|
||||
if ($lastCheck !== null) {
|
||||
$lastNotification = \App\Models\Notification::where('notification_code', $notificationCode)
|
||||
->whereNotNull('filter_params')
|
||||
->orderBy('created_at', 'DESC')
|
||||
->first();
|
||||
|
||||
if ($lastNotification && $lastNotification->filter_params) {
|
||||
$lastParams = is_array($lastNotification->filter_params)
|
||||
? $lastNotification->filter_params
|
||||
: json_decode($lastNotification->filter_params, true);
|
||||
if (isset($lastParams['conditions']['line_number'])) {
|
||||
$alreadyNotifiedLines = is_array($lastParams['conditions']['line_number'])
|
||||
? $lastParams['conditions']['line_number']
|
||||
: [$lastParams['conditions']['line_number']];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$missingLines = [];
|
||||
|
||||
foreach ($weldlogLines as $weldLine) {
|
||||
if ($lastCheck !== null && in_array($weldLine->line_number, $alreadyNotifiedLines)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$existsInLineList = DB::table('line_lists')
|
||||
->where('line_no', $weldLine->line_number)
|
||||
->exists();
|
||||
|
||||
if (!$existsInLineList) {
|
||||
$missingLines[] = $weldLine->line_number;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($missingLines)) {
|
||||
$this->info('No new discrepancies found for Check 2.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($lastCheck !== null && !empty($alreadyNotifiedLines)) {
|
||||
$missingLines = array_values(array_unique(array_merge($alreadyNotifiedLines, $missingLines)));
|
||||
} else {
|
||||
$missingLines = array_values(array_unique($missingLines));
|
||||
}
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'weld_logs',
|
||||
'conditions' => [
|
||||
'line_number' => $missingLines
|
||||
]
|
||||
];
|
||||
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping Check 2: Same issue already notified and no new records.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($missingLines, $alreadyNotifiedLines)) : count($missingLines);
|
||||
$totalRecords = count($missingLines);
|
||||
$message = sprintf(
|
||||
'%d weld log line(s) could not be matched in the Line List. Please review and add the missing line records.',
|
||||
$totalRecords
|
||||
);
|
||||
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null,
|
||||
'Weldlog Not in Line List',
|
||||
$filterParams,
|
||||
$totalRecords
|
||||
);
|
||||
|
||||
// Update totals for handle method
|
||||
if ($totalSent !== null) {
|
||||
$totalSent += $sentCount;
|
||||
$totalRecords += $totalRecords;
|
||||
$totalNew += $newCount;
|
||||
}
|
||||
|
||||
$this->info("Sent {$sentCount} batch notification(s) for Check 2 ({$totalRecords} lines, {$newCount} new).");
|
||||
return $sentCount > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 3: Line List entries with missing NDT ratio
|
||||
*/
|
||||
private function checkLineListMissingNDT(&$totalSent = null, &$totalRecords = null, &$totalNew = null)
|
||||
{
|
||||
$this->info('Check 3: Line List entries missing NDT ratio...');
|
||||
|
||||
$notificationCode = 'notification_line_list_ndt_missing';
|
||||
|
||||
// Skip if checked recently
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping Check 3: Last notification sent less than 5 minutes ago.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$query = DB::table('line_lists')
|
||||
->select('line_lists.id', 'line_lists.line_no', 'line_lists.ndt')
|
||||
->join('weld_logs', 'weld_logs.line_number', '=', 'line_lists.line_no')
|
||||
->where(function($q) {
|
||||
$q->whereNull('line_lists.ndt')
|
||||
->orWhere('line_lists.ndt', '')
|
||||
->orWhere('line_lists.ndt', '0');
|
||||
})
|
||||
->whereNotNull('weld_logs.welding_date')
|
||||
->distinct();
|
||||
|
||||
if ($lastCheck !== null) {
|
||||
$query->where(function($q) use ($lastCheck) {
|
||||
$q->where('line_lists.created_at', '>', $lastCheck)
|
||||
->orWhere('line_lists.updated_at', '>', $lastCheck)
|
||||
->orWhere('weld_logs.created_at', '>', $lastCheck)
|
||||
->orWhere('weld_logs.updated_at', '>', $lastCheck);
|
||||
});
|
||||
}
|
||||
|
||||
$missingNDT = $query->limit(50)->get();
|
||||
|
||||
if ($missingNDT->isEmpty()) {
|
||||
$this->info('No new discrepancies found for Check 3.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lineNumbers = array_values(array_unique($missingNDT->pluck('line_no')->toArray()));
|
||||
|
||||
// Get already notified line numbers
|
||||
$alreadyNotifiedLines = [];
|
||||
if ($lastCheck !== null) {
|
||||
$lastNotification = \App\Models\Notification::where('notification_code', $notificationCode)
|
||||
->whereNotNull('filter_params')
|
||||
->orderBy('created_at', 'DESC')
|
||||
->first();
|
||||
|
||||
if ($lastNotification && $lastNotification->filter_params) {
|
||||
$lastParams = is_array($lastNotification->filter_params)
|
||||
? $lastNotification->filter_params
|
||||
: json_decode($lastNotification->filter_params, true);
|
||||
if (isset($lastParams['conditions']['line_no'])) {
|
||||
$alreadyNotifiedLines = is_array($lastParams['conditions']['line_no'])
|
||||
? $lastParams['conditions']['line_no']
|
||||
: [$lastParams['conditions']['line_no']];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($lastCheck !== null && !empty($alreadyNotifiedLines)) {
|
||||
$lineNumbers = array_values(array_unique(array_merge($alreadyNotifiedLines, $lineNumbers)));
|
||||
}
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'line_lists',
|
||||
'conditions' => [
|
||||
'line_no' => $lineNumbers
|
||||
]
|
||||
];
|
||||
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping Check 3: Same issue already notified and no new records.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($lineNumbers, $alreadyNotifiedLines)) : count($lineNumbers);
|
||||
$totalRecords = count($lineNumbers);
|
||||
$message = sprintf(
|
||||
'%d Line List entry/entries used in Weldlog are missing NDT ratio. Please update the Line List records.',
|
||||
$totalRecords
|
||||
);
|
||||
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null,
|
||||
'Line List NDT Missing',
|
||||
$filterParams,
|
||||
$totalRecords
|
||||
);
|
||||
|
||||
// Update totals for handle method
|
||||
if ($totalSent !== null) {
|
||||
$totalSent += $sentCount;
|
||||
$totalRecords += $totalRecords;
|
||||
$totalNew += $newCount;
|
||||
}
|
||||
|
||||
$this->info("Sent {$sentCount} batch notification(s) for Check 3 ({$totalRecords} lines, {$newCount} new).");
|
||||
return $sentCount > 0 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckNDTCalculation extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-ndt-calculation';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check NDT calculation warnings based on material grades, thicknesses, and joint types';
|
||||
|
||||
/**
|
||||
* Stainless steel material groups
|
||||
*/
|
||||
private $ssGroups = ['11', '8', '9', 'M11', 'M111', 'M9'];
|
||||
|
||||
/**
|
||||
* Carbon steel material group
|
||||
*/
|
||||
private $csGroup = ['1', 'M01'];
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking NDT calculation warnings...');
|
||||
|
||||
$totalCount = 0;
|
||||
$totalSent = 0;
|
||||
$totalRecords = 0;
|
||||
$totalNew = 0;
|
||||
|
||||
try {
|
||||
// Check 1: PMI test missing for SS or CS+SS
|
||||
$result1 = $this->checkPMIMissing($totalSent, $totalRecords, $totalNew);
|
||||
if ($result1 > 0) {
|
||||
$totalCount += 1;
|
||||
}
|
||||
|
||||
// Check 2: FN test missing for SS with temperature > 350
|
||||
$result2 = $this->checkFNMissing($totalSent, $totalRecords, $totalNew);
|
||||
if ($result2 > 0) {
|
||||
$totalCount += 1;
|
||||
}
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, $totalSent, $totalRecords, $totalNew, $duration);
|
||||
|
||||
$this->info("Total notifications sent: {$totalCount}");
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 1: PMI test missing for SS or CS+SS materials
|
||||
*/
|
||||
private function checkPMIMissing(&$totalSent = null, &$totalRecords = null, &$totalNew = null)
|
||||
{
|
||||
$this->info('Check 1: PMI test missing for SS or CS+SS materials...');
|
||||
|
||||
$ids = [];
|
||||
|
||||
$weldLogs = DB::table('weld_logs')
|
||||
->whereNotNull('welding_date')
|
||||
->where('welding_date', '!=', '')
|
||||
->where(function($query) {
|
||||
$query->whereIn('ru_material_group_1', array_merge($this->ssGroups, $this->csGroup))
|
||||
->orWhereIn('ru_material_group_2', array_merge($this->ssGroups, $this->csGroup));
|
||||
})
|
||||
->where(function($query) {
|
||||
$query->whereNull('pmi_request_no')
|
||||
->orWhere('pmi_request_no', '');
|
||||
})
|
||||
->select('id', 'ru_material_group_1', 'ru_material_group_2')
|
||||
->get();
|
||||
|
||||
foreach ($weldLogs as $weld) {
|
||||
$mat1 = $weld->ru_material_group_1 ?? '';
|
||||
$mat2 = $weld->ru_material_group_2 ?? '';
|
||||
|
||||
$isSS = in_array($mat1, $this->ssGroups) || in_array($mat2, $this->ssGroups);
|
||||
$isCS_SS = (in_array($mat1, $this->csGroup) && in_array($mat2, $this->ssGroups)) ||
|
||||
(in_array($mat1, $this->ssGroups) && in_array($mat2, $this->csGroup));
|
||||
|
||||
if ($isSS || $isCS_SS) {
|
||||
$ids[] = $weld->id;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->sendWeldLogBatch(
|
||||
'notification_ndt_pmi_missing',
|
||||
'PMI Test Missing',
|
||||
'%d joint(s) have SS/CS+SS materials but PMI test requests are missing.',
|
||||
$ids,
|
||||
$totalSent,
|
||||
$totalRecords,
|
||||
$totalNew
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check 2: FN (Ferrite) test missing for SS materials with operating temperature > 350°C
|
||||
*/
|
||||
private function checkFNMissing(&$totalSent = null, &$totalRecords = null, &$totalNew = null)
|
||||
{
|
||||
$this->info('Check 2: FN test missing for SS materials with temperature > 350...');
|
||||
|
||||
$ids = DB::table('weld_logs')
|
||||
->whereNotNull('welding_date')
|
||||
->where('welding_date', '!=', '')
|
||||
->where(function($query) {
|
||||
$query->whereIn('ru_material_group_1', $this->ssGroups)
|
||||
->orWhereIn('ru_material_group_2', $this->ssGroups);
|
||||
})
|
||||
->where(function($query) {
|
||||
// Temperature control: operating_temperature_s > 350
|
||||
$query->whereRaw('CAST(operating_temperature_s AS DECIMAL(10,2)) > 350');
|
||||
})
|
||||
->where(function($query) {
|
||||
$query->whereNull('ferrite_request_no')
|
||||
->orWhere('ferrite_request_no', '');
|
||||
})
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
return $this->sendWeldLogBatch(
|
||||
'notification_ndt_fn_missing',
|
||||
'FN Test Missing',
|
||||
'%d joint(s) with SS materials and operating temperature > 350°C are missing FN (Ferrite) test requests.',
|
||||
$ids,
|
||||
$totalSent,
|
||||
$totalRecords,
|
||||
$totalNew
|
||||
);
|
||||
}
|
||||
|
||||
private function sendWeldLogBatch(string $notificationCode, string $title, string $messageTemplate, array $ids, &$totalSent = null, &$totalRecords = null, &$totalNew = null): int
|
||||
{
|
||||
$ids = array_values(array_unique(array_filter($ids)));
|
||||
if (empty($ids)) {
|
||||
$this->info("No {$title} records found.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Skip if checked recently
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info("Skipping {$title}: Last notification sent less than 5 minutes ago.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
// Filter IDs: only keep new ones if not first run
|
||||
if ($lastCheck !== null) {
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$newIds = array_diff($ids, $alreadyNotifiedIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info("Skipping {$title}: No new records since last notification.");
|
||||
return 0;
|
||||
}
|
||||
$ids = array_values(array_unique(array_merge($alreadyNotifiedIds, $ids)));
|
||||
}
|
||||
}
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'weld_logs',
|
||||
'conditions' => [
|
||||
'id' => $ids
|
||||
]
|
||||
];
|
||||
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info("Skipping {$title}: Same issue already notified and no new records.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode))) : count($ids);
|
||||
$totalRecords = count($ids);
|
||||
$sentCount = sendBatchNotificationWithFilter(
|
||||
$notificationCode,
|
||||
$title,
|
||||
$messageTemplate,
|
||||
$filterParams,
|
||||
$totalRecords
|
||||
);
|
||||
|
||||
// Update totals for handle method
|
||||
if ($totalSent !== null) {
|
||||
$totalSent += $sentCount;
|
||||
$totalRecords += $totalRecords;
|
||||
$totalNew += $newCount;
|
||||
}
|
||||
|
||||
$this->info("Sent batch notification for {$title} ({$totalRecords} joints, {$newCount} new).");
|
||||
return $sentCount > 0 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class NotificationCheckNDTRequestOverdue extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-ndt-request-overdue';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check if NDT request dates are older than 10 days without test results';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking NDT request dates older than 10 days...');
|
||||
|
||||
$notificationCode = 'notification_ndt_request_overdue';
|
||||
|
||||
try {
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$overdueThreshold = Carbon::now()->subDays(10)->toDateString();
|
||||
|
||||
$invalidIds = [];
|
||||
|
||||
// Get already notified IDs
|
||||
$alreadyNotifiedIds = $lastCheck !== null ? getAlreadyNotifiedIds($notificationCode) : [];
|
||||
|
||||
// Request date and result fields to check
|
||||
$requestChecks = [
|
||||
['request' => 'rt_request_date', 'result' => 'rt_result', 'name' => 'RT'],
|
||||
['request' => 'vt_request_date', 'result' => 'vt_result', 'name' => 'VT'],
|
||||
['request' => 'ut_request_date', 'result' => 'ut_result', 'name' => 'UT'],
|
||||
['request' => 'pt_request_date', 'result' => 'pt_result', 'name' => 'PT'],
|
||||
['request' => 'mt_request_date', 'result' => 'mt_result', 'name' => 'MT'],
|
||||
['request' => 'ht_request_date', 'result' => 'ht_result', 'name' => 'HT'],
|
||||
['request' => 'pmi_request_date', 'result' => 'pmi_result', 'name' => 'PMI'],
|
||||
['request' => 'pwht_request_date', 'result' => 'pwht_result', 'name' => 'PWHT'],
|
||||
['request' => 'ferrite_request_date', 'result' => 'ferrite_result', 'name' => 'Ferrite'],
|
||||
];
|
||||
|
||||
foreach ($requestChecks as $check) {
|
||||
$this->info("Checking {$check['name']} requests...");
|
||||
|
||||
$query = DB::table('weld_logs')
|
||||
->whereNotNull($check['request'])
|
||||
->where($check['request'], '!=', '')
|
||||
->where($check['request'], '!=', '0000-00-00')
|
||||
->where($check['request'], '<=', $overdueThreshold)
|
||||
->where(function($q) use ($check) {
|
||||
$q->whereNull($check['result'])
|
||||
->orWhere($check['result'], '')
|
||||
->orWhere($check['result'], 'Pending');
|
||||
});
|
||||
|
||||
// If not first run, only check records that might have changed
|
||||
// (request date is still overdue, but result might have been updated)
|
||||
if ($lastCheck !== null) {
|
||||
$query->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (!empty($ids)) {
|
||||
$this->warn("Found " . count($ids) . " overdue {$check['name']} requests (10+ days)");
|
||||
$invalidIds = array_merge($invalidIds, $ids);
|
||||
}
|
||||
}
|
||||
|
||||
$invalidIds = array_values(array_unique($invalidIds));
|
||||
|
||||
if (empty($invalidIds)) {
|
||||
$this->info('No new overdue NDT requests found. All requests within 10 days or have results.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare filter params
|
||||
$filterParams = [
|
||||
'table' => 'weld_logs',
|
||||
'conditions' => []
|
||||
];
|
||||
|
||||
// If not first run, merge with existing IDs
|
||||
if ($lastCheck !== null && !empty($alreadyNotifiedIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($alreadyNotifiedIds, $invalidIds)));
|
||||
$newIds = array_diff($invalidIds, $alreadyNotifiedIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info('Skipping: No new records since last notification.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'No new records since last notification');
|
||||
return 0;
|
||||
}
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $invalidIds;
|
||||
}
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: Same issue already notified and no new records.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Same issue already notified and no new records');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($invalidIds, $alreadyNotifiedIds)) : count($invalidIds);
|
||||
$message = sprintf(
|
||||
'%d weld log record(s) have NDT request dates older than 10 days without test results (RT, VT, UT, PT, MT, HT, PWHT). Please follow up with testing laboratory.',
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null,
|
||||
'NDT Request Overdue (10+ Days)',
|
||||
$filterParams,
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$totalRecords = count($filterParams['conditions']['id']);
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $sentCount, $totalRecords, $newCount, $duration);
|
||||
|
||||
$this->warn("Sent notification for {$totalRecords} overdue NDT request(s) ({$newCount} new).");
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckPDFDocuments extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-pdf-documents';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check for missing PDF documents in WPS, NAKS Welder, Welding Equipment, and WPQR tables';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking for missing PDF documents...');
|
||||
|
||||
$totalNotifications = 0;
|
||||
$totalSent = 0;
|
||||
$totalRecords = 0;
|
||||
$totalNew = 0;
|
||||
|
||||
try {
|
||||
$pdfNotificationCodes = [
|
||||
'notification_wps_pdf_missing',
|
||||
'notification_naks_welder_cert_missing',
|
||||
'notification_welding_equipment_pdf_missing',
|
||||
'notification_wpq_followup_pdf_missing',
|
||||
];
|
||||
$result1 = $this->sendBatchNotification(
|
||||
'w_p_s',
|
||||
'download',
|
||||
'notification_wps_pdf_missing',
|
||||
'WPS PDF Document Missing',
|
||||
'download',
|
||||
[
|
||||
'message' => '%d WPS document(s) have no PDF file uploaded. Please add the missing PDF files.'
|
||||
],
|
||||
$totalSent,
|
||||
$totalRecords,
|
||||
$totalNew
|
||||
);
|
||||
if ($result1 > 0) {
|
||||
$totalNotifications += 1;
|
||||
}
|
||||
|
||||
$result2 = $this->sendBatchNotification(
|
||||
'naks_welders',
|
||||
'naks_certificate_no',
|
||||
'notification_naks_welder_cert_missing',
|
||||
'NAKS Welder Certificate Missing',
|
||||
'naks_certificate_no',
|
||||
[
|
||||
'message' => '%d NAKS welder(s) have no certificate number assigned. Please update each welder record.'
|
||||
],
|
||||
$totalSent,
|
||||
$totalRecords,
|
||||
$totalNew
|
||||
);
|
||||
if ($result2 > 0) {
|
||||
$totalNotifications += 1;
|
||||
}
|
||||
|
||||
$result3 = $this->sendBatchNotification(
|
||||
'welding_equipment',
|
||||
'download',
|
||||
'notification_welding_equipment_pdf_missing',
|
||||
'Welding Equipment PDF Missing',
|
||||
'download',
|
||||
[
|
||||
'message' => '%d welding equipment record(s) have no PDF document uploaded. Please add the missing files.',
|
||||
],
|
||||
$totalSent,
|
||||
$totalRecords,
|
||||
$totalNew
|
||||
);
|
||||
if ($result3 > 0) {
|
||||
$totalNotifications += 1;
|
||||
}
|
||||
|
||||
$result4 = $this->sendBatchNotification(
|
||||
'welder_tests',
|
||||
'download',
|
||||
'notification_wpq_followup_pdf_missing',
|
||||
'WPQR PDF Missing',
|
||||
'download',
|
||||
[
|
||||
'message' => '%d WPQR record(s) have no PDF document uploaded. Please add the missing files.',
|
||||
],
|
||||
$totalSent,
|
||||
$totalRecords,
|
||||
$totalNew
|
||||
);
|
||||
if ($result4 > 0) {
|
||||
$totalNotifications += 1;
|
||||
}
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $totalSent, $totalRecords, $totalNew, $duration);
|
||||
|
||||
$this->info("Total batch notifications sent: {$totalNotifications}");
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a batch notification for a table/column combination
|
||||
*/
|
||||
private function sendBatchNotification(
|
||||
string $table,
|
||||
string $column,
|
||||
string $notificationCode,
|
||||
string $title,
|
||||
string $columnKeyForFilter,
|
||||
array $options = [],
|
||||
&$totalSent = null,
|
||||
&$totalRecords = null,
|
||||
&$totalNew = null
|
||||
): int {
|
||||
$this->info("Checking {$title}...");
|
||||
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info("Skipping {$title}: Last notification sent less than 5 minutes ago.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$query = DB::table($table)->where(function($q) use ($column) {
|
||||
$q->whereNull($column)
|
||||
->orWhere($column, '');
|
||||
});
|
||||
|
||||
// If not first run, only check new/updated records
|
||||
if ($lastCheck !== null) {
|
||||
$query->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
|
||||
// Get already notified IDs
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->info("No new missing records found for {$title}.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare filter params
|
||||
$filterParams = [
|
||||
'table' => $table,
|
||||
'conditions' => [
|
||||
'or' => [
|
||||
[$columnKeyForFilter => null],
|
||||
[$columnKeyForFilter => '']
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
// If not first run, merge with existing IDs
|
||||
if ($lastCheck !== null) {
|
||||
$existingIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($existingIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($existingIds, $ids)));
|
||||
$newIds = array_diff($ids, $existingIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info("Skipping {$title}: No new records since last notification.");
|
||||
return 0;
|
||||
}
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info("Skipping {$title}: Same issue already notified and no new records.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode))) : count($ids);
|
||||
$totalRecords = count($filterParams['conditions']['id'] ?? $ids);
|
||||
$message = $options['message'] ?? '%d record(s) have missing data. Please review the filtered list.';
|
||||
$message = sprintf($message, $totalRecords);
|
||||
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null,
|
||||
$title,
|
||||
$filterParams,
|
||||
$totalRecords
|
||||
);
|
||||
|
||||
// Update totals if references provided
|
||||
if ($totalSent !== null) {
|
||||
$totalSent += $sentCount;
|
||||
$totalRecords += $totalRecords;
|
||||
$totalNew += $newCount;
|
||||
}
|
||||
|
||||
$this->info("Sent batch notification for {$title} ({$totalRecords} records, {$newCount} new).");
|
||||
return $sentCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckPaintSystem extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-paint-system-values';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check for Paint Systems with incomplete customer-agreed paint values';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking Paint Systems for incomplete customer-agreed values...');
|
||||
|
||||
$notificationCode = 'notification_paint_system_incomplete';
|
||||
|
||||
try {
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$query = DB::table('paint_systems')
|
||||
->where(function($q) {
|
||||
$q->whereNull('primer_coat_name_1')
|
||||
->orWhere('primer_coat_name_1', '')
|
||||
->orWhereNull('brand_name_1')
|
||||
->orWhere('brand_name_1', '')
|
||||
->orWhereNull('thickness_1')
|
||||
->orWhere('thickness_1', 0);
|
||||
})
|
||||
->whereExists(function($q) {
|
||||
$q->select(DB::raw(1))
|
||||
->from('line_lists')
|
||||
->whereColumn('line_lists.painting_cycle', 'paint_systems.paint_cycle');
|
||||
});
|
||||
|
||||
// If not first run, only check new/updated records
|
||||
if ($lastCheck !== null) {
|
||||
$query->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
|
||||
// Get already notified IDs
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->info('No new incomplete Paint Systems detected.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare filter params
|
||||
$filterParams = [
|
||||
'table' => 'paint_systems',
|
||||
'conditions' => []
|
||||
];
|
||||
|
||||
// If not first run, merge with existing IDs
|
||||
if ($lastCheck !== null) {
|
||||
$existingIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($existingIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($existingIds, $ids)));
|
||||
$newIds = array_diff($ids, $existingIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info('Skipping: No new records since last notification.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'No new records since last notification');
|
||||
return 0;
|
||||
}
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: Same issue already notified and no new records.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Same issue already notified and no new records');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode))) : count($ids);
|
||||
$title = 'Paint System Missing';
|
||||
$message = sprintf(
|
||||
'%d paint system(s) used by Line Lists have missing customer-agreed values. Please complete the paint system details.',
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$sentCount = sendBatchNotificationWithFilter(
|
||||
$notificationCode,
|
||||
$title,
|
||||
$message,
|
||||
$filterParams,
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$totalRecords = count($filterParams['conditions']['id']);
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $sentCount, $totalRecords, $newCount, $duration);
|
||||
|
||||
$this->info("Sent batch notification for incomplete Paint Systems ({$totalRecords} records, {$newCount} new).");
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckRepairLog extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-repair-log-new-joint';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check repair logs for missing new joint numbers and send notifications';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking repair logs for missing new joint numbers...');
|
||||
|
||||
$notificationCode = 'notification_repair_log_missing_new_joint';
|
||||
|
||||
try {
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
// Build query
|
||||
$query = DB::table('repair_logs')
|
||||
->where(function($q) {
|
||||
$q->whereNull('new_joint_no')
|
||||
->orWhere('new_joint_no', '');
|
||||
});
|
||||
|
||||
// If not first run, only check new records
|
||||
if ($lastCheck !== null) {
|
||||
$query->where('created_at', '>', $lastCheck);
|
||||
|
||||
// Get already notified IDs to prevent duplicates
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
// Get IDs of records that need notification
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->info('No new repair logs with missing new joint numbers found.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare filter parameters
|
||||
$filterParams = [
|
||||
'table' => 'repair_logs',
|
||||
'conditions' => [
|
||||
'or' => [
|
||||
['new_joint_no' => null],
|
||||
['new_joint_no' => '']
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
// If not first run, merge with existing IDs (additive approach)
|
||||
if ($lastCheck !== null) {
|
||||
$existingIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($existingIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($existingIds, $ids)));
|
||||
$newIds = array_diff($ids, $existingIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info('Skipping: No new records since last notification.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'No new records since last notification');
|
||||
return 0;
|
||||
}
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: Same issue already notified and no new records.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Same issue already notified and no new records');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$title = 'Repair Logs Missing New Joint Number';
|
||||
$newCount = $lastCheck !== null ? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode))) : count($ids);
|
||||
$message = sprintf(
|
||||
'%d repair log(s) have no new joint number assigned. Please update each record with the new joint number.',
|
||||
count($filterParams['conditions']['id'] ?? $ids)
|
||||
);
|
||||
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null,
|
||||
$title,
|
||||
$filterParams,
|
||||
count($filterParams['conditions']['id'] ?? $ids)
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$totalRecords = count($filterParams['conditions']['id'] ?? $ids);
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $sentCount, $totalRecords, $newCount, $duration);
|
||||
|
||||
$this->info("Sent {$sentCount} notification(s) covering {$totalRecords} repair logs ({$newCount} new).");
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class NotificationCheckRepairRate extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-daily-repair-rate';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check if daily repair rate exceeds 12% AND overall repair rate exceeds 5%';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking daily repair rate...');
|
||||
|
||||
$notificationCode = 'notification_daily_repair_rate_high';
|
||||
|
||||
try {
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$today = Carbon::today()->toDateString();
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
// If not first run, check if new repairs/welds since last check
|
||||
if ($lastCheck !== null) {
|
||||
$newRepairs = DB::table('repair_logs')
|
||||
->whereDate('repair_date', $today)
|
||||
->where('created_at', '>', $lastCheck)
|
||||
->count();
|
||||
|
||||
$newWelds = DB::table('weld_logs')
|
||||
->whereDate('welding_date', $today)
|
||||
->where('created_at', '>', $lastCheck)
|
||||
->count();
|
||||
|
||||
if ($newRepairs == 0 && $newWelds == 0) {
|
||||
$this->info('No new repairs or welds since last check. Skipping.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'No new repairs or welds since last check');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Count today's repairs
|
||||
$repairCount = DB::table('repair_logs')
|
||||
->whereDate('repair_date', $today)
|
||||
->count();
|
||||
|
||||
// Count today's welds
|
||||
$weldCount = DB::table('weld_logs')
|
||||
->whereDate('welding_date', $today)
|
||||
->count();
|
||||
|
||||
if ($weldCount == 0) {
|
||||
$this->info("No welding records for today. Skipping repair rate check.");
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'No welding records for today');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$repairRate = ($repairCount / $weldCount) * 100;
|
||||
$threshold = 12;
|
||||
|
||||
$this->info("Today's stats: {$repairCount} repairs / {$weldCount} welds = " . number_format($repairRate, 2) . "%");
|
||||
|
||||
if ($repairRate > $threshold) {
|
||||
// Check for duplicate (same day already notified)
|
||||
if ($lastCheck !== null && $lastCheck->isToday()) {
|
||||
$this->info('Skipping: Already notified for today.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Already notified for today');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$title = 'Daily Repair Rate Exceeds 12%';
|
||||
$message = sprintf(
|
||||
'Daily repair rate is %.2f%% (%d repairs out of %d welds), which exceeds the %d%% threshold. QC team attention required.',
|
||||
$repairRate,
|
||||
$repairCount,
|
||||
$weldCount,
|
||||
$threshold
|
||||
);
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'repair_logs',
|
||||
'conditions' => [
|
||||
'repair_date' => $today
|
||||
]
|
||||
];
|
||||
|
||||
$sentCount = sendNotification($notificationCode, $message, null, $title, $filterParams);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, $sentCount, 1, 1, $duration);
|
||||
|
||||
$this->warn("⚠️ HIGH REPAIR RATE: Notification sent to QC teams.");
|
||||
|
||||
// Check overall (project-wide) repair rate
|
||||
$this->checkOverallRepairRate();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->info("✓ Daily repair rate is within acceptable limits.");
|
||||
|
||||
// Check overall (project-wide) repair rate
|
||||
$this->checkOverallRepairRate();
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check overall repair rate for entire project
|
||||
*/
|
||||
private function checkOverallRepairRate()
|
||||
{
|
||||
$this->info('Checking overall project repair rate...');
|
||||
|
||||
$notificationCode = 'notification_overall_repair_rate_high';
|
||||
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping overall rate: Last notification sent less than 5 minutes ago.');
|
||||
return;
|
||||
}
|
||||
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
// If not first run, check if new repairs/welds since last check
|
||||
if ($lastCheck !== null) {
|
||||
$newRepairs = DB::table('repair_logs')
|
||||
->where('created_at', '>', $lastCheck)
|
||||
->count();
|
||||
|
||||
$newWelds = DB::table('weld_logs')
|
||||
->whereNotNull('welding_date')
|
||||
->where('welding_date', '!=', '')
|
||||
->where('welding_date', '!=', '0000-00-00')
|
||||
->where('created_at', '>', $lastCheck)
|
||||
->count();
|
||||
|
||||
if ($newRepairs == 0 && $newWelds == 0) {
|
||||
$this->info('No new repairs or welds since last check. Skipping overall rate.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Count total repairs (all time)
|
||||
$totalRepairs = DB::table('repair_logs')->count();
|
||||
|
||||
// Count total welds (all time)
|
||||
$totalWelds = DB::table('weld_logs')
|
||||
->whereNotNull('welding_date')
|
||||
->where('welding_date', '!=', '')
|
||||
->where('welding_date', '!=', '0000-00-00')
|
||||
->count();
|
||||
|
||||
if ($totalWelds == 0) {
|
||||
$this->info("No welding records found. Skipping overall repair rate check.");
|
||||
return;
|
||||
}
|
||||
|
||||
$overallRepairRate = ($totalRepairs / $totalWelds) * 100;
|
||||
$threshold = 5;
|
||||
|
||||
$this->info("Overall stats: {$totalRepairs} repairs / {$totalWelds} welds = " . number_format($overallRepairRate, 2) . "%");
|
||||
|
||||
if ($overallRepairRate > $threshold) {
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, ['table' => 'repair_logs', 'conditions' => []])) {
|
||||
$this->info('Skipping: Already notified for overall rate.');
|
||||
return;
|
||||
}
|
||||
|
||||
$title = 'Overall Repair Rate Exceeds 5%';
|
||||
$message = sprintf(
|
||||
'Overall project repair rate is %.2f%% (%d repairs out of %d welds), which exceeds the %d%% threshold. Quality improvement measures recommended.',
|
||||
$overallRepairRate,
|
||||
$totalRepairs,
|
||||
$totalWelds,
|
||||
$threshold
|
||||
);
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'repair_logs',
|
||||
'conditions' => []
|
||||
];
|
||||
|
||||
sendNotification($notificationCode, $message, null, $title, $filterParams);
|
||||
|
||||
$this->warn("⚠️ HIGH OVERALL REPAIR RATE: Notification sent.");
|
||||
} else {
|
||||
$this->info("✓ Overall repair rate is within acceptable limits.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckSupportLogWeldlog extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-support-log-weldlog';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check if Support Log weld count matches Weldlog count by line number (Process Piping Support only)';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking Support Log vs Weldlog count discrepancies by line...');
|
||||
|
||||
$notificationCode = 'notification_support_log_missing_in_weldlog';
|
||||
|
||||
try {
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
// Get line_number based weld count from supports (erection_type = 'weld')
|
||||
$supportWeldCountsQuery = DB::table('supports')
|
||||
->select('line_number', DB::raw('COUNT(*) as support_count'))
|
||||
->where('erection_type', 'weld')
|
||||
->whereNotNull('line_number')
|
||||
->where('line_number', '!=', '')
|
||||
->groupBy('line_number');
|
||||
|
||||
// If not first run, only check lines with new/updated supports or welds
|
||||
if ($lastCheck !== null) {
|
||||
$supportWeldCountsQuery->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
}
|
||||
|
||||
$supportWeldCounts = $supportWeldCountsQuery->get()->keyBy('line_number');
|
||||
|
||||
if ($supportWeldCounts->isEmpty()) {
|
||||
$this->info('No new welded supports found in Support Log.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->info("Found {$supportWeldCounts->count()} unique lines with welded supports");
|
||||
|
||||
// Get already notified line numbers
|
||||
$alreadyNotifiedLines = [];
|
||||
if ($lastCheck !== null) {
|
||||
$lastNotification = \App\Models\Notification::where('notification_code', $notificationCode)
|
||||
->whereNotNull('filter_params')
|
||||
->orderBy('created_at', 'DESC')
|
||||
->first();
|
||||
|
||||
if ($lastNotification && $lastNotification->filter_params) {
|
||||
$lastParams = is_array($lastNotification->filter_params)
|
||||
? $lastNotification->filter_params
|
||||
: json_decode($lastNotification->filter_params, true);
|
||||
if (isset($lastParams['conditions']['line_number'])) {
|
||||
$alreadyNotifiedLines = is_array($lastParams['conditions']['line_number'])
|
||||
? $lastParams['conditions']['line_number']
|
||||
: [$lastParams['conditions']['line_number']];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$discrepantLines = [];
|
||||
|
||||
foreach ($supportWeldCounts as $lineNum => $supportData) {
|
||||
// Skip if already notified (unless first run)
|
||||
if ($lastCheck !== null && in_array($lineNum, $alreadyNotifiedLines)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Count matching records in weld_logs (piping_type = 'Process Piping Support')
|
||||
$weldlogCount = DB::table('weld_logs')
|
||||
->where('line_number', $lineNum)
|
||||
->where('piping_type', 'Process Piping Support')
|
||||
->count();
|
||||
|
||||
// If counts don't match, add to discrepancy list
|
||||
if ($supportData->support_count != $weldlogCount) {
|
||||
$discrepantLines[] = [
|
||||
'line' => $lineNum,
|
||||
'support_count' => $supportData->support_count,
|
||||
'weldlog_count' => $weldlogCount
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($discrepantLines)) {
|
||||
$this->info('No new mismatched counts found.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lineNumbers = array_column($discrepantLines, 'line');
|
||||
|
||||
// Merge with existing lines if not first run
|
||||
if ($lastCheck !== null && !empty($alreadyNotifiedLines)) {
|
||||
$lineNumbers = array_values(array_unique(array_merge($alreadyNotifiedLines, $lineNumbers)));
|
||||
} else {
|
||||
$lineNumbers = array_values(array_unique($lineNumbers));
|
||||
}
|
||||
|
||||
$filterParams = [
|
||||
'table' => 'supports',
|
||||
'conditions' => [
|
||||
'line_number' => $lineNumbers
|
||||
]
|
||||
];
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: Same issue already notified and no new records.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Same issue already notified and no new records');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($lineNumbers, $alreadyNotifiedLines)) : count($lineNumbers);
|
||||
$message = sprintf(
|
||||
'%d line(s) have mismatched support weld counts between Support Log (erection_type=weld) and Weldlog (piping_type=Process Piping Support). Please review and reconcile.',
|
||||
count($lineNumbers)
|
||||
);
|
||||
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null,
|
||||
'Support Log - Weldlog Count Mismatch',
|
||||
$filterParams,
|
||||
count($lineNumbers)
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$totalRecords = count($lineNumbers);
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $sentCount, $totalRecords, $newCount, $duration);
|
||||
|
||||
$this->info("Sent {$sentCount} batch notification(s) for {$totalRecords} line(s) with mismatched counts ({$newCount} new).");
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckTestLogPDF extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-test-log-pdf';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check NDT test logs with results but missing PDF reports (VT, RT, UT, PT, MT, HT, PMI, PWHT, Ferrite)';
|
||||
|
||||
/**
|
||||
* Test log tables with their result field names
|
||||
*/
|
||||
private $testLogs = [
|
||||
'v_t_logs' => ['result' => 'vt_result', 'name' => 'VT'],
|
||||
'radiographic_tests' => ['result' => 'rt_result', 'name' => 'RT'],
|
||||
'ultrasonic_tests' => ['result' => 'ut_result', 'name' => 'UT'],
|
||||
'p_t_logs' => ['result' => 'pt_result', 'name' => 'PT'],
|
||||
'magnetic_tests' => ['result' => 'mt_result', 'name' => 'MT'],
|
||||
'hardness_tests' => ['result' => 'ht_result', 'name' => 'HT'],
|
||||
'p_m_i_tests' => ['result' => 'pmi_result', 'name' => 'PMI'],
|
||||
'p_w_h_t_s' => ['result' => 'pwht_result', 'name' => 'PWHT'],
|
||||
'ferrits' => ['result' => 'ferrite_result', 'name' => 'Ferrite'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking test logs for missing PDF reports...');
|
||||
|
||||
$totalNotifications = 0;
|
||||
$totalSent = 0;
|
||||
$totalRecords = 0;
|
||||
$totalNew = 0;
|
||||
|
||||
try {
|
||||
foreach ($this->testLogs as $table => $config) {
|
||||
$result = $this->checkTestLog($table, $config['result'], $config['name'], $totalSent, $totalRecords, $totalNew);
|
||||
if ($result > 0) {
|
||||
$totalNotifications += 1; // Count as notification sent
|
||||
}
|
||||
}
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, $totalSent, $totalRecords, $totalNew, $duration);
|
||||
|
||||
$this->info("Total notifications sent: {$totalNotifications}");
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a specific test log table for missing PDFs
|
||||
*/
|
||||
private function checkTestLog(string $table, string $resultField, string $testName, &$totalSent, &$totalRecords, &$totalNew): int
|
||||
{
|
||||
$this->info("Checking {$testName} Log...");
|
||||
|
||||
$notificationCode = 'notification_test_log_pdf_missing_' . strtolower($testName);
|
||||
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info("Skipping {$testName}: Last notification sent less than 5 minutes ago.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$query = DB::table($table)
|
||||
->whereNotNull($resultField)
|
||||
->where($resultField, '!=', '')
|
||||
->where($resultField, '!=', 'Cancel')
|
||||
->where(function($q) {
|
||||
$q->whereNull('report_file')
|
||||
->orWhere('report_file', '');
|
||||
});
|
||||
|
||||
// If not first run, only check new/updated records
|
||||
if ($lastCheck !== null) {
|
||||
$query->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
|
||||
// Get already notified IDs
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->info("No new missing PDFs found in {$testName} Log.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare filter params
|
||||
$filterParams = [
|
||||
'table' => $table,
|
||||
'conditions' => []
|
||||
];
|
||||
|
||||
// If not first run, merge with existing IDs
|
||||
if ($lastCheck !== null) {
|
||||
$existingIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($existingIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($existingIds, $ids)));
|
||||
$newIds = array_diff($ids, $existingIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info("Skipping {$testName}: No new records since last notification.");
|
||||
return 0;
|
||||
}
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info("Skipping {$testName}: Same issue already notified and no new records.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode))) : count($ids);
|
||||
$recordCount = count($filterParams['conditions']['id']);
|
||||
$message = sprintf(
|
||||
'%d %s test record(s) have results (non-Cancel) but missing PDF report files. Please upload the missing reports.',
|
||||
$recordCount,
|
||||
$testName
|
||||
);
|
||||
|
||||
$sentCount = sendBatchNotificationWithFilter(
|
||||
$notificationCode,
|
||||
"{$testName} Test - PDF Report Missing",
|
||||
$message,
|
||||
$filterParams,
|
||||
$recordCount
|
||||
);
|
||||
|
||||
// Update totals for handle method
|
||||
$totalSent += $sentCount;
|
||||
$totalRecords += $recordCount;
|
||||
$totalNew += $newCount;
|
||||
|
||||
$this->info("Sent batch notification for {$testName} Log ({$recordCount} records, {$newCount} new).");
|
||||
return $sentCount > 0 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckWeldlogCertificate extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-weldlog-certificate';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check weldlog records for missing certificate PDFs after welding date is entered';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking weldlog records for missing certificates...');
|
||||
|
||||
$notificationCode = 'notification_weldlog_certificate_missing';
|
||||
|
||||
try {
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$query = DB::table('weld_logs')
|
||||
->whereNotNull('welding_date')
|
||||
->where('welding_date', '!=', '')
|
||||
->where(function($q) {
|
||||
$q->whereNull('certificate_number_of_1')
|
||||
->orWhere('certificate_number_of_1', '')
|
||||
->orWhereNull('certificate_number_of_2')
|
||||
->orWhere('certificate_number_of_2', '');
|
||||
});
|
||||
|
||||
// If not first run, only check new/updated records
|
||||
if ($lastCheck !== null) {
|
||||
$query->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
|
||||
// Get already notified IDs
|
||||
$alreadyNotifiedIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (empty($ids)) {
|
||||
$this->info('No new missing certificates found in weldlog.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare filter params
|
||||
$filterParams = [
|
||||
'table' => 'weld_logs',
|
||||
'conditions' => []
|
||||
];
|
||||
|
||||
// If not first run, merge with existing IDs
|
||||
if ($lastCheck !== null) {
|
||||
$existingIds = getAlreadyNotifiedIds($notificationCode);
|
||||
if (!empty($existingIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($existingIds, $ids)));
|
||||
$newIds = array_diff($ids, $existingIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info('Skipping: No new records since last notification.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'No new records since last notification');
|
||||
return 0;
|
||||
}
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $ids;
|
||||
}
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: Same issue already notified and no new records.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Same issue already notified and no new records');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($ids, getAlreadyNotifiedIds($notificationCode))) : count($ids);
|
||||
$title = 'Certificate Number 1-2 Missing';
|
||||
$message = sprintf(
|
||||
'%d weld log record(s) have missing certificate numbers. Please update certificate_number_of_1/2.',
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$sentCount = sendBatchNotificationWithFilter(
|
||||
$notificationCode,
|
||||
$title,
|
||||
$message,
|
||||
$filterParams,
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$totalRecords = count($filterParams['conditions']['id']);
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $sentCount, $totalRecords, $newCount, $duration);
|
||||
|
||||
$this->info("Sent batch notification for weldlog certificates ({$totalRecords} records, {$newCount} new).");
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class NotificationCheckWeldlogTestDates extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'notifications:check-weldlog-test-dates';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check if NDT test dates are before welding_date in weld_logs';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$commandName = $this->signature;
|
||||
|
||||
// Log command start
|
||||
logNotificationStart($commandName);
|
||||
|
||||
$this->info('Checking weldlog test dates vs welding dates...');
|
||||
|
||||
$notificationCode = 'notification_weldlog_test_date_invalid';
|
||||
|
||||
try {
|
||||
// Skip if checked recently (within 5 minutes)
|
||||
if (!shouldCheckNotification($notificationCode)) {
|
||||
$this->info('Skipping: Last notification sent less than 5 minutes ago.');
|
||||
logNotificationSkip($commandName, 'Last notification sent less than 5 minutes ago');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get last check time (null = first run, check all records)
|
||||
$lastCheck = getLastCheckTimestamp($notificationCode);
|
||||
|
||||
$invalidIds = [];
|
||||
|
||||
// Test date fields to check
|
||||
$testDateFields = [
|
||||
'rt_test_date' => 'RT',
|
||||
'date_of_vt' => 'VT',
|
||||
'ut_test_date' => 'UT',
|
||||
'pt_test_date' => 'PT',
|
||||
'mt_test_date' => 'MT',
|
||||
'ht_test_date' => 'HT',
|
||||
'pmi_test_date' => 'PMI',
|
||||
'pwht_date' => 'PWHT',
|
||||
'date_of_ferrite_check' => 'Ferrite',
|
||||
];
|
||||
|
||||
// Get already notified IDs
|
||||
$alreadyNotifiedIds = $lastCheck !== null ? getAlreadyNotifiedIds($notificationCode) : [];
|
||||
|
||||
foreach ($testDateFields as $testField => $testName) {
|
||||
$this->info("Checking {$testName} test dates...");
|
||||
|
||||
$query = DB::table('weld_logs')
|
||||
->whereNotNull('welding_date')
|
||||
->where('welding_date', '!=', '')
|
||||
->where('welding_date', '!=', '0000-00-00')
|
||||
->whereNotNull($testField)
|
||||
->where($testField, '!=', '')
|
||||
->where($testField, '!=', '0000-00-00')
|
||||
->whereColumn($testField, '<', 'welding_date');
|
||||
|
||||
// If not first run, only check new/updated records
|
||||
if ($lastCheck !== null) {
|
||||
$query->where(function($q) use ($lastCheck) {
|
||||
$q->where('created_at', '>', $lastCheck)
|
||||
->orWhere('updated_at', '>', $lastCheck);
|
||||
});
|
||||
|
||||
if (!empty($alreadyNotifiedIds)) {
|
||||
$query->whereNotIn('id', $alreadyNotifiedIds);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = $query->pluck('id')->toArray();
|
||||
|
||||
if (!empty($ids)) {
|
||||
$this->warn("Found " . count($ids) . " records with {$testName} test date before welding date");
|
||||
$invalidIds = array_merge($invalidIds, $ids);
|
||||
}
|
||||
}
|
||||
|
||||
$invalidIds = array_values(array_unique($invalidIds));
|
||||
|
||||
if (empty($invalidIds)) {
|
||||
$this->info('No new invalid test dates found. All test dates are >= welding_date.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationComplete($commandName, 0, 0, 0, $duration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare filter params
|
||||
$filterParams = [
|
||||
'table' => 'weld_logs',
|
||||
'conditions' => []
|
||||
];
|
||||
|
||||
// If not first run, merge with existing IDs
|
||||
if ($lastCheck !== null && !empty($alreadyNotifiedIds)) {
|
||||
$allIds = array_values(array_unique(array_merge($alreadyNotifiedIds, $invalidIds)));
|
||||
$newIds = array_diff($invalidIds, $alreadyNotifiedIds);
|
||||
if (empty($newIds)) {
|
||||
$this->info('Skipping: No new records since last notification.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'No new records since last notification');
|
||||
return 0;
|
||||
}
|
||||
$filterParams['conditions']['id'] = $allIds;
|
||||
} else {
|
||||
$filterParams['conditions']['id'] = $invalidIds;
|
||||
}
|
||||
|
||||
// Check for duplicate
|
||||
if ($lastCheck !== null && isNotificationDuplicate($notificationCode, $filterParams)) {
|
||||
$this->info('Skipping: Same issue already notified and no new records.');
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationSkip($commandName, 'Same issue already notified and no new records');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$newCount = $lastCheck !== null ? count(array_diff($invalidIds, $alreadyNotifiedIds)) : count($invalidIds);
|
||||
$message = sprintf(
|
||||
'%d weld log record(s) have NDT test dates that are before welding_date. Please review and correct (RT, VT, UT, PT, MT, HT, PWHT).',
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$sentCount = sendNotification(
|
||||
$notificationCode,
|
||||
$message,
|
||||
null,
|
||||
'Weldlog Test Date Before Welding Date',
|
||||
$filterParams,
|
||||
count($filterParams['conditions']['id'])
|
||||
);
|
||||
|
||||
$duration = microtime(true) - $startTime;
|
||||
$totalRecords = count($filterParams['conditions']['id']);
|
||||
|
||||
// Log completion
|
||||
logNotificationComplete($commandName, $sentCount, $totalRecords, $newCount, $duration);
|
||||
|
||||
$this->warn("Sent notification for {$totalRecords} record(s) with invalid test dates ({$newCount} new).");
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$duration = microtime(true) - $startTime;
|
||||
logNotificationError($commandName, $e->getMessage(), [
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'duration' => $duration,
|
||||
]);
|
||||
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SummaryCalculation extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'summary:calculation';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Dispatch blade view caching for summary calculation';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Starting summary calculation cache dispatch...');
|
||||
|
||||
$cacheViews = [
|
||||
[
|
||||
'view' => 'admin.type.summary-nocache',
|
||||
'cache' => 'summary-dashboard'
|
||||
],
|
||||
[
|
||||
'view' => 'admin.dashboard.module.dashboard',
|
||||
'cache' => 'dashboard'
|
||||
],
|
||||
[
|
||||
'view' => 'admin-ajax.ndt-calculation-no-cache',
|
||||
'cache' => 'ndt-calculation'
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
if (function_exists('dispatchCacheBladeViews')) {
|
||||
dispatchCacheBladeViews($cacheViews);
|
||||
$this->info('Dispatched cache jobs successfully.');
|
||||
} else {
|
||||
$this->error('Helper function dispatchCacheBladeViews not found.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\WeldLog;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class VerifyMechanicalSummaries extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'mechanical:verify-summaries
|
||||
{--columns=nps_1,nps_2,outside_diameter_1,outside_diameter_2 : Comma separated numeric columns to inspect}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Compare SUM values for welded vs mechanical joints across selected columns';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$columns = collect(explode(',', $this->option('columns')))
|
||||
->map(fn ($column) => trim($column))
|
||||
->filter()
|
||||
->unique()
|
||||
->values();
|
||||
|
||||
if ($columns->isEmpty()) {
|
||||
$this->error('Please provide at least one column via --columns option.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
|
||||
foreach ($columns as $column) {
|
||||
if (!Schema::hasColumn('weld_logs', $column)) {
|
||||
$this->warn("Column '{$column}' does not exist on weld_logs table. Skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
$weldedSum = apply_welded_filter(WeldLog::query())->sum($column);
|
||||
$mechanicalSum = apply_mechanical_filter(WeldLog::query())->sum($column);
|
||||
$total = $weldedSum + $mechanicalSum;
|
||||
|
||||
$rows[] = [
|
||||
'Column' => $column,
|
||||
'SUM (Welded)' => $weldedSum,
|
||||
'SUM (Mechanical)' => $mechanicalSum,
|
||||
'SUM (Total)' => $total,
|
||||
];
|
||||
}
|
||||
|
||||
if (empty($rows)) {
|
||||
$this->warn('No valid columns were processed.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->table(
|
||||
['Column', 'SUM (Welded)', 'SUM (Mechanical)', 'SUM (Total)'],
|
||||
$rows
|
||||
);
|
||||
|
||||
$this->info('Verification completed. Ensure mechanical sums are zero to confirm filtering.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
protected $commands = [
|
||||
\App\Console\Commands\MigrateFromDate::class,
|
||||
\App\Console\Commands\DocumentSyncFolders::class,
|
||||
\App\Console\Commands\NotificationCheckAll::class,
|
||||
\App\Console\Commands\NotificationCheckDeletedJoints::class,
|
||||
\App\Console\Commands\NotificationCheckRepairLog::class,
|
||||
\App\Console\Commands\NotificationCheckRepairRate::class,
|
||||
\App\Console\Commands\NotificationCheckLineList::class,
|
||||
\App\Console\Commands\NotificationCheckPDFDocuments::class,
|
||||
\App\Console\Commands\NotificationCheckWeldlogCertificate::class,
|
||||
\App\Console\Commands\NotificationCheckIncomingControlCertificate::class,
|
||||
\App\Console\Commands\NotificationCheckNDEMatrixDiscrepancies::class,
|
||||
\App\Console\Commands\NotificationCheckNDTCalculation::class,
|
||||
\App\Console\Commands\NotificationCheckManageNDT::class,
|
||||
\App\Console\Commands\NotificationCheckSupportLogWeldlog::class,
|
||||
\App\Console\Commands\NotificationCheckPaintSystem::class,
|
||||
\App\Console\Commands\NotificationCheckWeldlogTestDates::class,
|
||||
\App\Console\Commands\NotificationCheckNDTRequestOverdue::class,
|
||||
\App\Console\Commands\NotificationCheckTestLogPDF::class,
|
||||
\App\Console\Commands\VerifyMechanicalSummaries::class,
|
||||
\App\Console\Commands\SummaryCalculation::class,
|
||||
\App\Console\Commands\CacheBladeViews::class,
|
||||
\App\Console\Commands\NotificationCheckCalibrationDue::class,
|
||||
\App\Console\Commands\NaksSync::class,
|
||||
];
|
||||
/**
|
||||
* Define the application's command schedule.
|
||||
*
|
||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||
* @return void
|
||||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
// Cache Blade Views - Run every 10 minutes
|
||||
|
||||
$schedule->command('cache:blade-views')
|
||||
->everyTenMinutes()
|
||||
->withoutOverlapping(10);
|
||||
|
||||
|
||||
// CheckNDTReminders command removed - outdated checks (overdue, report missing, results pending)
|
||||
|
||||
// Notification checks - Run every 5 minutes (real-time notifications)
|
||||
// All commands use incremental checking: first run scans all records, subsequent runs only check new records
|
||||
|
||||
// Log when schedule:run starts
|
||||
$schedule->call(function () {
|
||||
logSchedulerExecution('schedule_run_started', [
|
||||
'run_time' => now()->toDateTimeString(),
|
||||
]);
|
||||
})->name('schedule-run-logger')->everyMinute()->withoutOverlapping(1);
|
||||
|
||||
// Helper function to add logging to scheduled commands
|
||||
$addScheduledCommand = function($command, $schedule, $scheduleMethod, ...$args) {
|
||||
$event = $schedule->command($command);
|
||||
|
||||
// Apply schedule method (everyFiveMinutes, cron, etc.)
|
||||
if (method_exists($event, $scheduleMethod)) {
|
||||
$event = $event->$scheduleMethod(...$args);
|
||||
}
|
||||
|
||||
// Send output to log file instead of /dev/null
|
||||
// This ensures before/after callbacks work correctly
|
||||
$logFile = storage_path('logs/scheduler-' . date('Y-m-d') . '.log');
|
||||
$event->sendOutputTo($logFile);
|
||||
$event->appendOutputTo($logFile);
|
||||
|
||||
// Add before callback to log when command is scheduled to run
|
||||
$event->before(function () use ($command) {
|
||||
logSchedulerExecution('command_scheduled', [
|
||||
'command' => $command,
|
||||
'scheduled_time' => now()->toDateTimeString(),
|
||||
]);
|
||||
});
|
||||
|
||||
// Add after callback to log when command finishes
|
||||
$event->after(function () use ($command) {
|
||||
logSchedulerExecution('command_finished', [
|
||||
'command' => $command,
|
||||
'finished_time' => now()->toDateTimeString(),
|
||||
]);
|
||||
});
|
||||
|
||||
return $event;
|
||||
};
|
||||
|
||||
$addScheduledCommand('notifications:check-deleted-joints', $schedule, 'everyFiveMinutes')
|
||||
->withoutOverlapping(10);
|
||||
|
||||
$addScheduledCommand('notifications:check-repair-log-new-joint', $schedule, 'everyFiveMinutes')
|
||||
->withoutOverlapping(10);
|
||||
|
||||
$addScheduledCommand('notifications:check-daily-repair-rate', $schedule, 'everyFiveMinutes')
|
||||
->withoutOverlapping(10);
|
||||
|
||||
// --- Batch 2 (Minutes: 1, 6, 11, 16...) ---
|
||||
$addScheduledCommand('notifications:check-line-list-missing-data', $schedule, 'cron', '1-59/5 * * * *')
|
||||
->withoutOverlapping(10);
|
||||
|
||||
$addScheduledCommand('notifications:check-pdf-documents', $schedule, 'cron', '1-59/5 * * * *')
|
||||
->withoutOverlapping(10);
|
||||
|
||||
$addScheduledCommand('notifications:check-weldlog-certificate', $schedule, 'cron', '1-59/5 * * * *')
|
||||
->withoutOverlapping(10);
|
||||
|
||||
// --- Batch 3 (Minutes: 2, 7, 12, 17...) ---
|
||||
$addScheduledCommand('notifications:check-incoming-control-certificate', $schedule, 'cron', '2-59/5 * * * *')
|
||||
->withoutOverlapping(10);
|
||||
|
||||
$addScheduledCommand('notifications:check-nde-matrix-discrepancies', $schedule, 'cron', '2-59/5 * * * *')
|
||||
->withoutOverlapping(10);
|
||||
|
||||
$addScheduledCommand('notifications:check-ndt-calculation', $schedule, 'cron', '2-59/5 * * * *')
|
||||
->withoutOverlapping(10);
|
||||
|
||||
// --- Batch 4 (Minutes: 3, 8, 13, 18...) ---
|
||||
$addScheduledCommand('notifications:check-manage-ndt-unnecessary', $schedule, 'cron', '3-59/5 * * * *')
|
||||
->withoutOverlapping(10);
|
||||
|
||||
$addScheduledCommand('notifications:check-support-log-weldlog', $schedule, 'cron', '3-59/5 * * * *')
|
||||
->withoutOverlapping(10);
|
||||
|
||||
$addScheduledCommand('notifications:check-paint-system-values', $schedule, 'cron', '3-59/5 * * * *')
|
||||
->withoutOverlapping(10);
|
||||
|
||||
// --- Batch 5 (Minutes: 4, 9, 14, 19...) ---
|
||||
$addScheduledCommand('notifications:check-weldlog-test-dates', $schedule, 'cron', '4-59/5 * * * *')
|
||||
->withoutOverlapping(10);
|
||||
|
||||
$addScheduledCommand('notifications:check-ndt-request-overdue', $schedule, 'cron', '4-59/5 * * * *')
|
||||
->withoutOverlapping(10);
|
||||
|
||||
$addScheduledCommand('notifications:check-test-log-pdf', $schedule, 'cron', '4-59/5 * * * *')
|
||||
->withoutOverlapping(10);
|
||||
|
||||
// Calibration notifications - due within 20 days (run in same batch)
|
||||
$addScheduledCommand('notifications:check-calibration-due', $schedule, 'cron', '4-59/5 * * * *')
|
||||
->withoutOverlapping(10);
|
||||
|
||||
$schedule->command('summary:calculation')
|
||||
->hourly()
|
||||
->withoutOverlapping(10);
|
||||
|
||||
// NAKS Multi-Module Cross-Site Sync - Run daily at 02:00
|
||||
// Synchronizes all NAKS data (certificates, welders, consumables, experts, equipment)
|
||||
// from all project sites
|
||||
$schedule->command('naks:sync --module=all')
|
||||
->hourly()
|
||||
->withoutOverlapping(120)
|
||||
->appendOutputTo(storage_path('logs/naks-sync.log'));
|
||||
|
||||
$schedule->command('telescope:prune')->daily();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the commands for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function commands()
|
||||
{
|
||||
$this->load(__DIR__.'/Commands');
|
||||
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Contents extends Model
|
||||
{
|
||||
//
|
||||
use SoftDeletes;
|
||||
protected $table = 'contents';
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = true;
|
||||
public $timestamps = true;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
}
|
||||
public function getRouteKeyName(): string
|
||||
{
|
||||
return 'slug';
|
||||
}
|
||||
public static function getTree(string $slug, array $tree = []): array
|
||||
{
|
||||
$lowestLevel = Contents::where('slug', $slug)->select("title","slug","id","kid")->first();
|
||||
|
||||
if (!$lowestLevel) {
|
||||
return $tree;
|
||||
}
|
||||
|
||||
$tree[] = $lowestLevel->toArray();
|
||||
|
||||
if ($lowestLevel->kid !== 0) {
|
||||
$tree = Contents::getTree($lowestLevel->kid, $tree);
|
||||
}
|
||||
|
||||
return $tree;
|
||||
|
||||
}
|
||||
public static function type(string $type,int $take=10) {
|
||||
return Contents::where("type",$type)->take($take)->get();
|
||||
}
|
||||
public static function getBreadcrumbs(string $slug, $model="<li class='breadcrumb-item'><a href='{slug}'>{title}</a></li>"): string
|
||||
{
|
||||
$tree = Contents::getTree($slug);
|
||||
$tree = array_reverse($tree);
|
||||
$breadcrumbs = "";
|
||||
foreach($tree AS $alan => $deger) {
|
||||
$sonuc = str_replace("{slug}",$deger['slug'],$model);
|
||||
$sonuc = str_replace("{title}",$deger['title'],$sonuc);
|
||||
$breadcrumbs .= $sonuc;
|
||||
}
|
||||
return $breadcrumbs;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,644 @@
|
||||
<?php
|
||||
namespace App\DevExtreme;
|
||||
|
||||
use App\DevExtreme\SummaryContext;
|
||||
class AggregateHelper {
|
||||
const MIN_OP = "MIN";
|
||||
const MAX_OP = "MAX";
|
||||
const AVG_OP = "AVG";
|
||||
const COUNT_OP = "COUNT";
|
||||
const SUM_OP = "SUM";
|
||||
const AS_OP = "AS";
|
||||
const GENERATED_FIELD_PREFIX = "dx_";
|
||||
private static function _RecalculateGroupCountAndSummary(&$dataItem, $groupInfo) {
|
||||
if ($groupInfo["groupIndex"] <= $groupInfo["groupCount"] - 3) {
|
||||
$items = $dataItem["items"];
|
||||
foreach ($items as $item) {
|
||||
$grInfo = $groupInfo;
|
||||
$grInfo["groupIndex"]++;
|
||||
self::_RecalculateGroupCountAndSummary($item, $grInfo);
|
||||
}
|
||||
}
|
||||
if (isset($groupInfo["summaryTypes"]) && $groupInfo["groupIndex"] < $groupInfo["groupCount"] - 2) {
|
||||
$result = array();
|
||||
$items = $dataItem["items"];
|
||||
$itemsCount = count($items);
|
||||
foreach ($items as $index => $item) {
|
||||
$currentSummaries = $item["summary"];
|
||||
if ($index == 0) {
|
||||
foreach ($currentSummaries as $summaryItem) {
|
||||
$result[] = $summaryItem;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
foreach ($groupInfo["summaryTypes"] as $si => $stItem) {
|
||||
if ($stItem == self::MIN_OP) {
|
||||
if ($result[$si] > $currentSummaries[$si]) {
|
||||
$result[$si] = $currentSummaries[$si];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($stItem == self::MAX_OP) {
|
||||
if ($result[$si] < $currentSummaries[$si]) {
|
||||
$result[$si] = $currentSummaries[$si];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$result[$si] += $currentSummaries[$si];
|
||||
}
|
||||
}
|
||||
foreach ($groupInfo["summaryTypes"] as $si => $stItem) {
|
||||
if ($stItem == self::AVG_OP) {
|
||||
$result[$si] /= $itemsCount;
|
||||
}
|
||||
}
|
||||
$dataItem["summary"] = $result;
|
||||
}
|
||||
}
|
||||
private static function _GetNewDataItem($row, $groupInfo) {
|
||||
$dataItem = array();
|
||||
$dataFieldCount = count($groupInfo["dataFieldNames"]);
|
||||
for ($index = 0; $index < $dataFieldCount; $index++) {
|
||||
$dataItem[$groupInfo["dataFieldNames"][$index]] = $row[$groupInfo["groupCount"] + $index];
|
||||
}
|
||||
return $dataItem;
|
||||
}
|
||||
private static function _GetNewGroupItem($row, $groupInfo, $explicitKey = null) {
|
||||
$groupIndexOffset = $groupInfo["lastGroupExpanded"] ? 1 : 2;
|
||||
|
||||
// Direkt row değerini al, explode kullanma
|
||||
$item = $explicitKey !== null ? $explicitKey : (isset($row[$groupInfo["groupIndex"]]) ? trim($row[$groupInfo["groupIndex"]]) : null);
|
||||
|
||||
$groupItem = array();
|
||||
$groupItem["key"] = ($item === "" || $item === null) ? null : $item;
|
||||
$groupItem["items"] = $groupInfo["groupIndex"] < $groupInfo["groupCount"] - $groupIndexOffset ? array() :
|
||||
($groupInfo["lastGroupExpanded"] ? array() : NULL);
|
||||
if ($groupInfo["groupIndex"] == $groupInfo["groupCount"] - $groupIndexOffset) {
|
||||
if (isset($groupInfo["summaryTypes"])) {
|
||||
$summaries = array();
|
||||
$endIndex = $groupInfo["groupIndex"] + count($groupInfo["summaryTypes"]) + 1;
|
||||
for ($index = $groupInfo["groupCount"]; $index <= $endIndex; $index++) {
|
||||
$summaries[] = $row[$index];
|
||||
}
|
||||
$groupItem["summary"] = $summaries;
|
||||
}
|
||||
if (!$groupInfo["lastGroupExpanded"]) {
|
||||
$groupItem["count"] = $row[$groupInfo["groupIndex"] + 1];
|
||||
}
|
||||
else {
|
||||
$groupItem["items"][] = self::_GetNewDataItem($row, $groupInfo);
|
||||
}
|
||||
}
|
||||
|
||||
return $groupItem;
|
||||
}
|
||||
private static function _GroupData($row, &$resultItems, $groupInfo) {
|
||||
$itemsCount = count($resultItems);
|
||||
if (!isset($row) && !$itemsCount) {
|
||||
return;
|
||||
}
|
||||
$currentItem = NULL;
|
||||
$groupIndexOffset = $groupInfo["lastGroupExpanded"] ? 1 : 2;
|
||||
|
||||
// Eğer bu bir tarih filtresi ise, özel yönetim yapalım
|
||||
$dateSequenceInfo = self::_getDateSequence($groupInfo);
|
||||
|
||||
if ($dateSequenceInfo['isDateSequence']) {
|
||||
// Tarih sırası algılandı, özel tarih işleme mantığını kullan
|
||||
self::_processDateSequence($row, $resultItems, $groupInfo, $dateSequenceInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
// Standart (tarih olmayan) gruplamalar için normal akış
|
||||
$currentKey = isset($row[$groupInfo["groupIndex"]]) ? trim($row[$groupInfo["groupIndex"]]) : null;
|
||||
|
||||
// Comma separation logic
|
||||
if (isset($groupInfo["processCommas"]) && $groupInfo["processCommas"] === true &&
|
||||
$currentKey !== null && strpos($currentKey, ',') !== false) {
|
||||
|
||||
$keys = explode(',', $currentKey);
|
||||
foreach ($keys as $key) {
|
||||
self::_processGroupDataItem($row, $resultItems, $groupInfo, trim($key));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Standart grup işlemlerini yapan yardımcı fonksiyon
|
||||
self::_processGroupDataItem($row, $resultItems, $groupInfo, $currentKey);
|
||||
}
|
||||
|
||||
// Standart grup işlemlerini yapan yardımcı fonksiyon
|
||||
private static function _processGroupDataItem($row, &$resultItems, $groupInfo, $explicitKey = null) {
|
||||
$currentKey = $explicitKey;
|
||||
if ($currentKey === "") {
|
||||
$currentKey = null;
|
||||
}
|
||||
|
||||
$groupIndexOffset = $groupInfo["lastGroupExpanded"] ? 1 : 2;
|
||||
|
||||
// Eğer bu key zaten varsa, ilgili item'ı kullan
|
||||
$keyExists = false;
|
||||
$currentItem = null;
|
||||
|
||||
foreach($resultItems as $index => $item) {
|
||||
if(isset($item["key"]) && (trim($item["key"]) === trim($currentKey) ||
|
||||
($item["key"] === null && ($currentKey === null || $currentKey === "")))) {
|
||||
$keyExists = true;
|
||||
$currentItem = &$resultItems[$index];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$keyExists) {
|
||||
$currentItem = self::_GetNewGroupItem($row, $groupInfo, $currentKey);
|
||||
$resultItems[] = &$currentItem;
|
||||
} else {
|
||||
// MERGE LOGIC for leaf groups
|
||||
if ($groupInfo["groupIndex"] == $groupInfo["groupCount"] - $groupIndexOffset) {
|
||||
// Update count
|
||||
if (isset($currentItem["count"])) {
|
||||
$currentItem["count"] += $row[$groupInfo["groupIndex"] + 1];
|
||||
}
|
||||
|
||||
// Update summaries if needed
|
||||
if (isset($currentItem["summary"]) && isset($groupInfo["summaryTypes"])) {
|
||||
$newSummaries = array();
|
||||
$endIndex = $groupInfo["groupIndex"] + count($groupInfo["summaryTypes"]) + 1;
|
||||
for ($index = $groupInfo["groupCount"]; $index <= $endIndex; $index++) {
|
||||
$newSummaries[] = $row[$index];
|
||||
}
|
||||
|
||||
foreach ($groupInfo["summaryTypes"] as $si => $stItem) {
|
||||
if ($stItem == self::MIN_OP) {
|
||||
if ($currentItem["summary"][$si] > $newSummaries[$si]) $currentItem["summary"][$si] = $newSummaries[$si];
|
||||
} else if ($stItem == self::MAX_OP) {
|
||||
if ($currentItem["summary"][$si] < $newSummaries[$si]) $currentItem["summary"][$si] = $newSummaries[$si];
|
||||
} else if ($stItem == self::SUM_OP || $stItem == self::COUNT_OP) {
|
||||
$currentItem["summary"][$si] += $newSummaries[$si];
|
||||
} else if ($stItem == self::AVG_OP) {
|
||||
$count1 = isset($currentItem["count"]) ? $currentItem["count"] : 1;
|
||||
$count2 = isset($row[$groupInfo["groupIndex"] + 1]) ? $row[$groupInfo["groupIndex"] + 1] : 1;
|
||||
|
||||
if (($count1 + $count2) > 0)
|
||||
$currentItem["summary"][$si] = (($currentItem["summary"][$si] * $count1) + ($newSummaries[$si] * $count2)) / ($count1 + $count2);
|
||||
else
|
||||
$currentItem["summary"][$si] = ($currentItem["summary"][$si] + $newSummaries[$si]) / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If expanded, append items
|
||||
if ($groupInfo["lastGroupExpanded"]) {
|
||||
$currentItem["items"][] = self::_GetNewDataItem($row, $groupInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($groupInfo["groupIndex"] < $groupInfo["groupCount"] - $groupIndexOffset) {
|
||||
$groupInfo["groupIndex"]++;
|
||||
self::_GroupData($row, $currentItem["items"], $groupInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// Tarih dizisini tanımla ve işle
|
||||
private static function _getDateSequence($groupInfo) {
|
||||
// Tarih sırası bilgisi
|
||||
$result = [
|
||||
'isDateSequence' => false,
|
||||
'sequence' => []
|
||||
];
|
||||
|
||||
if (!isset($groupInfo["groupNames"]) || count($groupInfo["groupNames"]) < 2) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$dateFields = [];
|
||||
|
||||
// Tüm tarih alanlarını tespit et
|
||||
foreach ($groupInfo["groupNames"] as $index => $name) {
|
||||
if (strpos($name, 'dx_') !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = '';
|
||||
if (strpos($name, '_year') !== false) {
|
||||
$type = 'year';
|
||||
} else if (strpos($name, '_month') !== false) {
|
||||
$type = 'month';
|
||||
} else if (strpos($name, '_day') !== false) {
|
||||
$type = 'day';
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dateFields[] = [
|
||||
'index' => $index,
|
||||
'type' => $type,
|
||||
'name' => $name
|
||||
];
|
||||
}
|
||||
|
||||
// Eğer en az 2 tarih alanı varsa, tarih sırası oluştur
|
||||
if (count($dateFields) >= 2) {
|
||||
// Yıl, ay, gün sırasına göre sırala
|
||||
usort($dateFields, function($a, $b) {
|
||||
$order = ['year' => 0, 'month' => 1, 'day' => 2];
|
||||
return $order[$a['type']] - $order[$b['type']];
|
||||
});
|
||||
|
||||
$result['isDateSequence'] = true;
|
||||
$result['sequence'] = $dateFields;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Tarih dizisini doğru bir şekilde işle
|
||||
private static function _processDateSequence($row, &$resultItems, $groupInfo, $dateSequenceInfo) {
|
||||
$groupIndexOffset = $groupInfo["lastGroupExpanded"] ? 1 : 2;
|
||||
$sequence = $dateSequenceInfo['sequence'];
|
||||
|
||||
// İlk seviye için gruplama yap (genellikle yıl)
|
||||
$firstLevelIndex = $sequence[0]['index'];
|
||||
$firstLevelType = $sequence[0]['type'];
|
||||
|
||||
$currentKey = isset($row[$firstLevelIndex]) ? trim($row[$firstLevelIndex]) : null;
|
||||
if ($currentKey === "") {
|
||||
$currentKey = null;
|
||||
}
|
||||
|
||||
// İlk seviyede (yıl) item ara
|
||||
$yearItem = null;
|
||||
foreach($resultItems as &$item) {
|
||||
if(isset($item["key"]) && (trim($item["key"]) === trim($currentKey) ||
|
||||
($item["key"] === null && ($currentKey === null || $currentKey === "")))) {
|
||||
$yearItem = &$item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Yıl item'ı yoksa oluştur
|
||||
if ($yearItem === null) {
|
||||
// Özel yıl item'ı oluştur
|
||||
$tempGroupInfo = $groupInfo;
|
||||
$tempGroupInfo["groupIndex"] = $firstLevelIndex;
|
||||
|
||||
$yearItem = self::_GetNewGroupItem($row, $tempGroupInfo);
|
||||
$yearItem["dateLevel"] = $firstLevelType;
|
||||
$resultItems[] = &$yearItem;
|
||||
}
|
||||
|
||||
// İkinci seviye için (genellikle ay)
|
||||
if (count($sequence) > 1) {
|
||||
$secondLevelIndex = $sequence[1]['index'];
|
||||
$secondLevelType = $sequence[1]['type'];
|
||||
$secondKey = isset($row[$secondLevelIndex]) ? trim($row[$secondLevelIndex]) : null;
|
||||
if ($secondKey === "") {
|
||||
$secondKey = null;
|
||||
}
|
||||
|
||||
// İkinci seviyede (ay) item ara
|
||||
$monthItem = null;
|
||||
foreach($yearItem["items"] as &$item) {
|
||||
if(isset($item["key"]) && (trim($item["key"]) === trim($secondKey) ||
|
||||
($item["key"] === null && ($secondKey === null || $secondKey === "")))) {
|
||||
$monthItem = &$item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Ay item'ı yoksa oluştur
|
||||
if ($monthItem === null) {
|
||||
// Özel ay item'ı oluştur
|
||||
$tempGroupInfo = $groupInfo;
|
||||
$tempGroupInfo["groupIndex"] = $secondLevelIndex;
|
||||
|
||||
$monthItem = self::_GetNewGroupItem($row, $tempGroupInfo);
|
||||
$monthItem["dateLevel"] = $secondLevelType;
|
||||
$yearItem["items"][] = &$monthItem;
|
||||
}
|
||||
|
||||
// Üçüncü seviye için (genellikle gün)
|
||||
if (count($sequence) > 2) {
|
||||
$thirdLevelIndex = $sequence[2]['index'];
|
||||
$thirdLevelType = $sequence[2]['type'];
|
||||
$thirdKey = isset($row[$thirdLevelIndex]) ? trim($row[$thirdLevelIndex]) : null;
|
||||
if ($thirdKey === "") {
|
||||
$thirdKey = null;
|
||||
}
|
||||
|
||||
// Üçüncü seviyede (gün) item ara
|
||||
$dayItem = null;
|
||||
foreach($monthItem["items"] as &$item) {
|
||||
if(isset($item["key"]) && (trim($item["key"]) === trim($thirdKey) ||
|
||||
($item["key"] === null && ($thirdKey === null || $thirdKey === "")))) {
|
||||
$dayItem = &$item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Gün item'ı yoksa oluştur
|
||||
if ($dayItem === null) {
|
||||
// Özel gün item'ı oluştur
|
||||
$tempGroupInfo = $groupInfo;
|
||||
$tempGroupInfo["groupIndex"] = $thirdLevelIndex;
|
||||
|
||||
$dayItem = self::_GetNewGroupItem($row, $tempGroupInfo);
|
||||
$dayItem["dateLevel"] = $thirdLevelType;
|
||||
$monthItem["items"][] = &$dayItem;
|
||||
}
|
||||
|
||||
// Veriler için
|
||||
if ($groupInfo["lastGroupExpanded"]) {
|
||||
$dataItem = self::_GetNewDataItem($row, $groupInfo);
|
||||
$dayItem["items"][] = $dataItem;
|
||||
}
|
||||
} else if ($groupInfo["lastGroupExpanded"]) {
|
||||
// İki seviye varsa (sadece yıl ve ay), veriyi ay seviyesinde göster
|
||||
$dataItem = self::_GetNewDataItem($row, $groupInfo);
|
||||
$monthItem["items"][] = $dataItem;
|
||||
}
|
||||
} else if ($groupInfo["lastGroupExpanded"]) {
|
||||
// Tek seviye varsa (sadece yıl), veriyi yıl seviyesinde göster
|
||||
$dataItem = self::_GetNewDataItem($row, $groupInfo);
|
||||
$yearItem["items"][] = $dataItem;
|
||||
}
|
||||
}
|
||||
public static function GetGroupedDataFromQuery($queryResult, $groupSettings) {
|
||||
$result = array();
|
||||
$row = NULL;
|
||||
$groupSummaryTypes = NULL;
|
||||
$dataFieldNames = NULL;
|
||||
$startSummaryFieldIndex = NULL;
|
||||
$endSummaryFieldIndex = NULL;
|
||||
if ($groupSettings["lastGroupExpanded"]) {
|
||||
$queryFields = $queryResult->fetch_fields();
|
||||
$dataFieldNames = array();
|
||||
for ($i = $groupSettings["groupCount"]; $i < count($queryFields); $i++) {
|
||||
$dataFieldNames[] = $queryFields[$i]->name;
|
||||
}
|
||||
}
|
||||
if (isset($groupSettings["summaryTypes"])) {
|
||||
$groupSummaryTypes = $groupSettings["summaryTypes"];
|
||||
$startSummaryFieldIndex = $groupSettings["groupCount"] - 1;
|
||||
$endSummaryFieldIndex = $startSummaryFieldIndex + count($groupSummaryTypes);
|
||||
}
|
||||
|
||||
// Grup isimlerini al (tarih filtreleri için gerekli)
|
||||
$groupNames = array();
|
||||
if (isset($groupSettings["groupNames"])) {
|
||||
$groupNames = $groupSettings["groupNames"];
|
||||
} elseif ($queryResult && $queryResult->field_count > 0) {
|
||||
$fields = $queryResult->fetch_fields();
|
||||
for ($i = 0; $i < $groupSettings["groupCount"]; $i++) {
|
||||
if (isset($fields[$i])) {
|
||||
$groupNames[] = $fields[$i]->name;
|
||||
}
|
||||
}
|
||||
// Sonuçlar için yeniden başa dönmek gerek
|
||||
$queryResult->data_seek(0);
|
||||
}
|
||||
|
||||
// Tarih alanlarını sıralamak için düzenleme (yıl, ay, gün)
|
||||
$dateGroupIndices = self::getDateGroupIndices($groupNames);
|
||||
|
||||
$groupInfo = array(
|
||||
"groupCount" => $groupSettings["groupCount"],
|
||||
"groupIndex" => 0,
|
||||
"summaryTypes" => $groupSummaryTypes,
|
||||
"lastGroupExpanded" => $groupSettings["lastGroupExpanded"],
|
||||
"dataFieldNames" => $dataFieldNames,
|
||||
"processCommas" => isset($groupSettings["processCommas"]) ? $groupSettings["processCommas"] : true,
|
||||
"groupNames" => $groupNames,
|
||||
"dateGroupIndices" => $dateGroupIndices
|
||||
);
|
||||
|
||||
while ($row = $queryResult->fetch_array(MYSQLI_NUM)) {
|
||||
if (isset($startSummaryFieldIndex)) {
|
||||
for ($i = $startSummaryFieldIndex; $i <= $endSummaryFieldIndex; $i++) {
|
||||
$row[$i] = Utils::StringToNumber($row[$i]);
|
||||
}
|
||||
}
|
||||
self::_GroupData($row, $result, $groupInfo);
|
||||
}
|
||||
if (!$groupSettings["lastGroupExpanded"]) {
|
||||
self::_GroupData($row, $result, $groupInfo);
|
||||
}
|
||||
else {
|
||||
if (isset($groupSettings["skip"]) && $groupSettings["skip"] >= 0 &&
|
||||
isset($groupSettings["take"]) && $groupSettings["take"] >= 0) {
|
||||
$result = array_slice($result, $groupSettings["skip"], $groupSettings["take"]);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private static function getDateGroupIndices($groupNames) {
|
||||
$yearIndex = -1;
|
||||
$monthIndex = -1;
|
||||
$dayIndex = -1;
|
||||
|
||||
foreach ($groupNames as $index => $name) {
|
||||
if (strpos($name, 'dx_') === 0) {
|
||||
if (strpos($name, '_year') !== false) {
|
||||
$yearIndex = $index;
|
||||
} else if (strpos($name, '_month') !== false) {
|
||||
$monthIndex = $index;
|
||||
} else if (strpos($name, '_day') !== false) {
|
||||
$dayIndex = $index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'year' => $yearIndex,
|
||||
'month' => $monthIndex,
|
||||
'day' => $dayIndex
|
||||
];
|
||||
}
|
||||
public static function IsLastGroupExpanded($items) {
|
||||
$result = true;
|
||||
$itemsCount = count($items);
|
||||
if ($itemsCount > 0) {
|
||||
$lastItem = $items[$itemsCount - 1];
|
||||
if (gettype($lastItem) === "object") {
|
||||
$result = isset($lastItem->isExpanded) ? $lastItem->isExpanded === true : true;
|
||||
}
|
||||
else {
|
||||
$result = true;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
public static function GetFieldSetBySelectors($items) {
|
||||
$group = "";
|
||||
$sort = "";
|
||||
$select = "";
|
||||
foreach ($items as $item) {
|
||||
$groupField = NULL;
|
||||
$sortField = NULL;
|
||||
$selectField = NULL;
|
||||
$desc = false;
|
||||
if (is_string($item) && strlen($item = trim($item))) {
|
||||
$selectField = $groupField = $sortField = Utils::QuoteStringValue($item);
|
||||
}
|
||||
else if (gettype($item) === "object" && isset($item->selector)) {
|
||||
$quoteSelector = Utils::QuoteStringValue($item->selector);
|
||||
$desc = isset($item->desc) ? $item->desc : false;
|
||||
|
||||
// Tarih gruplamalarını düzenle
|
||||
if (isset($item->groupInterval)) {
|
||||
if (is_int($item->groupInterval)) {
|
||||
$groupField = Utils::QuoteStringValue(sprintf("%s%s_%d", self::GENERATED_FIELD_PREFIX, $item->selector, $item->groupInterval));
|
||||
$selectField = sprintf("(%s - (%s %% %d)) %s %s",
|
||||
$quoteSelector,
|
||||
$quoteSelector,
|
||||
$item->groupInterval,
|
||||
self::AS_OP,
|
||||
$groupField);
|
||||
}
|
||||
else {
|
||||
// Tarih gruplamalarını düzgün şekilde yap
|
||||
$interval = strtolower($item->groupInterval);
|
||||
$groupField = Utils::QuoteStringValue(sprintf("%s%s_%s", self::GENERATED_FIELD_PREFIX, $item->selector, $interval));
|
||||
|
||||
if ($interval == 'year') {
|
||||
$selectField = sprintf("YEAR(%s) %s %s",
|
||||
$quoteSelector,
|
||||
self::AS_OP,
|
||||
$groupField);
|
||||
}
|
||||
else if ($interval == 'month') {
|
||||
$selectField = sprintf("MONTH(%s) %s %s",
|
||||
$quoteSelector,
|
||||
self::AS_OP,
|
||||
$groupField);
|
||||
}
|
||||
else if ($interval == 'day') {
|
||||
$selectField = sprintf("DAY(%s) %s %s",
|
||||
$quoteSelector,
|
||||
self::AS_OP,
|
||||
$groupField);
|
||||
}
|
||||
else if ($interval == 'dayofweek') {
|
||||
$selectField = sprintf("DAYOFWEEK(%s) - 1 %s %s",
|
||||
$quoteSelector,
|
||||
self::AS_OP,
|
||||
$groupField);
|
||||
}
|
||||
else {
|
||||
$selectField = sprintf("%s(%s) %s %s",
|
||||
strtoupper($interval),
|
||||
$quoteSelector,
|
||||
self::AS_OP,
|
||||
$groupField);
|
||||
}
|
||||
}
|
||||
$sortField = $groupField;
|
||||
}
|
||||
else {
|
||||
$selectField = $groupField = $sortField = $quoteSelector;
|
||||
}
|
||||
}
|
||||
if (isset($selectField)) {
|
||||
$select .= (strlen($select) > 0 ? ", ".$selectField : $selectField);
|
||||
}
|
||||
if (isset($groupField)) {
|
||||
$group .= (strlen($group) > 0 ? ", ".$groupField : $groupField);
|
||||
}
|
||||
if (isset($sortField)) {
|
||||
$sort .= (strlen($sort) > 0 ? ", ".$sortField : $sortField).
|
||||
($desc ? " DESC" : "");
|
||||
}
|
||||
}
|
||||
return array(
|
||||
"group" => $group,
|
||||
"sort" => $sort,
|
||||
"select" => $select
|
||||
);
|
||||
}
|
||||
private static function _IsSummaryTypeValid($summaryType) {
|
||||
return in_array($summaryType, array(self::MIN_OP, self::MAX_OP, self::AVG_OP, self::COUNT_OP, self::SUM_OP));
|
||||
}
|
||||
public static function GetSummaryInfo($expression, $tableName = null) {
|
||||
$result = array();
|
||||
$fields = "";
|
||||
$summaryTypes = array();
|
||||
foreach ($expression as $index => $item) {
|
||||
if (gettype($item) === "object" && isset($item->summaryType)) {
|
||||
$summaryType = strtoupper(trim($item->summaryType));
|
||||
if (!self::_IsSummaryTypeValid($summaryType)) {
|
||||
continue;
|
||||
}
|
||||
$summaryTypes[] = $summaryType;
|
||||
$selector = (isset($item->selector) && is_string($item->selector)) ? $item->selector : null;
|
||||
$quotedSelector = isset($selector) ? Utils::QuoteStringValue($selector) : "1";
|
||||
if (self::shouldApplyWeldedFilter($tableName, $item, $summaryType)) {
|
||||
$quotedSelector = self::applyWeldedFilterExpression($quotedSelector, $summaryType);
|
||||
}
|
||||
$fields .= sprintf("%s(%s) %s %sf%d",
|
||||
strlen($fields) > 0 ? ", ".$summaryType : $summaryType,
|
||||
$quotedSelector,
|
||||
self::AS_OP,
|
||||
self::GENERATED_FIELD_PREFIX,
|
||||
$index);
|
||||
}
|
||||
}
|
||||
$result["fields"] = $fields;
|
||||
$result["summaryTypes"] = $summaryTypes;
|
||||
return $result;
|
||||
}
|
||||
|
||||
private static function shouldApplyWeldedFilter($tableName, $item, $summaryType) {
|
||||
if ($tableName !== 'weld_logs') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$hasExplicitFlag = isset($item->weldedOnly) && $item->weldedOnly;
|
||||
$forceByContext = SummaryContext::shouldExcludeMechanical();
|
||||
|
||||
if (!$hasExplicitFlag && !$forceByContext) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($summaryType, [self::SUM_OP, self::AVG_OP]);
|
||||
}
|
||||
|
||||
private static function getMechanicalTypesList() {
|
||||
static $cachedList = null;
|
||||
if ($cachedList !== null) {
|
||||
return $cachedList;
|
||||
}
|
||||
|
||||
if (function_exists('get_mechanical_joint_types')) {
|
||||
$types = get_mechanical_joint_types();
|
||||
} else {
|
||||
$types = ['BJ', 'FJ', 'TH', 'CJ'];
|
||||
}
|
||||
|
||||
$escaped = array_map(function($type) {
|
||||
$normalized = strtoupper(trim($type));
|
||||
return "'" . addslashes($normalized) . "'";
|
||||
}, $types);
|
||||
|
||||
$cachedList = implode(",", $escaped);
|
||||
if ($cachedList === '') {
|
||||
$cachedList = "''";
|
||||
}
|
||||
|
||||
return $cachedList;
|
||||
}
|
||||
|
||||
private static function applyWeldedFilterExpression($quotedSelector, $summaryType) {
|
||||
$mechanicalList = self::getMechanicalTypesList();
|
||||
$typeColumnExpr = "UPPER(TRIM(COALESCE(`type_of_welds`, '')))";
|
||||
$thenValue = $summaryType === self::AVG_OP ? "NULL" : "0";
|
||||
return sprintf(
|
||||
"CASE WHEN %s IN (%s) THEN %s ELSE %s END",
|
||||
$typeColumnExpr,
|
||||
$mechanicalList,
|
||||
$thenValue,
|
||||
$quotedSelector
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
namespace App\DevExtreme;
|
||||
class DataSourceLoader {
|
||||
public static function Load($dbSet, $params, $module = null) {
|
||||
$result = NULL;
|
||||
if (isset($dbSet) && get_class($dbSet) == "App\DevExtreme\DbSet" && isset($params) && is_array($params)) {
|
||||
// Check if distinctColumn parameter is provided (for header filter unique values)
|
||||
// distinctColumn can come as string (from GET) or already parsed
|
||||
$distinctColumn = null;
|
||||
if (isset($params["distinctColumn"])) {
|
||||
$distinctColumn = $params["distinctColumn"];
|
||||
// If it's a JSON string, decode it
|
||||
if (is_string($distinctColumn) && (substr($distinctColumn, 0, 1) === '[' || substr($distinctColumn, 0, 1) === '{')) {
|
||||
$decoded = json_decode($distinctColumn, true);
|
||||
if ($decoded !== null) {
|
||||
$distinctColumn = $decoded;
|
||||
}
|
||||
}
|
||||
// Log for debugging
|
||||
error_log("DataSourceLoader: distinctColumn parameter received: " . var_export($distinctColumn, true) . " (type: " . gettype($distinctColumn) . ")");
|
||||
}
|
||||
|
||||
if (!empty($distinctColumn) && is_string($distinctColumn)) {
|
||||
|
||||
// Apply filters first
|
||||
$dbSet->Filter(Utils::GetItemValueOrDefault($params, "filter"));
|
||||
|
||||
if ($module === 'weldlog-employer-view' && $dbSet->getTableName() === 'weld_logs') {
|
||||
self::ApplyEmployerViewFilters($dbSet);
|
||||
}
|
||||
|
||||
// Get distinct values
|
||||
error_log("DataSourceLoader: Getting distinct values for column: " . $distinctColumn . " in table: " . $dbSet->getTableName());
|
||||
$distinctValues = $dbSet->GetDistinctValues($distinctColumn);
|
||||
if ($dbSet->GetLastError() !== NULL) {
|
||||
error_log("GetDistinctValues error: " . $dbSet->GetLastError());
|
||||
// Return error response instead of NULL
|
||||
$result = array();
|
||||
$result["data"] = array();
|
||||
$result["hasBlanks"] = false;
|
||||
$result["error"] = $dbSet->GetLastError();
|
||||
return $result;
|
||||
}
|
||||
error_log("DataSourceLoader: Found " . count($distinctValues) . " distinct values");
|
||||
|
||||
// Check for blanks
|
||||
$hasBlanks = $dbSet->HasBlanks($distinctColumn);
|
||||
if ($dbSet->GetLastError() !== NULL) {
|
||||
error_log("HasBlanks error: " . $dbSet->GetLastError());
|
||||
// Continue even if HasBlanks fails
|
||||
}
|
||||
|
||||
// Format response for header filter
|
||||
$result = array();
|
||||
$result["data"] = array_map(function($value) {
|
||||
return array("value" => $value, "text" => $value);
|
||||
}, $distinctValues);
|
||||
$result["hasBlanks"] = $hasBlanks;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Normal data loading
|
||||
$dbSet->Select(Utils::GetItemValueOrDefault($params, "select"))
|
||||
->Filter(Utils::GetItemValueOrDefault($params, "filter"));
|
||||
|
||||
if ($module === 'weldlog-employer-view' && $dbSet->getTableName() === 'weld_logs') {
|
||||
self::ApplyEmployerViewFilters($dbSet);
|
||||
}
|
||||
$totalSummary = $dbSet->GetTotalSummary(Utils::GetItemValueOrDefault($params, "totalSummary"),
|
||||
Utils::GetItemValueOrDefault($params, "filter"));
|
||||
if ($dbSet->GetLastError() !== NULL) {
|
||||
error_log("GetTotalSummary error: " . $dbSet->GetLastError());
|
||||
return $result;
|
||||
}
|
||||
$totalCount = (isset($params["requireTotalCount"]) && $params["requireTotalCount"] === true)
|
||||
? $dbSet->GetCount() : NULL;
|
||||
if ($dbSet->GetLastError() !== NULL) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$dbSet->Sort(Utils::GetItemValueOrDefault($params, "sort"));
|
||||
$groupCount = NULL;
|
||||
$skip = Utils::GetItemValueOrDefault($params, "skip");
|
||||
$take = Utils::GetItemValueOrDefault($params, "take");
|
||||
if (isset($params["group"])) {
|
||||
$groupExpression = $params["group"];
|
||||
$groupSummary = Utils::GetItemValueOrDefault($params, "groupSummary");
|
||||
$dbSet->Group($groupExpression, $groupSummary, $skip, $take);
|
||||
if (isset($params["requireGroupCount"]) && $params["requireGroupCount"] === true) {
|
||||
$groupCount = $dbSet->GetGroupCount();
|
||||
}
|
||||
}
|
||||
else {
|
||||
$dbSet->SkipTake($skip, $take);
|
||||
}
|
||||
$result = array();
|
||||
$result["data"] = $dbSet->AsArray();
|
||||
if ($dbSet->GetLastError() !== NULL) {
|
||||
return $result;
|
||||
}
|
||||
if (isset($totalCount)) {
|
||||
$result["totalCount"] = $totalCount;
|
||||
}
|
||||
if (isset($totalSummary)) {
|
||||
$result["summary"] = $totalSummary;
|
||||
}
|
||||
if (isset($groupCount)) {
|
||||
$result["groupCount"] = $groupCount;
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new \Exception("Invalid params");
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply employer view specific filters to weld_logs table
|
||||
* Filters:
|
||||
* 1. Exclude records with repair_status = 'Repair' in repair_logs
|
||||
* 2. Exclude records with NDT results other than 'Accept / Годен' or empty/null
|
||||
*/
|
||||
private static function ApplyEmployerViewFilters($dbSet) {
|
||||
$weldLogsTable = $dbSet->getTableName();
|
||||
|
||||
$repairFilter = "NOT EXISTS (
|
||||
SELECT 1 FROM repair_logs
|
||||
WHERE repair_logs.iso_number = {$weldLogsTable}.iso_number
|
||||
AND repair_logs.new_joint_no = {$weldLogsTable}.no_of_the_joint_as_per_as_built_survey
|
||||
AND repair_logs.repair_status = 'Repair'
|
||||
)";
|
||||
|
||||
$ndtFields = [
|
||||
'vt_result',
|
||||
'rt_result',
|
||||
'ut_result',
|
||||
'pt_result',
|
||||
'mt_result',
|
||||
'pmi_result',
|
||||
'ferrite_result',
|
||||
'ht_result'
|
||||
];
|
||||
|
||||
$ndtFilters = [];
|
||||
foreach ($ndtFields as $field) {
|
||||
$ndtFilters[] = "({$weldLogsTable}.{$field} IN ('Accept / Годен', '') OR {$weldLogsTable}.{$field} IS NULL)";
|
||||
}
|
||||
|
||||
$ndtFilter = implode(' AND ', $ndtFilters);
|
||||
|
||||
$combinedFilter = "({$repairFilter}) AND ({$ndtFilter})";
|
||||
|
||||
$dbSet->AddCustomWhere($combinedFilter);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,624 @@
|
||||
<?php
|
||||
namespace App\DevExtreme;
|
||||
class DbSet {
|
||||
private static $SELECT_OP = "SELECT";
|
||||
private static $FROM_OP = "FROM";
|
||||
private static $WHERE_OP = "WHERE";
|
||||
private static $ORDER_OP = "ORDER BY";
|
||||
private static $GROUP_OP = "GROUP BY";
|
||||
private static $ALL_FIELDS = "*";
|
||||
private static $LIMIT_OP = "LIMIT";
|
||||
private static $INSERT_OP = "INSERT INTO";
|
||||
private static $VALUES_OP = "VALUES";
|
||||
private static $UPDATE_OP = "UPDATE";
|
||||
private static $SET_OP = "SET";
|
||||
private static $DELETE_OP = "DELETE";
|
||||
private static $MAX_ROW_INDEX = 2147483647;
|
||||
private $dbTableName;
|
||||
private $tableNameIndex = 0;
|
||||
private $lastWrappedTableName;
|
||||
private $resultQuery;
|
||||
private $mySQL;
|
||||
private $lastError;
|
||||
private $groupSettings;
|
||||
private $customWhereClause = null;
|
||||
public function __construct($mySQL, $table) {
|
||||
if (!is_a($mySQL, "\mysqli") || !isset($table)) {
|
||||
throw new \Exception("Invalid params");
|
||||
}
|
||||
$this->mySQL = $mySQL;
|
||||
$this->dbTableName = $table;
|
||||
$this->resultQuery = sprintf("%s %s %s %s",
|
||||
self::$SELECT_OP,
|
||||
self::$ALL_FIELDS,
|
||||
self::$FROM_OP,
|
||||
$this->dbTableName);
|
||||
}
|
||||
public function GetLastError() {
|
||||
return $this->lastError;
|
||||
}
|
||||
|
||||
public function getTableName() {
|
||||
return $this->dbTableName;
|
||||
}
|
||||
private function _WrapQuery() {
|
||||
$this->tableNameIndex++;
|
||||
$this->lastWrappedTableName = "{$this->dbTableName}_{$this->tableNameIndex}";
|
||||
$this->resultQuery = sprintf("%s %s %s (%s) %s %s",
|
||||
self::$SELECT_OP,
|
||||
self::$ALL_FIELDS,
|
||||
self::$FROM_OP,
|
||||
$this->resultQuery,
|
||||
AggregateHelper::AS_OP,
|
||||
$this->lastWrappedTableName);
|
||||
}
|
||||
private function _PrepareQueryForLastOperator($operator) {
|
||||
$operator = trim($operator);
|
||||
$lastOperatorPos = strrpos($this->resultQuery, " ".$operator." ");
|
||||
if ($lastOperatorPos !== false) {
|
||||
$lastBracketPos = strrpos($this->resultQuery, ")");
|
||||
if (($lastBracketPos !== false && $lastOperatorPos > $lastBracketPos) || ($lastBracketPos === false)) {
|
||||
$this->_WrapQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
public function Select($expression) {
|
||||
Utils::EscapeExpressionValues($this->mySQL, $expression);
|
||||
$this->_SelectImpl($expression);
|
||||
return $this;
|
||||
}
|
||||
private function _SelectImpl($expression, $needQuotes = true) {
|
||||
if (isset($expression)) {
|
||||
$fields = "";
|
||||
if (is_string($expression)) {
|
||||
$expression = explode(",", $expression);
|
||||
}
|
||||
if (is_array($expression)) {
|
||||
foreach ($expression as $field) {
|
||||
$fields .= (strlen($fields) ? ", " : "").($needQuotes ? Utils::QuoteStringValue(trim($field)) : trim($field));
|
||||
}
|
||||
}
|
||||
if (strlen($fields)) {
|
||||
$allFieldOperatorPos = strpos($this->resultQuery, self::$ALL_FIELDS);
|
||||
if ($allFieldOperatorPos == 7) {
|
||||
$this->resultQuery = substr_replace($this->resultQuery, $fields, 7, strlen(self::$ALL_FIELDS));
|
||||
}
|
||||
else {
|
||||
$this->_WrapQuery();
|
||||
$this->_SelectImpl($expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public function Filter($expression) {
|
||||
Utils::EscapeExpressionValues($this->mySQL, $expression);
|
||||
if (isset($expression) && is_array($expression)) {
|
||||
$result = FilterHelper::GetSqlExprByArray($expression);
|
||||
if (strlen($result)) {
|
||||
$this->_PrepareQueryForLastOperator(self::$WHERE_OP);
|
||||
$this->resultQuery .= sprintf(" %s %s",
|
||||
self::$WHERE_OP,
|
||||
$result);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom raw SQL WHERE clause
|
||||
* This is used for complex filters that cannot be expressed via FilterHelper
|
||||
* @param string $rawWhereClause Raw SQL WHERE clause (without WHERE keyword)
|
||||
* @return $this
|
||||
*/
|
||||
public function AddCustomWhere($rawWhereClause) {
|
||||
if (isset($rawWhereClause) && strlen(trim($rawWhereClause)) > 0) {
|
||||
$this->_PrepareQueryForLastOperator(self::$WHERE_OP);
|
||||
if (stripos($this->resultQuery, " " . self::$WHERE_OP . " ") !== false) {
|
||||
$this->resultQuery .= sprintf(" AND (%s)",
|
||||
trim($rawWhereClause));
|
||||
if ($this->customWhereClause === null) {
|
||||
$this->customWhereClause = trim($rawWhereClause);
|
||||
} else {
|
||||
$this->customWhereClause .= " AND (" . trim($rawWhereClause) . ")";
|
||||
}
|
||||
} else {
|
||||
$this->resultQuery .= sprintf(" %s %s",
|
||||
self::$WHERE_OP,
|
||||
trim($rawWhereClause));
|
||||
$this->customWhereClause = trim($rawWhereClause);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
public function Sort($expression) {
|
||||
Utils::EscapeExpressionValues($this->mySQL, $expression);
|
||||
if (isset($expression)) {
|
||||
$result = "";
|
||||
if (is_string($expression)) {
|
||||
$result = trim($expression);
|
||||
}
|
||||
if (is_array($expression)) {
|
||||
$fieldSet = AggregateHelper::GetFieldSetBySelectors($expression);
|
||||
$result = $fieldSet["sort"];
|
||||
}
|
||||
if (strlen($result)) {
|
||||
$this->_PrepareQueryForLastOperator(self::$ORDER_OP);
|
||||
$this->resultQuery .= sprintf(" %s %s",
|
||||
self::$ORDER_OP,
|
||||
$result);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
public function SkipTake($skip, $take) {
|
||||
$skip = (!isset($skip) || !is_int($skip) ? 0 : $skip);
|
||||
$take = (!isset($take) || !is_int($take) ? self::$MAX_ROW_INDEX : $take);
|
||||
if ($skip != 0 || $take != 0) {
|
||||
$this->_PrepareQueryForLastOperator(self::$LIMIT_OP);
|
||||
$this->resultQuery .= sprintf(" %s %0.0f, %0.0f",
|
||||
self::$LIMIT_OP,
|
||||
$skip,
|
||||
$take);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract WHERE clause from resultQuery
|
||||
* @return string WHERE clause without WHERE keyword, or empty string
|
||||
*/
|
||||
private function _ExtractWhereClause() {
|
||||
$whereClause = "";
|
||||
$wherePos = stripos($this->resultQuery, " " . self::$WHERE_OP . " ");
|
||||
if ($wherePos !== false) {
|
||||
$afterWhere = substr($this->resultQuery, $wherePos + strlen(" " . self::$WHERE_OP . " "));
|
||||
// Find the end of WHERE clause by looking for ORDER BY, GROUP BY, or LIMIT
|
||||
// Use regex to find the first occurrence of these keywords
|
||||
$endPattern = '/\s+(ORDER\s+BY|GROUP\s+BY|LIMIT)\s+/i';
|
||||
if (preg_match($endPattern, $afterWhere, $matches, PREG_OFFSET_CAPTURE)) {
|
||||
$whereClause = trim(substr($afterWhere, 0, $matches[0][1]));
|
||||
} else {
|
||||
$whereClause = trim($afterWhere);
|
||||
}
|
||||
}
|
||||
return $whereClause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get distinct values for a specific column
|
||||
* @param string $columnName Column name to get distinct values from
|
||||
* @return array Array of distinct values
|
||||
*/
|
||||
public function GetDistinctValues($columnName) {
|
||||
$result = array();
|
||||
if ($this->mySQL && isset($columnName) && strlen($columnName) > 0) {
|
||||
Utils::EscapeExpressionValues($this->mySQL, $columnName);
|
||||
$quotedColumn = Utils::QuoteStringValue($columnName);
|
||||
|
||||
// Build the distinct query - use WHERE clause from resultQuery if exists
|
||||
$distinctQuery = sprintf("%s DISTINCT %s %s %s",
|
||||
self::$SELECT_OP,
|
||||
$quotedColumn,
|
||||
self::$FROM_OP,
|
||||
$this->dbTableName);
|
||||
|
||||
// Extract WHERE clause from resultQuery (after WHERE keyword)
|
||||
$whereClause = "";
|
||||
$wherePos = stripos($this->resultQuery, " " . self::$WHERE_OP . " ");
|
||||
if ($wherePos !== false) {
|
||||
// Get everything after WHERE
|
||||
$afterWhere = substr($this->resultQuery, $wherePos + strlen(" " . self::$WHERE_OP . " "));
|
||||
|
||||
// Remove ORDER BY, GROUP BY, LIMIT if they exist (find first occurrence)
|
||||
$endPattern = '/\s+(ORDER\s+BY|GROUP\s+BY|LIMIT)\s+/i';
|
||||
if (preg_match($endPattern, $afterWhere, $matches, PREG_OFFSET_CAPTURE)) {
|
||||
$whereClause = trim(substr($afterWhere, 0, $matches[0][1]));
|
||||
} else {
|
||||
$whereClause = trim($afterWhere);
|
||||
}
|
||||
|
||||
if (strlen($whereClause) > 0) {
|
||||
$distinctQuery .= " " . self::$WHERE_OP . " " . $whereClause;
|
||||
}
|
||||
}
|
||||
|
||||
// Also add customWhereClause if exists (from AddCustomWhere)
|
||||
if ($this->customWhereClause !== null) {
|
||||
if (strlen($whereClause) > 0) {
|
||||
$distinctQuery .= " AND (" . $this->customWhereClause . ")";
|
||||
} else {
|
||||
$distinctQuery .= " " . self::$WHERE_OP . " " . $this->customWhereClause;
|
||||
}
|
||||
}
|
||||
|
||||
// Add ORDER BY for sorted results
|
||||
$distinctQuery .= sprintf(" %s %s",
|
||||
self::$ORDER_OP,
|
||||
$quotedColumn);
|
||||
|
||||
// Limit results to prevent memory issues with very large distinct value sets
|
||||
// If there's a WHERE clause (filtered), we can return more results
|
||||
// If no filter, limit to first 1000 to prevent UI freezing
|
||||
$hasFilter = ($wherePos !== false && strlen($whereClause) > 0) || ($this->customWhereClause !== null);
|
||||
if (!$hasFilter) {
|
||||
$distinctQuery .= sprintf(" %s 1000",
|
||||
self::$LIMIT_OP);
|
||||
}
|
||||
|
||||
$this->lastError = NULL;
|
||||
$sanitizedQuery = $this->_sanitizeDateValues($distinctQuery);
|
||||
|
||||
// Debug: Log the query for troubleshooting
|
||||
error_log("GetDistinctValues SQL: " . $sanitizedQuery);
|
||||
error_log("GetDistinctValues resultQuery: " . $this->resultQuery);
|
||||
|
||||
$queryResult = $this->mySQL->query($sanitizedQuery);
|
||||
if (!$queryResult) {
|
||||
$this->lastError = $this->mySQL->error;
|
||||
error_log("GetDistinctValues query error: " . $this->lastError);
|
||||
}
|
||||
else {
|
||||
while ($row = $queryResult->fetch_array(MYSQLI_NUM)) {
|
||||
$value = $row[0];
|
||||
if ($value !== null && $value !== '') {
|
||||
$result[] = $value;
|
||||
}
|
||||
}
|
||||
$queryResult->close();
|
||||
error_log("GetDistinctValues found " . count($result) . " distinct values");
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are blank (NULL or empty) values for a specific column
|
||||
* @param string $columnName Column name to check
|
||||
* @return bool True if blanks exist, false otherwise
|
||||
*/
|
||||
public function HasBlanks($columnName) {
|
||||
$result = false;
|
||||
if ($this->mySQL && isset($columnName) && strlen($columnName) > 0) {
|
||||
Utils::EscapeExpressionValues($this->mySQL, $columnName);
|
||||
$quotedColumn = Utils::QuoteStringValue($columnName);
|
||||
|
||||
// Build the blank check query
|
||||
$blankQuery = sprintf("%s COUNT(1) %s %s",
|
||||
self::$SELECT_OP,
|
||||
self::$FROM_OP,
|
||||
$this->dbTableName);
|
||||
|
||||
// Extract WHERE clause from resultQuery
|
||||
$wherePos = stripos($this->resultQuery, " " . self::$WHERE_OP . " ");
|
||||
$blankCondition = sprintf("(%s IS NULL OR %s = '')",
|
||||
$quotedColumn,
|
||||
$quotedColumn);
|
||||
|
||||
if ($wherePos !== false) {
|
||||
// Get everything after WHERE
|
||||
$afterWhere = substr($this->resultQuery, $wherePos + strlen(" " . self::$WHERE_OP . " "));
|
||||
|
||||
// Remove ORDER BY, GROUP BY, LIMIT if they exist
|
||||
$endPattern = '/\s+(ORDER\s+BY|GROUP\s+BY|LIMIT)\s+/i';
|
||||
if (preg_match($endPattern, $afterWhere, $matches, PREG_OFFSET_CAPTURE)) {
|
||||
$whereClause = trim(substr($afterWhere, 0, $matches[0][1]));
|
||||
} else {
|
||||
$whereClause = trim($afterWhere);
|
||||
}
|
||||
|
||||
if (strlen($whereClause) > 0) {
|
||||
$blankQuery .= " " . self::$WHERE_OP . " " . $whereClause . " AND " . $blankCondition;
|
||||
} else {
|
||||
$blankQuery .= " " . self::$WHERE_OP . " " . $blankCondition;
|
||||
}
|
||||
} else {
|
||||
// No WHERE clause, add blank condition
|
||||
$blankQuery .= " " . self::$WHERE_OP . " " . $blankCondition;
|
||||
}
|
||||
|
||||
$this->lastError = NULL;
|
||||
$sanitizedQuery = $this->_sanitizeDateValues($blankQuery);
|
||||
$queryResult = $this->mySQL->query($sanitizedQuery);
|
||||
if (!$queryResult) {
|
||||
$this->lastError = $this->mySQL->error;
|
||||
}
|
||||
else if ($queryResult->num_rows > 0) {
|
||||
$row = $queryResult->fetch_array(MYSQLI_NUM);
|
||||
$result = Utils::StringToNumber($row[0]) > 0;
|
||||
$queryResult->close();
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
private function _CreateGroupCountQuery($firstGroupField, $skip = NULL, $take = NULL) {
|
||||
$groupCount = $this->groupSettings["groupCount"];
|
||||
$lastGroupExpanded = $this->groupSettings["lastGroupExpanded"];
|
||||
if (!$lastGroupExpanded) {
|
||||
if ($groupCount === 2) {
|
||||
$this->groupSettings["groupItemCountQuery"] = sprintf("%s COUNT(1) %s (%s) AS %s_%d",
|
||||
self::$SELECT_OP,
|
||||
self::$FROM_OP,
|
||||
$this->resultQuery,
|
||||
$this->dbTableName,
|
||||
$this->tableNameIndex + 1);
|
||||
if (isset($skip) || isset($take)) {
|
||||
$this->SkipTake($skip, $take);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$groupQuery = sprintf("%s COUNT(1) %s %s %s %s",
|
||||
self::$SELECT_OP,
|
||||
self::$FROM_OP,
|
||||
$this->dbTableName,
|
||||
self::$GROUP_OP,
|
||||
$firstGroupField);
|
||||
$this->groupSettings["groupItemCountQuery"] = sprintf("%s COUNT(1) %s (%s) AS %s_%d",
|
||||
self::$SELECT_OP,
|
||||
self::$FROM_OP,
|
||||
$groupQuery,
|
||||
$this->dbTableName,
|
||||
$this->tableNameIndex + 1);
|
||||
if (isset($skip) || isset($take)) {
|
||||
$this->groupSettings["skip"] = isset($skip) ? Utils::StringToNumber($skip) : 0;
|
||||
$this->groupSettings["take"] = isset($take) ? Utils::StringToNumber($take) : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
public function Group($expression, $groupSummary = NULL, $skip = NULL, $take = NULL) {
|
||||
Utils::EscapeExpressionValues($this->mySQL, $expression);
|
||||
Utils::EscapeExpressionValues($this->mySQL, $groupSummary);
|
||||
$this->groupSettings = NULL;
|
||||
if (isset($expression)) {
|
||||
$groupFields = "";
|
||||
$sortFields = "";
|
||||
$selectFields = "";
|
||||
$lastGroupExpanded = true;
|
||||
$groupCount = 0;
|
||||
if (is_string($expression)) {
|
||||
$selectFields = $sortFields = $groupFields = trim($expression);
|
||||
$groupCount = count(explode(",", $expression));
|
||||
}
|
||||
if (is_array($expression)) {
|
||||
$groupCount = count($expression);
|
||||
$fieldSet = AggregateHelper::GetFieldSetBySelectors($expression);
|
||||
$groupFields = $fieldSet["group"];
|
||||
$selectFields = $fieldSet["select"];
|
||||
$sortFields = $fieldSet["sort"];
|
||||
$lastGroupExpanded = AggregateHelper::IsLastGroupExpanded($expression);
|
||||
}
|
||||
if ($groupCount > 0) {
|
||||
if (!$lastGroupExpanded) {
|
||||
$groupSummaryData = isset($groupSummary) && is_array($groupSummary) ? AggregateHelper::GetSummaryInfo($groupSummary, $this->dbTableName) : NULL;
|
||||
$selectExpression = sprintf("%s, %s(1)%s",
|
||||
strlen($selectFields) ? $selectFields : $groupFields,
|
||||
AggregateHelper::COUNT_OP,
|
||||
(isset($groupSummaryData) && isset($groupSummaryData["fields"]) && strlen($groupSummaryData["fields"]) ?
|
||||
", ".$groupSummaryData["fields"] : ""));
|
||||
$groupCount++;
|
||||
$this->_WrapQuery();
|
||||
$this->_SelectImpl($selectExpression, false);
|
||||
$this->resultQuery .= sprintf(" %s %s",
|
||||
self::$GROUP_OP,
|
||||
$groupFields);
|
||||
$this->Sort($sortFields);
|
||||
}
|
||||
else {
|
||||
$this->_WrapQuery();
|
||||
$selectExpression = "{$selectFields}, {$this->lastWrappedTableName}.*";
|
||||
$this->_SelectImpl($selectExpression, false);
|
||||
$this->resultQuery .= sprintf(" %s %s",
|
||||
self::$ORDER_OP,
|
||||
$sortFields);
|
||||
}
|
||||
$lastGroupExpanded = true;
|
||||
$this->groupSettings = array();
|
||||
$this->groupSettings["groupCount"] = $groupCount;
|
||||
$this->groupSettings["lastGroupExpanded"] = $lastGroupExpanded;
|
||||
$this->groupSettings["summaryTypes"] = !$lastGroupExpanded ? $groupSummaryData["summaryTypes"] : NULL;
|
||||
$firstGroupField = explode(",", $groupFields)[0];
|
||||
$this->_CreateGroupCountQuery($firstGroupField, $skip, $take);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
public function GetTotalSummary($expression, $filterExpression = NULL) {
|
||||
Utils::EscapeExpressionValues($this->mySQL, $expression);
|
||||
Utils::EscapeExpressionValues($this->mySQL, $filterExpression);
|
||||
$result = NULL;
|
||||
if (isset($expression) && is_array($expression)) {
|
||||
$summaryInfo = AggregateHelper::GetSummaryInfo($expression, $this->dbTableName);
|
||||
$fields = $summaryInfo["fields"];
|
||||
if (strlen($fields) > 0) {
|
||||
$filter = "";
|
||||
if (isset($filterExpression)) {
|
||||
if (is_string($filterExpression)) {
|
||||
$filter = trim($filterExpression);
|
||||
}
|
||||
if (is_array($filterExpression)) {
|
||||
$filter = FilterHelper::GetSqlExprByArray($filterExpression);
|
||||
}
|
||||
}
|
||||
|
||||
$combinedFilter = "";
|
||||
if (strlen($filter) > 0 && $this->customWhereClause !== null) {
|
||||
$combinedFilter = "(" . $filter . ") AND (" . $this->customWhereClause . ")";
|
||||
} else if (strlen($filter) > 0) {
|
||||
$combinedFilter = $filter;
|
||||
} else if ($this->customWhereClause !== null) {
|
||||
$combinedFilter = $this->customWhereClause;
|
||||
}
|
||||
|
||||
$totalSummaryQuery = sprintf("%s %s %s %s %s",
|
||||
self::$SELECT_OP,
|
||||
$fields,
|
||||
self::$FROM_OP,
|
||||
$this->dbTableName,
|
||||
strlen($combinedFilter) > 0 ? self::$WHERE_OP." ".$combinedFilter : "");
|
||||
$this->lastError = NULL;
|
||||
$queryResult = $this->mySQL->query($totalSummaryQuery);
|
||||
if (!$queryResult) {
|
||||
$this->lastError = $this->mySQL->error;
|
||||
}
|
||||
else if ($queryResult->num_rows > 0) {
|
||||
$result = $queryResult->fetch_array(MYSQLI_NUM);
|
||||
foreach ($result as $i => $item) {
|
||||
$result[$i] = Utils::StringToNumber($item);
|
||||
}
|
||||
}
|
||||
if ($queryResult !== false) {
|
||||
$queryResult->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
public function GetGroupCount() {
|
||||
$result = 0;
|
||||
if ($this->mySQL && isset($this->groupSettings) && isset($this->groupSettings["groupItemCountQuery"])) {
|
||||
$this->lastError = NULL;
|
||||
$queryResult = $this->mySQL->query($this->groupSettings["groupItemCountQuery"]);
|
||||
if (!$queryResult) {
|
||||
$this->lastError = $this->mySQL->error;
|
||||
}
|
||||
else if ($queryResult->num_rows > 0) {
|
||||
$row = $queryResult->fetch_array(MYSQLI_NUM);
|
||||
$result = Utils::StringToNumber($row[0]);
|
||||
}
|
||||
if ($queryResult !== false) {
|
||||
$queryResult->close();
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
public function GetCount() {
|
||||
$result = 0;
|
||||
if ($this->mySQL) {
|
||||
$countQuery = sprintf("%s %s(1) %s (%s) %s %s_%d",
|
||||
self::$SELECT_OP,
|
||||
AggregateHelper::COUNT_OP,
|
||||
self::$FROM_OP,
|
||||
$this->resultQuery,
|
||||
AggregateHelper::AS_OP,
|
||||
$this->dbTableName,
|
||||
$this->tableNameIndex + 1);
|
||||
$this->lastError = NULL;
|
||||
$countQuery = $this->_sanitizeDateValues($countQuery);
|
||||
$queryResult = $this->mySQL->query($countQuery);
|
||||
if (!$queryResult) {
|
||||
$this->lastError = $this->mySQL->error;
|
||||
}
|
||||
else if ($queryResult->num_rows > 0) {
|
||||
$row = $queryResult->fetch_array(MYSQLI_NUM);
|
||||
$result = Utils::StringToNumber($row[0]);
|
||||
}
|
||||
if ($queryResult !== false) {
|
||||
$queryResult->close();
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
private function _sanitizeDateValues($query) {
|
||||
return preg_replace("/'0NaN-NaN-NaN'|'\d*NaN-NaN-NaN'/", "NULL", $query);
|
||||
}
|
||||
public function AsArray() {
|
||||
$result = NULL;
|
||||
if ($this->mySQL) {
|
||||
$this->lastError = NULL;
|
||||
$sanitizedQuery = $this->_sanitizeDateValues($this->resultQuery);
|
||||
$queryResult = $this->mySQL->query($sanitizedQuery);
|
||||
if (!$queryResult) {
|
||||
$this->lastError = $this->mySQL->error;
|
||||
}
|
||||
else {
|
||||
if (isset($this->groupSettings)) {
|
||||
$result = AggregateHelper::GetGroupedDataFromQuery($queryResult, $this->groupSettings);
|
||||
}
|
||||
else {
|
||||
$result = $queryResult->fetch_all(MYSQLI_ASSOC);
|
||||
}
|
||||
$queryResult->close();
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
public function Insert($values) {
|
||||
Utils::EscapeExpressionValues($this->mySQL, $values);
|
||||
$result = NULL;
|
||||
if (isset($values) && is_array($values)) {
|
||||
$fields = "";
|
||||
$fieldValues = "";
|
||||
foreach ($values as $prop => $value) {
|
||||
$fields .= (strlen($fields) ? ", " : "").Utils::QuoteStringValue($prop);
|
||||
$fieldValues .= (strlen($fieldValues) ? ", " : "").Utils::QuoteStringValue($value, false);
|
||||
}
|
||||
if (strlen($fields) > 0) {
|
||||
$queryString = sprintf("%s %s (%s) %s(%s)",
|
||||
self::$INSERT_OP,
|
||||
$this->dbTableName,
|
||||
$fields,
|
||||
self::$VALUES_OP,
|
||||
$fieldValues);
|
||||
$this->lastError = NULL;
|
||||
if ($this->mySQL->query($queryString) == true) {
|
||||
$result = $this->mySQL->affected_rows;
|
||||
}
|
||||
else {
|
||||
$this->lastError = $this->mySQL->error;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
public function Update($key, $values) {
|
||||
Utils::EscapeExpressionValues($this->mySQL, $key);
|
||||
Utils::EscapeExpressionValues($this->mySQL, $values);
|
||||
$result = NULL;
|
||||
if (isset($key) && is_array($key) && isset($values) && is_array($values)) {
|
||||
$fields = "";
|
||||
foreach ($values as $prop => $value) {
|
||||
$templ = strlen($fields) == 0 ? "%s = %s" : ", %s = %s";
|
||||
$fields .= sprintf($templ,
|
||||
Utils::QuoteStringValue($prop),
|
||||
Utils::QuoteStringValue($value, false));
|
||||
}
|
||||
if (strlen($fields) > 0) {
|
||||
$queryString = sprintf("%s %s %s %s %s %s",
|
||||
self::$UPDATE_OP,
|
||||
$this->dbTableName,
|
||||
self::$SET_OP,
|
||||
$fields,
|
||||
self::$WHERE_OP,
|
||||
FilterHelper::GetSqlExprByKey($key));
|
||||
$this->lastError = NULL;
|
||||
if ($this->mySQL->query($queryString) == true) {
|
||||
$result = $this->mySQL->affected_rows;
|
||||
}
|
||||
else {
|
||||
$this->lastError = $this->mySQL->error;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
public function Delete($key) {
|
||||
Utils::EscapeExpressionValues($this->mySQL, $key);
|
||||
$result = NULL;
|
||||
if (isset($key) && is_array($key)) {
|
||||
$queryString = sprintf("%s %s %s %s %s",
|
||||
self::$DELETE_OP,
|
||||
self::$FROM_OP,
|
||||
$this->dbTableName,
|
||||
self::$WHERE_OP,
|
||||
FilterHelper::GetSqlExprByKey($key));
|
||||
$this->lastError = NULL;
|
||||
if ($this->mySQL->query($queryString) == true) {
|
||||
$result = $this->mySQL->affected_rows;
|
||||
}
|
||||
else {
|
||||
$this->lastError = $this->mySQL->error;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
namespace App\DevExtreme;
|
||||
class FilterHelper {
|
||||
private static $AND_OP = "AND";
|
||||
private static $OR_OP = "OR";
|
||||
private static $LIKE_OP = "LIKE";
|
||||
private static $NOT_OP = "NOT";
|
||||
private static $IS_OP = "IS";
|
||||
private static function _GetSqlFieldName($field) {
|
||||
$fieldParts = explode(".", $field);
|
||||
$result = "";
|
||||
$fieldName = Utils::QuoteStringValue(trim($fieldParts[0]));
|
||||
if (count($fieldParts) == 2) {
|
||||
$dateProperty = trim($fieldParts[1]);
|
||||
$sqlDateFunction = "";
|
||||
$fieldPattern = "";
|
||||
switch ($dateProperty) {
|
||||
case "year":
|
||||
case "month":
|
||||
case "day": {
|
||||
$sqlDateFunction = strtoupper($dateProperty);
|
||||
$fieldPattern = "%s(%s)";
|
||||
break;
|
||||
}
|
||||
case "dayOfWeek": {
|
||||
$sqlDateFunction = strtoupper($dateProperty);
|
||||
$fieldPattern = "%s(%s) - 1";
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new \Exception("The \"".$dateProperty."\" command is not supported");
|
||||
}
|
||||
}
|
||||
$result = sprintf($fieldPattern, $sqlDateFunction, $fieldName);
|
||||
}
|
||||
else {
|
||||
$result = $fieldName;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
private static function _GetSimpleSqlExpr($expression) {
|
||||
$result = "";
|
||||
$itemsCount = count($expression);
|
||||
$fieldName = self::_GetSqlFieldName(trim($expression[0]));
|
||||
|
||||
// Known operators list
|
||||
$knownOperators = ['=', '<>', '>', '>=', '<', '<=', 'startswith', 'endswith', 'contains', 'notcontains', 'in', '==='];
|
||||
|
||||
if ($itemsCount == 2) {
|
||||
$val = $expression[1];
|
||||
$valLower = is_string($val) ? strtolower(trim($val)) : '';
|
||||
|
||||
// Check if second element is an operator (means searching for empty values)
|
||||
if (in_array($valLower, $knownOperators)) {
|
||||
// This is an operator without value - treat as empty value search
|
||||
switch ($valLower) {
|
||||
case 'contains':
|
||||
case 'startswith':
|
||||
case 'endswith':
|
||||
case '=':
|
||||
// Search for NULL or empty string
|
||||
$result = sprintf("(%s IS NULL OR %s = '')", $fieldName, $fieldName);
|
||||
break;
|
||||
case '<>':
|
||||
case 'notcontains':
|
||||
// Search for NOT NULL and NOT empty
|
||||
$result = sprintf("(%s IS NOT NULL AND %s != '')", $fieldName, $fieldName);
|
||||
break;
|
||||
default:
|
||||
// For other operators, just check IS NULL
|
||||
$result = sprintf("%s IS NULL", $fieldName);
|
||||
break;
|
||||
}
|
||||
} elseif ($val === "(Empty)" || $val === "(Boş)" || $val === "") {
|
||||
$result = sprintf("%s IS NULL", $fieldName);
|
||||
} else {
|
||||
$result = sprintf("%s = %s", $fieldName, Utils::QuoteStringValue($val, false));
|
||||
}
|
||||
}
|
||||
else if ($itemsCount == 3) {
|
||||
$clause = strtolower(trim($expression[1]));
|
||||
$val = $expression[2];
|
||||
|
||||
if ($val === "(Empty)" || $val === "(Boş)" || $val === "" || is_null($val)) {
|
||||
switch ($clause) {
|
||||
case "=":
|
||||
case "contains":
|
||||
case "startswith":
|
||||
case "endswith": {
|
||||
// Hem NULL hem empty string için kontrol
|
||||
if(strpos($fieldName, 'date') !== false) {
|
||||
$result = sprintf("(%s IS NULL)", $fieldName);
|
||||
} else {
|
||||
$result = sprintf("(%s IS NULL OR %s = '')", $fieldName, $fieldName);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
case "<>":
|
||||
case "notcontains": {
|
||||
// Ne NULL ne de empty string
|
||||
$result = sprintf("(%s IS NOT NULL AND %s != '')", $fieldName, $fieldName);
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch ($clause) {
|
||||
case "===": {
|
||||
// Exact match - DO NOT split comma-separated values
|
||||
$result = sprintf("%s = %s", $fieldName, Utils::QuoteStringValue($val, false));
|
||||
return $result;
|
||||
}
|
||||
case "=": {
|
||||
// Exact match - treat comma-separated values as single entity
|
||||
$pattern = "%s %s %s";
|
||||
$val = Utils::QuoteStringValue($val, false);
|
||||
break;
|
||||
}
|
||||
case "<>": {
|
||||
$pattern = "%s %s %s";
|
||||
$val = Utils::QuoteStringValue($val, false);
|
||||
break;
|
||||
}
|
||||
case ">":
|
||||
case ">=":
|
||||
case "<":
|
||||
case "<=": {
|
||||
$pattern = "%s %s %s";
|
||||
$val = Utils::QuoteStringValue($val, false);
|
||||
break;
|
||||
}
|
||||
case "startswith": {
|
||||
$pattern = "%s %s '%s%%'";
|
||||
$clause = self::$LIKE_OP;
|
||||
$val = addcslashes($val, "%_");
|
||||
break;
|
||||
}
|
||||
case "endswith": {
|
||||
$pattern = "%s %s '%%%s'";
|
||||
$val = addcslashes($val, "%_");
|
||||
$clause = self::$LIKE_OP;
|
||||
break;
|
||||
}
|
||||
case "contains": {
|
||||
$pattern = "%s %s '%%%s%%'";
|
||||
$val = addcslashes($val, "%_");
|
||||
$clause = self::$LIKE_OP;
|
||||
break;
|
||||
}
|
||||
case "notcontains": {
|
||||
$pattern = "%s %s '%%%s%%'";
|
||||
$val = addcslashes($val, "%_");
|
||||
$clause = sprintf("%s %s", self::$NOT_OP, self::$LIKE_OP);
|
||||
break;
|
||||
}
|
||||
case "noneof": {
|
||||
if (is_array($val) && count($val) > 0) {
|
||||
$quotedValues = array_map(function($v) {
|
||||
return Utils::QuoteStringValue($v, false);
|
||||
}, $val);
|
||||
$result = sprintf("%s NOT IN (%s)", $fieldName, implode(", ", $quotedValues));
|
||||
return $result;
|
||||
}
|
||||
$pattern = "%s <> %s";
|
||||
$val = Utils::QuoteStringValue($val, false);
|
||||
break;
|
||||
}
|
||||
case "anyof":
|
||||
case "in": {
|
||||
if (is_array($val) && count($val) > 0) {
|
||||
$quotedValues = array_map(function($v) {
|
||||
return Utils::QuoteStringValue($v, false);
|
||||
}, $val);
|
||||
$result = sprintf("%s IN (%s)", $fieldName, implode(", ", $quotedValues));
|
||||
return $result;
|
||||
}
|
||||
// Fall through to default if not array
|
||||
}
|
||||
default: {
|
||||
$clause = $clause ?: "=";
|
||||
$pattern = "%s %s %s";
|
||||
if(!is_array($val)) {
|
||||
$val = Utils::QuoteStringValue($val, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($pattern)) {
|
||||
$result = sprintf($pattern, $fieldName, $clause, $val);
|
||||
$result = str_replace(" = ''", " is null", $result);
|
||||
} else {
|
||||
$result = ""; // Should not happen with new default logic but safe fallback
|
||||
}
|
||||
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
public static function GetSqlExprByArray($expression) {
|
||||
$result = "(";
|
||||
$prevItemWasArray = false;
|
||||
foreach ($expression as $index => $item) {
|
||||
if (is_string($item)) {
|
||||
$prevItemWasArray = false;
|
||||
if ($index == 0) {
|
||||
if ($item == "!") {
|
||||
$result .= sprintf("%s ", self::$NOT_OP);
|
||||
continue;
|
||||
}
|
||||
$result .= (isset($expression) && is_array($expression)) ? self::_GetSimpleSqlExpr($expression) : "";
|
||||
break;
|
||||
}
|
||||
$strItem = strtoupper(trim($item));
|
||||
if ($strItem == self::$AND_OP || $strItem == self::$OR_OP) {
|
||||
$result .= sprintf(" %s ", $strItem);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (is_array($item)) {
|
||||
if ($prevItemWasArray) {
|
||||
$result .= sprintf(" %s ", self::$AND_OP);
|
||||
}
|
||||
$result .= self::GetSqlExprByArray($item);
|
||||
$prevItemWasArray = true;
|
||||
}
|
||||
}
|
||||
$result .= ")";
|
||||
return $result;
|
||||
}
|
||||
public static function GetSqlExprByKey($key) {
|
||||
$result = "";
|
||||
foreach ($key as $prop => $value) {
|
||||
$templ = strlen($result) == 0 ?
|
||||
"%s = %s" :
|
||||
" ".self::$AND_OP." %s = %s";
|
||||
$result .= sprintf($templ,
|
||||
Utils::QuoteStringValue($prop),
|
||||
Utils::QuoteStringValue($value, false));
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace App\DevExtreme;
|
||||
class LoadHelper {
|
||||
public static function LoadModule($className) {
|
||||
$namespaceNamePos = strpos($className, __NAMESPACE__);
|
||||
if ($namespaceNamePos === 0) {
|
||||
$subFolderPath = substr($className, $namespaceNamePos + strlen(__NAMESPACE__));
|
||||
$filePath = __DIR__.str_replace("\\", DIRECTORY_SEPARATOR, $subFolderPath).".php";
|
||||
require_once($filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\DevExtreme;
|
||||
|
||||
class SummaryContext
|
||||
{
|
||||
protected static bool $excludeMechanical = false;
|
||||
|
||||
public static function setExcludeMechanical(bool $flag): void
|
||||
{
|
||||
self::$excludeMechanical = $flag;
|
||||
}
|
||||
|
||||
public static function shouldExcludeMechanical(): bool
|
||||
{
|
||||
return self::$excludeMechanical;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
namespace App\DevExtreme;
|
||||
class Utils {
|
||||
private static $NULL_VAL = "NULL";
|
||||
private static $FORBIDDEN_CHARACTERS = array(
|
||||
"`", "\"", "'", "~", "!", "@", "#", "\$",
|
||||
"%", "=", "[", "]", "\\", "/" , "|", "^",
|
||||
"&", "*", "(", ")", "+", "<", ">", ",", "{",
|
||||
"}", "?", ":", ";", "\r", "\n"
|
||||
);
|
||||
public static function StringToNumber($str) {
|
||||
$currentLocale = localeconv();
|
||||
$decimalPoint = $currentLocale["decimal_point"];
|
||||
$result = strpos($str, $decimalPoint) === false ? intval($str) : floatval($str);
|
||||
return $result;
|
||||
}
|
||||
public static function EscapeExpressionValues($mySql, &$expression = NULL) {
|
||||
if (isset($expression)) {
|
||||
if (is_string($expression)) {
|
||||
$expression = $mySql->real_escape_string($expression);
|
||||
}
|
||||
else if (is_array($expression)) {
|
||||
foreach ($expression as &$arr_value) {
|
||||
self::EscapeExpressionValues($mySql, $arr_value);
|
||||
}
|
||||
unset($arr_value);
|
||||
}
|
||||
else if (gettype($expression) === "object") {
|
||||
foreach ($expression as $prop => $value) {
|
||||
self::EscapeExpressionValues($mySql, $expression->$prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static function QuoteStringValue($value, $isFieldName = true) {
|
||||
if (!$isFieldName) {
|
||||
$value = self::_ConvertDateTimeToMySQLValue($value);
|
||||
} else {
|
||||
$value = str_replace(self::$FORBIDDEN_CHARACTERS, "", $value);
|
||||
}
|
||||
$resultPattern = $isFieldName ? "`%s`" : (is_bool($value) || is_null($value) ? "%s" : "'%s'");
|
||||
$stringValue = is_bool($value) ? ($value ? "1" : "0") : (is_null($value) ? self::$NULL_VAL : strval($value));
|
||||
$result = sprintf($resultPattern, $stringValue);
|
||||
return $result;
|
||||
}
|
||||
public static function GetItemValueOrDefault($params, $key, $defaultValue = NULL) {
|
||||
return isset($params[$key]) ? $params[$key] : $defaultValue;
|
||||
}
|
||||
private static function _ConvertDatePartToISOValue($date) {
|
||||
$dateParts = explode("/", $date);
|
||||
return sprintf("%s-%s-%s", $dateParts[2], $dateParts[0], $dateParts[1]);
|
||||
}
|
||||
private static function _ConvertDateTimeToMySQLValue($strValue) {
|
||||
$result = $strValue;
|
||||
if (preg_match("/^\d{1,2}\/\d{1,2}\/\d{4}$/", $strValue) === 1) {
|
||||
$result = self::_ConvertDatePartToISOValue($strValue);
|
||||
}
|
||||
else if (preg_match("/^\d{1,2}\/\d{1,2}\/\d{4} \d{2}:\d{2}:\d{2}\.\d{3}$/", $strValue) === 1) {
|
||||
$spacePos = strpos($strValue, " ");
|
||||
$datePart = substr($strValue, 0, $spacePos);
|
||||
$timePart = substr($strValue, $spacePos + 1);
|
||||
$result = sprintf("%s %s", self::_ConvertDatePartToISOValue($datePart), $timePart);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Docs\Strategies;
|
||||
|
||||
use Knuckles\Scribe\Extracting\Strategies\Strategy;
|
||||
use Knuckles\Camel\Extraction\ExtractedEndpointData;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Custom Scribe Strategy to add admin-ajax endpoints to documentation.
|
||||
*
|
||||
* This strategy scans the admin-ajax blade directory and adds
|
||||
* each endpoint to the API documentation automatically.
|
||||
*/
|
||||
class AddEndpointsStrategy extends Strategy
|
||||
{
|
||||
/**
|
||||
* The stage this strategy belongs to.
|
||||
*/
|
||||
public string $stage = 'responses';
|
||||
|
||||
/**
|
||||
* Trait this strategy applies to.
|
||||
*/
|
||||
public static function routableIf(ExtractedEndpointData $endpoint): bool
|
||||
{
|
||||
// Only apply to the dynamic endpoint route
|
||||
return Str::contains($endpoint->route->uri, 'endpoints/{endpoint}');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ExtractedEndpointData $endpointData
|
||||
* @param array $routeRules
|
||||
* @return array|null
|
||||
*/
|
||||
public function __invoke(ExtractedEndpointData $endpointData, array $routeRules = []): ?array
|
||||
{
|
||||
// This strategy doesn't modify responses directly
|
||||
// The actual endpoint enumeration happens via the Artisan command
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Docs\Strategies;
|
||||
|
||||
use Knuckles\Scribe\Extracting\Strategies\Strategy;
|
||||
use Knuckles\Camel\Extraction\ExtractedEndpointData;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Custom Scribe Strategy to fix request body for GET/HEAD methods.
|
||||
*
|
||||
* This strategy ensures that request body is only included for
|
||||
* POST, PUT, PATCH, DELETE methods, not for GET/HEAD.
|
||||
*/
|
||||
class FixRequestBodyStrategy extends Strategy
|
||||
{
|
||||
/**
|
||||
* The stage this strategy belongs to.
|
||||
*/
|
||||
public string $stage = 'bodyParameters';
|
||||
|
||||
/**
|
||||
* Trait this strategy applies to.
|
||||
*/
|
||||
public static function routableIf(ExtractedEndpointData $endpoint): bool
|
||||
{
|
||||
// Only apply to the dynamic endpoint route
|
||||
return Str::contains($endpoint->route->uri, '{table}/{action}');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ExtractedEndpointData $endpointData
|
||||
* @param array $routeRules
|
||||
* @return array|null
|
||||
*/
|
||||
public function __invoke(ExtractedEndpointData $endpointData, array $routeRules = []): ?array
|
||||
{
|
||||
// Get HTTP methods for this endpoint
|
||||
$httpMethods = $endpointData->httpMethods ?? [];
|
||||
|
||||
// Check if the first method (primary method) is GET or HEAD
|
||||
// Scribe creates separate endpoints for each HTTP method
|
||||
if (count($httpMethods) > 0) {
|
||||
$primaryMethod = strtoupper($httpMethods[0]);
|
||||
|
||||
// If primary method is GET or HEAD, remove body parameters
|
||||
if (in_array($primaryMethod, ['GET', 'HEAD']) && count($endpointData->bodyParameters) > 0) {
|
||||
// Return empty array to remove body parameters
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, return null to keep existing body parameters
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace App\Docs\Strategies;
|
||||
|
||||
use Knuckles\Scribe\Extracting\Strategies\Strategy;
|
||||
use Knuckles\Camel\Extraction\ExtractedEndpointData;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Custom Scribe Strategy to fix URL parameters for dynamic routes.
|
||||
*
|
||||
* This strategy ensures that path parameters are correctly extracted
|
||||
* for routes with dynamic segments like /api/{table}/{action}
|
||||
*/
|
||||
class FixUrlParametersStrategy extends Strategy
|
||||
{
|
||||
/**
|
||||
* The stage this strategy belongs to.
|
||||
*/
|
||||
public string $stage = 'urlParameters';
|
||||
|
||||
/**
|
||||
* Trait this strategy applies to.
|
||||
*/
|
||||
public static function routableIf(ExtractedEndpointData $endpoint): bool
|
||||
{
|
||||
// Only apply to the dynamic endpoint route
|
||||
return Str::contains($endpoint->route->uri, '{table}/{action}');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ExtractedEndpointData $endpointData
|
||||
* @param array $routeRules
|
||||
* @return array|null
|
||||
*/
|
||||
public function __invoke(ExtractedEndpointData $endpointData, array $routeRules = []): ?array
|
||||
{
|
||||
// Extract path parameters from route URI
|
||||
$uri = $endpointData->route->uri;
|
||||
$parameters = [];
|
||||
|
||||
// Match {param} patterns in the URI
|
||||
if (preg_match_all('/\{(\w+)\}/', $uri, $matches)) {
|
||||
foreach ($matches[1] as $paramName) {
|
||||
// Always add/override parameter to ensure it exists
|
||||
// This ensures path parameters are always present even if
|
||||
// default strategies didn't extract them correctly
|
||||
// Scribe expects array format, not object
|
||||
$parameters[$paramName] = [
|
||||
'name' => $paramName,
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'description' => $this->getParameterDescription($paramName),
|
||||
'example' => $this->getParameterExample($paramName),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Always return parameters if found
|
||||
// Scribe will merge these with existing parameters
|
||||
if (count($parameters) > 0) {
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
// If no parameters found, return null to let other strategies handle it
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get description for a parameter
|
||||
*/
|
||||
private function getParameterDescription(string $paramName): string
|
||||
{
|
||||
$descriptions = [
|
||||
'table' => 'The Module Slug or Table Name.',
|
||||
'action' => 'The operation to perform.',
|
||||
];
|
||||
|
||||
return $descriptions[$paramName] ?? ucfirst($paramName) . ' parameter.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get example value for a parameter
|
||||
*/
|
||||
private function getParameterExample(string $paramName): string
|
||||
{
|
||||
$examples = [
|
||||
'table' => 'users',
|
||||
'action' => 'read',
|
||||
];
|
||||
|
||||
return $examples[$paramName] ?? 'example';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Throwable;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use App\Models\FeedbackReport;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
* A list of the exception types that are not reported.
|
||||
*
|
||||
* @var array<int, class-string<Throwable>>
|
||||
*/
|
||||
protected $dontReport = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* A list of the inputs that are never flashed for validation exceptions.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $dontFlash = [
|
||||
'current_password',
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
|
||||
/**
|
||||
* Register the exception handling callbacks for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->reportable(function (Throwable $e) {
|
||||
//
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exports;
|
||||
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
|
||||
use Maatwebsite\Excel\Concerns\WithEvents;
|
||||
use Maatwebsite\Excel\Events\AfterSheet;
|
||||
|
||||
class ExportExcel implements FromCollection, WithHeadings, ShouldAutoSize
|
||||
{
|
||||
|
||||
public $tableName;
|
||||
public $columnNames;
|
||||
public $exceptsColumns;
|
||||
public $module;
|
||||
|
||||
public function __construct(string $tableName, $columnNames = "", $module = null)
|
||||
{
|
||||
$this->tableName = $tableName;
|
||||
$this->module = $module ?? get("module");
|
||||
|
||||
$exceptsColumns = [
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'json',
|
||||
'pic',
|
||||
'slug',
|
||||
'email_verified_at',
|
||||
'password',
|
||||
'permissions',
|
||||
'alias',
|
||||
'remember_token',
|
||||
'recover',
|
||||
'last_seen',
|
||||
'branslar',
|
||||
'ust',
|
||||
'uid',
|
||||
'note'
|
||||
];
|
||||
/*
|
||||
try {
|
||||
$columnNames = explode(",", $columnNames);
|
||||
$this->columnNames = $columnNames;
|
||||
} catch (\Throwable $th) {
|
||||
$this->columnNames = array_diff(Schema::getColumnListing($this->tableName), $exceptsColumns);
|
||||
}
|
||||
*/
|
||||
$this->columnNames = array_diff(Schema::getColumnListing($this->tableName), $exceptsColumns);
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function collection()
|
||||
{
|
||||
|
||||
$query = db($this->tableName)
|
||||
->select($this->columnNames);
|
||||
|
||||
if($this->tableName == "users") {
|
||||
$query = $query->whereNotIn("level", ['Admin']);
|
||||
}
|
||||
|
||||
// Apply employer view filters for weldlog-employer-view module
|
||||
if($this->module === 'weldlog-employer-view' && $this->tableName === 'weld_logs') {
|
||||
$query = $this->applyEmployerViewFilters($query);
|
||||
}
|
||||
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply employer view specific filters to weld_logs table
|
||||
* Filters:
|
||||
* 1. Exclude records with repair_status = 'Repair' in repair_logs
|
||||
* 2. Exclude records with NDT results other than 'Accept / Годен' or empty/null
|
||||
*/
|
||||
private function applyEmployerViewFilters($query)
|
||||
{
|
||||
// Filter 1: Exclude repair logs
|
||||
$repairFilter = "NOT EXISTS (
|
||||
SELECT 1 FROM repair_logs
|
||||
WHERE repair_logs.iso_number = weld_logs.iso_number
|
||||
AND repair_logs.new_joint_no = weld_logs.no_of_the_joint_as_per_as_built_survey
|
||||
AND repair_logs.repair_status = 'Repair'
|
||||
)";
|
||||
|
||||
// Filter 2: Only acceptable NDT results
|
||||
$ndtFields = [
|
||||
'vt_result',
|
||||
'rt_result',
|
||||
'ut_result',
|
||||
'pt_result',
|
||||
'mt_result',
|
||||
'pmi_result',
|
||||
'ferrite_result',
|
||||
'ht_result'
|
||||
];
|
||||
|
||||
$ndtFilters = [];
|
||||
foreach ($ndtFields as $field) {
|
||||
$ndtFilters[] = "({$field} IN ('Accept / Годен', '') OR {$field} IS NULL)";
|
||||
}
|
||||
|
||||
$ndtFilter = implode(' AND ', $ndtFilters);
|
||||
|
||||
$combinedFilter = "({$repairFilter}) AND ({$ndtFilter})";
|
||||
|
||||
return $query->whereRaw($combinedFilter);
|
||||
}
|
||||
|
||||
public function headings(): array
|
||||
{
|
||||
return $this->columnNames;
|
||||
}
|
||||
/*
|
||||
public function registerEvents(): array
|
||||
{
|
||||
return [
|
||||
AfterSheet::class => function(AfterSheet $event)
|
||||
{
|
||||
$cellRange = 'A1:G1'; // All headers
|
||||
$event->sheet->getDelegate()->getStyle($cellRange)->getFont()->setBorder($cellRange, 'thin');
|
||||
$event->sheet->getDelegate()->getStyle($cellRange)->getFont()->setSize(14);
|
||||
},
|
||||
];
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exports;
|
||||
|
||||
use App\Models\NaksCertificate;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
|
||||
class ExportNaksCertificate implements FromCollection
|
||||
{
|
||||
/**
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function collection()
|
||||
{
|
||||
return NaksCertificate::get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Fields extends Model
|
||||
{
|
||||
//
|
||||
//use SoftDeletes;
|
||||
protected $table = 'fields';
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = true;
|
||||
public $timestamps = true;
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php function active_projects() {
|
||||
return db("weld_logs")->whereNotNull("project")->groupBy("project")->get()->pluck("project")->toArray();
|
||||
} ?>
|
||||
@@ -0,0 +1,448 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Register Creator için Excel tablosuna yeni satır ekleme fonksiyonu.
|
||||
*
|
||||
* Bu fonksiyon belge bilgilerini alarak Excel tablosuna formatlı bir şekilde yeni satır ekler,
|
||||
* ilgili PDF dosyasını kopyalar ve log kaydı oluşturur.
|
||||
*
|
||||
* @param array $search Aranılan dosya yolları
|
||||
* @param array $selectDocument Seçilen doküman bilgileri (title, path vs.)
|
||||
* @param string $fullFolder Hedef klasör yolu
|
||||
* @param string $lineNumber Hat numarası veya benzeri tanımlayıcı
|
||||
* @param string $documentDate Doküman tarihi
|
||||
* @param int $rowNo Satır numarası (sıra)
|
||||
* @param object $sheet PhpSpreadsheet nesnesi
|
||||
* @param int $currentRow Mevcut satır pozisyonu
|
||||
* @param bool $override Mevcut dosyaların üzerine yazılıp yazılmayacağı
|
||||
*
|
||||
* @return int Yeni eklenen satırın sonraki pozisyonu veya satır eklenemediyse mevcut pozisyon
|
||||
*/
|
||||
function addRowInTable($search, $selectDocument, $fullFolder, $lineNumber, $documentDate, $rowNo, &$sheet, $currentRow, $override = false)
|
||||
{
|
||||
try {
|
||||
// Log başlangıcı
|
||||
$logPrefix = "[ROW-$rowNo]";
|
||||
Log::debug("$logPrefix Processing document: " . $selectDocument['title2'] . ', Line: ' . $lineNumber);
|
||||
|
||||
// Aynı rapor numarası için zaten satır eklenmiş mi kontrol et
|
||||
static $addedReports = [];
|
||||
$reportKey = $selectDocument['title2'] . '_' . $lineNumber;
|
||||
|
||||
if (in_array($reportKey, $addedReports)) {
|
||||
Log::debug("$logPrefix Duplicate report detected, skipping: " . $selectDocument['title2'] . ' - ' . $lineNumber);
|
||||
|
||||
// Tekrarlı rapor için basit log
|
||||
try {
|
||||
$fullFolder = str_replace("storage/documents/", "", $fullFolder);
|
||||
$duplicateLog = "🔄 DUPLICATE: " . $selectDocument['title2'] . ' - ' . $lineNumber . "\n";
|
||||
Storage::append($fullFolder . 'log.txt', $duplicateLog);
|
||||
} catch (\Throwable $logError) {
|
||||
echo("⚠️ Log write error\n");
|
||||
}
|
||||
|
||||
return $currentRow;
|
||||
}
|
||||
|
||||
// Bu raporu eklenmiş olarak işaretle
|
||||
$addedReports[] = $reportKey;
|
||||
|
||||
$constructor = Cache::get("rc_contractor");
|
||||
$firstPage = Cache::get("rc_firstPage");
|
||||
$lastPage = Cache::get("rc_lastPage");
|
||||
|
||||
$rowTitle = $selectDocument['title2'];
|
||||
|
||||
if(isset($selectDocument['title3'])) {
|
||||
$rowTitle = $selectDocument['title3'];
|
||||
$selectDocument['title2'] = $selectDocument['title3'];
|
||||
}
|
||||
|
||||
if(isset($selectDocument['title4'])) {
|
||||
$rowTitle = $selectDocument['title4'];
|
||||
}
|
||||
|
||||
$convertTerm = $selectDocument['path'];
|
||||
|
||||
if(strpos($convertTerm, "Naks_Consumables") !== false) {
|
||||
$convertTerm = $selectDocument['path'] . '_' . $selectDocument['type'];
|
||||
}
|
||||
|
||||
if(strpos($convertTerm, "Procedure") !== false) {
|
||||
$convertTerm = $lineNumber;
|
||||
}
|
||||
|
||||
|
||||
if(isset($selectDocument['incoming_control_description'])) {
|
||||
$convertTerm = $selectDocument['incoming_control_description'];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Get row title safely - convertRu might return array
|
||||
$rowTitleRaw = convertRu($convertTerm);
|
||||
$rowTitle = is_array($rowTitleRaw) ? json_encode($rowTitleRaw) : (string)$rowTitleRaw;
|
||||
|
||||
// Log if conversion returned unexpected type
|
||||
if (is_array($rowTitleRaw)) {
|
||||
Log::warning('convertRu returned array', [
|
||||
'term' => $convertTerm,
|
||||
'result' => $rowTitleRaw
|
||||
]);
|
||||
}
|
||||
|
||||
// Basit dosya kontrolü ve log hazırlığı
|
||||
$foundFiles = [];
|
||||
$notFoundFiles = [];
|
||||
|
||||
foreach ($search as $searchPath) {
|
||||
if (file_exists($searchPath) || Storage::exists(str_replace("storage/documents/", "", $searchPath))) {
|
||||
$foundFiles[] = $searchPath;
|
||||
} else {
|
||||
$notFoundFiles[] = $searchPath;
|
||||
}
|
||||
}
|
||||
|
||||
if(!empty($foundFiles)) {
|
||||
// İlk bulunan dosyayı kullan
|
||||
$firstFoundFile = $foundFiles[0];
|
||||
|
||||
// Dosya türü istatistiği
|
||||
$fileExtension = strtolower(pathinfo($firstFoundFile, PATHINFO_EXTENSION));
|
||||
if (!isset($GLOBALS['file_type_stats'])) {
|
||||
$GLOBALS['file_type_stats'] = [];
|
||||
}
|
||||
if (!isset($GLOBALS['file_type_stats'][$fileExtension])) {
|
||||
$GLOBALS['file_type_stats'][$fileExtension] = 0;
|
||||
}
|
||||
$GLOBALS['file_type_stats'][$fileExtension]++;
|
||||
|
||||
$order = $selectDocument['order'] + 1;
|
||||
$addInLineNumber = ['по сварке трубопроводов(ЖСР)'];
|
||||
if(in_array($selectDocument['title2'], $addInLineNumber)) {
|
||||
$fileName = "$order - {$selectDocument['title2']} - $lineNumber.pdf";
|
||||
$fullPath = "$fullFolder"."$fileName";
|
||||
} else {
|
||||
if(isset($selectDocument['file_name'])) {
|
||||
$fileName = "$order - {$selectDocument['file_name']}.pdf";
|
||||
} else {
|
||||
if(isset($selectDocument['title2'])) {
|
||||
$fileName = "$order - {$selectDocument['title2']}.pdf";
|
||||
} else {
|
||||
$fileName = "$order - {$selectDocument['title2']}.pdf";
|
||||
}
|
||||
}
|
||||
$fullPath = "$fullFolder"."$fileName";
|
||||
}
|
||||
Log::debug("File name: " . $fileName);
|
||||
$documentDate = df($documentDate);
|
||||
|
||||
// Sayfa sayısını al
|
||||
$allPath = "{$firstFoundFile}";
|
||||
$command = "pdftk '$allPath' dump_data | grep NumberOfPages";
|
||||
putenv('LANG=ru_RU.UTF-8');
|
||||
$output = shell_exec($command);
|
||||
|
||||
try {
|
||||
$pageCount = (int) trim(explode(" ", $output)[1]);
|
||||
} catch (\Throwable $th) {
|
||||
$pageCount = 1;
|
||||
}
|
||||
|
||||
if($firstPage == 0) {
|
||||
$firstPage = 1;
|
||||
} else {
|
||||
$firstPage = $lastPage + 1;
|
||||
}
|
||||
|
||||
$lastPage = $firstPage + $pageCount - 1;
|
||||
Cache::put("rc_lastPage", $lastPage);
|
||||
Cache::put("rc_firstPage", $firstPage);
|
||||
|
||||
// Basit log formatı oluştur
|
||||
$simpleLog = "";
|
||||
$simpleLog .= "📋 " . ($rowNo) . ". " . $rowTitle . " - " . $lineNumber . " | " . count($foundFiles) . " file(s)\n";
|
||||
|
||||
foreach ($foundFiles as $index => $file) {
|
||||
$simpleLog .= " -- " . basename($file) . "\n";
|
||||
}
|
||||
|
||||
if (!empty($notFoundFiles)) {
|
||||
$simpleLog .= " ❓ Not found: " . count($notFoundFiles) . " file(s)\n";
|
||||
foreach ($notFoundFiles as $index => $file) {
|
||||
$simpleLog .= " -- " . basename($file) . " (missing)\n";
|
||||
}
|
||||
}
|
||||
$simpleLog .= "\n";
|
||||
|
||||
// Sayfa aralığını oluştur
|
||||
if ($pageCount == 1) {
|
||||
if($firstPage == 1) {
|
||||
$pageIndex = $firstPage;
|
||||
} else {
|
||||
$prevPage = $firstPage - 1;
|
||||
$pageIndex = "$prevPage - $firstPage";
|
||||
}
|
||||
} else {
|
||||
$pageIndex = "$firstPage - $lastPage";
|
||||
}
|
||||
|
||||
// Excel'e veri ekle
|
||||
try {
|
||||
$templateRowCells = [];
|
||||
$templateFormulas = [];
|
||||
$templateRow = Cache::get("rc_template_row", $currentRow);
|
||||
|
||||
foreach ($sheet->getRowIterator($templateRow, $templateRow)->current()->getCellIterator() as $cell) {
|
||||
$colIndex = $cell->getColumn();
|
||||
$templateRowCells[$colIndex] = $cell->getValue();
|
||||
|
||||
if ($cell->isFormula()) {
|
||||
$templateFormulas[$colIndex] = $cell->getValue();
|
||||
}
|
||||
}
|
||||
|
||||
$mergedCells = [];
|
||||
foreach ($sheet->getMergeCells() as $mergeRange) {
|
||||
if (preg_match('/^\D*'.$templateRow.'$/', explode(':', $mergeRange)[0])) {
|
||||
$mergedCells[] = $mergeRange;
|
||||
}
|
||||
}
|
||||
|
||||
$sheet->insertNewRowBefore($currentRow + 1, 1);
|
||||
|
||||
$replacements = [];
|
||||
$replacements['{rowNo}'] = $rowNo;
|
||||
$replacements['{rowTitle}'] = $rowTitle;
|
||||
$replacements['{documentReportNumber}'] = $lineNumber;
|
||||
$replacements['{documentDate}'] = $documentDate;
|
||||
$replacements['{constructor}'] = $constructor;
|
||||
$replacements['{pageCount}'] = $pageCount;
|
||||
$replacements['{pageIndex}'] = $pageIndex;
|
||||
|
||||
// Safe get with type checking for all title fields
|
||||
$replacements['{title1}'] = isset($selectDocument['title1'])
|
||||
? (is_array($selectDocument['title1']) ? json_encode($selectDocument['title1']) : (string)$selectDocument['title1'])
|
||||
: '';
|
||||
|
||||
$replacements['{ndt_report_no}'] = isset($selectDocument['title2'])
|
||||
? (is_array($selectDocument['title2']) ? json_encode($selectDocument['title2']) : (string)$selectDocument['title2'])
|
||||
: '';
|
||||
|
||||
$replacements['{pdf_document_title}'] = isset($selectDocument['title3'])
|
||||
? (is_array($selectDocument['title3']) ? json_encode($selectDocument['title3']) : (string)$selectDocument['title3'])
|
||||
: '';
|
||||
|
||||
$replacements['{ndt_register_title}'] = isset($selectDocument['title4'])
|
||||
? (is_array($selectDocument['title4']) ? json_encode($selectDocument['title4']) : (string)$selectDocument['title4'])
|
||||
: '';
|
||||
|
||||
foreach ($templateRowCells as $colIndex => $cellValue) {
|
||||
if (isset($templateFormulas[$colIndex])) {
|
||||
$formula = $templateFormulas[$colIndex];
|
||||
$rowOffset = ($currentRow + 1) - $templateRow;
|
||||
$updatedFormula = preg_replace_callback(
|
||||
'/([A-Z]+)(\d+)/',
|
||||
function($matches) use ($rowOffset) {
|
||||
$col = $matches[1];
|
||||
$row = intval($matches[2]);
|
||||
$row += $rowOffset;
|
||||
return $col . $row;
|
||||
},
|
||||
$formula
|
||||
);
|
||||
$sheet->setCellValue($colIndex . ($currentRow + 1), $updatedFormula);
|
||||
} else {
|
||||
if (is_string($cellValue)) {
|
||||
foreach ($replacements as $placeholder => $replacement) {
|
||||
if (strpos($cellValue, $placeholder) !== false) {
|
||||
$cellValue = str_replace($placeholder, $replacement, $cellValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
$sheet->setCellValue($colIndex . ($currentRow + 1), $cellValue);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($mergedCells as $mergeRange) {
|
||||
$adjustedMergeRange = preg_replace_callback('/\d+/', function($matches) use ($currentRow, $templateRow) {
|
||||
return $matches[0] == $templateRow ? $currentRow + 1 : $matches[0];
|
||||
}, $mergeRange);
|
||||
|
||||
try {
|
||||
$sheet->mergeCells($adjustedMergeRange);
|
||||
} catch (\Throwable $th) {
|
||||
// Hata önemsiz
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($sheet->getRowIterator($templateRow, $templateRow)->current()->getCellIterator() as $cell) {
|
||||
$colIndex = $cell->getColumn();
|
||||
try {
|
||||
$style = $sheet->getStyle($colIndex . $templateRow);
|
||||
$sheet->duplicateStyle($style, $colIndex . ($currentRow + 1));
|
||||
} catch (\Throwable $th) {
|
||||
// Hata önemsiz
|
||||
}
|
||||
}
|
||||
|
||||
$templateRowHeight = $sheet->getRowDimension($templateRow)->getRowHeight();
|
||||
$sheet->getRowDimension($currentRow + 1)->setRowHeight($templateRowHeight);
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
$simpleLog .= " ❌ Excel error: " . $th->getMessage() . "\n\n";
|
||||
throw $th;
|
||||
}
|
||||
|
||||
// Dosya kopyalama
|
||||
$originalSearchPath = $firstFoundFile;
|
||||
$search[0] = str_replace("storage/documents/", "", $firstFoundFile);
|
||||
$fullFolder = str_replace("storage/documents/", "", $fullFolder);
|
||||
$fullPath = $fullFolder . basename($fileName);
|
||||
|
||||
try {
|
||||
$targetDir = dirname($fullPath);
|
||||
if (!Storage::exists($targetDir)) {
|
||||
Storage::makeDirectory($targetDir);
|
||||
}
|
||||
|
||||
$copyStatus = "";
|
||||
|
||||
if(Storage::exists($fullPath)) {
|
||||
if ($override) {
|
||||
Storage::delete($fullPath);
|
||||
$copyStatus = "overwritten";
|
||||
} else {
|
||||
$currentContent = md5(Storage::get($fullPath));
|
||||
$newContent = '';
|
||||
if (Storage::exists($search[0])) {
|
||||
$newContent = md5(Storage::get($search[0]));
|
||||
} else if (file_exists($originalSearchPath)) {
|
||||
$newContent = md5(file_get_contents($originalSearchPath));
|
||||
}
|
||||
|
||||
if ($currentContent !== $newContent) {
|
||||
Storage::delete($fullPath);
|
||||
$copyStatus = "updated";
|
||||
} else {
|
||||
$copyStatus = "same content, skipped";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$copyStatus = "copied";
|
||||
}
|
||||
|
||||
if ($copyStatus !== "same content, skipped") {
|
||||
if (Storage::exists($search[0])) {
|
||||
Storage::copy($search[0], $fullPath);
|
||||
} else if (file_exists($originalSearchPath)) {
|
||||
$fileContent = file_get_contents($originalSearchPath);
|
||||
Storage::put($fullPath, $fileContent);
|
||||
}
|
||||
}
|
||||
|
||||
$simpleLog .= " 📁 File: " . $copyStatus . "\n";
|
||||
|
||||
// Template dosyası kontrolü (xlsx)
|
||||
if(isset($selectDocument['type']) && $selectDocument['type'] === 'template') {
|
||||
$xlsxSourcePath = str_replace('.pdf', '.xlsx', $search[0]);
|
||||
$xlsxOriginalSourcePath = str_replace('.pdf', '.xlsx', $originalSearchPath);
|
||||
$xlsxTargetPath = str_replace('.pdf', '.xlsx', $fullPath);
|
||||
|
||||
if(Storage::exists($xlsxSourcePath) || file_exists($xlsxOriginalSourcePath)) {
|
||||
$xlsxCopyStatus = "";
|
||||
|
||||
if(Storage::exists($xlsxTargetPath)) {
|
||||
if ($override) {
|
||||
Storage::delete($xlsxTargetPath);
|
||||
$xlsxCopyStatus = "overwritten";
|
||||
} else {
|
||||
$currentXlsxContent = md5(Storage::get($xlsxTargetPath));
|
||||
$newXlsxContent = '';
|
||||
if (Storage::exists($xlsxSourcePath)) {
|
||||
$newXlsxContent = md5(Storage::get($xlsxSourcePath));
|
||||
} else if (file_exists($xlsxOriginalSourcePath)) {
|
||||
$newXlsxContent = md5(file_get_contents($xlsxOriginalSourcePath));
|
||||
}
|
||||
|
||||
if ($currentXlsxContent !== $newXlsxContent) {
|
||||
Storage::delete($xlsxTargetPath);
|
||||
$xlsxCopyStatus = "updated";
|
||||
} else {
|
||||
$xlsxCopyStatus = "same content, skipped";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$xlsxCopyStatus = "copied";
|
||||
}
|
||||
|
||||
if ($xlsxCopyStatus !== "same content, skipped") {
|
||||
if (Storage::exists($xlsxSourcePath)) {
|
||||
Storage::copy($xlsxSourcePath, $xlsxTargetPath);
|
||||
} else if (file_exists($xlsxOriginalSourcePath)) {
|
||||
$xlsxContent = file_get_contents($xlsxOriginalSourcePath);
|
||||
Storage::put($xlsxTargetPath, $xlsxContent);
|
||||
}
|
||||
}
|
||||
|
||||
$simpleLog .= " 📊 XLSX: " . $xlsxCopyStatus . "\n";
|
||||
} else {
|
||||
$simpleLog .= " ❓ XLSX: not found\n";
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
$simpleLog .= " ❌ Copy error: " . $th->getMessage() . "\n";
|
||||
}
|
||||
|
||||
$simpleLog .= "\n";
|
||||
|
||||
// Basit log'u dosyaya ve ekrana yaz
|
||||
echo($simpleLog);
|
||||
Storage::append($fullFolder . 'log.txt', $simpleLog);
|
||||
|
||||
// Başarılı işlem sayacı
|
||||
if (!isset($GLOBALS['successful_operations'])) {
|
||||
$GLOBALS['successful_operations'] = 0;
|
||||
}
|
||||
$GLOBALS['successful_operations']++;
|
||||
|
||||
return $currentRow + 1;
|
||||
|
||||
} else {
|
||||
// Dosya bulunamadı - basit log
|
||||
$notFoundLog = "❌ " . ($rowNo) . ". " . $rowTitle . " - " . $lineNumber . " | NO FILES FOUND\n";
|
||||
$notFoundLog .= " Search paths:\n";
|
||||
foreach ($search as $index => $searchPath) {
|
||||
$notFoundLog .= " -- " . basename($searchPath) . "\n";
|
||||
}
|
||||
$notFoundLog .= "\n";
|
||||
|
||||
echo($notFoundLog);
|
||||
|
||||
try {
|
||||
$fullFolder = str_replace("storage/documents/", "", $fullFolder);
|
||||
Storage::append($fullFolder . 'log.txt', $notFoundLog);
|
||||
} catch (\Throwable $logError) {
|
||||
echo("❌ Log write error\n");
|
||||
}
|
||||
|
||||
return $currentRow;
|
||||
}
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
Log::error("Unexpected error in addRowInTable: " . $th->getMessage());
|
||||
|
||||
try {
|
||||
$errorLog = "❌ " . ($rowNo ?? 'UNKNOWN') . ". ERROR: " . $th->getMessage() . "\n\n";
|
||||
$fullFolder = str_replace("storage/documents/", "", $fullFolder ?? '');
|
||||
Storage::append($fullFolder . 'log.txt', $errorLog);
|
||||
echo($errorLog);
|
||||
} catch (\Throwable $logError) {
|
||||
echo("❌ Fatal error occurred\n");
|
||||
}
|
||||
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php function addRowInTableTpCreator($search, $selectDocument, $fullFolder, $lineNumber, $documentDate, $rowNo)
|
||||
{
|
||||
Log::debug("➡️ addRowInTableTpCreator called with parameters:");
|
||||
Log::debug("📄 Has search results: " . (isset($search[0]) ? "Yes" : "No"));
|
||||
Log::debug("📑 Document title: " . $selectDocument['title2']);
|
||||
Log::debug("📁 Folder: " . $fullFolder);
|
||||
Log::debug("🔢 Line number: " . $lineNumber);
|
||||
Log::debug("📅 Document date: " . $documentDate);
|
||||
Log::debug("🔢 Row number: " . $rowNo);
|
||||
|
||||
$constructor = Cache::get("rc_contractor");
|
||||
$firstPage = Cache::get("rc_firstPage");
|
||||
$lastPage = Cache::get("rc_lastPage");
|
||||
/*
|
||||
Log::info("firstPage $firstPage");
|
||||
Log::info("lastPage $lastPage");
|
||||
*/
|
||||
|
||||
$rowTitle = $selectDocument['title2'];
|
||||
|
||||
if(isset($selectDocument['title3'])) {
|
||||
$rowTitle = $selectDocument['title3'];
|
||||
$selectDocument['title2'] = $selectDocument['title3'];
|
||||
}
|
||||
|
||||
if(isset($selectDocument['title4'])) {
|
||||
$rowTitle = $selectDocument['title4'];
|
||||
}
|
||||
|
||||
|
||||
if(isset($search[0])) {
|
||||
|
||||
$order = $selectDocument['order'] + 1;
|
||||
@mkdir($fullFolder, 0777, true);
|
||||
Log::info("creating folder $fullFolder");
|
||||
|
||||
$addInLineNumber = ['по сварке трубопроводов(ЖСР)'];
|
||||
if(in_array($selectDocument['title2'], $addInLineNumber)) {
|
||||
$fileName = "$order - {$selectDocument['title2']} - $lineNumber.pdf";
|
||||
$fullPath = "$fullFolder"."$fileName";
|
||||
Log::debug("🔖 Special case file name with line number: " . $fileName);
|
||||
} else {
|
||||
$fileName = "$order - {$selectDocument['title2']}.pdf";
|
||||
$fullPath = "$fullFolder"."$fileName";
|
||||
Log::debug("🔖 Standard file name: " . $fileName);
|
||||
}
|
||||
|
||||
$documentDate = df($documentDate);
|
||||
$output = null;
|
||||
$returnValue = null;
|
||||
$allPath = "{$search[0]}";
|
||||
/*
|
||||
$command = "pdftk '$allPath' dump_data | grep NumberOfPages";
|
||||
// dump($command);
|
||||
// dump($allPath);
|
||||
putenv('LANG=ru_RU.UTF-8');
|
||||
|
||||
$output = shell_exec($command);
|
||||
|
||||
|
||||
try {
|
||||
$pageCount = (int) trim(explode(" ", $output)[1]);
|
||||
} catch (\Throwable $th) {
|
||||
$pageCount = 1;
|
||||
}
|
||||
|
||||
if($firstPage == 0) {
|
||||
$firstPage = 1;
|
||||
} else {
|
||||
$firstPage = $lastPage + 1;
|
||||
}
|
||||
|
||||
// Son sayfa numarasını hesapla
|
||||
$lastPage = $firstPage + $pageCount - 1;
|
||||
|
||||
Cache::put("rc_lastPage", $lastPage);
|
||||
Cache::put("rc_firstPage", $firstPage);
|
||||
|
||||
// Sayfa aralığını oluştur
|
||||
$pageIndex = ($firstPage == $lastPage) ? $firstPage : "$firstPage - $lastPage";
|
||||
|
||||
|
||||
|
||||
|
||||
$tableRow = "
|
||||
<tr class='bordered'>
|
||||
<td >$rowNo</td>
|
||||
<td width='50%' colspan='2'>$rowTitle</td>
|
||||
<td>$lineNumber</td>
|
||||
<td>$documentDate</td>
|
||||
<td>$constructor</td>
|
||||
<td>$pageCount</td>
|
||||
<td>$pageIndex</td>
|
||||
</tr>
|
||||
|
||||
";
|
||||
*/
|
||||
|
||||
$search[0] = str_replace("storage/documents/", "", $search[0]);
|
||||
$fullFolder = str_replace("storage/documents/", "", $fullFolder);
|
||||
$fullPath = $fullFolder . basename($fileName);
|
||||
Log::debug("🛠️ File path processing:");
|
||||
Log::debug("📄 Original source path: storage/documents/" . $search[0]);
|
||||
Log::debug("📁 Original folder path: storage/documents/" . $fullFolder);
|
||||
Log::debug("📄 Target filename: " . basename($fileName));
|
||||
Log::debug("📄 Final target path: " . $fullPath);
|
||||
/*
|
||||
Log::info("fullPath: " . $fullPath);
|
||||
Log::info("search: " . $search[0]);
|
||||
Log::info("fullFolder: " . $fullFolder);
|
||||
*/
|
||||
|
||||
try {
|
||||
if(Storage::exists($fullPath)) {
|
||||
$currentContent = md5(Storage::get($fullPath));
|
||||
$newContent = md5(Storage::get($search[0]));
|
||||
|
||||
Log::debug("📁 Checking file: " . $fullPath);
|
||||
Log::debug("🔑 Current content MD5: " . $currentContent);
|
||||
Log::debug("🔑 New content MD5: " . $newContent);
|
||||
|
||||
if ($currentContent !== $newContent) {
|
||||
$log = "🔃✅".$search[0] . " old file deleted and new file copied" . "\n";
|
||||
Log::debug("📝 " . $log);
|
||||
Storage::delete($fullPath);
|
||||
Storage::copy($search[0], $fullPath);
|
||||
|
||||
} else {
|
||||
$log = "🟰" . $search[0] . " already same file, not copied" . "\n";
|
||||
Log::debug("📝 " . $log);
|
||||
}
|
||||
} else {
|
||||
$log = "✅".$search[0] . " file copied" . "\n";
|
||||
Log::debug("📝 " . $log);
|
||||
Storage::copy($search[0], $fullPath);
|
||||
}
|
||||
|
||||
echo($log);
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
$log = "❌" . $search[0] . " error ---> \n " . $th->getMessage() . "\n";
|
||||
echo($log);
|
||||
Log::debug("❌ Error copying file: " . $search[0]);
|
||||
Log::debug("❌ Error message: " . $th->getMessage());
|
||||
Log::debug("❌ Error trace: " . $th->getTraceAsString());
|
||||
}
|
||||
|
||||
|
||||
Storage::append($fullFolder . 'log.txt', $log);
|
||||
Log::debug("📜 Appended to log file: " . $fullFolder . 'log.txt');
|
||||
Log::debug("📜 Log entry: " . $log);
|
||||
|
||||
$return = null; //= $tableRow;
|
||||
|
||||
|
||||
|
||||
} else {
|
||||
$log = "❓$rowTitle - $lineNumber not found " . "\n";
|
||||
echo($log);
|
||||
Log::debug("❓ Document not found: " . $rowTitle . " - " . $lineNumber);
|
||||
Log::debug("❓ Search path likely was: storage/documents/{$selectDocument['path']}/*{$lineNumber}*.pdf");
|
||||
$fullFolder = str_replace("storage/documents/", "", $fullFolder);
|
||||
Storage::append($fullFolder . 'log.txt', $log);
|
||||
Log::debug("📜 Appended to log file: " . $fullFolder . 'log.txt');
|
||||
$return = null;
|
||||
}
|
||||
|
||||
return $return;
|
||||
} ?>
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/**
|
||||
* Excel dosyasını analiz eder ve bellek kullanımı, sorunlu formüller hakkında bilgi verir
|
||||
*
|
||||
* @param string $filePath Excel dosyasının geçici yolu
|
||||
* @return array Analiz sonuçları
|
||||
*/
|
||||
function analyzeExcelFile($filePath) {
|
||||
// Varsayılan sonuç
|
||||
$result = [
|
||||
'file_size' => filesize($filePath),
|
||||
'file_size_formatted' => formatBytes(filesize($filePath)),
|
||||
'memory_warning' => false,
|
||||
'problematic_formulas' => []
|
||||
];
|
||||
|
||||
// 5MB üzerindeki dosyalar için bellek uyarısı
|
||||
if ($result['file_size'] > 5 * 1024 * 1024) {
|
||||
$result['memory_warning'] = true;
|
||||
}
|
||||
|
||||
// PhpSpreadsheet kütüphanesini kullanarak Excel'i analiz et
|
||||
try {
|
||||
require_once base_path('vendor/autoload.php');
|
||||
|
||||
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile($filePath);
|
||||
$reader->setReadDataOnly(false);
|
||||
$spreadsheet = $reader->load($filePath);
|
||||
|
||||
// Tüm çalışma sayfalarını kontrol et
|
||||
foreach ($spreadsheet->getWorksheetIterator() as $worksheet) {
|
||||
$sheetTitle = $worksheet->getTitle();
|
||||
|
||||
// Formül içeren hücreleri bul
|
||||
foreach ($worksheet->getCoordinates() as $coordinate) {
|
||||
$cell = $worksheet->getCell($coordinate);
|
||||
|
||||
if ($cell->isFormula()) {
|
||||
$formula = $cell->getValue();
|
||||
|
||||
// Sorunlu formülleri kontrol et
|
||||
// 1. Çok geniş aralıkları kontrol et (örn. A1:Z1000000)
|
||||
if (preg_match('/[A-Z]+\d+:[A-Z]+\d+/', $formula, $matches)) {
|
||||
foreach ($matches as $range) {
|
||||
list($start, $end) = explode(':', $range);
|
||||
|
||||
// Sayısal kısmı al
|
||||
preg_match('/\d+/', $end, $endRow);
|
||||
$endRowNum = intval($endRow[0]);
|
||||
|
||||
// Çok büyük aralıklar için uyarı (örneğin 10000 satırdan fazla)
|
||||
if ($endRowNum > 10000) {
|
||||
$result['problematic_formulas'][] = [
|
||||
'sheet' => $sheetTitle,
|
||||
'cell' => $coordinate,
|
||||
'formula' => $formula,
|
||||
'issue' => "Çok geniş aralık kullanımı: $range ($endRowNum satır)"
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. MAX, MIN, SUM gibi fonksiyonlarda büyük aralıklar
|
||||
if (preg_match('/(SUM|MIN|MAX|AVERAGE|COUNT)\s*\([A-Z]+\d+:[A-Z]+\d+\)/i', $formula, $matches)) {
|
||||
foreach ($matches as $func) {
|
||||
if (preg_match('/\(([A-Z]+\d+:[A-Z]+\d+)\)/', $func, $rangeMatches)) {
|
||||
$range = $rangeMatches[1];
|
||||
list($start, $end) = explode(':', $range);
|
||||
|
||||
preg_match('/\d+/', $end, $endRow);
|
||||
$endRowNum = intval($endRow[0]);
|
||||
|
||||
if ($endRowNum > 10000) {
|
||||
$result['problematic_formulas'][] = [
|
||||
'sheet' => $sheetTitle,
|
||||
'cell' => $coordinate,
|
||||
'formula' => $formula,
|
||||
'issue' => "Fonksiyonda büyük aralık: $func ($endRowNum satır)"
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. OFFSET fonksiyonunun kontrolü
|
||||
if (stripos($formula, 'OFFSET') !== false) {
|
||||
$result['problematic_formulas'][] = [
|
||||
'sheet' => $sheetTitle,
|
||||
'cell' => $coordinate,
|
||||
'formula' => $formula,
|
||||
'issue' => "OFFSET fonksiyonu kullanımı bellek problemlerine neden olabilir"
|
||||
];
|
||||
}
|
||||
|
||||
// 4. Çok karmaşık iç içe formüller
|
||||
$openParenCount = substr_count($formula, '(');
|
||||
if ($openParenCount > 10) {
|
||||
$result['problematic_formulas'][] = [
|
||||
'sheet' => $sheetTitle,
|
||||
'cell' => $coordinate,
|
||||
'formula' => $formula,
|
||||
'issue' => "Çok karmaşık iç içe formül ($openParenCount seviyesinde iç içe geçmiş)"
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Hata durumunda
|
||||
$result['error'] = $e->getMessage();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bayt cinsinden boyutu okunabilir formata çevirir
|
||||
*/
|
||||
function formatBytes($bytes, $precision = 2) {
|
||||
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
|
||||
$bytes = max($bytes, 0);
|
||||
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
|
||||
$pow = min($pow, count($units) - 1);
|
||||
|
||||
$bytes /= pow(1024, $pow);
|
||||
|
||||
return round($bytes, $precision) . ' ' . $units[$pow];
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
function autocomplete_url($tableName, $column) {
|
||||
return url("admin/autocomplete/$tableName/$column?with-column");
|
||||
}
|
||||
function autocomplete_url2($tableName, $column) {
|
||||
return url("admin/autocomplete/$tableName/$column");
|
||||
}
|
||||
function autocomplete_type($type) {
|
||||
return url("admin/autocomplete_type/$type");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
function cacheBladeLoad($cacheName) {
|
||||
|
||||
try {
|
||||
// Fetch the cached content
|
||||
$cachedContent = Storage::get('cache/'. $cacheName .'.blade.php');
|
||||
|
||||
// Display the cached content
|
||||
echo $cachedContent;
|
||||
} catch (\Throwable $th) {
|
||||
//throw $th;
|
||||
Log::error('Cache blade load error: ' . $th->getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
use App\Jobs\CacheBladeViewJob;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Dispatch cache blade view jobs
|
||||
*
|
||||
* This helper function dispatches CacheBladeViewJob for specified views.
|
||||
* It can be used to refresh cached blade views after data updates.
|
||||
*
|
||||
* @param array $cacheViews Optional array of cache views to dispatch.
|
||||
* If provided, only these views will be dispatched (default views will be ignored).
|
||||
* If empty, default cache views will be dispatched.
|
||||
* Format: [['view' => 'view.path', 'cache' => 'cache-name'], ...]
|
||||
* @return void
|
||||
*/
|
||||
function dispatchCacheBladeViews(array $cacheViews = [])
|
||||
{
|
||||
|
||||
// Default cache views that should be refreshed after spool-related operations
|
||||
$defaultCacheViews = [
|
||||
|
||||
[
|
||||
'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'
|
||||
],
|
||||
];
|
||||
|
||||
// If cacheViews is empty, use defaultCacheViews; otherwise use only cacheViews
|
||||
if (empty($cacheViews)) {
|
||||
$uniqueCacheViews = $defaultCacheViews;
|
||||
} else {
|
||||
// Remove duplicates based on cache name (keep the last one)
|
||||
$seen = [];
|
||||
$uniqueCacheViews = [];
|
||||
|
||||
// Reverse to process from end, so we keep the last occurrence
|
||||
foreach (array_reverse($cacheViews) as $cacheView) {
|
||||
// Validate array structure
|
||||
if (!is_array($cacheView) || !isset($cacheView['cache']) || !isset($cacheView['view'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cacheName = $cacheView['cache'];
|
||||
if (!in_array($cacheName, $seen)) {
|
||||
$seen[] = $cacheName;
|
||||
$uniqueCacheViews[] = $cacheView;
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse back to original order
|
||||
$uniqueCacheViews = array_reverse($uniqueCacheViews);
|
||||
}
|
||||
|
||||
// Dispatch job for each cache view
|
||||
foreach ($uniqueCacheViews as $cacheView) {
|
||||
if (isset($cacheView['view']) && isset($cacheView['cache'])) {
|
||||
try {
|
||||
// Rate Limiting (Debounce): Aynı cache view için 60 saniyede bir kez job oluştur.
|
||||
// Batch excel gibi aynı anda binlerce satırın import edildiği senaryolarda kuyruğun dolmasını engeller.
|
||||
$rateLimitKey = "rate_limit_dispatch_{$cacheView['cache']}";
|
||||
|
||||
if (!\Illuminate\Support\Facades\Cache::has($rateLimitKey)) {
|
||||
// 60 saniyeliğine kilitle
|
||||
\Illuminate\Support\Facades\Cache::put($rateLimitKey, true, 60);
|
||||
|
||||
// Job'u 15 saniye gecikmeli gönder (arka plandaki güncellemelerin bitmesi için zaman tanı)
|
||||
CacheBladeViewJob::dispatch($cacheView['view'], $cacheView['cache'])->delay(now()->addSeconds(15));
|
||||
|
||||
Log::info('CacheBladeViewJob dispatched to queue with 15s delay (Rate limit started)', [
|
||||
'view' => $cacheView['view'],
|
||||
'cache' => $cacheView['cache']
|
||||
]);
|
||||
} else {
|
||||
// Log::debug('CacheBladeViewJob skipped due to rate limit (debounce)', ['cache' => $cacheView['cache']]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Silently fail to prevent breaking the main operation
|
||||
Log::error('Failed to dispatch CacheBladeViewJob', [
|
||||
'view' => $cacheView['view'],
|
||||
'cache' => $cacheView['cache'],
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
use App\Models\CalibrationLog;
|
||||
use App\Models\WeldingEquipment;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
if (!function_exists('syncCalibrationFromWeldingEquipment')) {
|
||||
/**
|
||||
* Sync a single WeldingEquipment record into CalibrationLog table.
|
||||
*
|
||||
* This allows NAKS Welding Equipments to automatically appear in the
|
||||
* QA -> Calibration Log module while still allowing manual entries.
|
||||
*
|
||||
* @param \App\Models\WeldingEquipment|array $equipment
|
||||
* @return \App\Models\CalibrationLog
|
||||
*/
|
||||
function syncCalibrationFromWeldingEquipment($equipment): CalibrationLog
|
||||
{
|
||||
if (is_array($equipment)) {
|
||||
$data = $equipment;
|
||||
} else {
|
||||
$data = $equipment->toArray();
|
||||
}
|
||||
|
||||
$sourceId = $data['id'] ?? null;
|
||||
|
||||
\Log::debug("Sync attempt for equipment ID: " . $sourceId);
|
||||
|
||||
$log = CalibrationLog::firstOrNew([
|
||||
'source_module' => 'naks_welding_equipment',
|
||||
'source_id' => $sourceId,
|
||||
]);
|
||||
|
||||
\Log::debug("CalibrationLog exists: " . ($log->exists ? "yes (id: {$log->id})" : "no, will create new"));
|
||||
|
||||
$log->instrument = 'Welding Equipment';
|
||||
$log->description = $data['type_of_welding_machine'] ?? null;
|
||||
$log->item_no = $log->item_no ?? null;
|
||||
$log->equipment_name = $data['brand'] ?? null; // brand -> equipment_name
|
||||
$log->manufacturer = $data['producer'] ?? null; // producer -> manufacturer
|
||||
$log->serial_no = $data['manufacturer_code'] ?? null; // manufacturer_code -> serial_no
|
||||
$log->passport_certificate_no = $data['attestation'] ?? null;
|
||||
$log->quantity = 1; // Always set quantity to 1
|
||||
|
||||
// Use date_of_issue as calibration_date and valid_until as calibration_due_date
|
||||
if (!empty($data['date_of_issue'])) {
|
||||
$log->calibration_date = $data['date_of_issue'];
|
||||
}
|
||||
|
||||
if (!empty($data['valid_until'])) {
|
||||
$log->calibration_due_date = $data['valid_until'];
|
||||
}
|
||||
|
||||
// Status calculation based on due date
|
||||
if (!empty($log->calibration_due_date)) {
|
||||
$today = Carbon::today();
|
||||
$dueDate = Carbon::parse($log->calibration_due_date)->startOfDay();
|
||||
|
||||
if ($dueDate->lt($today)) {
|
||||
$log->status = 'Out Of service'; // Expired -> Out Of service
|
||||
} elseif ($dueDate->lte($today->copy()->addDays(20))) {
|
||||
$log->status = 'Calibration-Verification'; // Due Soon -> needs calibration
|
||||
} else {
|
||||
$log->status = 'In use'; // Valid -> In use
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $log->save();
|
||||
\Log::debug("Save result: " . ($result ? "success, new id: {$log->id}" : "failed"));
|
||||
} catch (\Exception $e) {
|
||||
\Log::error("Save exception: " . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $log;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
function col($size, $title="", $color="0", $options=[], $topButtons=[], $columns=[]) {
|
||||
$id = str_slug($title);
|
||||
$colors = colors();
|
||||
$u = u();
|
||||
?>
|
||||
|
||||
|
||||
|
||||
<div class="<?php echo $size ?>">
|
||||
<div class="block block-themed block-rounded <?php echo isset($options['border']) ? "border" : "" ?>" id="<?php echo $id; ?>">
|
||||
<?php if($title!="") {
|
||||
?>
|
||||
<div class="block-header
|
||||
<?php if($color!=-1) {
|
||||
?>
|
||||
bg-<?php echo $colors[$color]; ?>
|
||||
<?php
|
||||
} ?>
|
||||
">
|
||||
<div class="block-title"><?php echo e2($title) ?></div>
|
||||
<?php if(!isset($options['no-options'])) {
|
||||
?>
|
||||
<div class="block-options">
|
||||
<div type="button" class="btn-block-option select-columns d-none" data-toggle="modal" data-target="#select-columns" >
|
||||
<i class="fa fa-table-columns"></i>
|
||||
</div>
|
||||
<?php foreach($options AS $icon => $href) {
|
||||
?>
|
||||
<a href="<?php echo $href ?>" class="btn-block-option"><i class="fa fa-<?php echo $icon ?>"></i></a>
|
||||
<?php
|
||||
} ?>
|
||||
<?php if(isAuth($id, "write")) { ?>
|
||||
<div class="btn btn-outline-success add-btn d-none" onclick="$('#new-modal').modal()"><i class="fa fa-plus"></i></div>
|
||||
<?php } ?>
|
||||
|
||||
<!-- To toggle fullscreen a block, just add the following properties to your button: data-toggle="block-option" data-action="fullscreen_toggle" -->
|
||||
<button type="button" class="btn-block-option d-none" data-toggle="block-option"
|
||||
<?php if(oturumesit("full-screen-block", $id)) {
|
||||
?>
|
||||
onclick="$.get('?ajax=full-screen-block-remove')"
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
onclick="$.get('?ajax=full-screen-block&id=<?php echo($id) ?>')"
|
||||
<?php } ?>
|
||||
data-action="fullscreen_toggle"><i class="si si-size-fullscreen"></i></button>
|
||||
<?php if(isset($options['export'])) { ?>
|
||||
<?php if($u->level == "Admin") { ?>
|
||||
|
||||
<a href="<?php echo url("admin/truncate/". $options['export']) ?>" <?php echo delete_teyit() ?> class="btn-block-option " title="<?php echo e2("Delete All") ?>" ><i class="fa fa-trash"></i></a>
|
||||
|
||||
<?php } ?>
|
||||
<?php //if(is_stellar()) {
|
||||
?>
|
||||
<a target="_blank" href="<?php echo url("admin-ajax/export-query?table_name=". $options['export']) ?>&module=<?php echo $id ?>&columns=<?php echo base64_encode(implode(",", $columns)) ?>" class="btn-block-option" title="<?php echo e2("Export to CSV") ?>" ><i class="fa fa-download"></i> CSV</a>
|
||||
<a target="_blank" href="<?php echo url("admin-ajax/export-query?file_type=xlsx&table_name=". $options['export']) ?>&module=<?php echo $id ?>&columns=<?php echo base64_encode(implode(",", $columns)) ?>" class="btn-block-option" title="<?php echo e2("Export to XLSX") ?>" ><i class="fa fa-download"></i> XLSX</a>
|
||||
|
||||
<a href="<?php echo url("admin/export/". $options['export']) ?>?&module=<?php echo $id ?>&columns=<?php echo base64_encode(implode(",", $columns)) ?>" class="btn-block-option d-none" title="<?php echo e2("Export to Excel") ?>" ><i class="fa fa-download"></i></a>
|
||||
<?php // } ?>
|
||||
<?php if(isAuth($id, "write")) {
|
||||
?>
|
||||
<label
|
||||
for="excel-file"
|
||||
class="btn-block-option d-none" click="" title="<?php echo e2("Import to Excel") ?>" ><i class="fa fa-upload"></i></label>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
<?php if(isset($topButtons)) {
|
||||
foreach($topButtons AS $topButton) {
|
||||
?>
|
||||
<a href="<?php echo $topButton['href'] ?>" class="<?php echo @$topButton['class'] ?>"><?php echo $topButton['html'] ?></a>
|
||||
<?php
|
||||
}
|
||||
} ?>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
</div>
|
||||
<?php
|
||||
} ?>
|
||||
|
||||
<div class="block-content <?php echo isset($options['content-class']) ? $options['content-class'] : "" ?>">
|
||||
|
||||
|
||||
<?php
|
||||
}
|
||||
function _col() {
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
function col2($size, $title="", $color="0", $options=[], $topButtons=[], $columns=[]) {
|
||||
$id = str_slug($title);
|
||||
$colors = colors();
|
||||
$u = u();
|
||||
?>
|
||||
|
||||
<div class="<?php echo $size ?>">
|
||||
<div class="block block-themed block-rounded <?php echo isset($options['border']) ? "border" : "" ?>" id="<?php echo $id; ?>">
|
||||
<?php if(!isset($options['no-options']) && $title!="") { ?>
|
||||
<!-- Fixed toolbar for buttons, positioned at the top-right -->
|
||||
<div class="fixed-toolbar" style="position: fixed; top: 70px; right: 0px; width: auto; z-index: 1000; background-color: rgba(255,255,255,0.8); padding: 5px; border-radius: 4px; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">
|
||||
<div type="button" class="btn-block-option select-columns d-none" data-toggle="modal" data-target="#select-columns" >
|
||||
<i class="fa fa-table-columns"></i>
|
||||
</div>
|
||||
<?php foreach($options AS $icon => $href) { ?>
|
||||
<a href="<?php echo $href ?>" class="btn-block-option"><i class="fa fa-<?php echo $icon ?>"></i></a>
|
||||
<?php } ?>
|
||||
<?php if(isAuth($id, "write")) { ?>
|
||||
<div class="btn btn-outline-success add-btn d-none" onclick="$('#new-modal').modal()"><i class="fa fa-plus"></i></div>
|
||||
<?php } ?>
|
||||
|
||||
<!-- To toggle fullscreen a block, just add the following properties to your button: data-toggle="block-option" data-action="fullscreen_toggle" -->
|
||||
<button type="button" class="btn-block-option d-none" data-toggle="block-option"
|
||||
<?php if(oturumesit("full-screen-block", $id)) { ?>
|
||||
onclick="$.get('?ajax=full-screen-block-remove')"
|
||||
<?php } else { ?>
|
||||
onclick="$.get('?ajax=full-screen-block&id=<?php echo($id) ?>')"
|
||||
<?php } ?>
|
||||
data-action="fullscreen_toggle"><i class="si si-size-fullscreen"></i></button>
|
||||
|
||||
<?php if(isset($options['export'])) { ?>
|
||||
<?php if($u->level == "Admin") { ?>
|
||||
<a href="<?php echo url("admin/truncate/". $options['export']) ?>" <?php echo delete_teyit() ?> class="btn-block-option " title="<?php echo e2("Delete All") ?>" ><i class="fa fa-trash"></i></a>
|
||||
<?php } ?>
|
||||
<a target="_blank" href="<?php echo url("admin-ajax/export-query?table_name=". $options['export']) ?>&module=<?php echo $id ?>&columns=<?php echo base64_encode(implode(",", $columns)) ?>" class="btn-block-option" title="<?php echo e2("Export to CSV") ?>" ><i class="fa fa-download"></i> CSV (Semicolon ;;;)</a>
|
||||
<a target="_blank" href="<?php echo url("admin-ajax/export-query?table_name=". $options['export']) ?>&module=<?php echo $id ?>&delimiter=,&columns=<?php echo base64_encode(implode(",", $columns)) ?>" class="btn-block-option" title="<?php echo e2("Export to CSV") ?>" ><i class="fa fa-download"></i> CSV (Comma ,,,)</a>
|
||||
<a target="_blank" href="<?php echo url("admin-ajax/export-query?file_type=xlsx&table_name=". $options['export']) ?>&module=<?php echo $id ?>&columns=<?php echo base64_encode(implode(",", $columns)) ?>" class="btn-block-option" title="<?php echo e2("Export to XLSX") ?>" ><i class="fa fa-download"></i> XLSX</a>
|
||||
|
||||
<a href="<?php echo url("admin/export/". $options['export']) ?>?&module=<?php echo $id ?>&columns=<?php echo base64_encode(implode(",", $columns)) ?>" class="btn-block-option d-none" title="<?php echo e2("Export to Excel") ?>" ><i class="fa fa-download"></i></a>
|
||||
<?php if(isAuth($id, "write")) { ?>
|
||||
<label for="excel-file" class="btn-block-option d-none" click="" title="<?php echo e2("Import to Excel") ?>" ><i class="fa fa-upload"></i></label>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
<?php if(isset($topButtons)) {
|
||||
foreach($topButtons AS $topButton) { ?>
|
||||
<a href="<?php echo $topButton['href'] ?>" class="<?php echo @$topButton['class'] ?>"><?php echo $topButton['html'] ?></a>
|
||||
<?php }
|
||||
} ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<div class="block-content <?php echo isset($options['content-class']) ? $options['content-class'] : "" ?>">
|
||||
|
||||
|
||||
<?php
|
||||
}
|
||||
function _col2() {
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
function convertMapCache() {
|
||||
// Converter-map verilerini al
|
||||
$converterMap = j(setting("converter-map"));
|
||||
|
||||
// Mapping arrays oluştur ve cache'le
|
||||
$cacheKey = "converter_map_data";
|
||||
$mappings = Cache::remember($cacheKey, 3600, function() use ($converterMap) {
|
||||
$termToRu = $termToEn = $enToRu = $ruToEn = $enToTerm = $ruToTerm = [];
|
||||
|
||||
if (is_array($converterMap)) {
|
||||
foreach ($converterMap as $row) {
|
||||
if (!empty($row['term']) && !empty($row['term_ru'])) {
|
||||
$termToRu[$row['term']] = $row['term_ru'];
|
||||
}
|
||||
if (!empty($row['term']) && !empty($row['term_eng'])) {
|
||||
$termToEn[$row['term']] = $row['term_eng'];
|
||||
}
|
||||
if (!empty($row['term_eng']) && !empty($row['term_ru'])) {
|
||||
$enToRu[$row['term_eng']] = $row['term_ru'];
|
||||
}
|
||||
if (!empty($row['term_ru']) && !empty($row['term_eng'])) {
|
||||
$ruToEn[$row['term_ru']] = $row['term_eng'];
|
||||
}
|
||||
if (!empty($row['term_eng']) && !empty($row['term'])) {
|
||||
$enToTerm[$row['term_eng']] = $row['term'];
|
||||
}
|
||||
if (!empty($row['term_ru']) && !empty($row['term'])) {
|
||||
$ruToTerm[$row['term_ru']] = $row['term'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'termToRu' => $termToRu,
|
||||
'termToEn' => $termToEn,
|
||||
'enToRu' => $enToRu,
|
||||
'ruToEn' => $ruToEn,
|
||||
'enToTerm' => $enToTerm,
|
||||
'ruToTerm' => $ruToTerm
|
||||
];
|
||||
});
|
||||
|
||||
// Her bir mapping'i ayrı cache key'e kaydet
|
||||
Cache::put('termToRu', $mappings['termToRu'], 3600);
|
||||
Cache::put('termToEn', $mappings['termToEn'], 3600);
|
||||
Cache::put('enToRu', $mappings['enToRu'], 3600);
|
||||
Cache::put('ruToEn', $mappings['ruToEn'], 3600);
|
||||
Cache::put('enToTerm', $mappings['enToTerm'], 3600);
|
||||
Cache::put('ruToTerm', $mappings['ruToTerm'], 3600);
|
||||
|
||||
return $mappings;
|
||||
}
|
||||
function convertMap() {
|
||||
$cacheKey = "converter_map_data";
|
||||
|
||||
// Önce cache'den okumayı dene
|
||||
if (Cache::has($cacheKey)) {
|
||||
$mappings = Cache::get($cacheKey);
|
||||
// Cache'den okunan veri geçerli mi kontrol et
|
||||
if (!empty($mappings) && is_array($mappings)) {
|
||||
return $mappings;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache yoksa veya geçersizse yeniden oluştur
|
||||
$mappings = j(setting("converter-map"));
|
||||
|
||||
// Yeni veriyi cache'e kaydet
|
||||
if (!empty($mappings) && is_array($mappings)) {
|
||||
Cache::put($cacheKey, $mappings, 60);
|
||||
}
|
||||
|
||||
return $mappings;
|
||||
}
|
||||
|
||||
function convertRu($term) {
|
||||
// Önce cache'de mapping var mı kontrol et
|
||||
$converterMap = Cache::get('termToRu');
|
||||
|
||||
// Eğer cache'de yoksa, convertMapCache'i çağır
|
||||
if (empty($converterMap)) {
|
||||
convertMapCache();
|
||||
$converterMap = Cache::get('termToRu');
|
||||
}
|
||||
|
||||
if(isset($converterMap[$term])) {
|
||||
return $converterMap[$term];
|
||||
} else {
|
||||
Log::info("convertRu not found", ['term' => $term]);
|
||||
}
|
||||
return $term;
|
||||
|
||||
}
|
||||
|
||||
function convertEn($term) {
|
||||
// Önce cache'de mapping var mı kontrol et
|
||||
$converterMap = Cache::get('termToEn');
|
||||
|
||||
// Eğer cache'de yoksa, convertMapCache'i çağır
|
||||
if (empty($converterMap)) {
|
||||
convertMapCache();
|
||||
$converterMap = Cache::get('termToEn');
|
||||
}
|
||||
|
||||
if(isset($converterMap[$term])) {
|
||||
return $converterMap[$term];
|
||||
}
|
||||
return $term;
|
||||
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
function converterMapReplacer(?Spreadsheet $spreadsheet, $type="replacer") {
|
||||
// Converter-map verilerini al
|
||||
$converterMap = j(setting("converter-map"));
|
||||
|
||||
// Mapping arrays oluştur ve cache'le
|
||||
$cacheKey = "converter_map_data";
|
||||
$mappings = Cache::remember($cacheKey, 3600, function() use ($converterMap) {
|
||||
$termToRu = $termToEn = $enToRu = $ruToEn = $enToTerm = $ruToTerm = [];
|
||||
|
||||
if (is_array($converterMap)) {
|
||||
foreach ($converterMap as $row) {
|
||||
if (!empty($row['term']) && !empty($row['term_ru'])) {
|
||||
$termToRu[$row['term']] = $row['term_ru'];
|
||||
}
|
||||
if (!empty($row['term']) && !empty($row['term_eng'])) {
|
||||
$termToEn[$row['term']] = $row['term_eng'];
|
||||
}
|
||||
if (!empty($row['term_eng']) && !empty($row['term_ru'])) {
|
||||
$enToRu[$row['term_eng']] = $row['term_ru'];
|
||||
}
|
||||
if (!empty($row['term_ru']) && !empty($row['term_eng'])) {
|
||||
$ruToEn[$row['term_ru']] = $row['term_eng'];
|
||||
}
|
||||
if (!empty($row['term_eng']) && !empty($row['term'])) {
|
||||
$enToTerm[$row['term_eng']] = $row['term'];
|
||||
}
|
||||
if (!empty($row['term_ru']) && !empty($row['term'])) {
|
||||
$ruToTerm[$row['term_ru']] = $row['term'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'termToRu' => $termToRu,
|
||||
'termToEn' => $termToEn,
|
||||
'enToRu' => $enToRu,
|
||||
'ruToEn' => $ruToEn,
|
||||
'enToTerm' => $enToTerm,
|
||||
'ruToTerm' => $ruToTerm
|
||||
];
|
||||
});
|
||||
|
||||
if ($type == "replacer") {
|
||||
// Sadece spreadsheet null değilse işle
|
||||
if ($spreadsheet !== null) {
|
||||
try {
|
||||
// Spreadsheet'teki her sheet'i dolaş
|
||||
foreach ($spreadsheet->getAllSheets() as $sheet) {
|
||||
// Her satır ve hücreyi dolaş
|
||||
foreach ($sheet->getRowIterator() as $row) {
|
||||
foreach ($row->getCellIterator() as $cell) {
|
||||
$cellValue = $cell->getValue();
|
||||
if (is_string($cellValue)) {
|
||||
$originalValue = $cellValue;
|
||||
|
||||
// _en pattern'lerini bul ve değiştir (TERM_en -> İngilizce karşılığı)
|
||||
$cellValue = preg_replace_callback('/(\w+)_en\b/', function($matches) use ($mappings) {
|
||||
$term = $matches[1];
|
||||
if (isset($mappings['termToEn'][$term])) {
|
||||
return $mappings['termToEn'][$term];
|
||||
}
|
||||
return $matches[0]; // Bulunamazsa orijinal değeri döndür
|
||||
}, $cellValue);
|
||||
|
||||
// _ru pattern'lerini bul ve değiştir (TERM_ru -> Rusça karşılığı)
|
||||
$cellValue = preg_replace_callback('/(\w+)_ru\b/', function($matches) use ($mappings) {
|
||||
$term = $matches[1];
|
||||
if (isset($mappings['termToRu'][$term])) {
|
||||
return $mappings['termToRu'][$term];
|
||||
}
|
||||
return $matches[0]; // Bulunamazsa orijinal değeri döndür
|
||||
}, $cellValue);
|
||||
|
||||
// _tr pattern'lerini bul ve değiştir (TERM_tr -> Türkçe karşılığı)
|
||||
$cellValue = preg_replace_callback('/(\w+)_tr\b/', function($matches) use ($mappings) {
|
||||
$term = $matches[1];
|
||||
// Türkçe için term alanını kullan (orijinal)
|
||||
return $term; // Terim kendisi zaten Türkçe
|
||||
}, $cellValue);
|
||||
|
||||
// Placeholder formatında da çeviri yap: {TERM_en}, {TERM_ru}
|
||||
$cellValue = preg_replace_callback('/\{(\w+)_en\}/', function($matches) use ($mappings) {
|
||||
$term = $matches[1];
|
||||
if (isset($mappings['termToEn'][$term])) {
|
||||
return $mappings['termToEn'][$term];
|
||||
}
|
||||
return $matches[0]; // Bulunamazsa orijinal değeri döndür
|
||||
}, $cellValue);
|
||||
|
||||
$cellValue = preg_replace_callback('/\{(\w+)_ru\}/', function($matches) use ($mappings) {
|
||||
$term = $matches[1];
|
||||
if (isset($mappings['termToRu'][$term])) {
|
||||
return $mappings['termToRu'][$term];
|
||||
}
|
||||
return $matches[0]; // Bulunamazsa orijinal değeri döndür
|
||||
}, $cellValue);
|
||||
|
||||
$cellValue = preg_replace_callback('/\{(\w+)_tr\}/', function($matches) use ($mappings) {
|
||||
$term = $matches[1];
|
||||
// Türkçe için term alanını kullan (orijinal)
|
||||
return $term; // Terim kendisi zaten Türkçe
|
||||
}, $cellValue);
|
||||
|
||||
// Eğer değişiklik olduysa hücreyi güncelle
|
||||
if ($cellValue !== $originalValue) {
|
||||
$cell->setValue($cellValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Değiştirilmiş spreadsheet'i döndür
|
||||
return $spreadsheet;
|
||||
} catch (\Throwable $th) {
|
||||
// Hata durumunda log ama devam et
|
||||
logProcessInfo('Error in converterMapReplacer', [
|
||||
'error' => $th->getMessage(),
|
||||
'trace' => $th->getTraceAsString()
|
||||
], 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Spreadsheet null ise veya hata oluştuysa boş array döndür
|
||||
return [];
|
||||
} else {
|
||||
// "data" tipi için mevcut terimleri placeholder listesi olarak döndür
|
||||
$placeholders = [];
|
||||
|
||||
if (is_array($converterMap)) {
|
||||
foreach ($converterMap as $row) {
|
||||
if (!empty($row['term'])) {
|
||||
$term = $row['term'];
|
||||
// Farklı dil pattern'leri ekle
|
||||
$placeholders[] = $term . "_en";
|
||||
$placeholders[] = $term . "_ru";
|
||||
$placeholders[] = $term . "_tr";
|
||||
$placeholders[] = "{" . $term . "_en}";
|
||||
$placeholders[] = "{" . $term . "_ru}";
|
||||
$placeholders[] = "{" . $term . "_tr}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($placeholders);
|
||||
}
|
||||
}
|
||||
|
||||
// String replacement için yardımcı fonksiyon
|
||||
function converterMapStringReplacer($string) {
|
||||
// Cache kullanarak converter-map verilerini al (1 saat süreyle)
|
||||
$mappings = Cache::remember('converter_map_string_data', 3600, function() {
|
||||
$converterMap = j(setting("converter-map"));
|
||||
$termToRu = $termToEn = [];
|
||||
|
||||
if (is_array($converterMap)) {
|
||||
foreach ($converterMap as $row) {
|
||||
if (!empty($row['term']) && !empty($row['term_ru'])) {
|
||||
$termToRu[$row['term']] = $row['term_ru'];
|
||||
}
|
||||
if (!empty($row['term']) && !empty($row['term_eng'])) {
|
||||
$termToEn[$row['term']] = $row['term_eng'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'termToRu' => $termToRu,
|
||||
'termToEn' => $termToEn
|
||||
];
|
||||
});
|
||||
|
||||
$termToRu = $mappings['termToRu'];
|
||||
$termToEn = $mappings['termToEn'];
|
||||
|
||||
|
||||
// _en pattern'lerini değiştir
|
||||
$string = preg_replace_callback('/(\w+)_en\b/', function($matches) use ($termToEn) {
|
||||
$term = $matches[1];
|
||||
if (isset($termToEn[$term])) {
|
||||
return $termToEn[$term];
|
||||
}
|
||||
return $matches[0];
|
||||
}, $string);
|
||||
|
||||
// _ru pattern'lerini değiştir
|
||||
$string = preg_replace_callback('/(\w+)_ru\b/', function($matches) use ($termToRu) {
|
||||
$term = $matches[1];
|
||||
if (isset($termToRu[$term])) {
|
||||
return $termToRu[$term];
|
||||
}
|
||||
return $matches[0];
|
||||
}, $string);
|
||||
|
||||
// _tr pattern'lerini değiştir (Türkçe - orijinal terim)
|
||||
$string = preg_replace_callback('/(\w+)_tr\b/', function($matches) {
|
||||
$term = $matches[1];
|
||||
return $term; // Terim kendisi zaten Türkçe
|
||||
}, $string);
|
||||
|
||||
// Placeholder formatında da çeviri yap
|
||||
$string = preg_replace_callback('/\{(\w+)_en\}/', function($matches) use ($termToEn) {
|
||||
$term = $matches[1];
|
||||
if (isset($termToEn[$term])) {
|
||||
return $termToEn[$term];
|
||||
}
|
||||
return $matches[0];
|
||||
}, $string);
|
||||
|
||||
$string = preg_replace_callback('/\{(\w+)_ru\}/', function($matches) use ($termToRu) {
|
||||
$term = $matches[1];
|
||||
if (isset($termToRu[$term])) {
|
||||
return $termToRu[$term];
|
||||
}
|
||||
return $matches[0];
|
||||
}, $string);
|
||||
|
||||
$string = preg_replace_callback('/\{(\w+)_tr\}/', function($matches) {
|
||||
$term = $matches[1];
|
||||
return $term; // Terim kendisi zaten Türkçe
|
||||
}, $string);
|
||||
|
||||
return $string;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
use App\Models\Counter;
|
||||
use Carbon\Carbon;
|
||||
|
||||
function get_counter($prefix, $type="1") {
|
||||
//eğer type boşsa o gün üretilen başka bir counter varsa onu al
|
||||
$run = true;
|
||||
if($type == "") {
|
||||
$todayCounter = Counter::whereDate("updated_at", Carbon::today())->where("prefix", $prefix)->first();
|
||||
if($todayCounter) {
|
||||
$get = $todayCounter->value;
|
||||
$run = false;
|
||||
} else {
|
||||
$run = true;
|
||||
}
|
||||
}
|
||||
|
||||
if($run) {
|
||||
$set = Counter::updateOrCreate(
|
||||
[
|
||||
'prefix' => $prefix
|
||||
])
|
||||
->increment('value');
|
||||
|
||||
$get = Counter::where("prefix", $prefix)->first()->value;
|
||||
}
|
||||
|
||||
$totalZero = 6;
|
||||
$totalGet = strlen($get);
|
||||
$resultZero = $totalZero - $totalGet;
|
||||
$result = "";
|
||||
for($k=1;$k<=$resultZero; $k++) {
|
||||
$result .= "0";
|
||||
}
|
||||
$result .= $get;
|
||||
|
||||
return $result;
|
||||
} ?>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
function csv_to_xlsx($csvFilePath, $outputFilePath, $delimiter= "\t")
|
||||
{
|
||||
try {
|
||||
// Create a new Spreadsheet object
|
||||
$spreadsheet = new Spreadsheet();
|
||||
|
||||
// Get the active sheet
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
|
||||
// Open the CSV file
|
||||
$file = fopen($csvFilePath, 'r');
|
||||
if (!$file) {
|
||||
throw new \Exception("Cannot open CSV file.");
|
||||
}
|
||||
|
||||
$rowIndex = 1;
|
||||
|
||||
// Read each row and add it to the spreadsheet
|
||||
while (($row = fgetcsv($file, 0, $delimiter)) !== false) {
|
||||
$colIndex = 'A';
|
||||
|
||||
foreach ($row as $cell) {
|
||||
// Veriyi hücreye yaz
|
||||
// if($cell == "NULL") $cell = "";
|
||||
$sheet->setCellValue($colIndex . $rowIndex, $cell);
|
||||
$colIndex++;
|
||||
}
|
||||
|
||||
$rowIndex++;
|
||||
}
|
||||
fclose($file);
|
||||
|
||||
// Write to XLSX file
|
||||
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
|
||||
$writer->save($outputFilePath);
|
||||
|
||||
return $outputFilePath;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$result = [];
|
||||
$result['error'] = "Error: " . $e->getMessage();
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,278 @@
|
||||
<?php function datagrid_scripts() {
|
||||
?>
|
||||
<!-- DevExtreme CSS -->
|
||||
<link rel="stylesheet" href="https://cdn3.devexpress.com/jslib/23.1.5/css/dx-diagram.css" />
|
||||
|
||||
<!-- DevExtreme Diagram JavaScript -->
|
||||
<script src="https://cdn3.devexpress.com/jslib/23.1.5/js/dx-diagram.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.4.0/polyfill.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/exceljs/4.1.1/exceljs.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.2/FileSaver.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.0.0/jspdf.umd.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/devextreme-dist/23.1.5/css/<?php echo setting("DevExpress_Theme", false,"dx.light.compact") ?>">
|
||||
<!-- Quill CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.quilljs.com/1.3.7/quill.snow.css">
|
||||
|
||||
<!-- Quill JS -->
|
||||
<script src="https://cdn.quilljs.com/1.3.7/quill.min.js"></script>
|
||||
|
||||
|
||||
<script type="text/javascript" src="https://cdn3.devexpress.com/jslib/23.1.5/js/dx.all.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.0.0/jspdf.umd.min.js"></script>
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
.dx-datagrid tr:nth-child(odd) {
|
||||
background-color:#e8e8e8;
|
||||
}
|
||||
#dataGrid {
|
||||
height: calc(100vh - 220px);
|
||||
}
|
||||
|
||||
.dx-datagrid .dx-row > td, .dx-datagrid .dx-row > tr > td {
|
||||
padding: 5px !important;
|
||||
}
|
||||
.dx-editor-cell .dx-texteditor .dx-texteditor-input {
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.dx-datagrid .dx-row-lines > td {
|
||||
border-bottom: 1px solid #d2d2d2;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
} ?>
|
||||
|
||||
<?php function datagrid_configurations() {
|
||||
?>
|
||||
toolbar: {
|
||||
items: [
|
||||
"addRowButton",
|
||||
"applyFilterButton",
|
||||
"columnChooserButton",
|
||||
"revertButton",
|
||||
"saveButton",
|
||||
"searchPanel",
|
||||
"exportButton",
|
||||
"groupPanel",
|
||||
{
|
||||
location: 'before',
|
||||
widget: 'dxButton',
|
||||
options: {
|
||||
icon: 'filter',
|
||||
text: '{{e2("Clear Filter")}}',
|
||||
onClick: function(e) {
|
||||
dataGrid.clearFilter();
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
paging: {
|
||||
pageSize: <?php echo setting('row_count') ?>,
|
||||
},
|
||||
allowColumnResizing: true,
|
||||
columnAutoWidth: true,
|
||||
allowColumnReordering: true,
|
||||
columnMinWidth: 150,
|
||||
columnMaxWidth: 250,
|
||||
columnResizingMode: "widget",
|
||||
showColumnLines: true,
|
||||
filterRow: { visible: true },
|
||||
searchPanel: { visible: true },
|
||||
columnFixing: {
|
||||
enabled: true
|
||||
},
|
||||
headerFilter: {
|
||||
visible: true,
|
||||
allowSearch: true,
|
||||
},
|
||||
|
||||
loadPanel: {
|
||||
enabled: true,
|
||||
},
|
||||
|
||||
scrolling: {
|
||||
mode: 'standart',
|
||||
rowRenderingMode: 'infinite',
|
||||
columnRenderingMode: 'infinite',
|
||||
},
|
||||
hoverStateEnabled: true,
|
||||
showBorders: true,
|
||||
|
||||
remoteOperations: {
|
||||
filtering: true,
|
||||
paging: true,
|
||||
sorting: true,
|
||||
groupPaging: true,
|
||||
grouping: true,
|
||||
summary: true
|
||||
},
|
||||
|
||||
|
||||
|
||||
onContextMenuPreparing: function(e) {
|
||||
console.log("onContextMenuPreparing");
|
||||
console.log(e);
|
||||
if (e.target == "content") {
|
||||
if (!e.items) e.items = [];
|
||||
|
||||
@if(isAuth($id, "modify"))
|
||||
|
||||
e.items.push({
|
||||
text: "<?php echo e2("Clear Value") ?>",
|
||||
onItemClick: function(args) {
|
||||
e.component.cellValue(e.rowIndex, e.columnIndex, null);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@endif
|
||||
|
||||
@if(isAuth($id, "write"))
|
||||
|
||||
e.items.push({
|
||||
text: "<?php echo e2("Clone Row") ?>",
|
||||
onItemClick: function(args) {
|
||||
$(".dx-icon-edit-button-addrow").trigger("click");
|
||||
selectedData = e.row.data;
|
||||
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@endif
|
||||
|
||||
@if(isAuth($id, "modify"))
|
||||
|
||||
e.items.push({
|
||||
text: "<?php echo e2("Copy Start") ?>",
|
||||
onItemClick: function(args) {
|
||||
copyData = e.component.cellValue(e.rowIndex, e.columnIndex);
|
||||
selectedColIndex = e.columnIndex;
|
||||
selectedRowIndex = e.rowIndex;
|
||||
console.log(e);
|
||||
console.log(selectedColIndex);
|
||||
console.log(selectedRowIndex);
|
||||
Swal.fire(copyData, "<?php echo e2("Data copied start") ?>", "success");
|
||||
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
e.items.push({
|
||||
text: "<?php echo e2("Until Paste") ?>",
|
||||
onItemClick: function(args) {
|
||||
|
||||
if(e.columnIndex != selectedColIndex) {
|
||||
Swal.fire("<?php echo e2("Wrong") ?>", "<?php echo e2("Please copy using the same columns.") ?>", "error");
|
||||
} else {
|
||||
for(var rowIndex = selectedRowIndex + 1; rowIndex<=e.rowIndex; rowIndex++) {
|
||||
e.component.cellValue(rowIndex, e.columnIndex, copyData);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@endif
|
||||
}
|
||||
},
|
||||
|
||||
onEditingStart(e) {
|
||||
var focusedSelector = $("td.dx-focused");
|
||||
var rowIndex = dataGrid.option("focusedRowIndex");
|
||||
var colIndex = dataGrid.option("focusedColumnIndex");
|
||||
try {
|
||||
var dataField = e.column.dataField;
|
||||
var allEntries = JSON.parse(localStorage.getItem("originalData" + rowIndex + colIndex)) || [];
|
||||
allEntries.push(e.data[dataField]);
|
||||
localStorage.setItem("originalData" + rowIndex + colIndex, JSON.stringify(allEntries));
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
onInitNewRow(e) {
|
||||
|
||||
if(selectedData) {
|
||||
e.data = selectedData;
|
||||
selectedData = null;
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
onRowInserting(e) {
|
||||
logEvent('RowInserting');
|
||||
console.log(e);
|
||||
},
|
||||
|
||||
onRowInserted() {
|
||||
// logEvent('RowInserted');
|
||||
},
|
||||
onRowUpdating() {
|
||||
},
|
||||
onRowUpdated() {
|
||||
// logEvent('RowUpdated');
|
||||
},
|
||||
onRowRemoving() {
|
||||
// logEvent('RowRemoving');
|
||||
},
|
||||
onRowRemoved() {
|
||||
// logEvent('RowRemoved');
|
||||
},
|
||||
onSaving() {
|
||||
|
||||
},
|
||||
onInitialized() {
|
||||
|
||||
$("#dataGrid td").on("click", function() {
|
||||
logEvent("#dataGrid td");
|
||||
});
|
||||
},
|
||||
onRowClick: function(e) {
|
||||
|
||||
/*
|
||||
console.log("onRowClick");
|
||||
console.log(e);
|
||||
selectedData = e.data;
|
||||
selectedData.id = null;
|
||||
if(e.rowType === "data") {
|
||||
e.component.editRow(e.rowIndex);
|
||||
}
|
||||
|
||||
*/
|
||||
},
|
||||
onSaved() {
|
||||
|
||||
logEvent('Saving Success');
|
||||
},
|
||||
onEditCanceling() {
|
||||
// logEvent('EditCanceling');
|
||||
},
|
||||
onEditCanceled() {
|
||||
// logEvent('EditCanceled');
|
||||
},
|
||||
onFocusedRowChanging(e) {
|
||||
// console.log(e);
|
||||
},
|
||||
|
||||
|
||||
sorting: {
|
||||
mode: "multiple" // or "multiple" | "none"
|
||||
},
|
||||
|
||||
|
||||
<?php
|
||||
} ?>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
function defect_names() {
|
||||
return array_map("trim", explode("\n", "Single Porosity
|
||||
Chain Porosity
|
||||
Cluster Porosity
|
||||
Single Slag
|
||||
Chain Slag
|
||||
Cluster Slag
|
||||
Single Tungsten
|
||||
Chain Tungsten
|
||||
Cluster Tungsten
|
||||
Incomplete Root Penetration
|
||||
Incomplete Inter-pass Fusion
|
||||
Incomplet Side Wall Fusion
|
||||
Longitudinal Crack
|
||||
Transverse Crack
|
||||
Branched Crack
|
||||
Root Concavity / Suck Back
|
||||
Root Convexity / Extensive Root
|
||||
Undercut
|
||||
High-Low
|
||||
Other"));
|
||||
}
|
||||
function defects() {
|
||||
|
||||
return [
|
||||
'aa',
|
||||
'ab',
|
||||
'ac',
|
||||
'ba',
|
||||
'bb',
|
||||
'bc',
|
||||
'ca',
|
||||
'cb',
|
||||
'cc',
|
||||
'da',
|
||||
'db',
|
||||
'dc',
|
||||
'ea',
|
||||
'eb',
|
||||
'ec',
|
||||
'fa',
|
||||
'fb',
|
||||
'fc',
|
||||
'fd',
|
||||
'other',
|
||||
];
|
||||
} ?>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
function deleteAfterR($input) {
|
||||
$pos = strpos($input, 'R'); // R karakterinin pozisyonunu bul
|
||||
if ($pos !== false) {
|
||||
// Eğer R karakteri bulunduysa, R karakterinden sonrasını sil
|
||||
$output = substr($input, 0, $pos);
|
||||
} else {
|
||||
// Eğer R karakteri bulunamazsa, input'u olduğu gibi döndür
|
||||
$output = $input;
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Helper function to detect changed fields
|
||||
*
|
||||
* Compares current data with previous data to identify which fields changed
|
||||
*
|
||||
* @param mixed $data Current data
|
||||
* @param mixed $beforeData Previous data (null for new records)
|
||||
* @return array Array of changed field names
|
||||
*/
|
||||
function detectChangedFields($data, $beforeData): array
|
||||
{
|
||||
if (is_null($beforeData)) {
|
||||
// New record - all fields are "changed"
|
||||
return array_keys((array) $data);
|
||||
}
|
||||
|
||||
$changedFields = [];
|
||||
$dataArray = (array) $data;
|
||||
$beforeDataArray = (array) $beforeData;
|
||||
|
||||
foreach ($dataArray as $key => $value) {
|
||||
$oldValue = $beforeDataArray[$key] ?? null;
|
||||
|
||||
// Compare values - consider both strict and loose equality for type changes
|
||||
if ($oldValue !== $value) {
|
||||
$changedFields[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return $changedFields;
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
function onPreparingVariables() {
|
||||
?>
|
||||
var options = e.editorOptions;
|
||||
var rowIndex = e.row.rowIndex;
|
||||
var rowData = e.row.data;
|
||||
var dataGrid = $("#dataGrid").dxDataGrid("instance");
|
||||
var exceptValue = ['undefined', 'null'];
|
||||
var column = e.dataField;
|
||||
var urlRowData = encodeURIComponent(JSON.stringify(rowData));
|
||||
<?php
|
||||
}
|
||||
|
||||
function dxAutocomplete($columnName, $tableName, $targetColumn, $filter = "", $affected = "") {
|
||||
if ($filter != "") {
|
||||
$filter2 = [];
|
||||
foreach ($filter as $filterColumn => $filterValue) {
|
||||
if (is_array($filterValue)) {
|
||||
$filter2[$filterColumn] = implode(",", $filterValue);
|
||||
} elseif (strpos($filterValue, '"') !== false) {
|
||||
$filterValue = str_replace('"', "", $filterValue);
|
||||
$filter2[$filterColumn] = "{$filterValue}";
|
||||
} else {
|
||||
$filter2[$filterColumn] = "'+ dataRow.{$filterValue} +'";
|
||||
}
|
||||
}
|
||||
$filter = "&filter=" . json_encode_tr($filter2);
|
||||
}
|
||||
?>
|
||||
<?php if ($affected == "") { ?>
|
||||
if (e.dataField == "<?php echo $columnName ?>") {
|
||||
e.editorName = "dxAutocomplete";
|
||||
e.editorOptions.searchEnabled = true;
|
||||
e.editorOptions.acceptCustomValue = true;
|
||||
e.editorOptions.dataSource = new DevExpress.data.ODataStore({
|
||||
url: '<?php echo autocomplete_url($tableName, $targetColumn) ?><?php echo $filter ?>',
|
||||
key: 'id',
|
||||
});
|
||||
e.editorOptions.valueExpr = '<?php echo $targetColumn ?>';
|
||||
e.editorOptions.displayExpr = '<?php echo $targetColumn ?>';
|
||||
}
|
||||
<?php } else { ?>
|
||||
var options = e.editorOptions;
|
||||
var rowIndex = e.row.rowIndex;
|
||||
var dataGrid = $("#dataGrid").dxDataGrid("instance");
|
||||
var exceptValue = ['undefined', 'null'];
|
||||
|
||||
if (e.dataField == "<?php echo $columnName ?>") {
|
||||
e.editorName = "dxAutocomplete";
|
||||
e.editorOptions.searchEnabled = true;
|
||||
e.editorOptions.acceptCustomValue = true;
|
||||
e.editorOptions.showClearButton = true;
|
||||
e.editorOptions.dataSource = new DevExpress.data.ODataStore({
|
||||
url: '<?php echo autocomplete_url($tableName, $targetColumn) ?><?php echo $filter ?>',
|
||||
key: 'id',
|
||||
});
|
||||
e.editorOptions.valueExpr = '<?php echo $targetColumn ?>';
|
||||
e.editorOptions.displayExpr = '<?php echo $targetColumn ?>';
|
||||
|
||||
var fetchRelatedData = function(typedValue) {
|
||||
if (typedValue === null || typedValue === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$.getJSON("<?php echo row_detail_url($tableName, $targetColumn) ?>?value=" + typedValue, function(responseJSON) {
|
||||
console.log(responseJSON);
|
||||
if (responseJSON && typeof responseJSON.<?php echo $targetColumn ?> !== 'undefined') {
|
||||
<?php
|
||||
$affectedCols = $affected;
|
||||
foreach ($affectedCols as $affectedCol => $valueCol) {
|
||||
$valueCol = str_replace('{', '${responseJSON.', $valueCol); ?>
|
||||
if (!exceptValue.includes(`<?php echo $valueCol ?>`)) {
|
||||
dataGrid.cellValue(rowIndex, "<?php echo $affectedCol ?>", `<?php echo $valueCol ?>`);
|
||||
} else {
|
||||
console.log("except value <?php echo $valueCol ?>");
|
||||
}
|
||||
<?php } ?>
|
||||
} else {
|
||||
console.log("undefined <?php echo $targetColumn ?>");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
options.onValueChanged = function(selectData) {
|
||||
console.log("on value changed");
|
||||
e.setValue(selectData.value);
|
||||
|
||||
if (options.__stellarExactMatchTimer) {
|
||||
clearTimeout(options.__stellarExactMatchTimer);
|
||||
}
|
||||
|
||||
options.__stellarExactMatchTimer = setTimeout(function() {
|
||||
var typedValue = (selectData && typeof selectData.value !== 'undefined') ? selectData.value : null;
|
||||
fetchRelatedData(typedValue);
|
||||
}, 250);
|
||||
}
|
||||
|
||||
options.onSelectionChanged = function(eSelection) {
|
||||
if (eSelection.selectedItem) {
|
||||
if (options.__stellarExactMatchTimer) {
|
||||
clearTimeout(options.__stellarExactMatchTimer);
|
||||
}
|
||||
// Use the value directly from the selected item
|
||||
var selectedValue = eSelection.selectedItem['<?php echo $targetColumn ?>'];
|
||||
fetchRelatedData(selectedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
<?php } ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
function dxSelectBox($columnName, $tableName, $targetColumn, $filter = "", $affected = "", $order = "") {
|
||||
if ($filter != "") {
|
||||
$filter2 = [];
|
||||
foreach ($filter as $filterColumn => $filterValue) {
|
||||
if (is_array($filterValue)) {
|
||||
$filter2[$filterColumn] = implode(",", $filterValue);
|
||||
} elseif (strpos($filterValue, '"') !== false) {
|
||||
$filterValue = str_replace('"', "", $filterValue);
|
||||
$filter2[$filterColumn] = "{$filterValue}";
|
||||
} else {
|
||||
$filter2[$filterColumn] = "'+ dataRow.{$filterValue} +'";
|
||||
}
|
||||
}
|
||||
$filter = "&filter=" . json_encode_tr($filter2);
|
||||
}
|
||||
?>
|
||||
<?php if ($affected == "") { ?>
|
||||
if (e.dataField == "<?php echo $columnName ?>") {
|
||||
e.editorName = "dxSelectBox";
|
||||
e.editorOptions.searchEnabled = true;
|
||||
e.editorOptions.acceptCustomValue = true;
|
||||
e.editorOptions.allowCustomValues = true;
|
||||
e.editorOptions.dataSource = new DevExpress.data.ODataStore({
|
||||
url: '<?php echo autocomplete_url($tableName, $targetColumn) ?><?php echo $filter ?>',
|
||||
key: 'id',
|
||||
});
|
||||
e.editorOptions.valueExpr = '<?php echo $targetColumn ?>';
|
||||
e.editorOptions.displayExpr = '<?php echo $targetColumn ?>';
|
||||
}
|
||||
<?php } else { ?>
|
||||
var options = e.editorOptions;
|
||||
var rowIndex = e.row.rowIndex;
|
||||
var dataGrid = $("#dataGrid").dxDataGrid("instance");
|
||||
var exceptValue = ['undefined', 'null'];
|
||||
|
||||
if (e.dataField == "<?php echo $columnName ?>") {
|
||||
e.editorName = "dxSelectBox";
|
||||
e.editorOptions.searchEnabled = true;
|
||||
e.editorOptions.acceptCustomValue = true;
|
||||
e.editorOptions.allowCustomValues = true;
|
||||
e.editorOptions.showClearButton = true;
|
||||
e.editorOptions.dataSource = new DevExpress.data.ODataStore({
|
||||
url: '<?php echo autocomplete_url($tableName, $targetColumn) ?><?php echo $filter ?>',
|
||||
key: 'id',
|
||||
});
|
||||
e.editorOptions.valueExpr = '<?php echo $targetColumn ?>';
|
||||
e.editorOptions.displayExpr = '<?php echo $targetColumn ?>';
|
||||
|
||||
options.onValueChanged = function(selectData) {
|
||||
e.setValue(selectData.value);
|
||||
|
||||
if (options.__stellarExactMatchTimer) {
|
||||
clearTimeout(options.__stellarExactMatchTimer);
|
||||
}
|
||||
|
||||
options.__stellarExactMatchTimer = setTimeout(function() {
|
||||
var typedValue = (selectData && typeof selectData.value !== 'undefined') ? selectData.value : null;
|
||||
if (typedValue === null || typedValue === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$.getJSON("<?php echo row_detail_url($tableName, $targetColumn) ?>?order=<?php echo $order ?>&nolike=1&value=" + encodeURIComponent(typedValue), function(responseJSON) {
|
||||
console.log(responseJSON);
|
||||
if (responseJSON && typeof responseJSON.<?php echo $targetColumn ?> !== 'undefined') {
|
||||
<?php
|
||||
$affectedCols = $affected;
|
||||
foreach ($affectedCols as $affectedCol => $valueCol) {
|
||||
$valueCol = str_replace('{', '${responseJSON.', $valueCol); ?>
|
||||
if (!exceptValue.includes(`<?php echo $valueCol ?>`)) {
|
||||
dataGrid.cellValue(rowIndex, "<?php echo $affectedCol ?>", `<?php echo $valueCol ?>`);
|
||||
} else {
|
||||
console.log("except value <?php echo $valueCol ?>");
|
||||
}
|
||||
<?php } ?>
|
||||
} else {
|
||||
console.log("undefined <?php echo $targetColumn ?>");
|
||||
}
|
||||
});
|
||||
}, 250);
|
||||
}
|
||||
}
|
||||
<?php } ?>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php function dxDataStore($tableName, $columnName, $filter = "") {
|
||||
?>
|
||||
new DevExpress.data.ODataStore({
|
||||
url: '<?php echo autocomplete_url2($tableName, $columnName) ?><?php echo $filter ?>,
|
||||
key: 'id',
|
||||
});
|
||||
<?php
|
||||
} ?>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php function document_template($id) {
|
||||
$document = db("document_templates")->orWhere("slug", $id)->orWhere("id", $id)->first();
|
||||
return $document;
|
||||
} ?>
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
use PhpOffice\PhpWord\TemplateProcessor;
|
||||
|
||||
function replacePlaceholdersInDocx($templateFilePath, $outputFilePath, $replacements) {
|
||||
// Load the DOCX template
|
||||
//$storagePath = "storage/documents/$outputFilePath";
|
||||
$storagePath = $outputFilePath;
|
||||
$directoryPath = dirname($storagePath);
|
||||
|
||||
if (!is_dir($directoryPath)) {
|
||||
mkdir($directoryPath, 0777, true);
|
||||
}
|
||||
|
||||
$templateProcessor = new TemplateProcessor($templateFilePath);
|
||||
|
||||
$templateProcessor->setValue("text.ncr_no", "test");
|
||||
|
||||
// $templateProcessor->setValue("corrective_action_ru", "teasdasdast");
|
||||
/*
|
||||
$reflectionClass = new \ReflectionClass($templateProcessor);
|
||||
|
||||
// Accessing the protected/private property
|
||||
$property = $reflectionClass->getProperty('tempDocumentMainPart');
|
||||
$property->setAccessible(true);
|
||||
|
||||
// Get the value of the property
|
||||
$xmlContent = $property->getValue($templateProcessor);
|
||||
|
||||
$xmlContent = str_replace("corrective_action_ru", "test", $xmlContent);
|
||||
|
||||
$property->setValue($templateProcessor, $xmlContent);
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
|
||||
|
||||
$formTypes = ['date', 'time', 'text', 'textarea', 'radio', 'checkbox', 'select'];
|
||||
|
||||
// Replace placeholders
|
||||
|
||||
foreach ($replacements as $search => $replace) {
|
||||
|
||||
foreach($formTypes AS $formType)
|
||||
{
|
||||
|
||||
$isValidDate = false;
|
||||
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $replace)) {
|
||||
// YYYY-MM-DD formatında tarih
|
||||
$isValidDate = true;
|
||||
} elseif (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $replace)) {
|
||||
// YYYY-MM-DD HH:MM:SS formatında tarih ve saat
|
||||
$isValidDate = true;
|
||||
} elseif (preg_match('/^\d{2}.\d{2}.\d{4}$/', $replace)) {
|
||||
// DD.MM.YYYY formatında tarih
|
||||
$isValidDate = true;
|
||||
} elseif (preg_match('/^\d{2}.\d{2}.\d{4} \d{2}:\d{2}:\d{2}$/', $replace)) {
|
||||
// DD.MM.YYYY HH:MM:SS formatında tarih ve saat
|
||||
$isValidDate = true;
|
||||
}
|
||||
|
||||
if($isValidDate)
|
||||
{
|
||||
if (strtotime($replace)) {
|
||||
$dateTime = new DateTime($replace);
|
||||
|
||||
// Eğer $replace saati de içeriyorsa
|
||||
if ($dateTime->format('H:i:s') !== '00:00:00') {
|
||||
$replace = $dateTime->format('H:i');
|
||||
} else {
|
||||
$replace = $dateTime->format('d.m.Y');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array($formType, ['radio', 'checkbox'])) {
|
||||
$replacePlaceHolder = $formType . '.' . $search . '.' . $replace;
|
||||
$okValue = '✓';
|
||||
$cellValue = $okValue;
|
||||
|
||||
$otherPattern = "/<w:t>\{$formType\.$search\.[^}]*\}<\/w:t>/";
|
||||
|
||||
//$xmlContent = preg_replace($otherPattern, "", /$xmlContent);
|
||||
} else {
|
||||
$replacePlaceHolder = $formType . '.' . $search; // Note the curly braces
|
||||
$cellValue = $replace;
|
||||
|
||||
$pattern = '/\{' . preg_quote($formType, '/') . '\.<\/w:t><\/w:r><w:r[^>]*><w:rPr>.*?<\/w:rPr><w:t>' . preg_quote($search, '/') . '<\/w:t>/';
|
||||
dump($pattern);
|
||||
// $xmlContent = preg_replace($pattern, $cellValue, $xmlContent);
|
||||
$property->setValue($templateProcessor, $xmlContent);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// dump($pattern);
|
||||
// dump($cellValue);
|
||||
|
||||
// preg_replace ile değişim
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
$templateProcessor->saveAs($storagePath);
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
function docx_to_html($docx, $html_dir) {
|
||||
// Ensure the LANG environment variable is set
|
||||
putenv('LANG=ru_RU.UTF-8');
|
||||
|
||||
// Ensure the output directory exists
|
||||
if (!is_dir($html_dir)) {
|
||||
mkdir($html_dir, 0777, true);
|
||||
}
|
||||
|
||||
// Construct the command
|
||||
$command = "libreoffice --headless --convert-to html --outdir " . escapeshellarg($html_dir) . " " . escapeshellarg($docx);
|
||||
// Execute the command
|
||||
$output = shell_exec($command);
|
||||
$output = extract_between_markers_docx($output);
|
||||
|
||||
update_src_paths($output, $html_dir);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function extract_between_markers_docx($input_str) {
|
||||
// Define the regular expression pattern to match the content between the markers
|
||||
$pattern = '/->\s*(.*?)\s*using filter : HTML \(StarWriter\)/';
|
||||
|
||||
// Perform the regex match
|
||||
if (preg_match($pattern, $input_str, $matches)) {
|
||||
// Return the matched content
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
// Return null if no match is found
|
||||
return null;
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
function export_excel($dizi, $dosya_adi) {
|
||||
$dizi = $dizi->toArray();
|
||||
|
||||
$excepts = ['created_at', 'updated_at'];
|
||||
|
||||
$filtered = [];
|
||||
foreach($dizi AS $key => $value) {
|
||||
foreach($excepts AS $except) {
|
||||
$value = (Array) $value;
|
||||
unset($value[$except]);
|
||||
}
|
||||
$filtered[] = $value;
|
||||
}
|
||||
|
||||
|
||||
$dizi = $filtered;
|
||||
|
||||
$dosya_adi = (String) $dosya_adi;
|
||||
$icerik = "";
|
||||
foreach($dizi[0] AS $column => $value) {
|
||||
$icerik .= "$column\t";
|
||||
}
|
||||
$icerik .= "\r\n";
|
||||
foreach($dizi AS $column) {
|
||||
foreach($column AS $col => $value) {
|
||||
$icerik .= "$value\t";
|
||||
}
|
||||
$icerik .= "\r\n";
|
||||
}
|
||||
$icerik = trim($icerik);
|
||||
|
||||
return response($icerik)
|
||||
->header('Content-type','application/ms-excel')
|
||||
->header('Content-Disposition','attachment; filename="'.$dosya_adi.'.xls"')
|
||||
->send();
|
||||
} ?>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
|
||||
function file_force_contents( $fullPath, $contents, $flags = 0 ){
|
||||
$parts = explode( '/', $fullPath );
|
||||
array_pop( $parts );
|
||||
$dir = implode( '/', $parts );
|
||||
|
||||
if( !is_dir( $dir ) )
|
||||
mkdir( $dir, 0777, true );
|
||||
|
||||
file_put_contents( $fullPath, $contents, $flags );
|
||||
}
|
||||
function pdf_create($path, $html)
|
||||
{
|
||||
$pdf = App::make('dompdf.wrapper');
|
||||
$pdf->setPaper('A4',$j['paper']);
|
||||
$pdf->setOption(['dpi' => $j['dpi'], ]);
|
||||
$pdf->loadHTML($html);
|
||||
|
||||
$path = "$path.pdf";
|
||||
|
||||
Storage::delete($path);
|
||||
Storage::put($path, $pdf->output());
|
||||
}
|
||||
|
||||
function html_create($path, $html, $paper="portrait")
|
||||
{
|
||||
$path = "$path.html";
|
||||
Storage::delete($path);
|
||||
Storage::put($path, $html);
|
||||
|
||||
|
||||
$htmlPath = "storage/documents/" . $path;
|
||||
$pdfPath = str_replace(".html", ".pdf", $htmlPath);
|
||||
html_to_pdf($htmlPath, $pdfPath, $paper);
|
||||
unlink($htmlPath);
|
||||
} ?>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php function findItemObject($text, $column, $object) {
|
||||
$array = @json_decode(json_encode($object));
|
||||
$find = array_search($text, array_column($array, $column));
|
||||
if(!$find) {
|
||||
return false;
|
||||
} else {
|
||||
return $object[$find];
|
||||
}
|
||||
|
||||
} ?>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* Try catch kullanarak index değerine göre yoksa ekler
|
||||
*/
|
||||
function firstOrCreate($data, $table, $debug=false) {
|
||||
|
||||
try {
|
||||
$data['created_at'] = simdi();
|
||||
$id = db($table)->insertGetId($data);
|
||||
return $id;
|
||||
} catch (\Throwable $th) {
|
||||
if($debug) {
|
||||
dump($th);
|
||||
}
|
||||
}
|
||||
|
||||
} ?>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* Try catch kullanarak ekler veya günceller
|
||||
* Transaction management added to prevent lock issues
|
||||
*/
|
||||
function firstOrUpdate($data, $table, $where, $debug = false) {
|
||||
|
||||
if(isset($data['id'])) {
|
||||
if($data['id'] == "") {
|
||||
unset($data['id']);
|
||||
}
|
||||
}
|
||||
|
||||
return \DB::transaction(function() use ($data, $table, $where, $debug) {
|
||||
try {
|
||||
$data['created_at'] = simdi();
|
||||
$id = db($table)->insertGetId($data);
|
||||
return $id;
|
||||
} catch (\Throwable $th) {
|
||||
|
||||
if($debug) {
|
||||
dump($th);
|
||||
}
|
||||
|
||||
// Use lockForUpdate to prevent race conditions
|
||||
$affectedRows = db($table)
|
||||
->where($where)
|
||||
->lockForUpdate()
|
||||
->get();
|
||||
|
||||
$affectedRows = db($table)->where($where)->update($data);
|
||||
return "update $affectedRows";
|
||||
}
|
||||
}, 3); // 3 retry attempts
|
||||
|
||||
} ?>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
/**
|
||||
* Generate a unique report number with prefix and identifier
|
||||
*
|
||||
* @param string $prefix The prefix for the report number
|
||||
* @param string $identifier The identifier (line+spool) for the report
|
||||
* @return string The formatted report number
|
||||
*/
|
||||
if (!function_exists('generateReportNumber')) {
|
||||
function generateReportNumber($prefix, $identifier) {
|
||||
$date = date('Ymd');
|
||||
$randomNumber = mt_rand(100, 999);
|
||||
return $prefix . '-' . $identifier . '-' . $date . '-' . $randomNumber;
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php function generate_hash_link($type, $title="") {
|
||||
$hash = Hash::make($type);
|
||||
$showLink = url("mail-link?title=$title&type=$type&hash=$hash");
|
||||
return $showLink;
|
||||
} ?>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
function getBeforeSlash($input) {
|
||||
$input = str_replace("/141", "", $input);
|
||||
$input = str_replace("/111", "", $input);
|
||||
return $input;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
function getKeyColumns($table) {
|
||||
$keyColumnsMap = [
|
||||
"line_lists" => ["line", "fluid_code"],
|
||||
"supports" => ["id"],
|
||||
"paint_matrices" => ["line", "fluid_code"],
|
||||
"nde_matrices" => ["line", "fluid", "type_of_joint"],
|
||||
"document_revisions" => ["drawing_no", "zone"],
|
||||
"m_t_o_s" => ["line", "component_code_id"],
|
||||
"test_packages" => ["test_package_number"],
|
||||
"test_pack_base_statuses" => ["test_package_no"],
|
||||
"punch_lists" => ["test_package", "punch_list_no"],
|
||||
"weld_logs" => ["iso", "joint"]
|
||||
];
|
||||
|
||||
// log_test_types() tabloları için özel kontrol
|
||||
if (in_array($table, log_test_types())) {
|
||||
return ["iso", "joint", "welding_date"];
|
||||
}
|
||||
|
||||
// Tablo için tanımlı sütunları döndür
|
||||
return $keyColumnsMap[$table] ?? [];
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php function get_revision_from_string($string) {
|
||||
|
||||
preg_match('/(?i)(?<=rev)\w+/', $string, $matches);
|
||||
$number = $matches[0] ?? "";
|
||||
return $number;
|
||||
} ?>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php function get_variables_from_pattern($patternString) {
|
||||
preg_match_all('/{(.*?)}/',$patternString, $matches);
|
||||
return $matches[1];
|
||||
} ?>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
function getLatestSpoolStatus($spoolStatuses) {
|
||||
// Eğer tüm spool_status değerleri "Waiting" ise
|
||||
if (count(array_filter($spoolStatuses, fn($status) => $status !== 'Waiting')) === 0) {
|
||||
return 'Waiting';
|
||||
}
|
||||
|
||||
// Spool durumlarını sayısal değerlere göre sıralayıp en yüksek olanı döndür
|
||||
$statusValues = [
|
||||
'Waiting',
|
||||
'On Going',
|
||||
'Spool Release',
|
||||
'NDT Release',
|
||||
'Paint',
|
||||
'Completed'
|
||||
];
|
||||
|
||||
// Geçerli spool durumlarını kontrol et ve en yüksek durumu bul
|
||||
$latestStatus = null; // Başlangıçta en yüksek durum yok
|
||||
foreach ($statusValues as $status) {
|
||||
if (in_array($status, $spoolStatuses)) {
|
||||
if ($latestStatus === null || array_search($status, $statusValues) > array_search($latestStatus, $statusValues)) {
|
||||
$latestStatus = $status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $latestStatus;
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
function getLowestSpoolStatus($spoolStatuses) {
|
||||
// Eğer tüm spool_status değerleri "Completed" ise
|
||||
if (count(array_filter($spoolStatuses, fn($status) => $status !== 'Completed')) === 0) {
|
||||
return 'Completed';
|
||||
}
|
||||
|
||||
// Spool durumlarını sayısal değerlere göre sıralayıp en düşük olanı döndür
|
||||
$statusValues = [
|
||||
'Waiting',
|
||||
'On Going',
|
||||
'Spool Release',
|
||||
'NDT Release',
|
||||
'Paint',
|
||||
'Completed'
|
||||
];
|
||||
|
||||
// Geçerli spool durumlarını kontrol et ve en düşük durumu bul
|
||||
$lowestStatus = null; // Başlangıçta en düşük durum yok
|
||||
foreach ($statusValues as $status) {
|
||||
if (in_array($status, $spoolStatuses)) {
|
||||
if ($lowestStatus === null || array_search($status, $statusValues) < array_search($lowestStatus, $statusValues)) {
|
||||
$lowestStatus = $status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $lowestStatus;
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php function get_number() {
|
||||
return date("ynjg") . rand(111,999);
|
||||
} ?>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php function get_url_json($tableName, $columnName, $value="") {
|
||||
return url("admin/get/$tableName/$columnName/$value") . "/";
|
||||
} ?>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php function get_welder_id($metin) {
|
||||
$pattern = "/\[([A-Z0-9]+)\]/"; // "[BZ7X]" gibi örüntüyü yakalar
|
||||
|
||||
if (preg_match($pattern, $metin, $matches)) {
|
||||
$deger = $matches[1];
|
||||
$deger = trim($deger);
|
||||
return $deger;
|
||||
} else {
|
||||
return $metin;
|
||||
}
|
||||
} ?>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
function get_welder_name($welder_id, $type = "en") {
|
||||
$welderName = db("naks_welders")->where("welder_id", $welder_id)->first();
|
||||
if($welderName) {
|
||||
if($type == "en") {
|
||||
return $welderName->welder_name_en;
|
||||
} else {
|
||||
return $welderName->welder_name_ru;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
function get_welder_info($welder_id) {
|
||||
return db("naks_welders")->where("welder_id", $welder_id)->first();
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php function html_to_pdf($html, $pdf, $paper="portrait")
|
||||
{
|
||||
$params = "";
|
||||
if($paper == "landscape") {
|
||||
$params = "-O landscape";
|
||||
}
|
||||
putenv('LANG=ru_RU.UTF-8');
|
||||
$wk_path = env("wk_path");
|
||||
$command = "$wk_path $params '$html' '$pdf'";
|
||||
return shell_exec($command);
|
||||
} ?>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php function icon($type) {
|
||||
echo "<span class=\"material-symbols-outlined\">
|
||||
$type
|
||||
</span>";
|
||||
} ?>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php function get_cache_id($prefix) {
|
||||
$lastId = 0;
|
||||
|
||||
if(Cache::has($prefix)) {
|
||||
$lastId = Cache::get($prefix);
|
||||
}
|
||||
|
||||
return $lastId;
|
||||
}
|
||||
|
||||
function set_cache_id($prefix, $lastId) {
|
||||
Cache::put($prefix, $lastId);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Get inspection history configuration for a module from settings
|
||||
*
|
||||
* @param string $moduleSlug Module slug to get configuration for
|
||||
* @param string|null $moduleTitle Optional module title for path generation
|
||||
* @return array|null Returns array with 'path', 'pattern', and 'active' keys, or null if not found
|
||||
*/
|
||||
function getInspectionHistoryConfig($moduleSlug, $moduleTitle = null)
|
||||
{
|
||||
if (empty($moduleSlug)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the module settings value
|
||||
$settingsJson = setting('inspection_history_management', false, '{}');
|
||||
$settings = json_decode($settingsJson, true);
|
||||
|
||||
$moduleConfig = null;
|
||||
|
||||
if (isset($settings[$moduleSlug])) {
|
||||
$moduleConfig = $settings[$moduleSlug];
|
||||
} else {
|
||||
// Try kebab-case version if underscore version not found
|
||||
$kebabSlug = str_replace('_', '-', $moduleSlug);
|
||||
if (isset($settings[$kebabSlug])) {
|
||||
$moduleConfig = $settings[$kebabSlug];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$moduleConfig) {
|
||||
if ($moduleSlug === 'Other') {
|
||||
$moduleConfig = [
|
||||
'active' => true,
|
||||
'path_pattern' => '',
|
||||
'file_pattern' => '{timestamp}_{file_name}'
|
||||
];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the main path setting
|
||||
$mainPath = setting('inspection_history_main_path', false, '');
|
||||
|
||||
// If no main path is set, return null (feature not configured)
|
||||
if (empty($mainPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get module title if not provided
|
||||
if (empty($moduleTitle)) {
|
||||
$module = \App\Types::where('slug', $moduleSlug)->first(['title']);
|
||||
$moduleTitle = $module ? $module->title : $moduleSlug;
|
||||
}
|
||||
|
||||
// Clean module title for use as directory name
|
||||
$cleanTitle = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $moduleTitle);
|
||||
$cleanTitle = preg_replace('/_+/', '_', $cleanTitle);
|
||||
$cleanTitle = trim($cleanTitle, '_');
|
||||
|
||||
// Build the full path: main_path/module_title
|
||||
$fullPath = $mainPath . '/' . $cleanTitle;
|
||||
|
||||
return [
|
||||
'path' => $fullPath,
|
||||
'path' => $fullPath,
|
||||
'path_pattern' => $moduleConfig['path_pattern'] ?? null,
|
||||
'file_pattern' => $moduleConfig['file_pattern'] ?? $moduleConfig['pattern'] ?? null,
|
||||
'pattern' => $moduleConfig['pattern'] ?? null, // Keep for backward compatibility elsewhere if needed
|
||||
'active' => $moduleConfig['active'] ?? false
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php function isAdmin($u = null) {
|
||||
if(is_null($u)) {
|
||||
$u = u();
|
||||
}
|
||||
if($u->level=="Admin") {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} ?>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php function is_stellar() {
|
||||
$stellar = db("subcontractors")->where("company_name_en", "like", "%stellar%")->first()->company_name_en;
|
||||
$u = u();
|
||||
if($u->subcontructer == $stellar) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} ?>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
function isoToLine()
|
||||
{
|
||||
// ISO numaralarına karşılık gelen line_number'ları almak
|
||||
$isoToLine = db("weld_logs")
|
||||
->select('line_number', 'iso_number')
|
||||
->pluck('line_number', 'iso_number')
|
||||
->toArray();
|
||||
|
||||
return $isoToLine;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php function job_descriptions() {
|
||||
$jobDesc = db("job_descriptions")->get()->pluck("title")->toArray();
|
||||
$jobDesc[] = null;
|
||||
$jobDesc[] = "";
|
||||
return $jobDesc;
|
||||
} ?>
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
use App\Services\JointTypeService;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
if (!function_exists('joint_type_service')) {
|
||||
function joint_type_service(): JointTypeService
|
||||
{
|
||||
return app(JointTypeService::class);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('is_welded_joint')) {
|
||||
/**
|
||||
* Check if a joint type is a welded joint
|
||||
*
|
||||
* @param string $jointType - Joint type short_name_en or naks_name
|
||||
* @return bool
|
||||
*/
|
||||
function is_welded_joint($jointType) {
|
||||
return joint_type_service()->isWelded($jointType);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('requires_ndt')) {
|
||||
/**
|
||||
* Check if a joint type requires NDT
|
||||
*
|
||||
* @param string $jointType - Joint type short_name_en or naks_name
|
||||
* @return bool
|
||||
*/
|
||||
function requires_ndt($jointType) {
|
||||
return joint_type_service()->requiresNdt($jointType);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('is_mechanical_joint')) {
|
||||
/**
|
||||
* Check if a joint type is mechanical
|
||||
*
|
||||
* @param string $jointType - Joint type short_name_en or naks_name
|
||||
* @return bool
|
||||
*/
|
||||
function is_mechanical_joint($jointType) {
|
||||
return joint_type_service()->isMechanical($jointType);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('get_welded_joint_types')) {
|
||||
/**
|
||||
* Get array of welded joint types for query filtering
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function get_welded_joint_types() {
|
||||
return joint_type_service()->weldedTypes();
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('get_mechanical_joint_types')) {
|
||||
/**
|
||||
* Get array of mechanical joint types for query filtering
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function get_mechanical_joint_types() {
|
||||
return joint_type_service()->mechanicalTypes();
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('clear_joint_types_cache')) {
|
||||
/**
|
||||
* Clear joint types cache
|
||||
* Call this after updating joint_types table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function clear_joint_types_cache() {
|
||||
joint_type_service()->clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('apply_welded_filter')) {
|
||||
/**
|
||||
* Apply welded joint filtering to a query builder or table name
|
||||
*
|
||||
* @param Builder|EloquentBuilder|string $queryOrTable
|
||||
* @param string $column
|
||||
* @return Builder|EloquentBuilder
|
||||
*/
|
||||
function apply_welded_filter($queryOrTable, string $column = 'type_of_welds') {
|
||||
$builder = resolve_builder_from_argument($queryOrTable);
|
||||
return joint_type_service()->filterWelded($builder, $column);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('apply_mechanical_filter')) {
|
||||
/**
|
||||
* Apply mechanical joint filtering to a query builder or table name
|
||||
*
|
||||
* @param Builder|EloquentBuilder|string $queryOrTable
|
||||
* @param string $column
|
||||
* @return Builder|EloquentBuilder
|
||||
*/
|
||||
function apply_mechanical_filter($queryOrTable, string $column = 'type_of_welds') {
|
||||
$builder = resolve_builder_from_argument($queryOrTable);
|
||||
return joint_type_service()->filterMechanical($builder, $column);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('resolve_builder_from_argument')) {
|
||||
/**
|
||||
* @param Builder|EloquentBuilder|string $queryOrTable
|
||||
* @return Builder|EloquentBuilder
|
||||
*/
|
||||
function resolve_builder_from_argument($queryOrTable) {
|
||||
if ($queryOrTable instanceof Builder || $queryOrTable instanceof EloquentBuilder) {
|
||||
return $queryOrTable;
|
||||
}
|
||||
|
||||
return DB::table($queryOrTable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
function languages() {
|
||||
$diller = explode(",","en,tr,ru");
|
||||
return $diller;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
if (!function_exists('levelColor')) {
|
||||
function levelColor($level) {
|
||||
$colors = [
|
||||
1 => 'success',
|
||||
2 => 'primary',
|
||||
3 => 'warning',
|
||||
4 => 'info'
|
||||
];
|
||||
return $colors[$level] ?? 'secondary';
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
function permission_type_index($type) {
|
||||
$permissionTypeToIndex = [
|
||||
'full_control' => 1,
|
||||
'write' => 2,
|
||||
'read' => 3,
|
||||
'modify' => 4,
|
||||
];
|
||||
return $permissionTypeToIndex[$type];
|
||||
}
|
||||
function levels_old() {
|
||||
// user no, full control, write, read, modify
|
||||
return [
|
||||
'Admin' => [1,1,1,1,1],
|
||||
'Manager (Center Office)' => [2,0,1,1,1],
|
||||
'Manager (QC)' => [2,0,1,1,0],
|
||||
'Manager (PTO)' => [2,0,1,1,0],
|
||||
'Manager (Lead)' => [3,0,1,1,0],
|
||||
'Welder (Subcontractor)' => [4,0,1,1,0],
|
||||
'Painter (Subcontractor)' => [5,0,1,1,0],
|
||||
'Insulator (Subcontractor)' => [6,0,1,1,0],
|
||||
'Welder, Painter, Insulator (Subcontractor / Payrollless)' => [7,0,1,1,0],
|
||||
'Quality Staff' => [8,0,1,1,1],
|
||||
'Document Staff' => [9,0,1,1,1],
|
||||
'Field Staff' => [10,0,0,1,0],
|
||||
];
|
||||
}
|
||||
|
||||
function levels() {
|
||||
// user no, full control, write, read, modify
|
||||
$userLevels = db("user_levels")->get();
|
||||
$levels = [];
|
||||
|
||||
foreach($userLevels AS $userLevel) {
|
||||
$levels[$userLevel->title] = [
|
||||
$userLevel->level_index,
|
||||
$userLevel->full_control,
|
||||
$userLevel->write,
|
||||
$userLevel->read,
|
||||
$userLevel->modify,
|
||||
];
|
||||
}
|
||||
|
||||
return $levels;
|
||||
}
|
||||
|
||||
function getLevelIndex($level) {
|
||||
$levels = levels();
|
||||
$k = 1;
|
||||
$index = null;
|
||||
|
||||
foreach($levels AS $thisLevel => $permissions) {
|
||||
if($thisLevel == $level) {
|
||||
$index = $permissions[0];
|
||||
}
|
||||
$k++;
|
||||
}
|
||||
return $index;
|
||||
}
|
||||
function levels2() {
|
||||
return [
|
||||
'Welder',
|
||||
'Engineer',
|
||||
];
|
||||
}
|
||||
|
||||
function level_keys() {
|
||||
return array_keys(levels());
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
function lineToIso()
|
||||
{
|
||||
// ISO numaralarına karşılık gelen line_number'ları almak
|
||||
$isoToLine = db("weld_logs")
|
||||
->select('line_number', 'iso_number')
|
||||
->pluck('iso_number', 'line_number')
|
||||
->toArray();
|
||||
|
||||
return $isoToLine;
|
||||
}
|
||||
|
||||
function lineTypeWelderToIso()
|
||||
{
|
||||
// (line_number, type_of_welds, welder) kombinasyonuna karşılık gelen iso_number'ları almak
|
||||
$rows = db("weld_logs")
|
||||
->select('line_number', 'type_of_welds', 'welder_1', 'welder_2', 'iso_number')
|
||||
->get();
|
||||
|
||||
$map = [];
|
||||
foreach ($rows as $row) {
|
||||
// welder_1 için map oluştur
|
||||
if (!empty($row->welder_1)) {
|
||||
$key = $row->line_number . '|' . $row->type_of_welds . '|' . $row->welder_1;
|
||||
$map[$key] = $row->iso_number;
|
||||
}
|
||||
|
||||
// welder_2 için de map oluştur (eğer welder_1'den farklıysa)
|
||||
if (!empty($row->welder_2) && $row->welder_2 !== $row->welder_1) {
|
||||
$key = $row->line_number . '|' . $row->type_of_welds . '|' . $row->welder_2;
|
||||
$map[$key] = $row->iso_number;
|
||||
}
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php function locations() {
|
||||
return json_encode_tr(db("locations")->get()->pluck("title")->toArray());
|
||||
} ?>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php function log_test_types() {
|
||||
return [
|
||||
'ht' => 'hardness_tests',
|
||||
'rt' => 'radiographic_tests',
|
||||
'ut' => 'ultrasonic_tests',
|
||||
'mt' => 'magnetic_tests',
|
||||
'pmi' => 'p_m_i_tests',
|
||||
'vt' => 'v_t_logs',
|
||||
'pt' => 'p_t_logs',
|
||||
'ferrite' => 'ferrits',
|
||||
'pwht' => 'p_w_h_t_s',
|
||||
];
|
||||
}
|
||||
|
||||
function get_key_by_value($value) {
|
||||
$array = log_test_types();
|
||||
$key = array_search($value, $array);
|
||||
return $key !== false ? $key : null; // Eğer değer bulunamazsa null döner
|
||||
}
|
||||
|
||||
function log_paths() {
|
||||
$mainPath = "004_QA/";
|
||||
return [
|
||||
'ht' => $mainPath . '0008_HT',
|
||||
'rt' => $mainPath . '0001_RT',
|
||||
'ut' => $mainPath . '0002_UT',
|
||||
'mt' => $mainPath . '0003_MT',
|
||||
'pmi' => $mainPath . '0005_PMI',
|
||||
'vt' => $mainPath . '0000_VT',
|
||||
'pt' => $mainPath . '0004_PT',
|
||||
'ferrite' => $mainPath . '0006_Ferrite',
|
||||
'pwht' => $mainPath . '0007_PWHT',
|
||||
];
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
function mailtemp($mail,$name,$data="") {
|
||||
$temp = db("mail_templates")->where("title",$name)->first();
|
||||
$html = $temp->html;
|
||||
$subject = $temp->title2;
|
||||
if(is_array($data)) {
|
||||
foreach($data AS $a => $d) {
|
||||
$html = str_replace("{".$a."}",$d,$html);
|
||||
$subject = str_replace("{".$a."}",$d,$subject);
|
||||
}
|
||||
}
|
||||
|
||||
@mailsend($mail,$subject,$html);
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
use App\Models\MaterialGroupMap;
|
||||
|
||||
|
||||
function materialGroupMap($ru1, $ru2)
|
||||
{
|
||||
|
||||
// Step 1: Get qualified materials
|
||||
$qualifiedMaterials = MaterialGroupMap::orWhere("material_name", $ru1)
|
||||
->orWhere("material_name", $ru2)
|
||||
->select("qualified_materials")
|
||||
->first()?->qualified_materials;
|
||||
|
||||
if ($qualifiedMaterials === null) {
|
||||
$response = [];
|
||||
} else {
|
||||
$groupArray = explode(",", $qualifiedMaterials);
|
||||
$response = $groupArray;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
if (!function_exists('whatMaterialGroup')) {
|
||||
|
||||
function whatMaterialGroup($thisSteelGrade, $materialMap) {
|
||||
foreach($materialMap as $steelGrade => $group) {
|
||||
$similar = similar_text($steelGrade, $thisSteelGrade, $percentage);
|
||||
|
||||
if($percentage > 95) {
|
||||
Log::debug("Benzerlik eşiği aşıldı, grup döndürülüyor", [
|
||||
'aranan_steel_grade' => $thisSteelGrade,
|
||||
'eslesen_steel_grade' => $steelGrade,
|
||||
'benzerlik_orani' => $percentage,
|
||||
'dondurulen_group' => $group
|
||||
]);
|
||||
return $group;
|
||||
}
|
||||
}
|
||||
Log::debug("Uygun material group bulunamadı", [
|
||||
'aranan_steel_grade' => $thisSteelGrade
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
function materialGroupUpdater($id) {
|
||||
// Material Group Finder
|
||||
//if ($shouldRunMaterialGroupUpdate) {
|
||||
// Material verilerini çek ve logla
|
||||
$materials = db("materials")->where("steel_grade", "<>", "*")->whereNotNull("steel_grade")->get();
|
||||
Log::debug("Material verileri çekildi", [
|
||||
'material_count' => $materials->count(),
|
||||
'material_examples' => $materials->take(3)->toArray()
|
||||
]);
|
||||
|
||||
$materialMap = [];
|
||||
foreach($materials as $material) {
|
||||
$material->steel_grade = trim($material->steel_grade);
|
||||
$materialMap[$material->steel_grade] = $material->ru_group;
|
||||
}
|
||||
Log::debug("MaterialMap oluşturuldu", [
|
||||
'materialMap_count' => count($materialMap),
|
||||
'materialMap_examples' => array_slice($materialMap, 0, 3, true)
|
||||
]);
|
||||
|
||||
$weldmaps = db("weld_logs")
|
||||
->where('id', $id)
|
||||
->get();
|
||||
|
||||
$materialGroupUpdateCount = 0;
|
||||
|
||||
foreach($weldmaps AS $weldmap) {
|
||||
$weldmap->material_no_1 = trim($weldmap->material_no_1);
|
||||
$group1 = whatMaterialGroup($weldmap->material_no_1, $materialMap); //isset($materialMap[$weldmap->material_no_1]) ? $materialMap[$weldmap->material_no_1] : "";
|
||||
$group2 = whatMaterialGroup($weldmap->material_no_2, $materialMap); //isset($materialMap[$weldmap->material_no_2]) ? $materialMap[$weldmap->material_no_2] : "";
|
||||
|
||||
|
||||
db("weld_logs")
|
||||
->where("id", $weldmap->id)
|
||||
->update([
|
||||
"ru_material_group_1" => $group1,
|
||||
"ru_material_group_2" => $group2
|
||||
]);
|
||||
|
||||
//dump($group1);
|
||||
//dump($group2);
|
||||
|
||||
$materialGroupUpdateCount++;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
return [
|
||||
'materialGroupUpdateCount' => $materialGroupUpdateCount,
|
||||
'ru_material_group_1' => $group1,
|
||||
'ru_material_group_2' => $group2,
|
||||
'material_no_1' => $weldmap->material_no_1,
|
||||
'material_no_2' => $weldmap->material_no_2,
|
||||
'id' => $weldmap->id,
|
||||
];
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,575 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Notification;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Send notification to users based on notification code and role mapping
|
||||
*
|
||||
* Performance optimizations:
|
||||
* - Single query to get target roles from settings
|
||||
* - Bulk insert for notifications (one query for all users)
|
||||
* - Uses whereIn for efficient user filtering
|
||||
* - Chunked inserts (500 per chunk) to avoid max packet size issues
|
||||
*
|
||||
* Note: Direct execution is fast enough (1-2 seconds for typical 10-50 users)
|
||||
* Queue integration was removed to avoid unnecessary complexity and worker dependency.
|
||||
*
|
||||
* @param string $notificationCode Notification code from catalog
|
||||
* @param string $message Notification message
|
||||
* @param string|null $link Optional link for notification
|
||||
* @param string|null $title Optional custom title (if null, uses default from code)
|
||||
* @param array|null $filterParams Optional filter parameters for filtered list view
|
||||
* @param int $itemCount Item count for batch notifications (default: 1)
|
||||
* @return int Number of notifications created
|
||||
*/
|
||||
function sendNotification($notificationCode, $message, $link = null, $title = null, $filterParams = null, $itemCount = 1) {
|
||||
try {
|
||||
// Get target roles from settings
|
||||
$targetRoles = j(setting($notificationCode));
|
||||
|
||||
// If no roles defined or empty, log warning and return early
|
||||
if (!is_array($targetRoles) || empty($targetRoles)) {
|
||||
Log::warning("Notification not sent: No roles defined in settings", [
|
||||
'notification_code' => $notificationCode,
|
||||
'message' => $message,
|
||||
'title' => $title,
|
||||
]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get active users with target roles in single query
|
||||
// Performance: Uses whereIn for efficient filtering
|
||||
$users = DB::table('users')
|
||||
->select('id', 'level', 'name', 'email')
|
||||
->whereIn('level', $targetRoles)
|
||||
->whereNotNull('level') // Ensure level is not null
|
||||
->get();
|
||||
|
||||
// Remove level, name, email from selection after logging (we only need id for insert)
|
||||
$userIds = $users->pluck('id');
|
||||
|
||||
// Debug: Log all users found
|
||||
Log::info("Notification target users found", [
|
||||
'notification_code' => $notificationCode,
|
||||
'target_roles' => $targetRoles,
|
||||
'users_count' => $users->count(),
|
||||
'user_ids' => $users->pluck('id')->toArray(),
|
||||
'user_levels' => $users->pluck('level')->toArray(),
|
||||
]);
|
||||
|
||||
// If no users found, log warning and return early
|
||||
if ($users->isEmpty()) {
|
||||
// Debug: Check what users exist with these levels
|
||||
$allUsersWithLevels = DB::table('users')
|
||||
->select('id', 'level', 'name', 'email')
|
||||
->whereNotNull('level')
|
||||
->get()
|
||||
->groupBy('level');
|
||||
|
||||
Log::warning("Notification not sent: No users found with target roles", [
|
||||
'notification_code' => $notificationCode,
|
||||
'target_roles' => $targetRoles,
|
||||
'message' => $message,
|
||||
'available_levels' => $allUsersWithLevels->keys()->toArray(),
|
||||
'users_by_level' => $allUsersWithLevels->map(function($group) {
|
||||
return $group->pluck('id')->toArray();
|
||||
})->toArray(),
|
||||
]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Generate title if not provided
|
||||
if ($title === null) {
|
||||
$title = ucwords(str_replace('_', ' ', $notificationCode));
|
||||
}
|
||||
|
||||
// If filterParams provided, generate link to filtered list page
|
||||
// We'll update link after insert with notification ID
|
||||
$useFilteredList = !empty($filterParams);
|
||||
if ($useFilteredList && $link === null) {
|
||||
$link = '/admin/notifications/filtered-list'; // Will be updated with notification ID after insert
|
||||
}
|
||||
|
||||
// Prepare bulk insert data
|
||||
// Performance: Single insert query for all notifications
|
||||
$now = now();
|
||||
$notificationsData = [];
|
||||
|
||||
foreach ($userIds as $userId) {
|
||||
$notificationsData[] = [
|
||||
'user_id' => $userId,
|
||||
'notification_code' => $notificationCode,
|
||||
'title' => $title,
|
||||
'message' => $message,
|
||||
'link' => $link,
|
||||
'is_read' => false,
|
||||
'filter_params' => $filterParams ? json_encode($filterParams) : null,
|
||||
'item_count' => $itemCount,
|
||||
'notification_type' => $filterParams ? 'batch' : 'single',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
}
|
||||
|
||||
// Bulk insert notifications
|
||||
// Performance: One query instead of N queries
|
||||
if (!empty($notificationsData)) {
|
||||
// Split into chunks to avoid max packet size issues
|
||||
$chunks = array_chunk($notificationsData, 500);
|
||||
$totalInserted = 0;
|
||||
|
||||
foreach ($chunks as $chunk) {
|
||||
// If using filtered list, we need to update links with notification IDs after insert
|
||||
if ($useFilteredList) {
|
||||
$ids = [];
|
||||
foreach ($chunk as $data) {
|
||||
$id = DB::table('notifications')->insertGetId($data);
|
||||
$ids[] = $id;
|
||||
}
|
||||
// Update links with notification IDs
|
||||
foreach ($ids as $id) {
|
||||
DB::table('notifications')
|
||||
->where('id', $id)
|
||||
->update(['link' => '/admin/notifications/filtered-list?id=' . $id]);
|
||||
}
|
||||
$totalInserted += count($chunk);
|
||||
} else {
|
||||
DB::table('notifications')->insert($chunk);
|
||||
$totalInserted += count($chunk);
|
||||
}
|
||||
}
|
||||
|
||||
return $totalInserted;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// Log error but don't break the main flow
|
||||
Log::error('Notification send error: ' . $e->getMessage(), [
|
||||
'code' => $notificationCode,
|
||||
'message' => $message,
|
||||
]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Send batch notification with filter params and count-based message
|
||||
*
|
||||
* @param string $notificationCode
|
||||
* @param string $title
|
||||
* @param string $messageTemplate String with %d placeholder for count
|
||||
* @param array $filterParams
|
||||
* @param int $count
|
||||
* @return int
|
||||
*/
|
||||
function sendBatchNotificationWithFilter($notificationCode, $title, $messageTemplate, array $filterParams, int $count)
|
||||
{
|
||||
$count = (int) $count;
|
||||
if ($count <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$message = $messageTemplate ? sprintf($messageTemplate, $count) : sprintf('%d record(s) require attention.', $count);
|
||||
return sendNotification($notificationCode, $message, null, $title, $filterParams, $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send notification to specific users (by user IDs)
|
||||
* Performance: Bulk insert for multiple users
|
||||
*
|
||||
* @param array $userIds Array of user IDs
|
||||
* @param string $notificationCode Notification code
|
||||
* @param string $message Notification message
|
||||
* @param string|null $link Optional link
|
||||
* @param string|null $title Optional title
|
||||
* @return int Number of notifications created
|
||||
*/
|
||||
function sendNotificationToUsers($userIds, $notificationCode, $message, $link = null, $title = null) {
|
||||
try {
|
||||
if (empty($userIds)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Generate title if not provided
|
||||
if ($title === null) {
|
||||
$title = ucwords(str_replace('_', ' ', $notificationCode));
|
||||
}
|
||||
|
||||
// Prepare bulk insert data
|
||||
$now = now();
|
||||
$notificationsData = [];
|
||||
|
||||
foreach ($userIds as $userId) {
|
||||
$notificationsData[] = [
|
||||
'user_id' => $userId,
|
||||
'notification_code' => $notificationCode,
|
||||
'title' => $title,
|
||||
'message' => $message,
|
||||
'link' => $link,
|
||||
'is_read' => false,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
}
|
||||
|
||||
// Bulk insert
|
||||
if (!empty($notificationsData)) {
|
||||
$chunks = array_chunk($notificationsData, 500);
|
||||
$totalInserted = 0;
|
||||
|
||||
foreach ($chunks as $chunk) {
|
||||
DB::table('notifications')->insert($chunk);
|
||||
$totalInserted += count($chunk);
|
||||
}
|
||||
|
||||
return $totalInserted;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Notification send error (specific users): ' . $e->getMessage());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send notification to single user
|
||||
* Performance: Single insert query
|
||||
*
|
||||
* @param int $userId User ID
|
||||
* @param string $notificationCode Notification code
|
||||
* @param string $message Notification message
|
||||
* @param string|null $link Optional link
|
||||
* @param string|null $title Optional title
|
||||
* @return bool Success status
|
||||
*/
|
||||
function sendNotificationToUser($userId, $notificationCode, $message, $link = null, $title = null) {
|
||||
try {
|
||||
if ($title === null) {
|
||||
$title = ucwords(str_replace('_', ' ', $notificationCode));
|
||||
}
|
||||
|
||||
Notification::create([
|
||||
'user_id' => $userId,
|
||||
'notification_code' => $notificationCode,
|
||||
'title' => $title,
|
||||
'message' => $message,
|
||||
'link' => $link,
|
||||
'is_read' => false,
|
||||
]);
|
||||
|
||||
return true;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Notification send error (single user): ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up old notifications
|
||||
* Performance: Bulk delete query with date filter
|
||||
*
|
||||
* @param int $days Number of days to keep (default: 90)
|
||||
* @return int Number of deleted notifications
|
||||
*/
|
||||
function cleanupOldNotifications($days = 90) {
|
||||
try {
|
||||
return Notification::deleteOldNotifications($days);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Notification cleanup error: ' . $e->getMessage());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy existing notifications to new users based on their level
|
||||
* DISABLED: This function was causing duplicate notifications and system issues
|
||||
*
|
||||
* @param string $notificationCode Notification code to copy
|
||||
* @return int Always returns 0 (disabled)
|
||||
*/
|
||||
function copyNotificationsToNewUsers($notificationCode) {
|
||||
// DISABLED: This function was causing excessive duplicate notifications
|
||||
// All calls to this function should be removed from notification check commands
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize filter_params for comparison
|
||||
*
|
||||
* @param mixed $filterParams
|
||||
* @return string
|
||||
*/
|
||||
function normalizeFilterParams($filterParams): string
|
||||
{
|
||||
if (empty($filterParams)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$decoded = is_string($filterParams)
|
||||
? json_decode($filterParams, true)
|
||||
: $filterParams;
|
||||
|
||||
if ($decoded === null || !is_array($decoded)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
ksort($decoded);
|
||||
return json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up duplicate notifications by notification_code + user_id + filter_params
|
||||
* This is more aggressive and handles cases where same notification_code is sent multiple times
|
||||
* Uses direct SQL queries with minimal memory usage
|
||||
*
|
||||
* @return array{total_deleted: int, notifications_before: int, notifications_after: int, error?: string}
|
||||
*/
|
||||
function cleanupDuplicateNotificationsByCode(): array
|
||||
{
|
||||
try {
|
||||
$stats = [
|
||||
'total_deleted' => 0,
|
||||
'notifications_before' => 0,
|
||||
'notifications_after' => 0,
|
||||
];
|
||||
|
||||
$stats['notifications_before'] = DB::table('notifications')->count();
|
||||
|
||||
// Process in very small batches to avoid memory issues
|
||||
$batchSize = 10; // Process 10 users at a time
|
||||
$totalDeleted = 0;
|
||||
$usersAffected = [];
|
||||
|
||||
// Get all notification codes one by one
|
||||
$notificationCodes = DB::table('notifications')
|
||||
->select('notification_code')
|
||||
->distinct()
|
||||
->pluck('notification_code');
|
||||
|
||||
foreach ($notificationCodes as $notificationCode) {
|
||||
// Get user IDs for this code in batches
|
||||
$offset = 0;
|
||||
while (true) {
|
||||
$userIds = DB::table('notifications')
|
||||
->where('notification_code', $notificationCode)
|
||||
->select('user_id')
|
||||
->distinct()
|
||||
->offset($offset)
|
||||
->limit($batchSize)
|
||||
->pluck('user_id');
|
||||
|
||||
if ($userIds->isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($userIds as $userId) {
|
||||
// Get notification IDs for this code + user, ordered by created_at desc
|
||||
// Process in small chunks
|
||||
$notificationOffset = 0;
|
||||
$notificationLimit = 100;
|
||||
$seenFilterParams = [];
|
||||
$idsToDelete = [];
|
||||
|
||||
while (true) {
|
||||
$notifications = DB::table('notifications')
|
||||
->where('notification_code', $notificationCode)
|
||||
->where('user_id', $userId)
|
||||
->select('id', 'filter_params', 'created_at')
|
||||
->orderBy('created_at', 'desc')
|
||||
->offset($notificationOffset)
|
||||
->limit($notificationLimit)
|
||||
->get();
|
||||
|
||||
if ($notifications->isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($notifications as $notification) {
|
||||
// Normalize filter_params
|
||||
$filterParams = normalizeFilterParams($notification->filter_params);
|
||||
|
||||
// If we've seen this filter_params before, it's a duplicate
|
||||
if (isset($seenFilterParams[$filterParams])) {
|
||||
$idsToDelete[] = $notification->id;
|
||||
} else {
|
||||
// First occurrence - keep it
|
||||
$seenFilterParams[$filterParams] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$notificationOffset += $notificationLimit;
|
||||
|
||||
// If we got less than limit, we're done
|
||||
if ($notifications->count() < $notificationLimit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete duplicates for this user
|
||||
if (!empty($idsToDelete)) {
|
||||
$deleteChunks = array_chunk($idsToDelete, 500);
|
||||
foreach ($deleteChunks as $chunk) {
|
||||
$deleted = DB::table('notifications')->whereIn('id', $chunk)->delete();
|
||||
$totalDeleted += $deleted;
|
||||
}
|
||||
|
||||
if (!in_array($userId, $usersAffected)) {
|
||||
$usersAffected[] = $userId;
|
||||
}
|
||||
}
|
||||
|
||||
// Free memory
|
||||
unset($notifications, $idsToDelete, $seenFilterParams);
|
||||
}
|
||||
|
||||
$offset += $batchSize;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear cache for affected users
|
||||
foreach ($usersAffected as $userId) {
|
||||
Cache::forget("user_notifications_{$userId}");
|
||||
Cache::forget("user_unread_type_count_{$userId}");
|
||||
}
|
||||
|
||||
$stats['total_deleted'] = $totalDeleted;
|
||||
$stats['notifications_after'] = DB::table('notifications')->count();
|
||||
|
||||
Log::info('Duplicate notifications cleanup by code completed', $stats);
|
||||
|
||||
return $stats;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error cleaning up duplicate notifications by code: ' . $e->getMessage());
|
||||
return [
|
||||
'error' => $e->getMessage(),
|
||||
'total_deleted' => 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up duplicate notifications
|
||||
* Removes duplicate notifications keeping only the most recent one for each user
|
||||
* Duplicates are identified by: user_id + message + link + normalized filter_params
|
||||
*
|
||||
* Performance: Uses SQL-based approach with chunking to avoid memory issues
|
||||
*
|
||||
* @return array{total_deleted: int, users_affected: int, notifications_before: int, notifications_after: int, error?: string}
|
||||
*/
|
||||
function cleanupDuplicateNotifications(): array
|
||||
{
|
||||
try {
|
||||
$stats = [
|
||||
'total_deleted' => 0,
|
||||
'users_affected' => 0,
|
||||
'notifications_before' => 0,
|
||||
'notifications_after' => 0,
|
||||
];
|
||||
|
||||
// Get total count before cleanup
|
||||
$stats['notifications_before'] = DB::table('notifications')->count();
|
||||
|
||||
// Process one user at a time to minimize memory usage
|
||||
$userIds = DB::table('notifications')
|
||||
->select('user_id')
|
||||
->distinct()
|
||||
->pluck('user_id');
|
||||
|
||||
$usersAffected = [];
|
||||
$totalDeleted = 0;
|
||||
|
||||
foreach ($userIds as $userId) {
|
||||
// Get notifications for this user, ordered by created_at desc (newest first)
|
||||
// Process in smaller chunks to avoid memory issues
|
||||
$offset = 0;
|
||||
$limit = 500;
|
||||
$userHasDuplicates = false;
|
||||
$seen = [];
|
||||
$idsToDelete = [];
|
||||
|
||||
while (true) {
|
||||
$notifications = DB::table('notifications')
|
||||
->where('user_id', $userId)
|
||||
->select('id', 'message', 'link', 'filter_params', 'created_at')
|
||||
->orderBy('created_at', 'desc')
|
||||
->offset($offset)
|
||||
->limit($limit)
|
||||
->get();
|
||||
|
||||
if ($notifications->isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($notifications as $notification) {
|
||||
// Normalize filter_params
|
||||
$filterParams = normalizeFilterParams($notification->filter_params);
|
||||
|
||||
$key = $notification->message . '|' . ($notification->link ?? '') . '|' . $filterParams;
|
||||
|
||||
// If we've seen this key before, it's a duplicate
|
||||
if (isset($seen[$key])) {
|
||||
$idsToDelete[] = $notification->id;
|
||||
$userHasDuplicates = true;
|
||||
} else {
|
||||
// First occurrence - keep it
|
||||
$seen[$key] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$offset += $limit;
|
||||
|
||||
// If we got less than limit, we're done with this user
|
||||
if ($notifications->count() < $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete duplicates for this user
|
||||
if (!empty($idsToDelete)) {
|
||||
$deleteChunks = array_chunk($idsToDelete, 500);
|
||||
foreach ($deleteChunks as $chunk) {
|
||||
$deleted = DB::table('notifications')->whereIn('id', $chunk)->delete();
|
||||
$totalDeleted += $deleted;
|
||||
}
|
||||
|
||||
if ($userHasDuplicates) {
|
||||
$usersAffected[] = $userId;
|
||||
// Clear cache for this user
|
||||
Cache::forget("user_notifications_{$userId}");
|
||||
Cache::forget("user_unread_type_count_{$userId}");
|
||||
}
|
||||
}
|
||||
|
||||
// Free memory
|
||||
unset($notifications, $idsToDelete, $seen);
|
||||
}
|
||||
|
||||
$stats['total_deleted'] = $totalDeleted;
|
||||
$stats['users_affected'] = count($usersAffected);
|
||||
|
||||
// Get total count after cleanup
|
||||
$stats['notifications_after'] = DB::table('notifications')->count();
|
||||
|
||||
Log::info('Duplicate notifications cleanup completed', $stats);
|
||||
|
||||
return $stats;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error cleaning up duplicate notifications: ' . $e->getMessage());
|
||||
return [
|
||||
'error' => $e->getMessage(),
|
||||
'total_deleted' => 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user