915 lines
31 KiB
PHP
915 lines
31 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\DocumentRevision;
|
|
use App\Models\WeldLog;
|
|
use App\Models\WelderTest;
|
|
use App\Models\MaterialGroupMap;
|
|
use App\Models\IncomingControl;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Validation\Rule;
|
|
|
|
class PdfEditorLookupController extends Controller
|
|
{
|
|
private const DRAWING_TYPES = [
|
|
'engineering' => [
|
|
'label' => 'Engineering Drawing',
|
|
'column' => 'pdf_engineering',
|
|
'folder' => 'storage/documents/001_Drawings/001_Engineering',
|
|
],
|
|
'spool' => [
|
|
'label' => 'Spool Drawing',
|
|
'column' => 'pdf_spool_iso',
|
|
'folder' => 'storage/documents/001_Drawings/002_Iso',
|
|
],
|
|
'as_build' => [
|
|
'label' => 'As-Build Drawing',
|
|
'column' => 'pdf_as_build',
|
|
'folder' => 'storage/documents/001_Drawings/003_As_Build(ID)',
|
|
],
|
|
];
|
|
|
|
public function drawingTypes(): JsonResponse
|
|
{
|
|
$types = collect(self::DRAWING_TYPES)->map(function (array $config, string $key) {
|
|
return [
|
|
'value' => $key,
|
|
'label' => $config['label'],
|
|
];
|
|
})->values();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $types,
|
|
]);
|
|
}
|
|
|
|
public function drawings(Request $request): JsonResponse
|
|
{
|
|
$type = $request->query('type', 'engineering');
|
|
$config = self::DRAWING_TYPES[$type] ?? null;
|
|
|
|
if ($config === null) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Unsupported drawing type',
|
|
], 422);
|
|
}
|
|
|
|
$column = $config['column'];
|
|
|
|
$query = DocumentRevision::query()
|
|
->whereNotNull('drawing_no')
|
|
->where('drawing_no', '!=', '');
|
|
|
|
if ($column) {
|
|
$query->whereNotNull($column)->where($column, '!=', '');
|
|
}
|
|
|
|
$drawings = $query
|
|
->select('drawing_no')
|
|
->distinct()
|
|
->orderBy('drawing_no')
|
|
->get()
|
|
->map(fn ($row) => [
|
|
'value' => $row->drawing_no,
|
|
'label' => $row->drawing_no,
|
|
]);
|
|
|
|
if ($drawings->isEmpty() && isset($config['folder'])) {
|
|
$drawings = collect($this->scanDrawingFolder($config['folder']))
|
|
->map(fn (string $name) => [
|
|
'value' => $name,
|
|
'label' => $name,
|
|
]);
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $drawings,
|
|
]);
|
|
}
|
|
|
|
public function files(Request $request): JsonResponse
|
|
{
|
|
$type = $request->query('type');
|
|
$drawing = $request->query('drawing');
|
|
|
|
if (!$type || !$drawing) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Drawing type and drawing number are required',
|
|
], 422);
|
|
}
|
|
|
|
$config = self::DRAWING_TYPES[$type] ?? null;
|
|
|
|
if ($config === null) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Unsupported drawing type',
|
|
], 422);
|
|
}
|
|
|
|
$column = $config['column'];
|
|
$files = collect();
|
|
|
|
if ($column) {
|
|
$files = $this->buildFileListFromColumn($drawing, $column);
|
|
}
|
|
|
|
if ($files->isEmpty() && $type === 'as_build') {
|
|
$fallbackColumns = ['working_documentation', 'axonometry'];
|
|
|
|
foreach ($fallbackColumns as $fallbackColumn) {
|
|
$files = $this->buildFileListFromColumn($drawing, $fallbackColumn);
|
|
|
|
if ($files->isNotEmpty()) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($files->isEmpty() && isset($config['folder'])) {
|
|
$publicFolder = public_path($config['folder']);
|
|
if (is_dir($publicFolder)) {
|
|
$pattern = $publicFolder . '/' . $drawing . '*.pdf';
|
|
$matches = glob($pattern) ?: [];
|
|
|
|
$files = collect($matches)->map(function (string $filePath) {
|
|
$relativePath = $this->relativePublicPath($filePath);
|
|
$filename = basename($filePath);
|
|
|
|
return [
|
|
'id' => md5($filePath),
|
|
'revision' => null,
|
|
'label' => $filename,
|
|
'url' => $relativePath ? url($relativePath) : null,
|
|
'path' => $relativePath,
|
|
'is_latest' => false,
|
|
'exists' => true,
|
|
'updated_at' => Carbon::createFromTimestamp(filemtime($filePath))->toDateTimeString(),
|
|
];
|
|
})->sortByDesc('updated_at')->values();
|
|
|
|
if ($files->isNotEmpty()) {
|
|
$files = $files->map(function (array $item, int $index) {
|
|
$item['is_latest'] = $index === 0;
|
|
return $item;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $files->values(),
|
|
]);
|
|
}
|
|
|
|
public function updateDocumentStage(Request $request): JsonResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'drawing' => ['required', 'string'],
|
|
'stage' => ['required', Rule::in(['pdf_spool_iso', 'pdf_as_build'])],
|
|
'pdf_path' => ['required', 'string'],
|
|
'revision_id' => ['nullable', 'integer'],
|
|
]);
|
|
|
|
$pdfPath = trim($validated['pdf_path']);
|
|
|
|
if ($pdfPath === '') {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'PDF path cannot be empty.',
|
|
], 422);
|
|
}
|
|
|
|
$query = DocumentRevision::query()
|
|
->where('drawing_no', $validated['drawing']);
|
|
|
|
if (!empty($validated['revision_id'])) {
|
|
$query->where('id', $validated['revision_id']);
|
|
}
|
|
|
|
$updatedRows = $query->update([
|
|
$validated['stage'] => $pdfPath,
|
|
]);
|
|
|
|
if ($updatedRows === 0) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'No matching document revisions found for update.',
|
|
], 404);
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'updated_rows' => $updatedRows,
|
|
'stage' => $validated['stage'],
|
|
'pdf_path' => $pdfPath,
|
|
]);
|
|
}
|
|
|
|
private function buildFileListFromColumn(string $drawing, string $column): \Illuminate\Support\Collection
|
|
{
|
|
$records = DocumentRevision::query()
|
|
->where('drawing_no', $drawing)
|
|
->whereNotNull($column)
|
|
->where($column, '!=', '')
|
|
->orderByDesc('publish_date')
|
|
->orderByDesc('updated_at')
|
|
->get(['id', 'drawing_no', 'revision_no', 'publish_date', 'updated_at', $column]);
|
|
|
|
if ($records->isEmpty()) {
|
|
return collect();
|
|
}
|
|
|
|
$latestId = optional($records->first())->id;
|
|
|
|
return $records->map(function (DocumentRevision $revision) use ($latestId, $column) {
|
|
$path = $revision->{$column};
|
|
if (!$path) {
|
|
return null;
|
|
}
|
|
|
|
$fullPath = public_path($path);
|
|
$exists = $fullPath ? file_exists($fullPath) : false;
|
|
|
|
$timestamp = $revision->publish_date
|
|
? Carbon::parse($revision->publish_date)->startOfDay()
|
|
: ($revision->updated_at instanceof Carbon ? $revision->updated_at : Carbon::parse($revision->updated_at));
|
|
|
|
return [
|
|
'id' => $revision->id,
|
|
'revision' => $revision->revision_no,
|
|
'label' => $this->formatLabel($revision->revision_no, $path),
|
|
'url' => $path ? url($path) : null,
|
|
'path' => $path,
|
|
'is_latest' => $revision->id === $latestId,
|
|
'exists' => $exists,
|
|
'updated_at' => $timestamp?->toDateTimeString(),
|
|
];
|
|
})->filter()->values();
|
|
}
|
|
|
|
public function weldData(Request $request): JsonResponse
|
|
{
|
|
$drawing = trim((string) $request->query('drawing', ''));
|
|
|
|
if ($drawing === '') {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Drawing number is required',
|
|
], 422);
|
|
}
|
|
|
|
$logs = WeldLog::query()
|
|
->where('iso_number', $drawing)
|
|
->select([
|
|
'spool_number',
|
|
'spool_number2',
|
|
'no_of_the_joint_as_per_as_built_survey',
|
|
'type_of_joint',
|
|
'welder_1',
|
|
'welder_2',
|
|
'real_welder_1',
|
|
'real_welder_2',
|
|
])
|
|
->orderBy('spool_number')
|
|
->get();
|
|
|
|
if ($logs->isEmpty()) {
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'assignments' => [],
|
|
],
|
|
]);
|
|
}
|
|
|
|
$assignmentMap = [];
|
|
|
|
foreach ($logs as $log) {
|
|
$spool = $this->sanitizeValue($log->spool_number) ?? $this->sanitizeValue($log->spool_number2);
|
|
$joint = $this->sanitizeValue($log->no_of_the_joint_as_per_as_built_survey);
|
|
$jointType = $this->sanitizeValue($log->type_of_joint);
|
|
|
|
if (!$spool && !$joint) {
|
|
continue;
|
|
}
|
|
|
|
$key = $spool . '|' . $joint;
|
|
|
|
if (!isset($assignmentMap[$key])) {
|
|
$assignmentMap[$key] = [
|
|
'spool' => $spool,
|
|
'joint' => $joint,
|
|
'joint_type' => $jointType ? strtoupper($jointType) : null,
|
|
'welders' => [],
|
|
];
|
|
} elseif ($jointType && empty($assignmentMap[$key]['joint_type'])) {
|
|
$assignmentMap[$key]['joint_type'] = strtoupper($jointType);
|
|
}
|
|
|
|
$welders = [
|
|
$this->sanitizeValue($log->real_welder_1),
|
|
$this->sanitizeValue($log->real_welder_2),
|
|
$this->sanitizeValue($log->welder_1),
|
|
$this->sanitizeValue($log->welder_2),
|
|
];
|
|
|
|
foreach ($welders as $welder) {
|
|
if ($welder) {
|
|
$assignmentMap[$key]['welders'][$welder] = $welder;
|
|
}
|
|
}
|
|
}
|
|
|
|
$assignments = collect($assignmentMap)
|
|
->map(function (array $assignment) {
|
|
$assignment['welders'] = array_values($assignment['welders']);
|
|
return $assignment;
|
|
})
|
|
->values();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'assignments' => $assignments,
|
|
],
|
|
]);
|
|
}
|
|
|
|
private function formatLabel(?string $revision, ?string $path): string
|
|
{
|
|
$filename = $path ? basename($path) : null;
|
|
|
|
if ($revision && $filename) {
|
|
return sprintf('Rev %s — %s', $revision, $filename);
|
|
}
|
|
|
|
if ($revision) {
|
|
return sprintf('Rev %s', $revision);
|
|
}
|
|
|
|
return $filename ?? 'Unknown PDF';
|
|
}
|
|
|
|
private function relativePublicPath(string $absolutePath): ?string
|
|
{
|
|
$publicPath = public_path();
|
|
|
|
if (Str::startsWith($absolutePath, $publicPath)) {
|
|
return ltrim(str_replace($publicPath, '', $absolutePath), '/');
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function sanitizeValue($value): ?string
|
|
{
|
|
if ($value === null) {
|
|
return null;
|
|
}
|
|
|
|
$trimmed = trim((string) $value);
|
|
|
|
return $trimmed === '' ? null : $trimmed;
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
private function scanDrawingFolder(string $relativeFolder): array
|
|
{
|
|
$basePath = public_path($relativeFolder);
|
|
|
|
if (!is_dir($basePath)) {
|
|
return [];
|
|
}
|
|
|
|
$files = glob($basePath . DIRECTORY_SEPARATOR . '*.pdf') ?: [];
|
|
|
|
if (empty($files)) {
|
|
return [];
|
|
}
|
|
|
|
$names = collect($files)
|
|
->map(fn (string $path) => pathinfo($path, PATHINFO_FILENAME))
|
|
->filter()
|
|
->unique()
|
|
->sort()
|
|
->values()
|
|
->all();
|
|
|
|
return $names;
|
|
}
|
|
|
|
/**
|
|
* Get IC Log members for a drawing
|
|
* Note: Currently drawing_number field is empty in database, so we return all members
|
|
* TODO: When drawing_number is populated, filter by drawing_number
|
|
*/
|
|
public function getICLogMembers(Request $request): JsonResponse
|
|
{
|
|
$request->validate(['drawing' => 'required|string']);
|
|
$drawingNumber = $request->drawing;
|
|
|
|
// First try to get by drawing_number (when it's populated)
|
|
$members = IncomingControl::where('drawing_number', $drawingNumber)
|
|
->whereNotNull('description_ru')
|
|
->where('description_ru', '!=', '')
|
|
->distinct('description_ru')
|
|
->pluck('description_ru')
|
|
->filter()
|
|
->values()
|
|
->toArray();
|
|
|
|
// If no results found by drawing_number, return all members
|
|
// (This is a fallback since drawing_number field is currently empty)
|
|
if (empty($members)) {
|
|
$members = IncomingControl::whereNotNull('description_ru')
|
|
->where('description_ru', '!=', '')
|
|
->distinct('description_ru')
|
|
->pluck('description_ru')
|
|
->filter()
|
|
->values()
|
|
->toArray();
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $members,
|
|
'filtered_by_drawing' => !empty($members) && IncomingControl::where('drawing_number', $drawingNumber)->exists()
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Get IC Log member details
|
|
*/
|
|
public function getICLogMemberDetails(Request $request): JsonResponse
|
|
{
|
|
$request->validate(['member_description' => 'required|string']);
|
|
$memberDescription = $request->member_description;
|
|
|
|
$details = IncomingControl::where('description_ru', $memberDescription)
|
|
->select(
|
|
'material',
|
|
'odmm_1 as diameter', // Outer diameter in mm
|
|
'dn_1 as nps', // Nominal pipe size
|
|
'thicknessmm_1 as thickness' // Thickness in mm
|
|
)
|
|
->first(); // Get the first matching record
|
|
|
|
return response()->json(['success' => true, 'data' => $details]);
|
|
}
|
|
|
|
/**
|
|
* Check existing weldmap data for a drawing
|
|
*/
|
|
public function checkExistingWeldmapData(Request $request): JsonResponse
|
|
{
|
|
$request->validate(['drawing' => 'required|string']);
|
|
$drawingNumber = $request->drawing;
|
|
|
|
$existingJoints = WeldLog::where('iso_number', $drawingNumber)
|
|
->select(
|
|
'no_of_the_joint_as_per_as_built_survey as joint_no',
|
|
'joint_type',
|
|
'welder_1',
|
|
'welder_2',
|
|
'spool_number_1 as spool_no_1',
|
|
'spool_number_2 as spool_no_2',
|
|
'pose_number_1 as pose_no_1',
|
|
'pose_number_2 as pose_no_2',
|
|
'member_1',
|
|
'member_2',
|
|
'material_1',
|
|
'material_2',
|
|
'dia_1',
|
|
'dia_2',
|
|
'nps_1',
|
|
'nps_2',
|
|
'thickness_1',
|
|
'thickness_2',
|
|
'welding_date'
|
|
)
|
|
->get();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'has_data' => $existingJoints->isNotEmpty(),
|
|
'joints' => $existingJoints
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Get qualified welders based on material, welding method, and expiry dates
|
|
* Phase 3.1: API Endpoints for Joint Data
|
|
* Phase 6: Welder Qualification Filtering
|
|
*/
|
|
public function getQualifiedWelders(Request $request): JsonResponse
|
|
{
|
|
$request->validate([
|
|
'drawing_number' => 'required|string',
|
|
'material' => 'nullable|string',
|
|
'welding_method' => 'nullable|string',
|
|
'joint_no' => 'nullable|string',
|
|
'skip_qualification' => 'nullable|boolean', // Skip qualification check if true
|
|
]);
|
|
|
|
try {
|
|
$drawingNumber = $request->drawing_number;
|
|
$jointNo = $request->joint_no;
|
|
$skipQualification = $request->boolean('skip_qualification', false);
|
|
|
|
Log::debug('Phase 6: getQualifiedWelders called', [
|
|
'drawing_number' => $drawingNumber,
|
|
'joint_no' => $jointNo,
|
|
'skip_qualification' => $skipQualification
|
|
]);
|
|
|
|
// If skip_qualification is true, return all welders from weld_logs (legacy behavior)
|
|
if ($skipQualification) {
|
|
return $this->getWeldersFromWeldLogs($drawingNumber, $jointNo);
|
|
}
|
|
|
|
// Get joint data from weld_logs to determine qualification criteria
|
|
$jointData = null;
|
|
if (!empty($jointNo)) {
|
|
$jointData = WeldLog::where('iso_number', $drawingNumber)
|
|
->where('no_of_the_joint_as_per_as_built_survey', $jointNo)
|
|
->first();
|
|
}
|
|
|
|
// If no joint data found, try to get any record for this drawing
|
|
if (!$jointData) {
|
|
$jointData = WeldLog::where('iso_number', $drawingNumber)->first();
|
|
}
|
|
|
|
// If still no data, return empty list with message
|
|
if (!$jointData) {
|
|
Log::debug('Phase 6: No joint data found for qualification', [
|
|
'drawing_number' => $drawingNumber,
|
|
'joint_no' => $jointNo
|
|
]);
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => [],
|
|
'count' => 0,
|
|
'message' => 'No joint data found for qualification filtering'
|
|
]);
|
|
}
|
|
|
|
// Extract qualification criteria from joint data
|
|
$criteria = $this->extractQualificationCriteria($jointData);
|
|
|
|
Log::debug('Phase 6: Qualification criteria extracted', $criteria);
|
|
|
|
// Get qualified welders from WelderTest table
|
|
$qualifiedWelders = $this->filterWeldersByQualification($criteria, $drawingNumber);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $qualifiedWelders,
|
|
'count' => count($qualifiedWelders),
|
|
'criteria' => $criteria,
|
|
'message' => 'Welders filtered by qualification'
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error('Phase 6: getQualifiedWelders error', [
|
|
'error' => $e->getMessage(),
|
|
'trace' => $e->getTraceAsString()
|
|
]);
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Failed to get qualified welders',
|
|
'error' => $e->getMessage()
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get welders from weld_logs without qualification filtering (legacy behavior)
|
|
*/
|
|
private function getWeldersFromWeldLogs(string $drawingNumber, ?string $jointNo): JsonResponse
|
|
{
|
|
$query = WeldLog::where('iso_number', $drawingNumber)
|
|
->whereNotNull('welder_1')
|
|
->where('welder_1', '!=', '');
|
|
|
|
if (!empty($jointNo)) {
|
|
$query->where('no_of_the_joint_as_per_as_built_survey', $jointNo);
|
|
}
|
|
|
|
$welders1 = $query->pluck('welder_1')->unique()->filter()->values();
|
|
|
|
$query2 = WeldLog::where('iso_number', $drawingNumber)
|
|
->whereNotNull('welder_2')
|
|
->where('welder_2', '!=', '');
|
|
|
|
if (!empty($jointNo)) {
|
|
$query2->where('no_of_the_joint_as_per_as_built_survey', $jointNo);
|
|
}
|
|
|
|
$welders2 = $query2->pluck('welder_2')->unique()->filter()->values();
|
|
|
|
$allWelders = $welders1->merge($welders2)->unique()->sort()->values();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $allWelders->toArray(),
|
|
'count' => $allWelders->count(),
|
|
'message' => 'Welders from weld_logs (no qualification filtering)'
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Extract qualification criteria from joint data
|
|
*/
|
|
private function extractQualificationCriteria(WeldLog $jointData): array
|
|
{
|
|
return [
|
|
'material_group_1' => $jointData->ru_material_group_1 ?? null,
|
|
'material_group_2' => $jointData->ru_material_group_2 ?? null,
|
|
'welding_method' => $jointData->welding_method ?? null,
|
|
'welding_date' => $jointData->welding_date ?? now()->toDateString(),
|
|
'outside_diameter' => $jointData->outside_diameter_1 ?? null,
|
|
'wall_thickness' => $jointData->wall_thickness_1 ?? null,
|
|
'iso_number' => $jointData->iso_number ?? null,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Filter welders by qualification criteria using WelderTest table
|
|
* Based on existing welder-assignment/welder_1.php logic
|
|
*/
|
|
private function filterWeldersByQualification(array $criteria, string $drawingNumber): array
|
|
{
|
|
$logKey = 'PHASE6_WELDER_FILTER_' . uniqid();
|
|
Log::debug("[$logKey] Starting welder qualification filtering", $criteria);
|
|
|
|
$qualifiedWelders = [];
|
|
$today = Carbon::now();
|
|
$welderQualificationExpiredDays = (int) setting("WelderQualitificationStatusExpiredDate", 180);
|
|
|
|
// Build material combinations for qualification check
|
|
$qualifiedCombinations = $this->buildMaterialCombinations(
|
|
$criteria['material_group_1'],
|
|
$criteria['material_group_2']
|
|
);
|
|
|
|
Log::debug("[$logKey] Material combinations", [
|
|
'combinations' => $qualifiedCombinations
|
|
]);
|
|
|
|
// If no material info, skip qualification and return welders from weld_logs
|
|
if (empty($qualifiedCombinations)) {
|
|
Log::debug("[$logKey] No material combinations, falling back to weld_logs welders");
|
|
return $this->getWeldersFromWeldLogsArray($drawingNumber);
|
|
}
|
|
|
|
// Build base query with WelderTest
|
|
$query = WelderTest::leftJoin('naks_welders', function($join) {
|
|
$join->on('naks_welders.naks_certificate_no', '=', 'welder_tests.naks_no');
|
|
});
|
|
|
|
// Filter by material combinations
|
|
$query->where(function ($q) use ($qualifiedCombinations) {
|
|
foreach ($qualifiedCombinations as $combo) {
|
|
$q->orWhere(function($subQ) use ($combo) {
|
|
$subQ->where("material_group_1", "like", "%{$combo['mat1']}%")
|
|
->where("material_group_2", "like", "%{$combo['mat2']}%");
|
|
});
|
|
}
|
|
});
|
|
|
|
// Filter by diameter if available
|
|
if (!empty($criteria['outside_diameter']) && is_numeric($criteria['outside_diameter'])) {
|
|
$diameter = (float) $criteria['outside_diameter'];
|
|
$query->where("dia_min", "<=", $diameter)
|
|
->where(function($q) use ($diameter) {
|
|
$q->where("dia_max", ">=", $diameter)
|
|
->orWhereNull("dia_max")
|
|
->orWhere("dia_max", 0);
|
|
});
|
|
}
|
|
|
|
// Filter by thickness if available
|
|
if (!empty($criteria['wall_thickness']) && is_numeric($criteria['wall_thickness'])) {
|
|
$thickness = (float) $criteria['wall_thickness'];
|
|
$query->where("thk_min", "<=", $thickness)
|
|
->where(function($q) use ($thickness) {
|
|
$q->where("thk_max", ">=", $thickness)
|
|
->orWhereNull("thk_max")
|
|
->orWhere("thk_max", 0);
|
|
});
|
|
}
|
|
|
|
// Filter by welding method if available
|
|
if (!empty($criteria['welding_method'])) {
|
|
$weldingMethod = $criteria['welding_method'];
|
|
$weldingMethodParts = explode(",", $weldingMethod);
|
|
$reverseWeldingMethod = implode(",", array_reverse($weldingMethodParts));
|
|
|
|
$query->where(function($q) use ($weldingMethod, $reverseWeldingMethod) {
|
|
$q->where("welding_method", $weldingMethod)
|
|
->orWhere("welding_method", $reverseWeldingMethod);
|
|
});
|
|
}
|
|
|
|
// Filter by approval date (must be approved before welding date)
|
|
if (!empty($criteria['welding_date'])) {
|
|
$query->whereDate("approval_date", "<=", $criteria['welding_date']);
|
|
}
|
|
|
|
// Select and group results
|
|
$results = $query->select(
|
|
'welder_tests.naks_id',
|
|
'welder_tests.naks_validity',
|
|
DB::raw('MIN(naks_welders.period_of_validity) as period_of_validity')
|
|
)
|
|
->groupBy('welder_tests.naks_id', 'welder_tests.naks_validity')
|
|
->get();
|
|
|
|
Log::debug("[$logKey] Query results", [
|
|
'count' => $results->count()
|
|
]);
|
|
|
|
// Process results and add status indicators
|
|
foreach ($results as $welder) {
|
|
$welderDisplay = $welder->naks_id;
|
|
|
|
// Check NAKS validity
|
|
if (!empty($welder->naks_validity)) {
|
|
try {
|
|
$naksValidity = Carbon::parse($welder->naks_validity);
|
|
$remainingDays = $today->diffInDays($naksValidity, false);
|
|
|
|
if ($remainingDays < 10 && $remainingDays >= 0) {
|
|
$welderDisplay = "❗" . $welderDisplay; // Expiring soon
|
|
} elseif ($remainingDays < 0) {
|
|
$welderDisplay = "❌" . $welderDisplay; // Expired
|
|
continue; // Skip expired welders
|
|
}
|
|
} catch (\Exception $e) {
|
|
// Invalid date format, skip validity check
|
|
}
|
|
}
|
|
|
|
// Check if welder is already assigned to this ISO
|
|
$isAssigned = WeldLog::where('iso_number', $drawingNumber)
|
|
->where(function($q) use ($welder) {
|
|
$q->where('welder_1', 'like', "%{$welder->naks_id}%")
|
|
->orWhere('real_welder_1', 'like', "%{$welder->naks_id}%");
|
|
})
|
|
->exists();
|
|
|
|
if ($isAssigned) {
|
|
$welderDisplay = "🟡" . $welderDisplay; // Already assigned
|
|
}
|
|
|
|
// Check period of validity
|
|
if (!empty($welder->period_of_validity) && !empty($criteria['welding_date'])) {
|
|
try {
|
|
$weldingDate = Carbon::parse($criteria['welding_date']);
|
|
$periodOfValidity = Carbon::parse($welder->period_of_validity);
|
|
$expiredDate = $periodOfValidity->copy()->addDays($welderQualificationExpiredDays);
|
|
|
|
if ($weldingDate->greaterThan($expiredDate)) {
|
|
$welderDisplay = "❌📅" . $welderDisplay; // Welding date expired
|
|
continue; // Skip
|
|
}
|
|
} catch (\Exception $e) {
|
|
// Invalid date format, skip check
|
|
}
|
|
}
|
|
|
|
$qualifiedWelders[] = $welderDisplay;
|
|
}
|
|
|
|
Log::debug("[$logKey] Qualified welders", [
|
|
'count' => count($qualifiedWelders),
|
|
'welders' => $qualifiedWelders
|
|
]);
|
|
|
|
return array_values(array_unique($qualifiedWelders));
|
|
}
|
|
|
|
/**
|
|
* Build material combinations for qualification check
|
|
*/
|
|
private function buildMaterialCombinations(?string $mat1, ?string $mat2): array
|
|
{
|
|
if (empty($mat1) && empty($mat2)) {
|
|
return [];
|
|
}
|
|
|
|
$mat1 = $mat1 ?? '';
|
|
$mat2 = $mat2 ?? $mat1;
|
|
|
|
$isSameMaterial = ($mat1 === $mat2);
|
|
$materialCombination = $isSameMaterial ? $mat1 : "{$mat1}+{$mat2}";
|
|
|
|
$qualifiedCombinations = [];
|
|
|
|
// Check MaterialGroupMap for qualified materials
|
|
$exactMatch = MaterialGroupMap::where("material_name", $materialCombination)->first();
|
|
|
|
if ($exactMatch && !empty($exactMatch->qualified_materials)) {
|
|
$qualifiedMaterialsList = explode(",", $exactMatch->qualified_materials);
|
|
foreach ($qualifiedMaterialsList as $combo) {
|
|
$combo = trim($combo);
|
|
$parts = explode("+", $combo);
|
|
|
|
if (count($parts) == 2) {
|
|
$qualifiedCombinations[] = ['mat1' => trim($parts[0]), 'mat2' => trim($parts[1])];
|
|
} elseif (count($parts) == 1) {
|
|
$qualifiedCombinations[] = ['mat1' => trim($parts[0]), 'mat2' => trim($parts[0])];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Always include original combination
|
|
if ($isSameMaterial) {
|
|
$qualifiedCombinations[] = ['mat1' => $mat1, 'mat2' => $mat2];
|
|
} else {
|
|
$qualifiedCombinations[] = ['mat1' => $mat1, 'mat2' => $mat2];
|
|
$qualifiedCombinations[] = ['mat1' => $mat2, 'mat2' => $mat1]; // Reverse
|
|
}
|
|
|
|
return $qualifiedCombinations;
|
|
}
|
|
|
|
/**
|
|
* Get welders from weld_logs as array (helper for fallback)
|
|
*/
|
|
private function getWeldersFromWeldLogsArray(string $drawingNumber): array
|
|
{
|
|
$welders1 = WeldLog::where('iso_number', $drawingNumber)
|
|
->whereNotNull('welder_1')
|
|
->where('welder_1', '!=', '')
|
|
->pluck('welder_1')
|
|
->unique()
|
|
->filter()
|
|
->values();
|
|
|
|
$welders2 = WeldLog::where('iso_number', $drawingNumber)
|
|
->whereNotNull('welder_2')
|
|
->where('welder_2', '!=', '')
|
|
->pluck('welder_2')
|
|
->unique()
|
|
->filter()
|
|
->values();
|
|
|
|
return $welders1->merge($welders2)->unique()->sort()->values()->toArray();
|
|
}
|
|
|
|
/**
|
|
* Get type of weld options from joint_types table
|
|
* Phase 3: Type of Weld dropdown options
|
|
*/
|
|
public function getTypeOfWeldOptions(Request $request): JsonResponse
|
|
{
|
|
try {
|
|
$options = \DB::table('joint_types')
|
|
->select('short_name_en')
|
|
->whereNotNull('short_name_en')
|
|
->where('short_name_en', '!=', '')
|
|
->orderBy('short_name_en')
|
|
->distinct()
|
|
->pluck('short_name_en')
|
|
->filter()
|
|
->values()
|
|
->toArray();
|
|
|
|
// Fallback: If no options found, use default types
|
|
if (empty($options)) {
|
|
$options = ['BW', 'FW', 'SW', 'TW', 'BJ', 'FJ', 'TH', 'CJ'];
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $options
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Failed to get type of weld options',
|
|
'error' => $e->getMessage()
|
|
], 500);
|
|
}
|
|
}
|
|
}
|
|
|
|
|