580 lines
23 KiB
PHP
580 lines
23 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\InspectionHistory;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Facades\File;
|
|
|
|
class InspectionHistoryController extends Controller
|
|
{
|
|
/**
|
|
* Upload inspection history (photo + comment)
|
|
*
|
|
* @group Inspection History
|
|
* @bodyParam table_name string required The database table name. Example: weld_logs
|
|
* @bodyParam record_id integer required The primary key of the record. Example: 15
|
|
* @bodyParam comment string required Inspection comment. Example: Approved.
|
|
* @bodyParam photo file[] nullable Photos to upload.
|
|
* @bodyParam module_slug string nullable Optional module slug to fetch settings. Example: daily-weld
|
|
* @bodyParam file_name_pattern string nullable Optional custom filename pattern.
|
|
* @param \Illuminate\Http\Request $request
|
|
* @return \Illuminate\Http\JsonResponse
|
|
*/
|
|
public function upload(Request $request)
|
|
{
|
|
// Check authentication - using $request->user() for Sanctum compatibility
|
|
$u = $request->user();
|
|
if (!$u || !isset($u->level)) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Authentication required'
|
|
], 401);
|
|
}
|
|
|
|
try {
|
|
// Validate the request
|
|
// Manually validate using Validator facade for conditional logic
|
|
$validator = \Illuminate\Support\Facades\Validator::make($request->all(), [
|
|
'table_name' => 'required|string',
|
|
'record_id' => 'required|numeric',
|
|
'comment' => 'required|string',
|
|
'module_slug' => 'nullable|string',
|
|
'photo' => 'nullable', // Allow both, will validate structure below
|
|
]);
|
|
|
|
// Add conditional validation for photo
|
|
$validator->after(function ($validator) use ($request) {
|
|
if ($request->hasFile('photo')) {
|
|
$photos = $request->file('photo');
|
|
|
|
if (is_array($photos)) {
|
|
// Validate array of photos
|
|
$photoValidator = \Illuminate\Support\Facades\Validator::make($request->all(), [
|
|
'photo.*' => 'image|mimes:jpg,jpeg,png,gif,webp|max:20480',
|
|
]);
|
|
|
|
if ($photoValidator->fails()) {
|
|
foreach ($photoValidator->errors()->all() as $error) {
|
|
$validator->errors()->add('photo', $error);
|
|
}
|
|
}
|
|
} else {
|
|
// Validate single photo
|
|
$photoValidator = \Illuminate\Support\Facades\Validator::make($request->all(), [
|
|
'photo' => 'image|mimes:jpg,jpeg,png,gif,webp|max:20480',
|
|
]);
|
|
|
|
if ($photoValidator->fails()) {
|
|
foreach ($photoValidator->errors()->all() as $error) {
|
|
$validator->errors()->add('photo', $error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
if ($validator->fails()) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => $validator->errors()->first()
|
|
], 422);
|
|
}
|
|
|
|
$validated = $validator->validated();
|
|
|
|
$tableName = $validated['table_name'];
|
|
$recordId = $validated['record_id'];
|
|
$comment = $validated['comment'];
|
|
$moduleSlug = $validated['module_slug'] ?? null;
|
|
|
|
// Get settings configuration if module_slug provided
|
|
$inspectionConfig = null;
|
|
$fileNamePattern = null;
|
|
$pathPattern = null;
|
|
|
|
if ($moduleSlug) {
|
|
$inspectionConfig = getInspectionHistoryConfig($moduleSlug);
|
|
|
|
// Get patterns from config
|
|
if ($inspectionConfig) {
|
|
$pathPattern = $inspectionConfig['path_pattern'] ?? null;
|
|
$fileNamePattern = $inspectionConfig['file_pattern'] ?? $inspectionConfig['pattern'] ?? null;
|
|
}
|
|
}
|
|
|
|
// Determine default path
|
|
$basePath = 'inspection_history'; // Default path
|
|
if ($inspectionConfig && isset($inspectionConfig['path'])) {
|
|
$basePath = $inspectionConfig['path'];
|
|
}
|
|
|
|
// Get row data for pattern processing (only needed once)
|
|
$rowData = null;
|
|
if (!empty($fileNamePattern) || !empty($pathPattern)) {
|
|
try {
|
|
$rowData = DB::table($tableName)->where('id', $recordId)->first();
|
|
if ($rowData) {
|
|
$rowData = (array) $rowData;
|
|
}
|
|
} catch (\Exception $e) {
|
|
\Log::warning("InspectionHistoryController: Could not fetch row data for pattern processing. Table: $tableName, ID: $recordId");
|
|
$rowData = null;
|
|
}
|
|
}
|
|
|
|
$createdRecords = [];
|
|
|
|
// Handle photos if provided
|
|
if ($request->hasFile('photo')) {
|
|
$photos = $request->file('photo');
|
|
// Ensure it's an array even if single file (though validation expects array)
|
|
if (!is_array($photos)) {
|
|
$photos = [$photos];
|
|
}
|
|
|
|
foreach ($photos as $photo) {
|
|
$filePath = null;
|
|
$fileName = null;
|
|
$fileSize = null;
|
|
|
|
// Generate file name using pattern or original name
|
|
$originalName = $photo->getClientOriginalName();
|
|
$originalExtension = $photo->getClientOriginalExtension();
|
|
|
|
// --- 1. Determine Folder Name (Path) ---
|
|
$folderName = '';
|
|
|
|
if (!empty($pathPattern)) {
|
|
// Process path pattern (allow slashes for subdirectories)
|
|
$folderName = $this->processFileNamePattern($pathPattern, $rowData, '', true);
|
|
|
|
// Sanitize but keep slashes
|
|
$folderName = preg_replace('/[^A-Za-z0-9_\-\/]/', '_', $folderName);
|
|
$folderName = preg_replace('/_+/', '_', $folderName);
|
|
$folderName = trim($folderName, '_');
|
|
}
|
|
|
|
// Fallback to record_id if folder name is empty
|
|
if (empty($folderName)) {
|
|
$folderName = ($moduleSlug === 'Other' && $recordId == 0) ? date('Y-m-d') : $recordId;
|
|
}
|
|
|
|
// --- 2. Determine File Name ---
|
|
$fileNameForSave = '';
|
|
|
|
if (!empty($fileNamePattern)) {
|
|
// Process file pattern (STRICT sanitization, no slashes)
|
|
$fileNameForSave = $this->processFileNamePattern($fileNamePattern, $rowData, $originalExtension, false);
|
|
}
|
|
|
|
// Fallback to original filename
|
|
if (empty($fileNameForSave)) {
|
|
$fileNameForSave = preg_replace('/[^A-Za-z0-9_\-\.]/', '_', pathinfo($originalName, PATHINFO_FILENAME));
|
|
if ($originalExtension) {
|
|
$fileNameForSave .= '.' . $originalExtension;
|
|
}
|
|
}
|
|
|
|
// --- 3. Save File ---
|
|
|
|
// Ensure unique filename - use pattern-based folder name
|
|
$folderPath = storage_path("documents/{$basePath}/{$folderName}");
|
|
if (!File::isDirectory($folderPath)) {
|
|
File::makeDirectory($folderPath, 0777, true, true);
|
|
}
|
|
|
|
$finalFileName = $fileNameForSave;
|
|
$counter = 1;
|
|
while (File::exists($folderPath . '/' . $finalFileName)) {
|
|
$baseName = pathinfo($fileNameForSave, PATHINFO_FILENAME);
|
|
$extension = pathinfo($fileNameForSave, PATHINFO_EXTENSION);
|
|
$finalFileName = "{$baseName}_{$counter}.{$extension}";
|
|
$counter++;
|
|
}
|
|
|
|
// Move file
|
|
$photo->move($folderPath, $finalFileName);
|
|
|
|
// Set file information
|
|
$filePath = "documents/{$basePath}/" . ($folderName !== '' ? "{$folderName}/" : "") . $finalFileName;
|
|
$fileName = $originalName; // Store original name in database
|
|
$fileSize = File::size($folderPath . '/' . $finalFileName);
|
|
|
|
// Create inspection history record for each photo
|
|
$inspectionHistory = InspectionHistory::create([
|
|
'table_name' => $tableName,
|
|
'record_id' => $recordId,
|
|
'column_name' => null,
|
|
'comment' => $comment,
|
|
'file_path' => $filePath,
|
|
'file_name' => $fileName,
|
|
'file_size' => $fileSize,
|
|
'user_id' => $u->id,
|
|
'ip_address' => $request->ip(),
|
|
]);
|
|
|
|
$createdRecords[] = $inspectionHistory;
|
|
}
|
|
} else {
|
|
// No photos, just comment
|
|
$inspectionHistory = InspectionHistory::create([
|
|
'table_name' => $tableName,
|
|
'record_id' => $recordId,
|
|
'column_name' => null,
|
|
'comment' => $comment,
|
|
'file_path' => null,
|
|
'file_name' => null,
|
|
'file_size' => null,
|
|
'user_id' => $u->id,
|
|
'ip_address' => $request->ip(),
|
|
]);
|
|
$createdRecords[] = $inspectionHistory;
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Inspection history saved successfully',
|
|
'data' => $createdRecords
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
\Log::error("InspectionHistory upload error: " . $e->getMessage(), [
|
|
'trace' => $e->getTraceAsString()
|
|
]);
|
|
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Failed to save inspection history: ' . $e->getMessage()
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List inspection history for a specific record
|
|
*
|
|
* @group Inspection History
|
|
* @queryParam table_name string required The database table name. Example: weld_logs
|
|
* @queryParam record_id integer required The primary key of the record. Example: 15
|
|
* @param \Illuminate\Http\Request $request
|
|
* @return \Illuminate\Http\JsonResponse
|
|
*/
|
|
public function list(Request $request)
|
|
{
|
|
// Check authentication - using $request->user() for Sanctum compatibility
|
|
$u = $request->user();
|
|
if (!$u || !isset($u->level)) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Authentication required'
|
|
], 401);
|
|
}
|
|
|
|
try {
|
|
$request->validate([
|
|
'table_name' => 'required|string',
|
|
'record_id' => 'required|numeric',
|
|
]);
|
|
|
|
$tableName = $request->input('table_name');
|
|
$recordId = $request->input('record_id');
|
|
|
|
// Get inspection history records with user information
|
|
$historyRecords = InspectionHistory::where('table_name', $tableName)
|
|
->where('record_id', $recordId)
|
|
->with('user:id,name')
|
|
->orderBy('created_at', 'DESC')
|
|
->get()
|
|
->map(function ($record) {
|
|
return [
|
|
'id' => $record->id,
|
|
'table_name' => $record->table_name,
|
|
'record_id' => $record->record_id,
|
|
'comment' => $record->comment,
|
|
'file_path' => $record->file_path,
|
|
'file_name' => $record->file_name,
|
|
'file_size' => $record->file_size,
|
|
'user_name' => $record->user ? $record->user->name : 'Unknown',
|
|
'user_id' => $record->user_id,
|
|
'ip_address' => $record->ip_address,
|
|
'created_at' => $record->created_at->format('Y-m-d H:i:s'),
|
|
];
|
|
});
|
|
|
|
return response()->json($historyRecords);
|
|
|
|
} catch (\Exception $e) {
|
|
\Log::error("InspectionHistory list error: " . $e->getMessage());
|
|
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Failed to retrieve inspection history: ' . $e->getMessage()
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete inspection history record
|
|
*
|
|
* @group Inspection History
|
|
* @urlParam id integer required The ID of the inspection history record. Example: 1
|
|
* @param int $id
|
|
* @return \Illuminate\Http\JsonResponse
|
|
*/
|
|
public function delete(Request $request, $id)
|
|
{
|
|
// Check authentication - using $request->user() for Sanctum compatibility
|
|
$u = $request->user();
|
|
if (!$u || !isset($u->level)) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Authentication required'
|
|
], 401);
|
|
}
|
|
|
|
try {
|
|
$inspectionHistory = InspectionHistory::findOrFail($id);
|
|
|
|
// Delete file if exists
|
|
if ($inspectionHistory->file_path) {
|
|
$fullPath = storage_path($inspectionHistory->file_path);
|
|
if (File::exists($fullPath)) {
|
|
File::delete($fullPath);
|
|
}
|
|
}
|
|
|
|
// Delete record
|
|
$inspectionHistory->delete();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Inspection history deleted successfully'
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
\Log::error("InspectionHistory delete error: " . $e->getMessage());
|
|
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Failed to delete inspection history: ' . $e->getMessage()
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bulk delete inspection history records
|
|
*
|
|
* @group Inspection History
|
|
* @bodyParam ids int[] required Array of inspection history IDs to delete. Example: [1, 2]
|
|
* @param \Illuminate\Http\Request $request
|
|
* @return \Illuminate\Http\JsonResponse
|
|
*/
|
|
public function bulkDelete(Request $request)
|
|
{
|
|
// Check authentication
|
|
$u = $request->user();
|
|
if (!$u || !isset($u->level)) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Authentication required'
|
|
], 401);
|
|
}
|
|
|
|
try {
|
|
$request->validate([
|
|
'ids' => 'required|array',
|
|
'ids.*' => 'numeric'
|
|
]);
|
|
|
|
$ids = $request->input('ids');
|
|
$deletedCount = 0;
|
|
|
|
foreach ($ids as $id) {
|
|
try {
|
|
$inspectionHistory = InspectionHistory::find($id);
|
|
|
|
if ($inspectionHistory) {
|
|
// Delete file if exists
|
|
if ($inspectionHistory->file_path) {
|
|
$fullPath = storage_path($inspectionHistory->file_path);
|
|
if (File::exists($fullPath)) {
|
|
File::delete($fullPath);
|
|
}
|
|
}
|
|
|
|
// Delete record
|
|
$inspectionHistory->delete();
|
|
$deletedCount++;
|
|
}
|
|
} catch (\Exception $e) {
|
|
\Log::error("InspectionHistory bulk delete error for ID $id: " . $e->getMessage());
|
|
// Continue with other records
|
|
}
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => "$deletedCount records deleted successfully",
|
|
'deleted_count' => $deletedCount
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
\Log::error("InspectionHistory bulk delete error: " . $e->getMessage());
|
|
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Failed to delete records: ' . $e->getMessage()
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List active inspection history modules
|
|
*
|
|
* @group Inspection History
|
|
* @return \Illuminate\Http\JsonResponse
|
|
*/
|
|
public function modules(Request $request)
|
|
{
|
|
// Check authentication
|
|
$u = $request->user();
|
|
if (!$u || !isset($u->level)) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Authentication required'
|
|
], 401);
|
|
}
|
|
|
|
try {
|
|
// Get current settings
|
|
$currentSettingsJson = setting('inspection_history_management', false, '{}');
|
|
$currentSettings = json_decode($currentSettingsJson, true);
|
|
if (!is_array($currentSettings)) {
|
|
$currentSettings = [];
|
|
}
|
|
|
|
// Get all enabled modules from Types
|
|
$modules = \App\Types::where("enabled", true)
|
|
->whereNotNull("slug")
|
|
->where("slug", "!=", "")
|
|
->orderBy("s", "ASC")
|
|
->get(['id', 'slug', 'title']);
|
|
|
|
$activeModules = [];
|
|
|
|
foreach ($modules as $module) {
|
|
$slug = $module->slug;
|
|
$config = $currentSettings[$slug] ?? null;
|
|
$isActive = isset($config['active']) ? (bool)$config['active'] : false;
|
|
|
|
if ($isActive) {
|
|
$activeModules[] = [
|
|
'id' => $module->id,
|
|
'slug' => $module->slug,
|
|
'title' => $module->title,
|
|
'path_pattern' => $config['path_pattern'] ?? null,
|
|
'file_pattern' => $config['file_pattern'] ?? $config['pattern'] ?? null
|
|
];
|
|
}
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $activeModules
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
\Log::error("InspectionHistory modules error: " . $e->getMessage());
|
|
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Failed to retrieve modules: ' . $e->getMessage()
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process file name pattern and replace placeholders with row data
|
|
*
|
|
* @param string $pattern Pattern string with {column_name} placeholders
|
|
* @param array $rowData Row data array
|
|
* @param string $originalExtension Original file extension
|
|
* @param bool $allowSlashes Whether to allow slashes in the output (for directory paths)
|
|
* @return string Processed file name
|
|
*/
|
|
private function processFileNamePattern($pattern, $rowData, $originalExtension = '', $allowSlashes = false)
|
|
{
|
|
// Replace Date/Time placeholders first
|
|
$replacements = [
|
|
'{Y}' => date('Y'),
|
|
'{m}' => date('m'),
|
|
'{d}' => date('d'),
|
|
'{H}' => date('H'),
|
|
'{i}' => date('i'),
|
|
'{s}' => date('s'),
|
|
'{date}' => date('Y-m-d'),
|
|
'{time}' => date('H-i-s'),
|
|
'{timestamp}' => time(),
|
|
];
|
|
|
|
$processedPattern = str_replace(array_keys($replacements), array_values($replacements), $pattern);
|
|
|
|
// Find all placeholders in pattern
|
|
preg_match_all('/\{(\w+)\}/', $processedPattern, $matches);
|
|
$placeholders = $matches[1];
|
|
|
|
// Replace each placeholder with corresponding value from row data
|
|
foreach ($placeholders as $columnName) {
|
|
$value = isset($rowData[$columnName]) ? $rowData[$columnName] : '';
|
|
|
|
// Sanitize value for filename
|
|
$value = preg_replace('/[^A-Za-z0-9_\-\.]/', '_', (string)$value);
|
|
|
|
// Replace placeholder in pattern
|
|
$processedPattern = str_replace('{' . $columnName . '}', $value, $processedPattern);
|
|
}
|
|
|
|
// Check if extension is already in pattern
|
|
$hasExtension = preg_match('/\.[a-zA-Z0-9]+$/', $processedPattern);
|
|
|
|
// Add extension if not present and original extension exists (and not a path)
|
|
if (!$hasExtension && $originalExtension && !$allowSlashes) {
|
|
$processedPattern .= '.' . $originalExtension;
|
|
}
|
|
|
|
// Final sanitization
|
|
if ($allowSlashes) {
|
|
$processedPattern = preg_replace('/[^A-Za-z0-9_\-\.\/]/', '_', $processedPattern);
|
|
} else {
|
|
$processedPattern = preg_replace('/[^A-Za-z0-9_\-\.]/', '_', $processedPattern);
|
|
}
|
|
|
|
return $processedPattern;
|
|
}
|
|
|
|
/**
|
|
* Upload custom photo for "Other" module (no specific record required)
|
|
*
|
|
* @group Inspection History
|
|
* @bodyParam comment string required Inspection comment.
|
|
* @bodyParam photo file[] nullable Photos to upload.
|
|
* @param \Illuminate\Http\Request $request
|
|
* @return \Illuminate\Http\JsonResponse
|
|
*/
|
|
public function uploadOther(Request $request)
|
|
{
|
|
// Set defaults for "Other" upload
|
|
$request->merge([
|
|
'table_name' => 'Other',
|
|
'record_id' => 0,
|
|
'module_slug' => 'Other'
|
|
]);
|
|
|
|
// Re-use existing upload logic
|
|
return $this->upload($request);
|
|
}
|
|
}
|