İlk temizlik tamamlandı bir önceki projeden

This commit is contained in:
Ümit Tunç
2026-04-28 21:14:25 +03:00
commit f80443aec0
10000 changed files with 959965 additions and 0 deletions
@@ -0,0 +1,140 @@
<?php
namespace App\Services\WeldLogTriggers\Base;
use App\Services\WeldLogTriggers\Contracts\WeldLogTriggerInterface;
use Illuminate\Support\Facades\Log;
/**
* Base class for all WeldLog triggers
*
* Provides common functionality like timing, logging, and shouldRun logic
*/
abstract class BaseTrigger implements WeldLogTriggerInterface
{
/**
* @var float Start time for performance tracking
*/
protected $startTime;
/**
* @var array Execution context
*/
protected $context = [];
/**
* Get trigger name - must be implemented by child classes
*/
abstract public function getName(): string;
/**
* Get trigger order - must be implemented by child classes
*/
abstract public function getOrder(): int;
/**
* Get dependent fields - must be implemented by child classes
*/
abstract public function getDependentFields(): array;
/**
* Process trigger logic - must be implemented by child classes
*
* @param object $weldLogData Current weld log data
* @param object|null $beforeData Previous weld log data
* @param array $context Execution context
* @return array Result data
*/
abstract protected function process($weldLogData, $beforeData, array $context): array;
/**
* Execute the trigger with logging and error handling
*
* @param object $weldLogData Current weld log data
* @param object|null $beforeData Previous weld log data
* @param array $context Execution context
* @return array Result data
* @throws \Throwable
*/
public function execute($weldLogData, $beforeData = null, array $context = []): array
{
$this->startTime = microtime(true);
$this->context = $context;
Log::info("WeldLog Trigger [{$this->getOrder()}/14] {$this->getName()} - STARTED", [
'weld_log_id' => $weldLogData->id,
'process' => $this->getName()
]);
try {
$result = $this->process($weldLogData, $beforeData, $context);
$this->logCompletion($weldLogData->id, 'success', $result);
return $result;
} catch (\Throwable $th) {
$this->logCompletion($weldLogData->id, 'error', [
'error' => $th->getMessage(),
'file' => $th->getFile(),
'line' => $th->getLine()
]);
throw $th;
}
}
/**
* Determine if trigger should run based on changed fields
*
* @param array $changedFields List of changed field names
* @param bool $isNewRecord Whether this is a new record
* @return bool
*/
public function shouldRun(array $changedFields, bool $isNewRecord): bool
{
if ($isNewRecord) {
return true;
}
$dependentFields = $this->getDependentFields();
// If no dependent fields defined, always run
if (empty($dependentFields)) {
return true;
}
// Check if any changed field is in dependent fields
return !empty(array_intersect($changedFields, $dependentFields));
}
/**
* Check if trigger is async (default: false)
* Override in child classes if needed
*
* @return bool
*/
public function isAsync(): bool
{
return false;
}
/**
* Log trigger completion with performance metrics
*
* @param int $weldLogId Weld log ID
* @param string $status Execution status (success/error)
* @param array $additionalData Additional log data
*/
protected function logCompletion($weldLogId, string $status, array $additionalData = [])
{
$duration = round((microtime(true) - $this->startTime) * 1000, 2);
$logLevel = $status === 'error' ? 'error' : 'info';
Log::$logLevel("WeldLog Trigger [{$this->getOrder()}/14] {$this->getName()} - COMPLETED", array_merge([
'weld_log_id' => $weldLogId,
'process' => $this->getName(),
'status' => $status,
'duration_ms' => $duration,
'duration_sec' => round($duration / 1000, 3)
], $additionalData));
}
}
@@ -0,0 +1,62 @@
<?php
namespace App\Services\WeldLogTriggers\Contracts;
/**
* Interface for WeldLog triggers
*
* Each trigger must implement this interface to be registered
* in the WeldLogTriggerRegistry and managed by WeldLogTriggerManager
*/
interface WeldLogTriggerInterface
{
/**
* Get trigger name for logging purposes
*
* @return string The human-readable name of the trigger
*/
public function getName(): string;
/**
* Get trigger execution order/priority (1-13)
* Lower numbers execute first
*
* @return int The execution order
*/
public function getOrder(): int;
/**
* Check if trigger should run based on changed fields
*
* @param array $changedFields List of field names that changed
* @param bool $isNewRecord Whether this is a new record
* @return bool True if trigger should execute
*/
public function shouldRun(array $changedFields, bool $isNewRecord): bool;
/**
* Get fields that this trigger depends on
* Used to determine if trigger should run when fields change
*
* @return array List of field names this trigger depends on
*/
public function getDependentFields(): array;
/**
* Execute the trigger logic
*
* @param object $weldLogData Current weld log data
* @param object|null $beforeData Previous weld log data (null for new records)
* @param array $context Additional context (changed_fields, is_new_record, etc)
* @return array Result data from trigger execution
*/
public function execute($weldLogData, $beforeData = null, array $context = []): array;
/**
* Check if trigger can be executed asynchronously (queued)
*
* @return bool True if can be queued, false for synchronous execution
*/
public function isAsync(): bool;
}
+284
View File
@@ -0,0 +1,284 @@
# WeldLog Triggers System
## 🎯 Overview
Modular, service-based trigger system for WeldLog save operations. Replaces the monolithic 2,435-line trigger file with 12 separate, maintainable trigger classes.
## 📊 Quick Stats
- **Original**: 1 file, 2,435 lines
- **New System**: 16 files, ~3,600 lines (better organized)
- **Triggers**: 12 independent operations
- **Execution Order**: 1-12 (explicit ordering)
- **Test Coverage**: Ready for unit testing
## 🗂️ Structure
```
app/Services/WeldLogTriggers/
├── Contracts/
│ └── WeldLogTriggerInterface.php # Interface
├── Base/
│ └── BaseTrigger.php # Base class
├── Triggers/
│ ├── SpoolStatusChangerTrigger.php # Order 1
│ ├── LineListsUpdateTrigger.php # Order 2
│ ├── NdeMatrixUpdateTrigger.php # Order 3
│ ├── RequestDateOperationsTrigger.php # Order 4
│ ├── RepairLogsUpdateTrigger.php # Order 5
│ ├── NdeProjectUpdateTrigger.php # Order 6
│ ├── TestPackageOperationsTrigger.php # Order 7
│ ├── ConstructionPaintLogsTrigger.php # Order 8
│ ├── TestPackBaseStatusChangerTrigger.php # Order 9
│ ├── PaintFollowUpsSyncTrigger.php # Order 10
│ ├── HandoversSyncTrigger.php # Order 11
│ └── TestPackCleanupTrigger.php # Order 12
├── WeldLogTriggerRegistry.php # Central registry
├── WeldLogTriggerManager.php # Execution manager
└── README.md # This file
```
## 🚀 Quick Start
### Basic Usage
```php
use App\Services\WeldLogTriggers\WeldLogTriggerManager;
use App\Services\WeldLogTriggers\WeldLogTriggerRegistry;
// Initialize
$registry = new WeldLogTriggerRegistry();
$manager = new WeldLogTriggerManager($registry);
// Execute all triggers
$results = $manager->executeTriggers(
$weldLogData, // Current data
$beforeData, // Previous data (null for new)
$changedFields, // Array of changed field names
$isNewRecord // Boolean
);
```
### Adding a New Trigger
1. Create trigger class in `Triggers/` directory
2. Extend `BaseTrigger`
3. Implement required methods
4. Register in `WeldLogTriggerRegistry.php`
```php
class MyNewTrigger extends BaseTrigger
{
public function getName(): string { return 'My New Trigger'; }
public function getOrder(): int { return 14; }
public function getDependentFields(): array { return ['field1', 'field2']; }
protected function process($data, $beforeData, array $context): array
{
// Your logic here
return ['success' => true];
}
}
```
## ✨ Key Features
### 1. Modular Design
Each trigger is a separate, focused class with single responsibility.
### 2. Automatic Change Detection
System automatically detects which fields changed and runs only relevant triggers.
### 3. Standardized Logging
All triggers use consistent log format with timing information.
### 4. Error Isolation
One trigger's error doesn't affect others (except critical triggers).
### 5. Performance Tracking
Each trigger's execution time is measured and logged separately.
### 6. Future-Ready
Built-in support for async execution (to be implemented).
## 📚 Documentation
### Complete Documentation
See [resources/views/guide/weld-log-triggers-system.md](../../../resources/views/guide/weld-log-triggers-system.md) for:
- Detailed architecture
- Complete trigger descriptions
- API documentation
- Best practices
- Troubleshooting guide
### Migration Guide
See [resources/views/guide/weld-log-triggers-migration.md](../../../resources/views/guide/weld-log-triggers-migration.md) for:
- Step-by-step migration
- Rollback procedures
- Validation checklist
- Common issues and solutions
## 🔍 How It Works
```
1. WeldLog saved in database
↓
2. SaveTrigger called (weld_logs.php)
↓
3. Detect changed fields
↓
4. Initialize Registry & Manager
↓
5. For each trigger (1-12):
- Check if should run (based on changed fields)
- Execute if needed
- Log results
↓
6. Return consolidated results
```
## 🎨 Design Patterns
- **Strategy Pattern**: Each trigger is a strategy
- **Registry Pattern**: Central trigger registry
- **Template Method**: BaseTrigger defines execution template
- **Chain of Responsibility**: Triggers execute in sequence
## 📋 Trigger List
| # | Trigger | Purpose | Avg Time |
|---|---------|---------|----------|
| 1 | Spool Status Changer | Updates spool statuses | 50-100ms |
| 2 | Line Lists Update | Syncs line lists data | 100-200ms |
| 3 | NDE Matrix Update | Manages NDE matrices | 200-500ms |
| 4 | Request Date Operations | Generates request numbers | 100-300ms |
| 5 | Repair Logs Update | Updates repair statuses | 50-100ms |
| 6 | NDE Project Update | Updates project fields | 50-100ms |
| 7 | Test Package Operations | Complex test package logic | 500-2000ms |
| 8 | Construction Paint Logs | Construction paint sync | 200-400ms |
| 9 | Test Pack Base Status | Updates base statuses | 300-600ms |
| 10 | Paint Follow Ups Sync | Comprehensive paint sync | 250-450ms |
| 11 | Handovers Sync | Syncs handover data | 100-200ms |
| 12 | Test Pack Cleanup | Cleans orphaned records | 50-100ms |
**Total**: 2-4 seconds average
## ⚡ Performance
### Optimization Features
- Chunk processing for large datasets
- Transaction retry logic for deadlocks
- Configurable delays between chunks
- Memory management
- Database timeout settings
### Monitoring
Check logs for performance metrics:
```bash
tail -f storage/logs/laravel.log | grep "WeldLog Trigger"
```
Look for:
- `duration_ms`: Execution time per trigger
- `total_execution_time_sec`: Overall time
- `peak_memory_usage_mb`: Memory usage
## 🧪 Testing
### Manual Testing
```php
// Test with real weld log
$weldLog = WeldLog::find(1);
$beforeData = clone $weldLog;
$weldLog->spool_number = 'NEW-SPOOL';
$weldLog->save();
// Check logs for trigger execution
```
### Unit Testing (Future)
Each trigger can be unit tested independently:
```php
$trigger = new SpoolStatusChangerTrigger();
$result = $trigger->execute($data, $beforeData, []);
$this->assertTrue($result['success']);
```
## 🐛 Troubleshooting
### Common Issues
**Trigger not executing?**
- Check `getDependentFields()` includes changed field
- Check logs for "Skipping trigger" messages
- Verify trigger is registered in Registry
**Performance slow?**
- Check chunk sizes in TransactionHelper calls
- Add database indexes
- Monitor slow query log
**Deadlock errors?**
- Ensure queries ordered by 'id ASC'
- Use TransactionHelper with retry logic
- Increase delays between chunks
### Debug Mode
Enable detailed logging:
```php
Log::setDefaultDriver('daily');
Log::info("Debug info", ['data' => $data]);
```
## 📝 Changelog
### Version 1.1.0 (2025-11-15)
- ✅ Consolidated paint follow-up logic (LineList parity)
- ✅ Removed legacy PaintFollowUpTrigger
- ✅ Added cleanup + protection to construction paint sync
- ✅ Updated docs and registry ordering
## 🤝 Contributing
### Adding New Triggers
1. Create class in `Triggers/` directory
2. Extend `BaseTrigger`
3. Implement all required methods
4. Add to Registry
5. Update documentation
6. Test thoroughly
### Code Style
- Follow PSR-12 standards
- Use type hints
- Add PHPDoc comments
- Keep methods focused (single responsibility)
- Log important operations
## 📖 Additional Resources
- [Main Documentation](../../../resources/views/guide/weld-log-triggers-system.md)
- [Migration Guide](../../../resources/views/guide/weld-log-triggers-migration.md)
- [DevQMS Documentation](../../../resources/views/guide/)
## 🔐 Security
- All triggers use prepared statements
- Transaction safety ensured
- Input validation in place
- Error messages don't expose sensitive data
## 📞 Support
For issues or questions:
1. Check documentation
2. Review logs
3. Contact development team
---
**Version**: 1.0.0
**Status**: Production Ready ✅
**Last Updated**: October 30, 2025
**Maintainer**: DevQMS Development Team
@@ -0,0 +1,333 @@
<?php
namespace App\Services\WeldLogTriggers\Triggers;
use App\Services\WeldLogTriggers\Base\BaseTrigger;
use App\Models\PaintMatrix;
use App\Helpers\TransactionHelper;
use Illuminate\Support\Facades\Log;
/**
* Construction Paint Logs Trigger
*
* Aligns weld log driven construction paint sync with LineList trigger behaviour.
* - Requires matching Line List + painting cycle
* - Processes each spool once, only when shop joints exist
* - Protects finished records and cleans up orphan entries
*/
class ConstructionPaintLogsTrigger extends BaseTrigger
{
public function getName(): string
{
return 'Construction Paint Logs Sync';
}
public function getOrder(): int
{
return 9;
}
public function getDependentFields(): array
{
return [
'line_number',
'spool_number',
'type_of_joint',
'project',
'design_area',
'iso_number',
'fluid_code',
'painting_cycle',
'test_package_no',
'nps_1',
'nps_2',
'spool_status',
];
}
protected function process($data, $beforeData, array $context): array
{
$constructionPaintLogUpdatedCount = 0;
$constructionPaintLogCreatedCount = 0;
$deletedCleanupCount = 0;
try {
Log::debug('ConstructionPaintLogsTrigger: Starting process', [
'weldLogId' => $data->id
]);
$weldLog = db("weld_logs")->where("id", $data->id)->first();
if (!$weldLog) {
return ['success' => false, 'reason' => 'weld_log_not_found'];
}
if (empty($weldLog->line_number)) {
return ['success' => false, 'reason' => 'line_number_empty'];
}
$lineList = db("line_lists")
->where('line_no', $weldLog->line_number)
->first();
if (!$lineList) {
return ['success' => false, 'reason' => 'line_list_not_found'];
}
if (empty($lineList->painting_cycle)) {
return ['skipped' => true, 'reason' => 'painting_cycle_empty'];
}
$allWeldLogs = db("weld_logs")
->where("line_number", $lineList->line_no)
->where("design_area", $lineList->unit)
->where("fluid_code", $lineList->fluid_code)
->orderBy('id', 'ASC')
->get();
if ($allWeldLogs->isEmpty()) {
return ['skipped' => true, 'reason' => 'no_matching_weld_logs'];
}
$processedSpools = [];
$validShopSpools = $this->getValidShopSpools($lineList->line_no, $lineList->unit, $lineList->fluid_code);
TransactionHelper::chunkTransaction(
$allWeldLogs,
function ($weldLogChunk) use (
$lineList,
&$constructionPaintLogUpdatedCount,
&$constructionPaintLogCreatedCount,
&$processedSpools
) {
foreach ($weldLogChunk as $currentWeldLog) {
if (empty($currentWeldLog->spool_number)) {
continue;
}
if (in_array($currentWeldLog->spool_number, $processedSpools, true)) {
continue;
}
if (!$this->hasShopJoint($lineList->line_no, $currentWeldLog->spool_number)) {
continue;
}
$this->processWeldLogRecord(
$currentWeldLog,
$lineList,
$constructionPaintLogUpdatedCount,
$constructionPaintLogCreatedCount
);
$processedSpools[] = $currentWeldLog->spool_number;
}
return $weldLogChunk->count();
},
1,
10000
);
$deletedCleanupCount = $this->cleanupConstructionRecords($lineList->line_no, $validShopSpools);
} catch (\Throwable $th) {
Log::error("Construction Paint Logs sync error: " . $th->getMessage(), [
'weld_log_id' => $data->id
]);
throw $th;
}
return [
'success' => true,
'created' => $constructionPaintLogCreatedCount,
'updated' => $constructionPaintLogUpdatedCount,
'deleted_cleanup' => $deletedCleanupCount
];
}
protected function processWeldLogRecord($currentWeldLog, $lineList, &$updatedCount, &$createdCount)
{
$paintMatrix = PaintMatrix::where('line', $lineList->line_no)
->where('fluid_code', $lineList->fluid_code)
->first();
$baseConstructionPaintLogData = [
'construction_report_no' => $currentWeldLog->weld_map_no ?? '',
'test_package' => $currentWeldLog->test_package_no ?? '',
'rev' => $lineList->rev ?? '',
'engineering' => $lineList->engineering ?? '',
'area' => $currentWeldLog->project ?? '',
'unit' => $currentWeldLog->design_area ?? '',
'line' => $lineList->line_no,
'iso_drawings' => $currentWeldLog->iso_number ?? '',
'fluid_code' => $lineList->fluid_code ?? '',
'fluid_code_description' => $lineList->fluid_ru ?? '',
'isolation_info' => $lineList->external_finish_type ?? '',
'updated_at' => now()
];
if ($paintMatrix) {
$baseConstructionPaintLogData = array_merge($baseConstructionPaintLogData, [
'painting_system_type_1' => $paintMatrix->paint_cycle ?? '',
'painting_system_type_2' => $paintMatrix->paint_cycle ?? '',
'brend_name_1' => $paintMatrix->brend_name_1 ?? '',
'ral_1' => $paintMatrix->ral_code_1 ?? '',
'color_1' => $paintMatrix->colour_1 ?? '',
'thickness_1' => $paintMatrix->thickness_1 ?? '',
'brend_name_2' => $paintMatrix->brend_name_2 ?? '',
'ral_2' => $paintMatrix->ral_code_2 ?? '',
'color_2' => $paintMatrix->colour_2 ?? '',
'thickness_2' => $paintMatrix->thickness_2 ?? '',
'brend_name_3' => $paintMatrix->brend_name_3 ?? '',
'ral_3' => $paintMatrix->ral_code_3 ?? '',
'color_3' => $paintMatrix->colour_3 ?? '',
'thickness_3' => $paintMatrix->thickness_3 ?? '',
]);
}
$constructionPaintLogData = $baseConstructionPaintLogData;
$constructionPaintLogData['test_package'] = $currentWeldLog->test_package_no ?? '';
$constructionPaintLogData['spool'] = $currentWeldLog->spool_number ?? '';
$constructionPaintLogData['spool_status'] = $currentWeldLog->spool_status ?? 'Waiting';
$constructionPaintLogData['report_no'] = '';
$constructionPaintLogData['dn_1'] = $currentWeldLog->nps_1 ?? '';
$constructionPaintLogData['dn_2'] = $currentWeldLog->nps_2 ?? '';
$constructionPaintLogData['dn_3'] = '';
$uniqueConstraintCondition = [
'line' => $lineList->line_no,
'spool' => $currentWeldLog->spool_number,
'painting_system_type_1' => $paintMatrix->paint_cycle ?? ''
];
$whereCondition = [
'line' => $lineList->line_no,
'spool' => $currentWeldLog->spool_number
];
$existingRecordSameCycle = db("construction_paint_logs")
->where($uniqueConstraintCondition)
->first();
$recordsWithDifferentPaintCycle = db("construction_paint_logs")
->where($whereCondition)
->where('painting_system_type_1', '!=', $paintMatrix->paint_cycle ?? '')
->get();
if ($existingRecordSameCycle) {
$hasAllEmptyDates = $this->hasAllEmptyDates($existingRecordSameCycle);
if ($hasAllEmptyDates) {
db("construction_paint_logs")
->where('id', $existingRecordSameCycle->id)
->update($constructionPaintLogData);
$updatedCount++;
}
return;
}
if ($recordsWithDifferentPaintCycle->count() > 0) {
foreach ($recordsWithDifferentPaintCycle as $oldRecord) {
$hasAnyDateFilled = !$this->hasAllEmptyDates($oldRecord);
if (!$hasAnyDateFilled) {
db("construction_paint_logs")
->where('id', $oldRecord->id)
->update($constructionPaintLogData);
$updatedCount++;
return;
}
}
}
$constructionPaintLogData['created_at'] = now();
db("construction_paint_logs")->insert($constructionPaintLogData);
$createdCount++;
}
protected function hasAllEmptyDates($record): bool
{
$dateFields = [
'blasting_date',
'blasting_finish_date',
'painting_date_1',
'painting_finish_date_1',
'rfi_date_1',
'painting_date_2',
'painting_finish_date_2',
'rfi_date_2',
'painting_date_3',
'painting_finish_date_3',
'rfi_date_3'
];
foreach ($dateFields as $field) {
if (!empty($record->$field)) {
return false;
}
}
return true;
}
protected function hasShopJoint(string $lineNumber, string $spoolNumber): bool
{
return db("weld_logs")
->where('line_number', $lineNumber)
->where('spool_number', $spoolNumber)
->where('type_of_joint', 'S')
->exists();
}
protected function getValidShopSpools(string $lineNumber, ?string $unit, ?string $fluidCode): array
{
return db("weld_logs")
->where('line_number', $lineNumber)
->when($unit, function ($query, $unit) {
return $query->where('design_area', $unit);
})
->when($fluidCode, function ($query, $fluidCode) {
return $query->where('fluid_code', $fluidCode);
})
->where('type_of_joint', 'S')
->whereNotNull('spool_number')
->pluck('spool_number')
->unique()
->values()
->toArray();
}
protected function cleanupConstructionRecords(string $lineNumber, array $validShopSpools): int
{
$query = db("construction_paint_logs")
->where('line', $lineNumber);
if (!empty($validShopSpools)) {
$recordsToEvaluate = $query
->whereNotIn('spool', $validShopSpools)
->get();
} else {
$recordsToEvaluate = $query->get();
}
if ($recordsToEvaluate->isEmpty()) {
return 0;
}
$deletableIds = $recordsToEvaluate
->filter(function ($record) {
return $this->hasAllEmptyDates($record);
})
->pluck('id')
->toArray();
if (empty($deletableIds)) {
return 0;
}
return db("construction_paint_logs")
->whereIn('id', $deletableIds)
->delete();
}
}
@@ -0,0 +1,219 @@
<?php
namespace App\Services\WeldLogTriggers\Triggers;
use App\Services\WeldLogTriggers\Base\BaseTrigger;
use App\Helpers\TransactionHelper;
use Illuminate\Support\Facades\Log;
/**
* Handovers Sync Trigger
*
* Syncs data from WeldLogs to Handovers table
* Groups by line_number and creates/updates handover records
*/
class HandoversSyncTrigger extends BaseTrigger
{
public function getName(): string
{
return 'Handovers Sync';
}
public function getOrder(): int
{
return 12;
}
public function getDependentFields(): array
{
return [
];
}
protected function process($data, $beforeData, array $context): array
{
try {
// Get current weld log data
$weldLog = db("weld_logs")->where("id", $data->id)->first();
if (!$weldLog) {
return ['success' => false, 'reason' => 'weld_log_not_found'];
}
// Skip processing if essential fields are missing
if (empty($weldLog->line_number)) {
return ['success' => false, 'reason' => 'line_number_empty'];
}
// Get all weld logs with the same line_number
$allWeldLogs = db("weld_logs")
->where("line_number", $weldLog->line_number)
->orderBy('id', 'ASC') // Deadlock prevention
->get();
// Process each weld log
$handoversCreated = 0;
$handoversUpdated = 0;
// Group by line - handovers are stored by line
$uniqueLines = [];
foreach ($allWeldLogs as $currentWeldLog) {
$lineKey = $currentWeldLog->line_number;
if (!isset($uniqueLines[$lineKey])) {
$uniqueLines[$lineKey] = $currentWeldLog;
}
}
// Process Handovers in chunks with TransactionHelper
TransactionHelper::chunkTransaction(
collect($uniqueLines),
function ($handoverChunk) use (&$handoversCreated, &$handoversUpdated) {
foreach ($handoverChunk as $currentWeldLog) {
$this->processHandoverRecord($currentWeldLog, $handoversCreated, $handoversUpdated);
}
return $handoverChunk->count();
},
10, // Chunk size
10000 // 10ms delay
);
} catch (\Throwable $th) {
Log::error("Handovers sync error: " . $th->getMessage());
// Log detailed error for debugging
Log::error("Error details: ", [
'weldLogId' => $data->id,
'exception' => get_class($th),
'file' => $th->getFile(),
'line' => $th->getLine(),
'trace' => $th->getTraceAsString()
]);
throw $th;
}
return [
'success' => true,
'created' => $handoversCreated,
'updated' => $handoversUpdated
];
}
/**
* Process a single handover record
*/
protected function processHandoverRecord($currentWeldLog, &$createdCount, &$updatedCount)
{
// Check for required field
if (empty($currentWeldLog->line_number)) {
return;
}
// Get line list data (for additional information)
$lineList = db("line_lists")
->where('line_no', $currentWeldLog->line_number)
->first();
// Handover data
$handoverData = [
// Mapping: weld_logs -> handovers
'project' => $currentWeldLog->line_number, // line_number -> location (zone instead)
'work_type' => 'ТРУБКА', // piping_type -> work_type
'object' => $currentWeldLog->project ?? '', // project -> object
'location' => $currentWeldLog->design_area ?? '', // design_area -> project
// Status information
'status1' => 'In Progress',
'updated_at' => now()
];
// Where condition - location (line_number) and object (project) for unique record
$whereCondition = [
'project' => $currentWeldLog->line_number,
];
// Check if existing record exists
$existingRecord = db("handovers")
->where($whereCondition)
->first();
// Calculate volumes logic (mirrors hand-over.blade.php)
$collected = 0;
$notCollected = 1;
if ($existingRecord) {
$collected = (float) $existingRecord->collected_volumes;
// If existing record has specific value, use it, but check the "default to 1 if 0" rule
$notCollected = (float) $existingRecord->not_collected_volumes;
}
// Logic: if not_collected is 0 or empty, make it 1
if (empty($notCollected) || $notCollected == 0) {
$notCollected = 1;
}
$totalVolumes = $collected + $notCollected;
$handoverData['not_collected_volumes'] = $notCollected;
$handoverData['total_volumes'] = $totalVolumes;
// Calculate id_status logic
$handoverControlCount = (int) setting('handover_control_count', 3);
$idStatus = '';
$allAgreed = true;
$hasController = false;
for ($i = 1; $i <= $handoverControlCount; $i++) {
$statusField = "control{$i}_id_status";
$controllerField = "control{$i}_controller";
$statusValue = $existingRecord ? $existingRecord->$statusField : null;
$controllerValue = $existingRecord ? $existingRecord->$controllerField : null;
if ($controllerValue) {
$hasController = true;
if ($statusValue == 'Comment / замечание') {
$idStatus = 'Comment / замечание';
$allAgreed = false;
break;
}
if ($statusValue == 'Revision / Редакция') {
$idStatus = 'Revision / Редакция';
$allAgreed = false;
break;
}
if ($statusValue != 'Agreed / подписано') {
$allAgreed = false;
}
}
}
if ($idStatus === '') {
if ($allAgreed && $hasController) {
$archiveValue = $existingRecord ? $existingRecord->archive : null;
$idStatus = $archiveValue ? $archiveValue : 'Agreed / подписано';
} else {
$idStatus = 'Wait - Not Prepared';
}
}
$handoverData['id_status'] = $idStatus;
if ($existingRecord) {
// Update existing record
db("handovers")
->where('id', $existingRecord->id)
->update($handoverData);
$updatedCount++;
} else {
// Create new record
$handoverData['created_at'] = now();
db("handovers")->insert($handoverData);
$createdCount++;
}
}
}
@@ -0,0 +1,75 @@
<?php
namespace App\Services\WeldLogTriggers\Triggers;
use App\Services\WeldLogTriggers\Base\BaseTrigger;
use Illuminate\Support\Facades\Log;
/**
* Line Lists Update Trigger
*
* Updates line_lists table and syncs data from line_lists to weld_logs
* Triggers when line_number or design_area changes
*/
class LineListsUpdateTrigger extends BaseTrigger
{
public function getName(): string
{
return 'Line Lists Update';
}
public function getOrder(): int
{
return 2;
}
public function getDependentFields(): array
{
return [
'line_number',
'design_area',
'fluid_code',
'type_of_welds'
];
}
protected function process($data, $beforeData, array $context): array
{
try {
// Line lists updated_at trigger for cron
db("line_lists")
->where(['line_no'=> $data->line_number])
->where(['unit'=> $data->design_area])
->update(["updated_at" => simdi()]);
// Sync data from Line Lists to WeldLog via view render
$lineListsToWeldLogView = view('cron.line_lists-sync-from-linelists-to-weldlog', [
'line_number' => $data->line_number,
'design_area' => $data->design_area,
'commitSize' => 3, // Chunk size for transaction batching
'triggerMode' => true // Called from SaveTrigger
])->render();
Log::info("Line Lists to WeldLog sync completed via view render", [
'line_number' => $data->line_number,
'design_area' => $data->design_area
]);
return [
'success' => true,
'line_number' => $data->line_number,
'design_area' => $data->design_area
];
} catch (\Throwable $th) {
Log::error("Line Lists sync error: " . $th->getMessage(), [
'line_number' => $data->line_number,
'design_area' => $data->design_area
]);
throw $th;
}
}
}
@@ -0,0 +1,206 @@
<?php
namespace App\Services\WeldLogTriggers\Triggers;
use App\Services\WeldLogTriggers\Base\BaseTrigger;
use App\Models\NdeMatrix;
use App\Helpers\TransactionHelper;
use Illuminate\Support\Facades\Log;
/**
* NDE Matrix Update Trigger
*
* Syncs data between Line Lists, NDE Matrix, and WeldLogs
* Handles type_of_welds changes and updates NDE test scope percentages
*/
class NdeMatrixUpdateTrigger extends BaseTrigger
{
public function getName(): string
{
return 'NDE Matrix Update';
}
public function getOrder(): int
{
return 3;
}
public function getDependentFields(): array
{
return [
'type_of_welds',
'fluid_code',
'type_of_joint',
'line_number',
'design_area',
'project'
];
}
protected function process($data, $beforeData, array $context): array
{
try {
// Sync data from Line Lists to NDE Matrix via view render
$ndeMatrixView = view('cron.line_lists-sync-from-linelists-nde-matrix', [
'line_number' => $data->line_number,
'design_area' => $data->design_area,
'commitSize' => 5, // Chunk size for transaction batching
'triggerMode' => true // Called from SaveTrigger
])->render();
Log::info("Line Lists to NDE Matrix sync completed via view render", [
'line_number' => $data->line_number,
'design_area' => $data->design_area
]);
// Special handling for type_of_welds change
if (!is_null($beforeData) && $beforeData->type_of_welds !== $data->type_of_welds) {
TransactionHelper::retryTransaction(function () use ($data, $beforeData) {
// Where condition for old type
$oldTypeWhereData = [
'line' => $data->line_number,
'type_of_joint' => $beforeData->type_of_welds,
'fluid' => $data->fluid_code,
];
// Check if old type is still used by other WeldLog records
$otherRecordsUsingOldType = db("weld_logs")
->where('line_number', $data->line_number)
->where('type_of_welds', $beforeData->type_of_welds)
->where('fluid_code', $data->fluid_code)
->where('id', '!=', $data->id)
->orderBy('id', 'ASC') // Deadlock prevention
->exists();
// Delete from NDE Matrix only if old type is not used elsewhere
if (!$otherRecordsUsingOldType) {
$deleteResult = db("nde_matrices")
->where($oldTypeWhereData)
->delete();
Log::info("NDE Matrix old type cleaned up - no other weld logs using it", [
'line' => $data->line_number,
'old_type_of_joint' => $beforeData->type_of_welds,
'new_type_of_joint' => $data->type_of_welds,
'fluid' => $data->fluid_code,
'delete_result' => $deleteResult
]);
} else {
Log::info("NDE Matrix old type preserved - still used by other weld logs", [
'line' => $data->line_number,
'old_type_of_joint' => $beforeData->type_of_welds,
'new_type_of_joint' => $data->type_of_welds,
'fluid' => $data->fluid_code
]);
}
}, 5); // 5 attempts with exponential backoff
}
} catch (\Throwable $th) {
Log::error("NDE Matrix sync error: " . $th->getMessage(), [
'line_number' => $data->line_number,
'design_area' => $data->design_area
]);
throw $th;
}
// Sync from NDE Matrix to WeldLog
return $this->syncNdeMatrixToWeldLog($data);
}
/**
* Sync NDE Matrix data to WeldLog
* Updates scope percentages and other NDE-related fields
*/
protected function syncNdeMatrixToWeldLog($data): array
{
// fluid_code null check - get from line_lists if needed
$currentFluidCode = $data->fluid_code ?? '';
if(empty($currentFluidCode)) {
$lineListForFluid = db("line_lists")->where('line_no', $data->line_number)->first();
if($lineListForFluid) {
$currentFluidCode = $lineListForFluid->fluid_code ?? '';
}
}
Log::info("NDE Matrix to WeldLog sync started", [
'weld_log_id' => $data->id,
'line_number' => $data->line_number,
'type_of_welds' => $data->type_of_welds,
'fluid_code' => $currentFluidCode
]);
// Skip if fluid_code is null
if(empty($currentFluidCode)) {
Log::info("NDE Matrix to WeldLog sync skipped - fluid_code is null", [
'weld_log_id' => $data->id,
'line_number' => $data->line_number
]);
return ['skipped' => true, 'reason' => 'fluid_code_null'];
}
// Get NDE Matrix records
$ndeMatrices = NdeMatrix::where("line", $data->line_number)
->where("type_of_joint", $data->type_of_welds)
->where("fluid", $currentFluidCode)
->get();
Log::debug("NDE Matrix records retrieved", [
'matrix_count' => $ndeMatrices->count(),
'matrix_examples' => $ndeMatrices->take(2)->toArray()
]);
$ndeToWeldLogUpdateCount = 0;
// Process NDE Matrix in chunks with TransactionHelper
Log::debug("NDE Matrix to WeldLog sync starting", [
'total_records' => $ndeMatrices->count()
]);
TransactionHelper::chunkTransaction(
$ndeMatrices,
function ($ndeMatrixChunk) use (&$ndeToWeldLogUpdateCount) {
foreach($ndeMatrixChunk as $ndeMatrix) {
$weldLogUpdateData = [
'vt_scope' => 100,
'rt_scope' => $ndeMatrix->rt,
'ut_scope' => $ndeMatrix->ut,
'mt_scope' => $ndeMatrix->mt,
'pt_scope' => $ndeMatrix->pt,
'pmi_scope' => $ndeMatrix->pmi,
'ht_scope' => $ndeMatrix->ht,
'pwht' => $ndeMatrix->pwht_field,
'ferrite_scope' => $ndeMatrix->fn,
'ndt_percent' => $ndeMatrix->ndt,
'operating_temperature_s' => $ndeMatrix->operation_temp,
'operating_pressure_mpa' => $ndeMatrix->operations_pressure_kg,
'fluid_group' => $ndeMatrix->piping_group,
'main_material' => $ndeMatrix->material,
'updated_at' => simdi()
];
$result = db("weld_logs")
->where("line_number", $ndeMatrix->line)
->where("type_of_welds", $ndeMatrix->type_of_joint)
->orderBy('id', 'ASC') // Deadlock prevention
->update($weldLogUpdateData);
$ndeToWeldLogUpdateCount += $result;
}
return $ndeMatrixChunk->count();
},
10, // Chunk size increased from 3 to 10 for better performance
10000 // 10ms delay (reduced from 150ms) - sufficient for deadlock prevention
);
Log::info("NDE Matrix to WeldLog sync completed", [
'total_updated_records' => $ndeToWeldLogUpdateCount
]);
return [
'success' => true,
'updated_records' => $ndeToWeldLogUpdateCount
];
}
}
@@ -0,0 +1,63 @@
<?php
namespace App\Services\WeldLogTriggers\Triggers;
use App\Services\WeldLogTriggers\Base\BaseTrigger;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
/**
* NDE Project Update Trigger
*
* Updates NDE Matrix project field from weld_logs when it's null
* Syncs project information between tables
*/
class NdeProjectUpdateTrigger extends BaseTrigger
{
public function getName(): string
{
return 'NDE Project Update';
}
public function getOrder(): int
{
return 6;
}
public function getDependentFields(): array
{
return [
'line_number',
'project'
];
}
protected function process($data, $beforeData, array $context): array
{
// Update NDE matrices where project is null
$updatedCount = DB::table('nde_matrices')
->join('weld_logs', function($join) {
$join->on('nde_matrices.line', '=', 'weld_logs.line_number')
->on('nde_matrices.design_area', '=', 'weld_logs.design_area');
})
->whereNull('nde_matrices.project') // Find records where project column is empty
->where("weld_logs.id", $data->id)
->update([
'nde_matrices.project' => DB::raw('weld_logs.project') // Update with project value from weld_logs
]);
Log::info("NDE Matrix project field updated", [
'weld_log_id' => $data->id,
'line_number' => $data->line_number,
'design_area' => $data->design_area,
'project' => $data->project,
'updated_records' => $updatedCount
]);
return [
'success' => true,
'updated_records' => $updatedCount
];
}
}
@@ -0,0 +1,143 @@
<?php
namespace App\Services\WeldLogTriggers\Triggers;
use App\Services\WeldLogTriggers\Base\BaseTrigger;
use Illuminate\Support\Facades\Log;
/**
* NDT Calculation Cache Trigger
*
* Updates the NDT calculation cache when relevant fields are modified.
* This ensures the frontend displays up-to-date NDT backlog and status information
* without needing to run heavy calculations on every page load.
*/
class NdtCalculationCacheTrigger extends BaseTrigger
{
/**
* Get trigger name
*/
public function getName(): string
{
return 'NdtCalculationCacheTrigger';
}
/**
* Get trigger execution order
* Running as #14 (after all other updates)
*/
public function getOrder(): int
{
return 14;
}
/**
* Get fields that this trigger depends on
* Includes all fields used in ndt-calculation-no-cache.blade.php
*/
public function getDependentFields(): array
{
return [
// Identifiers
'iso_number',
'test_package_no',
'welder_1',
'welder_2',
'type_of_welds',
'welding_date',
// Scopes and Results
'rt_scope', 'rt_result', 'rt_test_date', 'rt_request_date',
'ut_scope', 'ut_result', 'ut_test_date', 'ut_request_date',
'pt_scope', 'pt_result', 'pt_test_date', 'pt_request_date',
'mt_scope', 'mt_result', 'mt_test_date', 'mt_request_date',
'pmi_scope', 'pmi_result', 'pmi_test_date', 'pmi_request_date',
'ferrite_scope', 'ferrite_result', 'ferrite_test_date', 'ferrite_request_date',
'ht_scope', 'ht_result', 'ht_test_date', 'ht_request_date',
'test_laboratory_ht',
'test_laboratory_rt',
'test_laboratory_ut',
'test_laboratory_pt',
'test_laboratory_mt',
'test_laboratory_pmi',
'test_laboratory_ferrite',
'test_laboratory_pwht',
'test_laboratory_vt',
'ut_request_date',
'pt_request_date',
'mt_request_date',
'pmi_request_date',
'ferrite_request_date',
'ht_request_date',
'rt_request_date',
'vt_request_date',
'pwht_request_date',
// PWHT
'pwht', 'pwht_date', 'pwht_result'
];
}
/**
* Process trigger logic
* Dispatches cache update job for NDT calculation
*/
protected function process($weldLogData, $beforeData, array $context): array
{
// Clone işleminde cache hesaplaması yapma
if (isset($context['action']) && $context['action'] === 'clone') {
Log::info('NdtCalculationCacheTrigger: Skipped due to clone action');
return ['skipped' => true, 'reason' => 'clone_action'];
}
$dispatched = false;
// Using the global helper function to dispatch the cache update
// This matches the usage in test2.blade.php provided by the user
if (function_exists('dispatchCacheBladeViews')) {
dispatchCacheBladeViews([
[
'view' => 'admin-ajax.request-ndt-no-cache',
'cache' => 'request-ndt'
],
[
'view' => 'admin-ajax.ndt-order.order-list-no-cache',
'cache' => 'ndt-order-list'
],
[
'view' => 'admin-ajax.repair-log-no-cache',
'cache' => 'repair-log'
],
[
'view' => 'admin-ajax.ndt-calculation-no-cache',
'cache' => 'ndt-calculation'
]
]);
$dispatched = true;
Log::info('NdtCalculationCacheTrigger: Cache update dispatched', [
'weld_log_id' => $weldLogData->id,
'iso' => $weldLogData->iso_number ?? 'unknown'
]);
} else {
Log::warning('NdtCalculationCacheTrigger: dispatchCacheBladeViews function not found');
}
return [
'cache_dispatched' => $dispatched
];
}
/**
* Allow async execution since this is just dispatching another job
*/
public function isAsync(): bool
{
return true;
}
}
@@ -0,0 +1,758 @@
<?php
namespace App\Services\WeldLogTriggers\Triggers;
use App\Services\WeldLogTriggers\Base\BaseTrigger;
use App\Models\PaintMatrix;
use App\Helpers\TransactionHelper;
use Illuminate\Support\Facades\Log;
use Carbon\Carbon;
/**
* Paint Follow Ups Sync Trigger
*
* Mirrors the LineList trigger logic while running during WeldLog saves:
* - Requires matching Line List + painting cycle
* - Generates SHOP + FIELD entries with temperature/volume data
* - Protects finished records, manages cycle changes, cleans up orphans
*/
class PaintFollowUpsSyncTrigger extends BaseTrigger
{
public function getName(): string
{
return 'Paint Follow Ups Sync';
}
public function getOrder(): int
{
return 11;
}
public function getDependentFields(): array
{
return [
'line_number',
'spool_number',
'no_of_the_joint_as_per_as_built_survey',
'fluid_code',
'iso_number',
'project',
'design_area',
'type_of_joint'
];
}
protected function process($data, $beforeData, array $context): array
{
$paintFollowUpCreated = 0;
$paintFollowUpUpdated = 0;
$deletedOrphanedCount = 0;
$finalCleanupCount = 0;
try {
$weldLog = db("weld_logs")->where("id", $data->id)->first();
if (!$weldLog) {
return ['success' => false, 'reason' => 'weld_log_not_found'];
}
if (empty($weldLog->line_number)) {
return ['success' => false, 'reason' => 'line_number_empty'];
}
$lineList = db("line_lists")
->where('line_no', $weldLog->line_number)
->first();
if (!$lineList) {
return ['success' => false, 'reason' => 'line_list_not_found'];
}
if (empty($lineList->painting_cycle)) {
return ['skipped' => true, 'reason' => 'painting_cycle_empty'];
}
$matchingWeldLogExists = db("weld_logs")
->where("line_number", $lineList->line_no)
->where("design_area", $lineList->unit)
->where("fluid_code", $lineList->fluid_code)
->exists();
if (!$matchingWeldLogExists) {
return ['skipped' => true, 'reason' => 'no_matching_weld_log'];
}
$deletedOrphanedCount = $this->cleanupOrphanedRecords($weldLog, $beforeData);
$resultFields = [
'vt_result',
'rt_result',
'ut_result',
'pt_result',
'mt_result',
'pmi_result',
'ht_result',
'ferrite_result'
];
$allWeldLogs = db("weld_logs")
->where("line_number", $lineList->line_no)
->where("design_area", $lineList->unit)
->where("fluid_code", $lineList->fluid_code)
->where(function ($query) {
$query->where("no_of_the_joint_as_per_as_built_survey", "not like", "%clone%");
})
->where(function ($query) use ($resultFields) {
foreach ($resultFields as $field) {
$query->where(function ($subQuery) use ($field) {
$subQuery->whereNull($field)
->orWhere($field, '')
->orWhere($field, 'Accept / Годен');
});
}
})
->orderBy('id', 'ASC')
->get();
if ($allWeldLogs->isEmpty()) {
return ['skipped' => true, 'reason' => 'no_weld_logs_for_line'];
}
$paintMatrix = PaintMatrix::where('line', $lineList->line_no)
->where('fluid_code', $lineList->fluid_code)
->where('area', $lineList->unit)
->where('paint_cycle', $lineList->painting_cycle)
->first();
if (!$paintMatrix) {
$paintMatrix = PaintMatrix::create([
'project' => $allWeldLogs->first()->project ?? '',
'area' => $lineList->unit,
'description' => "PIPE",
'line' => $lineList->line_no,
'fluid_code_description' => $lineList->fluid_ru,
'fluid_code' => $lineList->fluid_code,
'paint_cycle' => $lineList->painting_cycle,
'created_at' => now(),
'updated_at' => now()
]);
}
$temperatures = j(setting("temperatures"));
$todayTempData = null;
$blastingDateRecord = db('construction_paint_logs')
->where('line', $lineList->line_no)
->whereNotNull('blasting_date')
->first();
if (!is_null($temperatures) && $blastingDateRecord && !empty($blastingDateRecord->blasting_date)) {
$blastingDate = Carbon::parse($blastingDateRecord->blasting_date);
$dayOfYear = $blastingDate->format('z');
$todayTempData = $temperatures[$dayOfYear] ?? null;
}
$fAvgNps = $allWeldLogs->where("type_of_joint", "F")->avg("nps_1");
$sAvgNps = $allWeldLogs->where("type_of_joint", "S")->avg("nps_1");
$mtoTotal = db("m_t_o_s")
->where("description_en", "like", "%pipe%")
->where("line", $lineList->line_no)
->selectRaw("SUM(quantity * POWER(odmm_1/2000, 2) * PI()) AS total")
->first()->total ?? 0;
$fVolume = round(pi() * pow($fAvgNps, 2) * 200, 2);
$sVolume = round($mtoTotal, 2);
$processedPaintFollowUpIds = [];
$processedShopSpools = [];
$replacedJointNumbers = $this->determineReplacedJointNumbers($allWeldLogs);
TransactionHelper::chunkTransaction(
$allWeldLogs,
function ($paintWeldLogChunk) use (
$lineList,
$paintMatrix,
$todayTempData,
$fVolume,
$sVolume,
&$paintFollowUpCreated,
&$paintFollowUpUpdated,
&$processedPaintFollowUpIds,
&$processedShopSpools,
$replacedJointNumbers
) {
foreach ($paintWeldLogChunk as $currentWeldLog) {
if (empty($currentWeldLog->fluid_code)) {
continue;
}
if ($this->shouldSkipJoint($currentWeldLog, $replacedJointNumbers)) {
continue;
}
$basePaintFollowUpData = [
'project' => $currentWeldLog->project ?? '',
'description' => "PIPE",
'area' => $lineList->unit ?? '',
'line' => $lineList->line_no,
'iso_number' => $currentWeldLog->iso_number ?? '',
'fluid_code' => $lineList->fluid_code,
'fluid_code_description' => $lineList->fluid_ru ?? '',
'cycle' => $lineList->painting_cycle ?? '',
'primer_coat_name_1' => $paintMatrix->primer_coat ?? '',
'brend_name_1' => $paintMatrix->brend_name_1 ?? '',
'colour_1' => $paintMatrix->colour_1 ?? '',
'ral_code_1' => $paintMatrix->ral_code_1 ?? '',
'thickness_1' => $paintMatrix->thickness_1 ?? '',
'intermediate_coat_name_2' => $paintMatrix->intermediate_coat ?? '',
'brend_name_2' => $paintMatrix->brend_name_2 ?? '',
'colour_2' => $paintMatrix->colour_2 ?? '',
'ral_code_2' => $paintMatrix->ral_code_2 ?? '',
'thickness_2' => $paintMatrix->thickness_2 ?? '',
'final_coat_name_3' => $paintMatrix->final_coat ?? '',
'brend_name_3' => $paintMatrix->brend_name_3 ?? '',
'colour_3' => $paintMatrix->colour_3 ?? '',
'ral_code_3' => $paintMatrix->ral_code_3 ?? '',
'thickness_3' => $paintMatrix->thickness_3 ?? '',
'status' => 'In Progress',
'updated_at' => now()
];
$result = $this->processShopRecord(
$lineList,
$currentWeldLog,
$basePaintFollowUpData,
$paintMatrix,
$todayTempData,
$sVolume,
$paintFollowUpCreated,
$paintFollowUpUpdated,
$processedShopSpools
);
$paintFollowUpCreated = $result['created'];
$paintFollowUpUpdated = $result['updated'];
$processedShopSpools = $result['processed_spools'];
$fieldResult = $this->processFieldRecord(
$lineList,
$currentWeldLog,
$basePaintFollowUpData,
$paintMatrix,
$todayTempData,
$fVolume,
$paintFollowUpCreated,
$paintFollowUpUpdated,
$processedPaintFollowUpIds
);
$paintFollowUpCreated = $fieldResult['created'];
$paintFollowUpUpdated = $fieldResult['updated'];
$processedPaintFollowUpIds = $fieldResult['processed_ids'];
}
return $paintWeldLogChunk->count();
},
1,
10000
);
$finalCleanupCount = $this->cleanupNonMatchingRecords($lineList->line_no);
} catch (\Throwable $th) {
Log::error("Paint Follow Ups sync error: " . $th->getMessage(), [
'weld_log_id' => $data->id
]);
throw $th;
}
return [
'success' => true,
'created' => $paintFollowUpCreated,
'updated' => $paintFollowUpUpdated,
'deleted_orphaned' => $deletedOrphanedCount,
'deleted_final_cleanup' => $finalCleanupCount
];
}
protected function processShopRecord(
$lineList,
$weldLog,
$basePaintFollowUpData,
$paintMatrix,
$todayTempData,
$sVolume,
$paintFollowUpCreated,
$paintFollowUpUpdated,
$processedShopSpools
): array {
$hasShopJoint = db("weld_logs")
->where('line_number', $lineList->line_no)
->where('spool_number', $weldLog->spool_number)
->where('type_of_joint', 'S')
->exists();
if (!$hasShopJoint || empty($weldLog->spool_number)) {
return [
'created' => $paintFollowUpCreated,
'updated' => $paintFollowUpUpdated,
'processed_spools' => $processedShopSpools
];
}
if (in_array($weldLog->spool_number, $processedShopSpools, true)) {
return [
'created' => $paintFollowUpCreated,
'updated' => $paintFollowUpUpdated,
'processed_spools' => $processedShopSpools
];
}
$processedShopSpools[] = $weldLog->spool_number;
$shopData = $basePaintFollowUpData;
$shopData['location'] = 'SHOP';
$shopData['spool_no_joint_no'] = $weldLog->spool_number;
$shopData['surface_roughness'] = $paintMatrix->surface_preparation ?? '';
if ($todayTempData) {
$shopData['substrate_temprature'] = $todayTempData['temp_material_shop'] ?? null;
$shopData['ambient_temprature'] = $todayTempData['shop_ambient'] ?? null;
}
$shopData['volume_1'] = $sVolume;
$shopData['volume_2'] = $sVolume;
$shopData['volume_3'] = $sVolume;
$shopData['total_volume'] = $sVolume * 3;
$shopUniqueConstraintCondition = [
'line' => $lineList->line_no,
'spool_no_joint_no' => $weldLog->spool_number,
'cycle' => $lineList->painting_cycle,
'location' => 'SHOP'
];
$shopWhereCondition = [
'line' => $lineList->line_no,
'spool_no_joint_no' => $weldLog->spool_number,
'location' => 'SHOP'
];
$existingShopRecord = db("paint_follow_ups")
->where($shopUniqueConstraintCondition)
->first();
$recordsWithDifferentPaintCycle = db("paint_follow_ups")
->where($shopWhereCondition)
->where('cycle', '!=', $lineList->painting_cycle)
->get();
if ($existingShopRecord) {
$hasAllEmptyDates = $this->hasAllEmptyDates($existingShopRecord);
if ($hasAllEmptyDates) {
db("paint_follow_ups")
->where('id', $existingShopRecord->id)
->update($shopData);
$paintFollowUpUpdated++;
} else {
$tempVolumeData = $this->getTempVolumeUpdateData(
$existingShopRecord,
$todayTempData,
$sVolume,
'SHOP'
);
if (!empty($tempVolumeData)) {
db("paint_follow_ups")
->where('id', $existingShopRecord->id)
->update($tempVolumeData);
}
}
} else {
if ($recordsWithDifferentPaintCycle->count() > 0) {
foreach ($recordsWithDifferentPaintCycle as $oldRecord) {
$hasAnyDateFilled = !$this->hasAllEmptyDates($oldRecord);
if ($hasAnyDateFilled) {
db("paint_follow_ups")
->where('id', $oldRecord->id)
->update([
'status' => 'HOLD',
'updated_at' => now()
]);
} else {
db("paint_follow_ups")
->where('id', $oldRecord->id)
->update([
'cycle' => $lineList->painting_cycle,
'updated_at' => now()
]);
$paintFollowUpUpdated++;
return [
'created' => $paintFollowUpCreated,
'updated' => $paintFollowUpUpdated,
'processed_spools' => $processedShopSpools
];
}
}
}
$shopData['primer_coating_start_date'] = null;
$shopData['primer_coating_finish_date'] = null;
$shopData['start_intermediate_date2'] = null;
$shopData['finish_intermediate_date2'] = null;
$shopData['final_coat_start_date3'] = null;
$shopData['final_coat_finish_date3'] = null;
$shopData['created_at'] = now();
$shopData['updated_at'] = now();
db("paint_follow_ups")->insert($shopData);
$paintFollowUpCreated++;
}
return [
'created' => $paintFollowUpCreated,
'updated' => $paintFollowUpUpdated,
'processed_spools' => $processedShopSpools
];
}
protected function processFieldRecord(
$lineList,
$weldLog,
$basePaintFollowUpData,
$paintMatrix,
$todayTempData,
$fVolume,
$paintFollowUpCreated,
$paintFollowUpUpdated,
$processedPaintFollowUpIds
): array {
if (empty($weldLog->no_of_the_joint_as_per_as_built_survey)) {
return [
'created' => $paintFollowUpCreated,
'updated' => $paintFollowUpUpdated,
'processed_ids' => $processedPaintFollowUpIds
];
}
$jointNo = $weldLog->no_of_the_joint_as_per_as_built_survey;
$fieldData = $basePaintFollowUpData;
$fieldData['location'] = 'FIELD';
$fieldData['spool_no_joint_no'] = $jointNo;
$fieldData['surface_roughness'] = $paintMatrix->touch_up_of_damaged_parts ?? '';
if ($todayTempData) {
$fieldData['substrate_temprature'] = $todayTempData['temp_material_field'] ?? null;
$fieldData['ambient_temprature'] = $todayTempData['field_ambient'] ?? null;
}
$fieldData['volume_1'] = $fVolume;
$fieldData['volume_2'] = $fVolume;
$fieldData['volume_3'] = $fVolume;
$fieldData['total_volume'] = $fVolume * 3;
$fieldUniqueConstraintCondition = [
'line' => $lineList->line_no,
'spool_no_joint_no' => $jointNo,
'cycle' => $lineList->painting_cycle,
'location' => 'FIELD'
];
$fieldWhereCondition = [
'line' => $lineList->line_no,
'spool_no_joint_no' => $jointNo,
'location' => 'FIELD'
];
$existingFieldRecord = db("paint_follow_ups")
->where($fieldUniqueConstraintCondition)
->first();
$recordsWithDifferentPaintCycle = db("paint_follow_ups")
->where($fieldWhereCondition)
->where('cycle', '!=', $lineList->painting_cycle)
->get();
if ($existingFieldRecord) {
if (in_array($existingFieldRecord->id, $processedPaintFollowUpIds)) {
return [
'created' => $paintFollowUpCreated,
'updated' => $paintFollowUpUpdated,
'processed_ids' => $processedPaintFollowUpIds
];
}
$processedPaintFollowUpIds[] = $existingFieldRecord->id;
$hasAllEmptyDates = $this->hasAllEmptyDates($existingFieldRecord);
if ($hasAllEmptyDates) {
db("paint_follow_ups")
->where('id', $existingFieldRecord->id)
->update($fieldData);
$paintFollowUpUpdated++;
} else {
$tempVolumeData = $this->getTempVolumeUpdateData(
$existingFieldRecord,
$todayTempData,
$fVolume,
'FIELD'
);
if (!empty($tempVolumeData)) {
db("paint_follow_ups")
->where('id', $existingFieldRecord->id)
->update($tempVolumeData);
}
}
} else {
if ($recordsWithDifferentPaintCycle->count() > 0) {
foreach ($recordsWithDifferentPaintCycle as $oldRecord) {
$hasAnyDateFilled = !$this->hasAllEmptyDates($oldRecord);
if ($hasAnyDateFilled) {
db("paint_follow_ups")
->where('id', $oldRecord->id)
->update([
'status' => 'HOLD',
'updated_at' => now()
]);
} else {
db("paint_follow_ups")
->where('id', $oldRecord->id)
->update([
'cycle' => $lineList->painting_cycle,
'updated_at' => now()
]);
$paintFollowUpUpdated++;
return [
'created' => $paintFollowUpCreated,
'updated' => $paintFollowUpUpdated,
'processed_ids' => $processedPaintFollowUpIds
];
}
}
}
$fieldData['primer_coating_start_date'] = null;
$fieldData['primer_coating_finish_date'] = null;
$fieldData['start_intermediate_date2'] = null;
$fieldData['finish_intermediate_date2'] = null;
$fieldData['final_coat_start_date3'] = null;
$fieldData['final_coat_finish_date3'] = null;
$fieldData['created_at'] = now();
$fieldData['updated_at'] = now();
db("paint_follow_ups")->insert($fieldData);
$paintFollowUpCreated++;
}
return [
'created' => $paintFollowUpCreated,
'updated' => $paintFollowUpUpdated,
'processed_ids' => $processedPaintFollowUpIds
];
}
protected function cleanupOrphanedRecords($weldLog, $beforeData): int
{
if (!$beforeData) {
return 0;
}
$deletedCount = 0;
try {
if (isset($beforeData->no_of_the_joint_as_per_as_built_survey) &&
$beforeData->no_of_the_joint_as_per_as_built_survey != $weldLog->no_of_the_joint_as_per_as_built_survey &&
!empty($beforeData->no_of_the_joint_as_per_as_built_survey)) {
$oldJointNo = $beforeData->no_of_the_joint_as_per_as_built_survey;
$deleted = db("paint_follow_ups")
->where('line', $weldLog->line_number)
->where('spool_no_joint_no', $oldJointNo)
->where('location', 'FIELD')
->delete();
$deletedCount += $deleted;
}
if (isset($beforeData->spool_number) &&
$beforeData->spool_number != $weldLog->spool_number &&
!empty($beforeData->spool_number)) {
$oldSpoolNumber = $beforeData->spool_number;
$deleted = db("paint_follow_ups")
->where('line', $weldLog->line_number)
->where('spool_no_joint_no', $oldSpoolNumber)
->where('location', 'SHOP')
->delete();
$deletedCount += $deleted;
}
} catch (\Throwable $th) {
Log::warning("Failed to cleanup orphaned records", [
'error' => $th->getMessage(),
'weld_log_id' => $weldLog->id
]);
}
return $deletedCount;
}
protected function cleanupNonMatchingRecords(string $lineNumber): int
{
$deletedCount = 0;
try {
$validFieldJointNos = db("weld_logs")
->where("line_number", $lineNumber)
->whereNotNull("no_of_the_joint_as_per_as_built_survey")
->where("no_of_the_joint_as_per_as_built_survey", "!=", "")
->pluck("no_of_the_joint_as_per_as_built_survey")
->toArray();
$validShopSpoolNos = db("weld_logs")
->where("line_number", $lineNumber)
->where("type_of_joint", "S")
->whereNotNull("spool_number")
->where("spool_number", "!=", "")
->pluck("spool_number")
->toArray();
if (!empty($validFieldJointNos)) {
$deletedField = db("paint_follow_ups")
->where('line', $lineNumber)
->where('location', 'FIELD')
->whereNotIn('spool_no_joint_no', $validFieldJointNos)
->delete();
$deletedCount += $deletedField;
} else {
$deletedField = db("paint_follow_ups")
->where('line', $lineNumber)
->where('location', 'FIELD')
->delete();
$deletedCount += $deletedField;
}
if (!empty($validShopSpoolNos)) {
$deletedShop = db("paint_follow_ups")
->where('line', $lineNumber)
->where('location', 'SHOP')
->whereNotIn('spool_no_joint_no', $validShopSpoolNos)
->delete();
$deletedCount += $deletedShop;
} else {
$deletedShop = db("paint_follow_ups")
->where('line', $lineNumber)
->where('location', 'SHOP')
->delete();
$deletedCount += $deletedShop;
}
} catch (\Throwable $th) {
Log::warning("Failed to cleanup non-matching records", [
'error' => $th->getMessage(),
'line_number' => $lineNumber
]);
}
return $deletedCount;
}
protected function hasAllEmptyDates($record): bool
{
return empty($record->primer_coating_start_date) &&
empty($record->primer_coating_finish_date) &&
empty($record->start_intermediate_date2) &&
empty($record->finish_intermediate_date2) &&
empty($record->final_coat_start_date3) &&
empty($record->final_coat_finish_date3);
}
protected function getTempVolumeUpdateData($record, $todayTempData, $volume, $location): array
{
$updateData = [];
if ($todayTempData) {
$tempField = $location === 'SHOP' ? 'temp_material_shop' : 'temp_material_field';
$ambientField = $location === 'SHOP' ? 'shop_ambient' : 'field_ambient';
if (empty($record->substrate_temprature) && isset($todayTempData[$tempField])) {
$updateData['substrate_temprature'] = $todayTempData[$tempField];
}
if (empty($record->ambient_temprature) && isset($todayTempData[$ambientField])) {
$updateData['ambient_temprature'] = $todayTempData[$ambientField];
}
}
if (empty($record->volume_1)) {
$updateData['volume_1'] = $volume;
}
if (empty($record->volume_2)) {
$updateData['volume_2'] = $volume;
}
if (empty($record->volume_3)) {
$updateData['volume_3'] = $volume;
}
if (empty($record->total_volume)) {
$updateData['total_volume'] = $volume * 3;
}
return $updateData;
}
protected function determineReplacedJointNumbers($weldLogs): array
{
$toSkip = [];
foreach ($weldLogs as $log) {
$jointNo = $log->no_of_the_joint_as_per_as_built_survey;
if ($this->isRepairJoint($jointNo)) {
$baseJoint = $this->stripRepairSuffix($jointNo);
if (!empty($baseJoint)) {
$toSkip[] = $baseJoint;
}
}
}
return array_unique($toSkip);
}
protected function shouldSkipJoint($weldLog, array $replacedJointNumbers): bool
{
$jointNo = $weldLog->no_of_the_joint_as_per_as_built_survey;
if (empty($jointNo)) {
return false;
}
return in_array($jointNo, $replacedJointNumbers, true);
}
protected function isRepairJoint(?string $jointNo): bool
{
return !empty($jointNo) && strpos($jointNo, 'R') !== false;
}
protected function stripRepairSuffix(?string $jointNo): ?string
{
if (empty($jointNo)) {
return $jointNo;
}
$pos = strpos($jointNo, 'R');
if ($pos === false) {
return $jointNo;
}
return substr($jointNo, 0, $pos);
}
}
@@ -0,0 +1,92 @@
<?php
namespace App\Services\WeldLogTriggers\Triggers;
use App\Services\WeldLogTriggers\Base\BaseTrigger;
use Illuminate\Support\Facades\Log;
/**
* Register Creator Cache Trigger
*
* Updates the Register Creator cache when relevant fields are modified.
* This ensures the frontend displays up-to-date information in the register creator view.
*/
class RegisterCreatorCacheTrigger extends BaseTrigger
{
/**
* Get trigger name
*/
public function getName(): string
{
return 'RegisterCreatorCacheTrigger';
}
/**
* Get trigger execution order
* Running as #15 (after all other updates)
*/
public function getOrder(): int
{
return 15;
}
/**
* Get fields that this trigger depends on
*/
public function getDependentFields(): array
{
return [
'nps_1',
'welding_date',
'test_package_no',
'date_test',
'line_number',
'iso_number'
];
}
/**
* Process trigger logic
* Dispatches cache update job for Register Creator
*/
protected function process($weldLogData, $beforeData, array $context): array
{
// Skip for clone action
if (isset($context['action']) && $context['action'] === 'clone') {
Log::info('RegisterCreatorCacheTrigger: Skipped due to clone action');
return ['skipped' => true, 'reason' => 'clone_action'];
}
$dispatched = false;
if (function_exists('dispatchCacheBladeViews')) {
dispatchCacheBladeViews([
[
'view' => 'admin-ajax.register-no-cache',
'cache' => 'register-creator'
]
]);
$dispatched = true;
Log::info('RegisterCreatorCacheTrigger: Cache update dispatched', [
'weld_log_id' => $weldLogData->id,
'iso' => $weldLogData->iso_number ?? 'unknown'
]);
} else {
Log::warning('RegisterCreatorCacheTrigger: dispatchCacheBladeViews function not found');
}
return [
'cache_dispatched' => $dispatched
];
}
/**
* Allow async execution since this is just dispatching another job
*/
public function isAsync(): bool
{
return true;
}
}
@@ -0,0 +1,114 @@
<?php
namespace App\Services\WeldLogTriggers\Triggers;
use App\Services\WeldLogTriggers\Base\BaseTrigger;
use Illuminate\Support\Facades\Log;
/**
* Repair Logs Update Trigger
*
* Updates repair logs based on test results
* Determines repair status: "Not Done", "Done", or "Repair"
*/
class RepairLogsUpdateTrigger extends BaseTrigger
{
public function getName(): string
{
return 'Repair Logs Update';
}
public function getOrder(): int
{
return 5;
}
public function getDependentFields(): array
{
return [
'iso_number',
'no_of_the_joint_as_per_as_built_survey',
'welding_date',
'vt_result',
'rt_result',
'ut_result',
'pt_result',
'mt_result',
'pmi_result',
'ht_result',
'ferrite_result',
];
}
protected function process($data, $beforeData, array $context): array
{
// Test result fields to check
$testResultFields = [
'vt_result',
'rt_result',
'ut_result',
'pt_result',
'mt_result',
'pmi_result',
'ht_result',
'ferrite_result'
];
$hasRepairOrCut = false;
$allFieldsEmpty = true;
$rtAndUtEmpty = false;
// Check if RT or UT results are empty
if(empty($data->rt_result) && empty($data->ut_result)) {
$rtAndUtEmpty = true;
}
// Check all test result fields
foreach($testResultFields as $field) {
if(isset($data->$field) && !empty($data->$field)) {
$allFieldsEmpty = false;
if(in_array($data->$field, ['Repair / Ремонт', 'Cut / Резать'])) {
$hasRepairOrCut = true;
break;
}
}
}
// Determine repair status based on test results
if($allFieldsEmpty || $rtAndUtEmpty) {
$repairStatus = "Not Done";
} elseif(!$hasRepairOrCut) {
$repairStatus = "Done";
} else {
$repairStatus = "Repair";
}
// Update repair logs
$updateResult = db("repair_logs")
->where([
'iso_number' => $data->iso_number,
'new_joint_no' => $data->no_of_the_joint_as_per_as_built_survey,
])
->update([
'repair_date' => $data->welding_date,
'repair_status' => $repairStatus,
]);
Log::info("Repair logs updated", [
'iso_number' => $data->iso_number,
'joint_no' => $data->no_of_the_joint_as_per_as_built_survey,
'repair_status' => $repairStatus,
'updated_records' => $updateResult,
'has_repair_or_cut' => $hasRepairOrCut,
'all_fields_empty' => $allFieldsEmpty,
'rt_and_ut_empty' => $rtAndUtEmpty
]);
return [
'success' => true,
'repair_status' => $repairStatus,
'updated_records' => $updateResult
];
}
}
@@ -0,0 +1,384 @@
<?php
namespace App\Services\WeldLogTriggers\Triggers;
use App\Services\WeldLogTriggers\Base\BaseTrigger;
use Illuminate\Support\Facades\Log;
/**
* Request Date Operations Trigger
*
* Handles request number generation for all test types (RT, UT, MT, PT, VT, PWHT, Ferrite, PMI)
* Updates request numbers in both weld_logs and test-specific tables
*/
class RequestDateOperationsTrigger extends BaseTrigger
{
public function getName(): string
{
return 'Request Date Operations';
}
public function getOrder(): int
{
return 4;
}
public function getDependentFields(): array
{
return [
'iso_number',
'no_of_the_joint_as_per_as_built_survey',
'rt_request_date', 'rt_request_no', 'test_laboratory_rt',
'ht_request_date', 'ht_request_no', 'test_laboratory_ht',
'ut_request_date', 'ut_request_no', 'test_laboratory_ut',
'mt_request_date', 'mt_request_no', 'test_laboratory_mt',
'pt_request_date', 'pt_request_no', 'test_laboratory_pt',
'vt_request_date', 'vt_request_no', 'test_laboratory_vt',
'pwht_request_date', 'pwht_request_no', 'test_laboratory_pwht',
'ferrite_request_date', 'ferrite_request_no', 'test_laboratory_ferrite',
'pmi_request_date', 'pmi_request_no', 'test_laboratory_pmi',
'rt_test_date',
'ut_test_date',
'mt_test_date',
'pt_test_date',
'vt_test_date',
'pwht_test_date',
'pmi_test_date',
'ferrite_test_date',
];
}
protected function process($data, $beforeData, array $context): array
{
$logs_request_number_pattern = setting("logs_request_number_pattern");
$logTestTypes = log_test_types();
Log::info("Available test types for request date processing", [
'test_types' => array_keys($logTestTypes),
'iso_number' => $data->iso_number,
'joint_no' => $data->no_of_the_joint_as_per_as_built_survey
]);
$testNames = array_keys($logTestTypes);
$processedTests = [];
foreach($testNames AS $testName) {
$result = $this->processTestType(
$testName,
$logTestTypes[$testName],
$data,
$beforeData,
$logs_request_number_pattern
);
if ($result['processed']) {
$processedTests[] = $testName;
}
}
return [
'success' => true,
'processed_test_types' => $processedTests,
'total_processed' => count($processedTests)
];
}
/**
* Process a single test type for request number generation
*/
protected function processTestType(
string $testName,
string $testTableName,
$data,
$beforeData,
string $logs_request_number_pattern
): array {
$requestDate = $testName . "_request_date";
$run = false;
Log::info("Processing test type", [
'test_name' => $testName,
'request_date_field' => $requestDate,
'current_request_date' => $data->$requestDate ?? 'null',
'before_request_date' => $beforeData->$requestDate ?? 'null'
]);
// Check if request date is not empty
if($data->$requestDate != "") {
$requestNo = $testName . "_request_no";
$laboratoryCompany = "test_laboratory_" . $testName;
Log::info("Request date is not empty, checking conditions", [
'test_name' => $testName,
'request_date' => $data->$requestDate,
'current_request_no' => $data->$requestNo ?? 'null',
'current_laboratory' => $data->$laboratoryCompany ?? 'null',
'before_laboratory' => $beforeData->$laboratoryCompany ?? 'null'
]);
// Check conditions to run
if($data->$requestNo == "") {
// Request no not assigned yet
$run = true;
Log::info("Run condition met: Request number is empty", [
'test_name' => $testName,
'reason' => 'request_no_empty'
]);
}
if($data->$laboratoryCompany != $beforeData->$laboratoryCompany) {
// Laboratory changed
$run = true;
Log::info("Run condition met: Laboratory changed", [
'test_name' => $testName,
'reason' => 'laboratory_changed',
'old_laboratory' => $beforeData->$laboratoryCompany ?? 'null',
'new_laboratory' => $data->$laboratoryCompany ?? 'null'
]);
}
$requestDateChange = false;
if($data->$requestDate != $beforeData->$requestDate) {
// Request date changed
$run = true;
Log::info("Run condition met: Request date changed", [
'test_name' => $testName,
'reason' => 'request_date_changed',
'old_date' => $beforeData->$requestDate ?? 'null',
'new_date' => $data->$requestDate
]);
// Check if this date was already used
$isOldSignReqDate = db("weld_logs")
->where([
$requestDate => $data->$requestDate,
$laboratoryCompany => $data->$laboratoryCompany,
])
->where("id", "<>", $data->id)
->first();
if(!$isOldSignReqDate) {
$requestDateChange = true;
Log::info("Request date is unique, will generate new request number", [
'test_name' => $testName,
'request_date' => $data->$requestDate,
'laboratory' => $data->$laboratoryCompany ?? 'null'
]);
} else {
Log::info("Request date already exists, will reuse existing request number", [
'test_name' => $testName,
'request_date' => $data->$requestDate,
'laboratory' => $data->$laboratoryCompany ?? 'null',
'existing_record_id' => $isOldSignReqDate->id ?? 'null'
]);
}
}
if($run) {
$this->generateAndUpdateRequestNumber(
$testName,
$testTableName,
$data,
$requestDate,
$requestNo,
$laboratoryCompany,
$requestDateChange,
$logs_request_number_pattern
);
return ['processed' => true, 'test_name' => $testName];
} else {
Log::info("Request number generation skipped", [
'test_name' => $testName,
'reason' => 'run_condition_false',
'current_request_date' => $data->$requestDate ?? 'null',
'current_request_no' => $data->$requestNo ?? 'null'
]);
}
} else {
Log::info("Test processing skipped", [
'test_name' => $testName,
'reason' => 'request_date_empty',
'request_date_field' => $requestDate
]);
}
return ['processed' => false];
}
/**
* Generate and update request number for a test type
*/
protected function generateAndUpdateRequestNumber(
string $testName,
string $testTableName,
$data,
string $requestDate,
string $requestNo,
string $laboratoryCompany,
bool $requestDateChange,
string $logs_request_number_pattern
) {
Log::info("Starting request number generation process", [
'test_name' => $testName,
'request_date_changed' => $requestDateChange,
'current_request_no' => $data->$requestNo ?? 'null',
'laboratory' => $data->$laboratoryCompany ?? 'null'
]);
$thisRequestNumberPattern = $logs_request_number_pattern;
// Get company code
$companyCode = db("subcontractors")
->where("company_name_en", $data->$laboratoryCompany)
->first();
if(!is_null($companyCode)) {
$companyCode = $companyCode->company_code;
} else {
$companyCode = "";
}
Log::info("Company code resolved", [
'test_name' => $testName,
'laboratory_name' => $data->$laboratoryCompany ?? 'null',
'company_code' => $companyCode
]);
$thisRequestNumberPattern = str_replace("{company_code}", $companyCode, $thisRequestNumberPattern);
// Get counter
if($requestDateChange) {
// Different date, get new counter
$lastRequestNumber = get_counter($companyCode . $testName);
Log::info("Getting new counter for date change", [
'test_name' => $testName,
'counter_key' => $companyCode . $testName,
'new_counter' => $lastRequestNumber
]);
} else {
// Same date, get existing or new counter
$lastRequestNumber = get_counter($companyCode . $testName, "");
Log::info("Getting existing or new counter for same date", [
'test_name' => $testName,
'counter_key' => $companyCode . $testName,
'counter' => $lastRequestNumber
]);
}
$thisRequestNumberPattern = str_replace("{number}", $lastRequestNumber, $thisRequestNumberPattern);
$thisRequestNumberPattern = str_replace("{log_name}", strtoupper($testName), $thisRequestNumberPattern);
Log::info("Request number pattern generated", [
'test_name' => $testName,
'pattern' => $thisRequestNumberPattern,
'counter' => $lastRequestNumber
]);
// Check if same company/date already has a request number today
$todayDataThisCompanyThisTest = db("weld_logs")->where([
$requestDate => $data->$requestDate,
$laboratoryCompany => $data->$laboratoryCompany,
])->first();
if($todayDataThisCompanyThisTest) {
if($todayDataThisCompanyThisTest->$requestNo !="") {
$thisRequestNumberPattern = $todayDataThisCompanyThisTest->$requestNo;
Log::info("Reusing existing request number for same date/company", [
'test_name' => $testName,
'existing_request_no' => $thisRequestNumberPattern,
'existing_record_id' => $todayDataThisCompanyThisTest->id
]);
}
}
// Update weld_logs
$whereData = [
'iso_number' => $data->iso_number,
'no_of_the_joint_as_per_as_built_survey' => $data->no_of_the_joint_as_per_as_built_survey,
];
$updateData = [
$requestNo => $thisRequestNumberPattern,
"updated_at" => simdi()
];
Log::info("Updating weld_logs with request number", [
'test_name' => $testName,
'where_data' => $whereData,
'update_data' => $updateData,
'final_request_number' => $thisRequestNumberPattern
]);
$weldLogUpdateResult = db("weld_logs")
->where($whereData)
->update($updateData);
Log::info("Weld_logs update completed", [
'test_name' => $testName,
'affected_rows' => $weldLogUpdateResult,
'request_number' => $thisRequestNumberPattern
]);
// Update test table
$weldLogData = db("weld_logs")->where($whereData)->first();
$testTableUpdateData = [];
$testTableColumns = table_columns($testTableName);
foreach($weldLogData AS $weldLogColumn => $weldLogValue)
{
if(in_array($weldLogColumn, $testTableColumns))
{
$testTableUpdateData[$weldLogColumn] = $weldLogValue;
}
}
unset($testTableUpdateData['id']);
// Check if control_standart exists in test table columns
if(in_array('control_standart', $testTableColumns)) {
$ndeMatrixInfo = db("nde_matrices")
->where("line", $weldLogData->line_number)
->where("type_of_joint", $weldLogData->type_of_welds)
->first();
if ($ndeMatrixInfo) {
$testTableUpdateData['control_standart'] = $ndeMatrixInfo->control_standart;
Log::info("Added control_standart from NDE Matrix", [
'test_name' => $testName,
'control_standart' => $ndeMatrixInfo->control_standart
]);
} else {
Log::warning("Control standart not found in NDE Matrix", [
'test_name' => $testName,
'line' => $weldLogData->line_number,
'type_of_joint' => $weldLogData->type_of_welds
]);
}
} else {
Log::warning("Control standart column not found in test table", [
'test_name' => $testName,
'test_table' => $testTableName
]);
}
Log::info("Updating test table with weld log data", [
'test_name' => $testName,
'test_table' => $testTableName,
'where_data' => $whereData,
'update_fields_count' => count($testTableUpdateData)
]);
$testTableUpdateResult = db($testTableName)
->updateOrInsert($whereData, $testTableUpdateData);
Log::info("Test table update completed", [
'test_name' => $testName,
'test_table' => $testTableName,
'operation_result' => $testTableUpdateResult ? 'success' : 'failed'
]);
}
}
@@ -0,0 +1,67 @@
<?php
namespace App\Services\WeldLogTriggers\Triggers;
use App\Services\WeldLogTriggers\Base\BaseTrigger;
use Illuminate\Support\Facades\Log;
/**
* Spool Status Changer Trigger
*
* Handles spool status updates when spool_number, iso_number, or type_of_joint changes
* Also triggers the spool-status-changer cron view for both new and old values
*/
class SpoolStatusChangerTrigger extends BaseTrigger
{
public function getName(): string
{
return 'Spool Status Changer';
}
public function getOrder(): int
{
return 14;
}
public function getDependentFields(): array
{
return [
'spool_number',
'no_of_the_joint_as_per_as_built_survey',
'iso_number',
'type_of_joint',
'line_number',
'project',
'welding_date',
'welder_1',
'welder_2',
'real_welder_1',
'real_welder_2',
'design_area'
];
}
protected function process($data, $beforeData, array $context): array
{
$triggerSpoolStatusChanger = false;
$spoolStatusChangerParams = [];
// spool_number change check
if (isset($data->spool_number)) {
if(isset($data->iso_number)) {
$triggerSpoolStatusChanger = true;
spoolStatusChanger($data->iso_number, $data->spool_number);
Log::info("Spool Status Changer triggered", [
'iso_number' => $data->iso_number,
'spool_number' => $data->spool_number
]);
}
}
return [
'success' => true,
'triggered' => $triggerSpoolStatusChanger
];
}
}
@@ -0,0 +1,316 @@
<?php
namespace App\Services\WeldLogTriggers\Triggers;
use App\Services\WeldLogTriggers\Base\BaseTrigger;
use App\Models\TestPackage;
use App\Models\TestPackBaseStatus;
use App\Helpers\TransactionHelper;
use Illuminate\Support\Facades\Log;
/**
* Test Pack Base Status Changer Trigger
*
* Updates test package base statuses including:
* - WDI (Weld Diameter Inch) calculations
* - Welding progress
* - Repair statistics
* - Shop/Field weld tracking
*/
class TestPackBaseStatusChangerTrigger extends BaseTrigger
{
public function getName(): string
{
return 'Test Pack Base Status Changer';
}
public function getOrder(): int
{
return 9;
}
public function getDependentFields(): array
{
return [
'test_package_no',
'iso_number',
'line_number',
'welding_date',
'type_of_joint',
'nps_1'
];
}
protected function process($data, $beforeData, array $context): array
{
$testPackageNo = $data->test_package_no;
$lineNumber = $data->line_number;
$testPackages = TestPackage::where("test_package_number", $testPackageNo)->get();
$testPackagesIso = TestPackBaseStatus::where("test_package_no", $testPackageNo)->get();
$weldLogs = db("weld_logs")
->where("test_package_no", $testPackageNo)
->get();
$repairLogs = db("repair_logs")
->where("test_package_no", $testPackageNo)
->get();
$testPackagesSummary = [];
$testPackagesIsoSummary = [];
$totalFields = [
'total_wdi',
'total_complated_wdi',
'welding_progress',
'total_shop_wdi',
'total_complated_shop_wdi',
'total_field_wdi',
'total_complated_field_wdi',
];
// Calculate repair log summaries
$repairSummaries = $this->calculateRepairSummaries($repairLogs);
// Calculate weld log summaries
foreach($weldLogs AS $weldLog) {
foreach($totalFields AS $field) {
if(!isset($testPackagesSummary[$weldLog->test_package_no][$field]))
$testPackagesSummary[$weldLog->test_package_no][$field] = 0;
if(!isset($testPackagesIsoSummary[$weldLog->iso_number][$weldLog->test_package_no][$field]))
$testPackagesIsoSummary[$weldLog->iso_number][$weldLog->test_package_no][$field] = 0;
}
$wdi = (float) $weldLog->nps_1;
$testPackagesSummary[$weldLog->test_package_no]['total_wdi'] += $wdi ;
$testPackagesIsoSummary[$weldLog->iso_number][$weldLog->test_package_no]['total_wdi'] += $wdi;
if($weldLog->type_of_joint == "F") {
$testPackagesSummary[$weldLog->test_package_no]['total_field_wdi'] += $wdi;
$testPackagesIsoSummary[$weldLog->iso_number][$weldLog->test_package_no]['total_field_wdi'] += $wdi ;
if(!rejected_date($weldLog->welding_date)) {
$testPackagesSummary[$weldLog->test_package_no]['total_complated_field_wdi'] += $wdi;
$testPackagesIsoSummary[$weldLog->iso_number][$weldLog->test_package_no]['total_complated_field_wdi'] += $wdi;
}
}
if($weldLog->type_of_joint == "S") {
$testPackagesSummary[$weldLog->test_package_no]['total_shop_wdi'] += $wdi;
$testPackagesIsoSummary[$weldLog->iso_number][$weldLog->test_package_no]['total_shop_wdi'] += $wdi;
if(!rejected_date($weldLog->welding_date)) {
$testPackagesSummary[$weldLog->test_package_no]['total_complated_shop_wdi'] += $wdi;
$testPackagesIsoSummary[$weldLog->iso_number][$weldLog->test_package_no]['total_complated_shop_wdi'] += $wdi;
}
}
if(!rejected_date($weldLog->welding_date)) {
$testPackagesSummary[$weldLog->test_package_no]['total_complated_wdi'] += $wdi;
$testPackagesIsoSummary[$weldLog->iso_number][$weldLog->test_package_no]['total_complated_wdi'] += $wdi;
}
// Welding progress calculation
$totalWdi = $testPackagesSummary[$weldLog->test_package_no]['total_wdi'];
$totalComplatedWdi = $testPackagesSummary[$weldLog->test_package_no]['total_complated_wdi'];
if ($totalWdi > 0) {
$testPackagesSummary[$weldLog->test_package_no]['welding_progress'] =
round(($totalComplatedWdi * 100) / $totalWdi, 2);
} else {
$testPackagesSummary[$weldLog->test_package_no]['welding_progress'] = 0;
}
}
// Update test pack base statuses
$this->updateTestPackBaseStatuses($testPackagesIsoSummary, $repairSummaries);
// Update test packages summary
$this->updateTestPackagesSummary($testPackagesSummary, $repairSummaries['repairLogsSummary2']);
// Update test packages with WDI fields and status
$processedCount = $this->updateTestPackagesWDI($testPackages, $testPackagesSummary, $totalFields);
return [
'success' => true,
'processed_records' => $processedCount
];
}
/**
* Calculate repair log summaries
*/
protected function calculateRepairSummaries($repairLogs): array
{
$repairLogsSummary = [];
$repairLogsSummary2 = [];
$repairLogsSummaryCompleted = [];
$repairLogsSummaryCompleted2 = [];
$repairLogsSummaryRemaining = [];
$repairLogsSummaryRemaining2 = [];
foreach($repairLogs AS $repairLog) {
if(!isset($repairLogsSummary[$repairLog->iso_number][$repairLog->test_package_no]))
$repairLogsSummary[$repairLog->iso_number][$repairLog->test_package_no] = 0;
if(!isset($repairLogsSummary2[$repairLog->test_package_no]))
$repairLogsSummary2[$repairLog->test_package_no] = 0;
if(!isset($repairLogsSummaryCompleted[$repairLog->iso_number][$repairLog->test_package_no]))
$repairLogsSummaryCompleted[$repairLog->iso_number][$repairLog->test_package_no] = 0;
if(!isset($repairLogsSummaryRemaining[$repairLog->iso_number][$repairLog->test_package_no]))
$repairLogsSummaryRemaining[$repairLog->iso_number][$repairLog->test_package_no] = 0;
if(!isset($repairLogsSummaryCompleted2[$repairLog->test_package_no]))
$repairLogsSummaryCompleted2[$repairLog->test_package_no] = 0;
if(!isset($repairLogsSummaryRemaining2[$repairLog->test_package_no]))
$repairLogsSummaryRemaining2[$repairLog->test_package_no] = 0;
if($repairLog->repair_status == "Not Done") {
$repairLogsSummary[$repairLog->iso_number][$repairLog->test_package_no]++;
$repairLogsSummary2[$repairLog->test_package_no]++;
}
if(!rejected_date($repairLog->repair_date)) {
$repairLogsSummaryCompleted[$repairLog->iso_number][$repairLog->test_package_no]++;
$repairLogsSummaryCompleted2[$repairLog->test_package_no]++;
} else {
$repairLogsSummaryRemaining[$repairLog->iso_number][$repairLog->test_package_no]++;
$repairLogsSummaryRemaining2[$repairLog->test_package_no]++;
}
}
return [
'repairLogsSummary' => $repairLogsSummary,
'repairLogsSummary2' => $repairLogsSummary2,
'repairLogsSummaryCompleted' => $repairLogsSummaryCompleted,
'repairLogsSummaryCompleted2' => $repairLogsSummaryCompleted2,
'repairLogsSummaryRemaining' => $repairLogsSummaryRemaining,
'repairLogsSummaryRemaining2' => $repairLogsSummaryRemaining2,
];
}
/**
* Update test pack base statuses
*/
protected function updateTestPackBaseStatuses($testPackagesIsoSummary, $repairSummaries)
{
TransactionHelper::retryTransaction(function () use ($testPackagesIsoSummary, $repairSummaries) {
foreach($testPackagesIsoSummary AS $isoNumber => $data) {
foreach($data AS $tpNo => $data2) {
if($data2['total_complated_wdi'] == $data2['total_wdi']) {
$status = "Completed";
}
if($data2['total_complated_wdi'] < $data2['total_wdi']) {
$status = "On Going";
}
if($data2['total_complated_wdi'] == 0) {
$status = "Waiting";
}
$updateData = [
'welding_status' => $status,
'repair_qty' => @$repairSummaries['repairLogsSummary'][$isoNumber][$tpNo],
'repair_completed' => @$repairSummaries['repairLogsSummaryCompleted'][$isoNumber][$tpNo],
'repair_remaining' => @$repairSummaries['repairLogsSummaryRemaining'][$isoNumber][$tpNo],
];
db("test_pack_base_statuses")
->where("drawing_no", $isoNumber)
->where("test_package_no", $tpNo)
->update($updateData);
$updateData = [
'welding_status' => $status,
'repair_qty' => @$repairSummaries['repairLogsSummary2'][$tpNo],
'repair_completed' => @$repairSummaries['repairLogsSummaryCompleted2'][$tpNo],
'repair_remaining' => @$repairSummaries['repairLogsSummaryRemaining2'][$tpNo],
];
db("test_packages")
->where("test_package_number", $tpNo)
->update($updateData);
}
}
}, 5);
}
/**
* Update test packages summary
*/
protected function updateTestPackagesSummary($testPackagesSummary, $repairLogsSummary2)
{
TransactionHelper::retryTransaction(function () use ($testPackagesSummary, $repairLogsSummary2) {
foreach($testPackagesSummary AS $tpNo => $data2) {
if($data2['total_complated_wdi'] == $data2['total_wdi']) {
$status = "Completed";
}
if($data2['total_complated_wdi'] < $data2['total_wdi']) {
$status = "On Going";
}
if($data2['total_complated_wdi'] == 0) {
$status = "Waiting";
}
db("test_packages")
->where("test_package_number", $tpNo)
->update([
'welding_status' => $status,
'repair_status_total' => @$repairLogsSummary2[$tpNo]
]);
}
}, 5);
}
/**
* Update test packages with WDI fields and status
*/
protected function updateTestPackagesWDI($testPackages, $testPackagesSummary, $totalFields): int
{
$k = 0;
TransactionHelper::retryTransaction(function () use ($testPackages, $testPackagesSummary, $totalFields, &$k) {
foreach($testPackages AS $testPackage) {
$updateData = [];
if(isset($testPackagesSummary[$testPackage->test_package_number])) {
foreach($totalFields AS $field) {
$testPackage->$field = $testPackagesSummary[$testPackage->test_package_number][$field];
$updateData[$field] = $testPackagesSummary[$testPackage->test_package_number][$field];
}
}
$updateData = array_merge($updateData, updateTestPackageStatus($testPackage));
db("test_packages")->where("id", $testPackage->id)
->update($updateData);
db("test_pack_base_statuses")->where("test_package_no", $testPackage->test_package_number)
->update([
'tp_status' => $updateData['status'] ?? 'Waiting',
'priority' => $testPackage->priority,
'priority_info' => $testPackage->priority_info,
'responsible_person' => $testPackage->responsible_test,
'target_test_date' => $testPackage->planned_test_date,
]);
$k++;
}
return $k;
}, 5);
return $k;
}
}
@@ -0,0 +1,66 @@
<?php
namespace App\Services\WeldLogTriggers\Triggers;
use App\Services\WeldLogTriggers\Base\BaseTrigger;
use Illuminate\Support\Facades\Log;
/**
* Test Pack Cleanup Trigger
*
* Deletes non-matching test pack statuses
* Cleans up orphaned test package records
*/
class TestPackCleanupTrigger extends BaseTrigger
{
public function getName(): string
{
return 'Test Pack Cleanup';
}
public function getOrder(): int
{
return 13;
}
public function getDependentFields(): array
{
return [
'test_package_no',
'iso_number'
];
}
protected function process($data, $beforeData, array $context): array
{
try {
// Call the cleanup view to delete non-matching test pack statuses
$result = view('cron.weld_logs-delete-non-matching-test-pack-statuses')->render();
Log::info("Test Pack Cleanup completed successfully", [
'weld_log_id' => $data->id
]);
return [
'success' => true,
'result' => 'cleanup_completed'
];
} catch (\Throwable $th) {
Log::error("Test Pack Cleanup failed (non-critical)", [
'weld_log_id' => $data->id,
'error' => $th->getMessage(),
'file' => $th->getFile(),
'line' => $th->getLine()
]);
// This is a non-critical operation, don't throw exception
return [
'success' => false,
'error' => $th->getMessage(),
'note' => 'non_critical_operation'
];
}
}
}
@@ -0,0 +1,178 @@
<?php
namespace App\Services\WeldLogTriggers\Triggers;
use App\Services\WeldLogTriggers\Base\BaseTrigger;
use Illuminate\Support\Facades\Log;
/**
* Test Pack Sync Trigger
*
* Syncs weld log data to test packages when test_package_no changes or new record is inserted.
* This trigger calls the sync view with specific test_package_no filter.
*/
class TestPackSyncTrigger extends BaseTrigger
{
public function getName(): string
{
return 'Test Pack Sync from WeldLog';
}
public function getOrder(): int
{
return 8; // Run before TestPackBaseStatusChangerTrigger (order 9)
}
public function getDependentFields(): array
{
return [
'test_package_no',
'iso_number',
'welding_date',
'type_of_joint',
'nps_1',
'design_area',
'piping_type',
'circuit_number',
'p_id',
'type_of_test',
'test_pressure',
'no_of_the_joint_as_per_as_built_survey', // golden joints
'quantity_of_iso',
// NDT test dates
'rt_test_date',
'ut_test_date',
'pwht_test_date',
'pmi_test_date',
'ferrite_test_date',
'pt_test_date',
'mt_test_date',
'vt_test_date',
// NDT Request Dates and Numbers
'rt_request_date', 'rt_request_no',
'ut_request_date', 'ut_request_no',
'mt_request_no', 'mt_request_date',
'pt_request_no', 'pt_request_date',
'vt_request_no', 'vt_request_date',
'pwht_request_no', 'pwht_request_date',
'ferrite_request_no', 'ferrite_request_date',
'pmi_request_no', 'pmi_request_date',
'ht_request_no', 'ht_request_date',
];
}
/**
* Static registry to track already synced test packages in the current process
* to avoid redundant heavy sync calls.
*/
protected static $isSynced = [];
protected static $lastJobId = null;
protected function process($data, $beforeData, array $context): array
{
// Get current job ID if running in queue
$currentJobId = 'manual';
if (\Illuminate\Support\Facades\Request::header('X-Laravel-Job-Id')) {
$currentJobId = \Illuminate\Support\Facades\Request::header('X-Laravel-Job-Id');
} elseif (function_exists('app') && app()->bound(\Illuminate\Contracts\Queue\Job::class)) {
try {
$currentJobId = resolve(\Illuminate\Contracts\Queue\Job::class)->getJobId();
} catch (\Throwable $e) {
// Fallback if resolution fails despite being bound
}
}
// Reset registry if this is a new job
if (self::$lastJobId !== $currentJobId) {
self::$isSynced = [];
self::$lastJobId = $currentJobId;
}
$changedFields = $context['changed_fields'] ?? [];
$isNewRecord = $context['is_new_record'] ?? false;
// Collect all test package numbers that need to be synced
$testPackageNosToSync = [];
// Current test package
$currentTestPackageNo = $data->test_package_no;
if (!empty($currentTestPackageNo)) {
// Check if already synced in this process
if (!isset(self::$isSynced[$currentTestPackageNo])) {
$testPackageNosToSync[] = $currentTestPackageNo;
}
}
// If test_package_no changed, also sync the old test package
if (in_array('test_package_no', $changedFields) && $beforeData && !empty($beforeData->test_package_no)) {
$oldTestPackageNo = $beforeData->test_package_no;
if ($oldTestPackageNo !== $currentTestPackageNo && !in_array($oldTestPackageNo, $testPackageNosToSync)) {
if (!isset(self::$isSynced[$oldTestPackageNo])) {
$testPackageNosToSync[] = $oldTestPackageNo;
}
Log::info("TestPackSync: test_package_no changed, will sync both old and new", [
'old_test_package_no' => $oldTestPackageNo,
'new_test_package_no' => $currentTestPackageNo,
'weld_log_id' => $data->id
]);
}
}
// If no test packages to sync, return early
if (empty($testPackageNosToSync)) {
Log::info("TestPackSync: No test packages to sync", [
'weld_log_id' => $data->id,
'test_package_no' => $currentTestPackageNo
]);
return [
'success' => true,
'synced' => 0,
'reason' => 'no_test_package'
];
}
$syncedCount = 0;
$errors = [];
// Sync each test package
foreach ($testPackageNosToSync as $testPackageNo) {
try {
// Mark as synced before calling the view to prevent re-entry/collision
self::$isSynced[$testPackageNo] = true;
// Call the sync view with specific test_package_no
$result = view('cron.weld_logs-sync-from-weldlog-to-test-pack', [
'test_package_no' => $testPackageNo
])->render();
Log::info("TestPackSync: Sync completed for test package", [
'test_package_no' => $testPackageNo,
'weld_log_id' => $data->id
]);
$syncedCount++;
} catch (\Throwable $th) {
Log::error("TestPackSync: Sync failed for test package (non-critical)", [
'test_package_no' => $testPackageNo,
'weld_log_id' => $data->id,
'error' => $th->getMessage(),
'file' => $th->getFile(),
'line' => $th->getLine()
]);
$errors[] = [
'test_package_no' => $testPackageNo,
'error' => $th->getMessage()
];
}
}
return [
'success' => empty($errors),
'synced' => $syncedCount,
'test_packages_synced' => $testPackageNosToSync,
'errors' => $errors
];
}
}
@@ -0,0 +1,613 @@
<?php
namespace App\Services\WeldLogTriggers\Triggers;
use App\Services\WeldLogTriggers\Base\BaseTrigger;
use App\Models\TestPackage;
use App\Models\PunchList;
use App\Models\TestPackBaseStatus;
use App\Helpers\TransactionHelper;
use Illuminate\Support\Facades\Log;
/**
* Test Package Operations Trigger
*
* Comprehensive test package management including:
* - Supports updates
* - Punch list tracking
* - WDI calculations
* - Golden joints tracking
* - NDT calculations
* - Test package status updates
*/
class TestPackageOperationsTrigger extends BaseTrigger
{
public function getName(): string
{
return 'Test Package Operations';
}
public function getOrder(): int
{
return 7;
}
public function getDependentFields(): array
{
return [
'test_package_no',
'iso_number',
'nps_1',
'nps_2',
'outside_diameter_1',
'outside_diameter_2',
'line_number',
'project',
'design_area',
'piping_type',
'circuit_number',
'p_id',
'type_of_test',
'test_pressure',
'quantity_of_iso',
'welding_date',
'rt_test_date',
'ut_test_date',
'mt_test_date',
'pt_test_date',
'vt_test_date',
'pwht_test_date',
'pmi_test_date',
'ferrite_test_date',
'no_of_the_joint_as_per_as_built_survey'
];
}
protected function process($data, $beforeData, array $context): array
{
$weldLogs = db("weld_logs")->orderBy("test_package_no", "DESC")
->where("iso_number", $data->iso_number)
->where("test_package_no", $data->test_package_no)
->get();
$repairLogs = db("repair_logs")
->where("iso_number", $data->iso_number)
->where("test_package_no", $data->test_package_no)
->get();
$supports = db("supports")
->where("line_number", $data->line_number)
->get();
// Update supports weld_or_assembled_date when welding_date is available
if (!empty($data->welding_date) && !empty($data->line_number)) {
db("supports")
->where("line_number", $data->line_number)
->where(function($query) use ($data) {
$query->where("support_code", $data->element_code_1)
->orWhere("support_code", $data->element_code_2);
})
->update([
"weld_or_assembled_date" => $data->welding_date
]);
}
$punchLists = PunchList::where("line_isometric_no", $data->line_number)
->where("test_package", $data->test_package_no)
->get();
$logTypes = array_keys(log_test_types());
$testTypeToMedium = [
'PNEUMATIC' => 'Air',
'HYDRAULIC' => 'Water',
'VISUAL' => '-',
'ACUISTIC' => '-',
];
// Delete empty test packages
TestPackage::orWhere("test_package_number", "")->orWhereNull("test_package_number")->delete();
TestPackBaseStatus::orWhere("test_package_no", "")->orWhereNull("test_package_no")->delete();
$weldLogTestPackCount = 0;
// Calculate support statistics
$supportStats = $this->calculateSupportStats($supports);
// Calculate punch list statistics
$punchListStats = $this->calculatePunchListStats($punchLists);
// Calculate weld log statistics
$weldLogStats = $this->calculateWeldLogStats($weldLogs, $logTypes);
// Get NDT calculations
// Temporarily set $_GET['type'] for the view's getEsit() helper
$originalGetType = $_GET['type'] ?? null;
$_GET['type'] = 'test_packs';
try {
$ndtCalculation = j(view('admin-ajax.ndt-calculation-no-cache', [
'test_package_no' => $data->test_package_no,
'iso_number2' => $data->iso_number,
'type' => 'test_packs',
])->render());
} finally {
// Restore original $_GET['type']
if ($originalGetType !== null) {
$_GET['type'] = $originalGetType;
} else {
unset($_GET['type']);
}
}
$tpStats = $ndtCalculation['tpStats'] ?? [];
$tpIsoStats = $ndtCalculation['tpISOStats'] ?? [];
// Process Test Package data in chunks
$this->processTestPackageData(
$weldLogs,
$punchListStats,
$weldLogStats,
$tpStats,
$tpIsoStats,
$supportStats,
$testTypeToMedium,
$weldLogTestPackCount
);
// Sync weld logs to test packages for this specific test package
try {
if(isset($data->test_package_no) && $data->test_package_no != "") {
$syncParams = [
'test_package_no' => $data->test_package_no
];
$syncResult = view('cron.weld_logs-sync-from-weldlog-to-test-pack', $syncParams)->render();
Log::debug("Weld logs sync completed for test package: " . $data->test_package_no);
}
} catch (\Throwable $th) {
Log::error("Weld logs sync failed for test package: " . $data->test_package_no . " - " . $th->getMessage());
}
return [
'success' => true,
'processed_records' => $weldLogTestPackCount
];
}
/**
* Calculate support statistics
*/
protected function calculateSupportStats($supports): array
{
$supportStats = [];
$supportStatsTP = [];
$isoToTP = [];
foreach($supports AS $support) {
$tpNo = @$isoToTP[$support->line_number];
if(!isset($supportStatsTP[$tpNo]['support_remaining'])) {
$supportStatsTP[$tpNo]['support_remaining'] = 0;
$supportStatsTP[$tpNo]['welded_support_quantity'] = 0;
$supportStatsTP[$tpNo]['support_progress'] = 0;
}
if(!isset($supportStats[$support->line_number]['support_remaining']))
$supportStats[$support->line_number]['support_remaining'] = 0;
if(!isset($supportStats[$support->line_number]['welded_support_quantity']))
$supportStats[$support->line_number]['welded_support_quantity'] = 0;
if(!isset($supportStats[$support->line_number]['support_progress']))
$supportStats[$support->line_number]['support_progress'] = 0;
if(!rejected_date($support->weld_or_assembled_date) && $support->erection_type == "Weld") {
$supportStats[$support->line_number]['welded_support_quantity']+= $support->quantity;
$supportStatsTP[$tpNo]['welded_support_quantity']+= $support->quantity;
} else {
$supportStats[$support->line_number]['support_remaining']+= $support->quantity;
$supportStatsTP[$tpNo]['support_remaining']+= $support->quantity;
}
if($supportStats[$support->line_number]['welded_support_quantity'] > 0) {
$supportStats[$support->line_number]['support_progress'] =
100 - ($supportStats[$support->line_number]['support_remaining'] * 100) /
($supportStats[$support->line_number]['welded_support_quantity'] + $supportStats[$support->line_number]['support_remaining']);
} else {
$supportStats[$support->line_number]['support_progress'] = 0;
}
if($supportStatsTP[$tpNo]['welded_support_quantity'] > 0) {
$supportStatsTP[$tpNo]['support_progress'] =
100 - ($supportStatsTP[$tpNo]['support_remaining'] * 100) /
($supportStatsTP[$tpNo]['welded_support_quantity'] + $supportStatsTP[$tpNo]['support_remaining']);
} else {
$supportStatsTP[$tpNo]['support_progress'] = 0;
}
}
return ['supportStats' => $supportStats, 'supportStatsTP' => $supportStatsTP];
}
/**
* Calculate punch list statistics
*/
protected function calculatePunchListStats($punchLists): array
{
$stats = [];
$punchListNumbers = [];
foreach($punchLists AS $punchList) {
$drawingNo = $punchList->line_isometric_no;
if(!isset($stats[$drawingNo][$punchList->test_package]['punch_a_quantity']))
$stats[$drawingNo][$punchList->test_package]['punch_a_quantity'] = 0;
if(!isset($stats[$drawingNo][$punchList->test_package]['punch_b_quantity']))
$stats[$drawingNo][$punchList->test_package]['punch_b_quantity'] = 0;
if(!isset($stats[$drawingNo][$punchList->test_package]['punch_c_quantity']))
$stats[$drawingNo][$punchList->test_package]['punch_c_quantity'] = 0;
if(!isset($punchListNumbers[$punchList->test_package]))
$punchListNumbers[$punchList->test_package] = [];
if(!in_array($punchList->punch_list_no, $punchListNumbers[$punchList->test_package]))
$punchListNumbers[$punchList->test_package][] = $punchList->punch_list_no;
if(!isset($stats[$punchList->test_package]['punch_a_open_quantity']))
$stats[$punchList->test_package]['punch_a_open_quantity'] = 0;
if(!isset($stats[$punchList->test_package]['punch_b_open_quantity']))
$stats[$punchList->test_package]['punch_b_open_quantity'] = 0;
if(!isset($stats[$punchList->test_package]['punch_c_open_quantity']))
$stats[$punchList->test_package]['punch_c_open_quantity'] = 0;
if($punchList->category == "A") {
if($punchList->status == "Open") {
$stats[$punchList->test_package]['punch_a_open_quantity']++;
}
$stats[$drawingNo][$punchList->test_package]['punch_a_quantity']++;
}
if($punchList->category == "B") {
if($punchList->status == "Open") {
$stats[$punchList->test_package]['punch_b_open_quantity']++;
}
$stats[$drawingNo][$punchList->test_package]['punch_b_quantity']++;
}
if($punchList->category == "C") {
if($punchList->status == "Open") {
$stats[$punchList->test_package]['punch_c_open_quantity']++;
}
$stats[$drawingNo][$punchList->test_package]['punch_c_quantity']++;
}
$stats[$punchList->test_package]['found_date'] = $punchList->found_date;
$stats[$punchList->test_package]['punch_list'] = $punchList->punch_list;
}
return ['stats' => $stats, 'punchListNumbers' => $punchListNumbers];
}
/**
* Calculate weld log statistics including golden joints, WDI, and backlogs
*/
protected function calculateWeldLogStats($weldLogs, $logTypes): array
{
$stats = [];
$quantity_of_iso = [];
$addedSheet = [];
foreach($weldLogs AS $weldLog) {
$jointNo = $weldLog->no_of_the_joint_as_per_as_built_survey;
if(!isset($stats[$weldLog->iso_number][$weldLog->test_package_no]['goldenJoints']))
$stats[$weldLog->iso_number][$weldLog->test_package_no]['goldenJoints'] = 0;
if(!isset($quantity_of_iso[$weldLog->test_package_no]['total_qty']))
$quantity_of_iso[$weldLog->test_package_no]['total_qty'] = 0;
if(!isset($addedSheet[$weldLog->test_package_no]))
$addedSheet[$weldLog->test_package_no] = [];
if(!in_array($weldLog->quantity_of_iso, $addedSheet[$weldLog->test_package_no])) {
$quantity_of_iso[$weldLog->test_package_no]['total_qty']++;
$addedSheet[$weldLog->test_package_no][] = $weldLog->quantity_of_iso;
}
if(!isset($quantity_of_iso[$weldLog->iso_number][$weldLog->test_package_no]['total_qty']))
$quantity_of_iso[$weldLog->iso_number][$weldLog->test_package_no]['total_qty'] = 0;
if(!isset($addedSheet[$weldLog->iso_number][$weldLog->test_package_no]))
$addedSheet[$weldLog->iso_number][$weldLog->test_package_no] = [];
if(!in_array($weldLog->quantity_of_iso, $addedSheet[$weldLog->iso_number][$weldLog->test_package_no])) {
$quantity_of_iso[$weldLog->iso_number][$weldLog->test_package_no]['total_qty']++;
$addedSheet[$weldLog->iso_number][$weldLog->test_package_no][] = $weldLog->quantity_of_iso;
}
if(!isset($stats[$weldLog->test_package_no]['goldenJoints']))
$stats[$weldLog->test_package_no]['goldenJoints'] = 0;
if(strpos(strtoupper($jointNo), "GJ") !== false) {
$stats[$weldLog->iso_number][$weldLog->test_package_no]['goldenJoints']++;
$stats[$weldLog->test_package_no]['goldenJoints']++;
}
if(!isset($quantity_of_iso[$weldLog->iso_number][$weldLog->test_package_no]['total_iso_quantity']))
$quantity_of_iso[$weldLog->iso_number][$weldLog->test_package_no]['total_iso_quantity'] = 0;
if(!isset($stats[$weldLog->iso_number][$weldLog->test_package_no]['rt_ut_status']))
$stats[$weldLog->iso_number][$weldLog->test_package_no]['rt_ut_status'] = 0;
$backlogs = [
'rt_backlog', 'ut_backlog', 'mt_backlog', 'pmi_backlog',
'vt_backlog', 'pt_backlog', 'ferrit_backlog', 'pwht_backlog', 'total_backlog',
];
foreach($backlogs AS $backlog) {
if(!isset($stats[$weldLog->iso_number][$weldLog->test_package_no][$backlog]))
$stats[$weldLog->iso_number][$weldLog->test_package_no][$backlog] = 0;
if(!isset($stats[$weldLog->test_package_no][$backlog]))
$stats[$weldLog->test_package_no][$backlog] = 0;
}
if(!isset($stats[$weldLog->test_package_no]['total_backlog']))
$stats[$weldLog->test_package_no]['total_backlog'] = 0;
if(!isset($stats[$weldLog->iso_number][$weldLog->test_package_no]['rt_ut_done']))
$stats[$weldLog->iso_number][$weldLog->test_package_no]['rt_ut_done'] = 0;
if(!isset($stats[$weldLog->iso_number][$weldLog->test_package_no]['mt_pt_backlog']))
$stats[$weldLog->iso_number][$weldLog->test_package_no]['mt_pt_backlog'] = 0;
if(!isset($stats[$weldLog->iso_number][$weldLog->test_package_no]['mt_pt_done']))
$stats[$weldLog->iso_number][$weldLog->test_package_no]['mt_pt_done'] = 0;
if(!isset($stats[$weldLog->iso_number][$weldLog->test_package_no]['remaining_wdi']))
$stats[$weldLog->iso_number][$weldLog->test_package_no]['remaining_wdi'] = 0;
if(!isset($stats[$weldLog->iso_number][$weldLog->test_package_no]['total_wdi']))
$stats[$weldLog->iso_number][$weldLog->test_package_no]['total_wdi'] = 0;
if(!isset($stats[$weldLog->iso_number][$weldLog->test_package_no]['total_complated_wdi']))
$stats[$weldLog->iso_number][$weldLog->test_package_no]['total_complated_wdi'] = 0;
if(!isset($stats[$weldLog->iso_number][$weldLog->test_package_no]['total_joint_qty']))
$stats[$weldLog->iso_number][$weldLog->test_package_no]['total_joint_qty'] = 0;
if(!isset($stats[$weldLog->iso_number][$weldLog->test_package_no]['total_welded_joint_qty']))
$stats[$weldLog->iso_number][$weldLog->test_package_no]['total_welded_joint_qty'] = 0;
if(!isset($stats[$weldLog->iso_number][$weldLog->test_package_no]['welded_support_quantity']))
$stats[$weldLog->iso_number][$weldLog->test_package_no]['welded_support_quantity'] = 0;
foreach($logTypes AS $logType) {
$weldLogArray = (Array) $weldLog;
$stats[$weldLog->test_package_no]['total_backlog'] += (float) $weldLogArray[$logType . "_scope"];
}
if(!isset($addedSheet[$weldLog->iso_number][$weldLog->test_package_no]['total_iso']))
$addedSheet[$weldLog->iso_number][$weldLog->test_package_no]['total_iso'] = [];
if(!in_array($weldLog->quantity_of_iso, $addedSheet[$weldLog->iso_number][$weldLog->test_package_no]['total_iso'])) {
$quantity_of_iso[$weldLog->iso_number][$weldLog->test_package_no]['total_iso_quantity']++;
$addedSheet[$weldLog->iso_number][$weldLog->test_package_no]['total_iso'][] = $weldLog->quantity_of_iso;
}
if(rejected_date($weldLog->welding_date)) {
$stats[$weldLog->iso_number][$weldLog->test_package_no]['remaining_wdi'] += (float) $weldLog->nps_1;
}
// Backlog calculations for each test type
if(rejected_date($weldLog->rt_test_date)) {
$stats[$weldLog->iso_number][$weldLog->test_package_no]['rt_backlog']++;
$stats[$weldLog->test_package_no]['rt_backlog']++;
}
if(rejected_date($weldLog->ut_test_date)) {
$stats[$weldLog->iso_number][$weldLog->test_package_no]['ut_backlog']++;
$stats[$weldLog->test_package_no]['ut_backlog']++;
}
if(rejected_date($weldLog->pwht_test_date)) {
$stats[$weldLog->iso_number][$weldLog->test_package_no]['pwht_backlog']++;
$stats[$weldLog->test_package_no]['pwht_backlog']++;
}
if(rejected_date($weldLog->pmi_test_date)) {
$stats[$weldLog->iso_number][$weldLog->test_package_no]['pmi_backlog']++;
$stats[$weldLog->test_package_no]['pmi_backlog']++;
}
if(rejected_date($weldLog->ferrite_test_date)) {
$stats[$weldLog->iso_number][$weldLog->test_package_no]['ferrit_backlog']++;
$stats[$weldLog->test_package_no]['ferrit_backlog']++;
}
if(rejected_date($weldLog->pt_test_date)) {
$stats[$weldLog->iso_number][$weldLog->test_package_no]['pt_backlog']++;
$stats[$weldLog->test_package_no]['pt_backlog']++;
}
if(rejected_date($weldLog->mt_test_date)) {
$stats[$weldLog->iso_number][$weldLog->test_package_no]['mt_backlog']++;
$stats[$weldLog->test_package_no]['mt_backlog']++;
}
if(rejected_date($weldLog->vt_test_date)) {
$stats[$weldLog->iso_number][$weldLog->test_package_no]['vt_backlog']++;
$stats[$weldLog->test_package_no]['vt_backlog']++;
}
if(!rejected_date($weldLog->welding_date)) {
$stats[$weldLog->iso_number][$weldLog->test_package_no]['total_complated_wdi'] += (float) $weldLog->nps_1;
$stats[$weldLog->iso_number][$weldLog->test_package_no]['total_welded_joint_qty']++;
}
$stats[$weldLog->iso_number][$weldLog->test_package_no]['total_wdi'] += (float) $weldLog->nps_1;
// Welding progress calculation - ISO based stats
$totalWdi = $stats[$weldLog->iso_number][$weldLog->test_package_no]['total_wdi'];
$totalComplatedWdi = $stats[$weldLog->iso_number][$weldLog->test_package_no]['total_complated_wdi'];
if ($totalWdi > 0) {
$stats[$weldLog->iso_number][$weldLog->test_package_no]['welding_progress'] =
round(($totalComplatedWdi * 100) / $totalWdi, 2);
} else {
$stats[$weldLog->iso_number][$weldLog->test_package_no]['welding_progress'] = 0;
}
$stats[$weldLog->iso_number][$weldLog->test_package_no]['total_joint_qty']++;
}
return ['stats' => $stats, 'quantity_of_iso' => $quantity_of_iso];
}
/**
* Process and update test package data
*/
protected function processTestPackageData(
$weldLogs,
$punchListStats,
$weldLogStats,
$tpStats,
$tpIsoStats,
$supportStats,
$testTypeToMedium,
&$weldLogTestPackCount
) {
$punchListNumbers = $punchListStats['punchListNumbers'];
$stats = array_merge_recursive($punchListStats['stats'], $weldLogStats['stats']);
$quantity_of_iso = $weldLogStats['quantity_of_iso'];
$supportStatsData = $supportStats['supportStats'];
$supportStatsTP = $supportStats['supportStatsTP'];
// Process Test Package in chunks with TransactionHelper
TransactionHelper::chunkTransaction(
$weldLogs,
function ($weldLogChunk) use ($punchListNumbers, $stats, $tpStats, $tpIsoStats, $supportStatsTP, $quantity_of_iso, $testTypeToMedium, $supportStatsData, &$weldLogTestPackCount) {
foreach($weldLogChunk as $weldLog) {
try {
$punchListData = implode(",", $punchListNumbers[$weldLog->test_package_no] ?? []);
} catch (\Throwable $th) {
$punchListData = "";
}
$thisStats = @$stats[$weldLog->test_package_no];
$thisTpStats = @$tpStats[$weldLog->test_package_no];
$thisTpISOStats = @$tpIsoStats[$weldLog->test_package_no][$weldLog->iso_number];
$thisSupportStats = @$supportStatsTP[$weldLog->test_package_no];
$thisTotalBacklog =
(@$thisTpStats['vt'] ?? 0) +
(@$thisTpStats['ut'] ?? 0) +
(@$thisTpStats['mt'] ?? 0) +
(@$thisTpStats['pmi'] ?? 0) +
(@$thisTpStats['pt'] ?? 0) +
(@$thisTpStats['rt'] ?? 0) +
(@$thisTpStats['pwht'] ?? 0) +
(@$thisTpStats['ferrite'] ?? 0);
$testPackageData = [
'unit' => $weldLog->design_area,
'test_package_number' => $weldLog->test_package_no,
'discipline' => $weldLog->piping_type,
'circuit_number' => $weldLog->circuit_number,
'p_id' => $weldLog->p_id,
'iso_quantity' => $quantity_of_iso[$weldLog->test_package_no]['total_qty'],
'test_type' => $weldLog->type_of_test,
'test_medium' => @$testTypeToMedium[strtoupper($weldLog->type_of_test)],
'test_pressure' => $weldLog->test_pressure,
'golden_joints' => @$stats[$weldLog->iso_number][$weldLog->test_package_no]['goldenJoints'],
'walkdown_date' => @$stats[$weldLog->iso_number][$weldLog->test_package_no]['found_date'],
'punch_list' => $punchListData,
'welded_support_quantity' => @$thisSupportStats['welded_support_quantity'],
'support_remaining' => @$thisSupportStats['support_remaining'],
'support_progress' => @$thisSupportStats['support_progress'],
'a_punch_point_open' => @$stats[$weldLog->iso_number][$weldLog->test_package_no]['punch_a_open_quantity'],
'b_punch_point_open' => @$stats[$weldLog->iso_number][$weldLog->test_package_no]['punch_b_open_quantity'],
'c_punch_point_open' => @$stats[$weldLog->iso_number][$weldLog->test_package_no]['punch_c_open_quantity'],
'vt_backlog' => @$thisTpStats['vt'],
'ut_backlog' => @$thisTpStats['ut'],
'mt_backlog' => @$thisTpStats['mt'],
'pmi_backlog' => @$thisTpStats['pmi'],
'pt_backlog' => @$thisTpStats['pt'],
'rt_backlog' => @$thisTpStats['rt'],
'pwht_backlog' => @$thisTpStats['pwht'],
'ferrit_backlog' => @$thisTpStats['ferrite'],
'ndt_status' => @$thisTotalBacklog == 0 ? "Completed" : "Not Completed",
'welding_progress' => @$stats[$weldLog->iso_number][$weldLog->test_package_no]['welding_progress'],
];
$thisStats = @$stats[$weldLog->iso_number][$weldLog->test_package_no];
$thisSupportStats = @$supportStatsData[$weldLog->iso_number];
try {
$pipeProgress = round($thisStats['total_complated_wdi'] * 100 / $thisStats['total_wdi'], 2);
} catch (\Throwable $th) {
$pipeProgress = 0;
}
$testPackBaseStatusData = [
'area' => $weldLog->project,
'drawing_no' => $weldLog->iso_number,
'test_package_no' => $weldLog->test_package_no,
'discipline' => $weldLog->piping_type,
'iso_qty' => $quantity_of_iso[$weldLog->iso_number][$weldLog->test_package_no]['total_qty'],
'test_type' => $weldLog->type_of_test,
'test_medium' => @$testTypeToMedium[strtoupper($weldLog->type_of_test)],
'test_pressure_mpa' => $weldLog->test_pressure,
'tp_status' => '', // Will be filled from test pack base stat
'total_iso_quantity' => $quantity_of_iso[$weldLog->iso_number][$weldLog->test_package_no]['total_iso_quantity'],
'remaining_wdi' => $thisStats['remaining_wdi'],
'total_wdi' => $thisStats['total_wdi'],
'total_complated_wdi' => $thisStats['total_complated_wdi'],
'pipe_progress' => $pipeProgress,
'total_joint_qty' => $thisStats['total_joint_qty'],
'total_welded_joint_qty' => $thisStats['total_welded_joint_qty'],
'welded_support_quantity' => @$thisSupportStats['welded_support_quantity'],
'support_remaining' => @$thisSupportStats['support_remaining'],
'support_progress' => @$thisSupportStats['support_progress'],
'punch_a_quantity' => @$thisStats['punch_a_quantity'],
'punch_b_quantity' => @$thisStats['punch_b_quantity'],
'punch_c_quantity' => @$thisStats['punch_c_quantity'],
'rt_ut_status' => $thisStats['rt_ut_status'],
'rt_backlog' => @$thisTpISOStats['rt'],
'ut_backlog' => @$thisTpISOStats['ut'],
'mt_backlog' => @$thisTpISOStats['mt'],
'pmi_backlog' => @$thisTpISOStats['pmi'],
'vt_backlog' => @$thisTpISOStats['vt'],
'pt_backlog' => @$thisTpISOStats['pt'],
'pwht_backlog' => @$thisTpISOStats['pwht'],
'ferrit_backlog' => @$thisTpISOStats['ferrite'],
];
$testPackWhereData = [
'test_package_number' => $weldLog->test_package_no
];
$testPackBaseStatusWhereData = [
'test_package_no' => $weldLog->test_package_no,
'drawing_no' => $weldLog->iso_number,
];
if($weldLog->test_package_no != "" && !is_null($weldLog->test_package_no)) {
$testPackResult = TestPackage::updateOrCreate($testPackWhereData, $testPackageData);
$testPackBaseResult = TestPackBaseStatus::updateOrCreate($testPackBaseStatusWhereData, $testPackBaseStatusData);
$weldLogTestPackCount++;
}
}
return $weldLogChunk->count();
},
50, // Chunk size
1000 // 1ms delay
);
}
}
@@ -0,0 +1,249 @@
<?php
namespace App\Services\WeldLogTriggers;
use Illuminate\Support\Facades\Log;
use App\Helpers\TransactionHelper;
/**
* WeldLog Trigger Manager
*
* Manages the execution of all weld log triggers
* Handles trigger execution order, timing, and error handling
*/
class WeldLogTriggerManager
{
/**
* @var WeldLogTriggerRegistry Trigger registry
*/
protected $registry;
/**
* @var float Overall start time
*/
protected $overallStartTime;
/**
* Constructor
*
* @param WeldLogTriggerRegistry $registry
*/
public function __construct(WeldLogTriggerRegistry $registry)
{
$this->registry = $registry;
}
/**
* Execute all applicable triggers
*
* @param object $weldLogData Current weld log data
* @param object|null $beforeData Previous weld log data (null for new records)
* @param array $changedFields List of changed field names
* @param bool $isNewRecord Whether this is a new record
* @return array Execution results
*/
public function executeTriggers(
$weldLogData,
$beforeData = null,
array $changedFields = [],
bool $isNewRecord = false,
?string $action = null
): array {
$this->overallStartTime = microtime(true);
Log::info("=== WELD LOG SAVE TRIGGER STARTED ===", [
'weld_log_id' => $weldLogData->id,
'timestamp' => now()->toDateTimeString(),
'is_new_record' => $isNewRecord,
'changed_fields_count' => count($changedFields),
'changed_fields' => $changedFields,
'action' => $action,
'memory_usage_start' => round(memory_get_usage(true) / 1024 / 1024, 2) . ' MB'
]);
// Set database timeouts and memory limits
TransactionHelper::setDatabaseTimeouts(300, 600, 600);
ini_set('memory_limit', '2G');
ini_set('max_execution_time', 600); // 10 minutes
$results = [];
$triggers = $this->registry->getTriggersInOrder();
Log::info("Total triggers registered: " . count($triggers), [
'trigger_names' => array_map(function($t) { return $t->getName(); }, $triggers)
]);
foreach ($triggers as $trigger) {
// Check if trigger should run
if (!$trigger->shouldRun($changedFields, $isNewRecord)) {
Log::info("Skipping trigger: {$trigger->getName()}", [
'reason' => 'shouldRun returned false',
'order' => $trigger->getOrder(),
'dependent_fields' => $trigger->getDependentFields()
]);
$results[$trigger->getName()] = [
'skipped' => true,
'reason' => 'shouldRun returned false'
];
continue;
}
// Execute sync or async
if ($trigger->isAsync()) {
$this->executeAsync($trigger, $weldLogData, $beforeData, $changedFields);
$results[$trigger->getName()] = [
'queued' => true,
'execution' => 'async'
];
} else {
try {
$result = $trigger->execute($weldLogData, $beforeData, [
'changed_fields' => $changedFields,
'is_new_record' => $isNewRecord,
'action' => $action
]);
$results[$trigger->getName()] = array_merge($result, [
'executed' => true,
'execution' => 'sync'
]);
} catch (\Throwable $th) {
Log::error("Trigger execution failed: {$trigger->getName()}", [
'error' => $th->getMessage(),
'file' => $th->getFile(),
'line' => $th->getLine(),
'trace' => $th->getTraceAsString()
]);
$results[$trigger->getName()] = [
'executed' => false,
'error' => $th->getMessage(),
'execution' => 'sync'
];
// Re-throw critical errors, log others
if ($this->isCriticalTrigger($trigger->getName())) {
throw $th;
}
}
}
}
$this->logOverallCompletion($weldLogData->id, $results);
return $results;
}
/**
* Execute trigger asynchronously (via queue)
*
* @param \App\Services\WeldLogTriggers\Contracts\WeldLogTriggerInterface $trigger
* @param object $weldLogData
* @param object|null $beforeData
* @param array $changedFields
*/
protected function executeAsync($trigger, $weldLogData, $beforeData, $changedFields)
{
// Future implementation: Dispatch job to queue
// \App\Jobs\WeldLogTriggerJob::dispatch(
// get_class($trigger),
// $weldLogData->id,
// $beforeData,
// $changedFields
// );
Log::info("Trigger queued (async execution): {$trigger->getName()}", [
'weld_log_id' => $weldLogData->id,
'note' => 'Async execution not yet implemented, falling back to sync'
]);
// Fallback to sync execution for now
try {
$trigger->execute($weldLogData, $beforeData, [
'changed_fields' => $changedFields,
'is_new_record' => false,
// $action is not available here in the current signature, but async isn't used yet anyway
]);
} catch (\Throwable $th) {
Log::error("Async trigger execution failed: {$trigger->getName()}", [
'error' => $th->getMessage()
]);
}
}
/**
* Check if a trigger is critical (should stop execution on failure)
*
* @param string $triggerName
* @return bool
*/
protected function isCriticalTrigger(string $triggerName): bool
{
// Define which triggers are critical
$criticalTriggers = [
'Spool Status Changer',
'Line Lists Update',
'NDE Matrix Update',
'Construction Paint Logs Sync',
'Paint Follow Ups Sync',
];
return in_array($triggerName, $criticalTriggers);
}
/**
* Log overall execution completion
*
* @param int $weldLogId
* @param array $results
*/
protected function logOverallCompletion($weldLogId, array $results)
{
$overallDuration = round((microtime(true) - $this->overallStartTime) * 1000, 2);
// Count execution statistics
$executedCount = 0;
$skippedCount = 0;
$queuedCount = 0;
$failedCount = 0;
foreach ($results as $triggerName => $result) {
if (isset($result['executed']) && $result['executed']) {
$executedCount++;
} elseif (isset($result['skipped']) && $result['skipped']) {
$skippedCount++;
} elseif (isset($result['queued']) && $result['queued']) {
$queuedCount++;
} elseif (isset($result['executed']) && !$result['executed']) {
$failedCount++;
}
}
Log::info("=== WELD LOG SAVE TRIGGER COMPLETED ===", [
'weld_log_id' => $weldLogId,
'timestamp' => now()->toDateTimeString(),
'total_execution_time_ms' => $overallDuration,
'total_execution_time_sec' => round($overallDuration / 1000, 3),
'peak_memory_usage_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
'final_memory_usage_mb' => round(memory_get_usage(true) / 1024 / 1024, 2),
'statistics' => [
'total_triggers' => count($results),
'executed' => $executedCount,
'skipped' => $skippedCount,
'queued' => $queuedCount,
'failed' => $failedCount
]
]);
}
/**
* Get the registry instance
*
* @return WeldLogTriggerRegistry
*/
public function getRegistry(): WeldLogTriggerRegistry
{
return $this->registry;
}
}
@@ -0,0 +1,120 @@
<?php
namespace App\Services\WeldLogTriggers;
use App\Services\WeldLogTriggers\Triggers\SpoolStatusChangerTrigger;
use App\Services\WeldLogTriggers\Triggers\LineListsUpdateTrigger;
use App\Services\WeldLogTriggers\Triggers\NdeMatrixUpdateTrigger;
use App\Services\WeldLogTriggers\Triggers\RequestDateOperationsTrigger;
use App\Services\WeldLogTriggers\Triggers\RepairLogsUpdateTrigger;
use App\Services\WeldLogTriggers\Triggers\NdeProjectUpdateTrigger;
use App\Services\WeldLogTriggers\Triggers\TestPackageOperationsTrigger;
use App\Services\WeldLogTriggers\Triggers\ConstructionPaintLogsTrigger;
use App\Services\WeldLogTriggers\Triggers\TestPackSyncTrigger;
use App\Services\WeldLogTriggers\Triggers\TestPackBaseStatusChangerTrigger;
use App\Services\WeldLogTriggers\Triggers\PaintFollowUpsSyncTrigger;
use App\Services\WeldLogTriggers\Triggers\HandoversSyncTrigger;
use App\Services\WeldLogTriggers\Triggers\TestPackCleanupTrigger;
use App\Services\WeldLogTriggers\Triggers\NdtCalculationCacheTrigger;
use App\Services\WeldLogTriggers\Triggers\RegisterCreatorCacheTrigger;
/**
* WeldLog Trigger Registry
*
* Central registry for all weld log triggers
* Manages trigger registration and retrieval
*/
class WeldLogTriggerRegistry
{
/**
* @var array Registered triggers
*/
protected $triggers = [];
/**
* Constructor - automatically registers all triggers
*/
public function __construct()
{
$this->registerTriggers();
}
/**
* Register all trigger instances
*/
protected function registerTriggers()
{
// Order 1-12 based on trigger execution order
$this->register(new SpoolStatusChangerTrigger()); // Order 1
$this->register(new LineListsUpdateTrigger()); // Order 2
$this->register(new NdeMatrixUpdateTrigger()); // Order 3
$this->register(new RequestDateOperationsTrigger()); // Order 4
$this->register(new RepairLogsUpdateTrigger()); // Order 5
$this->register(new NdeProjectUpdateTrigger()); // Order 6
$this->register(new TestPackageOperationsTrigger()); // Order 7
$this->register(new TestPackSyncTrigger()); // Order 8 - Sync from WeldLog to Test Pack
$this->register(new ConstructionPaintLogsTrigger()); // Order 9
$this->register(new TestPackBaseStatusChangerTrigger()); // Order 10
$this->register(new PaintFollowUpsSyncTrigger()); // Order 11
$this->register(new HandoversSyncTrigger()); // Order 12
$this->register(new TestPackCleanupTrigger()); // Order 13
$this->register(new NdtCalculationCacheTrigger()); // Order 14
$this->register(new RegisterCreatorCacheTrigger()); // Order 15
}
/**
* Register a single trigger
*
* @param \App\Services\WeldLogTriggers\Contracts\WeldLogTriggerInterface $trigger
*/
public function register($trigger)
{
$this->triggers[$trigger->getName()] = $trigger;
}
/**
* Get all triggers sorted by execution order
*
* @return array
*/
public function getTriggersInOrder(): array
{
$triggers = $this->triggers;
usort($triggers, function($a, $b) {
return $a->getOrder() <=> $b->getOrder();
});
return $triggers;
}
/**
* Get a specific trigger by name
*
* @param string $name Trigger name
* @return \App\Services\WeldLogTriggers\Contracts\WeldLogTriggerInterface|null
*/
public function getTrigger(string $name)
{
return $this->triggers[$name] ?? null;
}
/**
* Get all registered trigger names
*
* @return array
*/
public function getTriggerNames(): array
{
return array_keys($this->triggers);
}
/**
* Get total count of registered triggers
*
* @return int
*/
public function count(): int
{
return count($this->triggers);
}
}