|
|
@@ -4,471 +4,413 @@ namespace App\Services;
|
|
|
|
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
+use Illuminate\Support\Str;
|
|
|
use Illuminate\Http\UploadedFile;
|
|
|
-use Exception;
|
|
|
|
|
|
class RecordFileService
|
|
|
{
|
|
|
/**
|
|
|
- * The storage disk to use for file operations
|
|
|
+ * Get client name from session, fallback to 'default'
|
|
|
*/
|
|
|
- private $disk;
|
|
|
+ private function getClientName()
|
|
|
+ {
|
|
|
+ $clientName = session('clientName', 'default');
|
|
|
|
|
|
- /**
|
|
|
- * Allowed file extensions for record attachments
|
|
|
- */
|
|
|
- private const ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx', 'xml', 'txt', 'csv', 'xls', 'xlsx'];
|
|
|
+ $clientName = Str::slug($clientName, '_');
|
|
|
|
|
|
- /**
|
|
|
- * Maximum file size in KB
|
|
|
- */
|
|
|
- private const MAX_FILE_SIZE = 10240; // 10MB
|
|
|
+ Log::info("Using client name for folders: {$clientName}");
|
|
|
+ return $clientName;
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
- * Constructor
|
|
|
+ * Create record folders with client structure
|
|
|
*/
|
|
|
- public function __construct()
|
|
|
+ public function createRecordFolders($recordId, $type)
|
|
|
{
|
|
|
- $this->disk = Storage::disk('s3');
|
|
|
+ $clientName = $this->getClientName();
|
|
|
+ $type = strtolower($type);
|
|
|
+
|
|
|
+ Log::info("Preparing S3 structure for client: {$clientName}, record {$recordId}, type: {$type}");
|
|
|
+ $folderPath = "{$clientName}/records/{$type}/{$recordId}/attachments";
|
|
|
+ Log::info("S3 folder structure: {$folderPath}");
|
|
|
+
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Upload record attachment file
|
|
|
- *
|
|
|
- * @param UploadedFile $attachmentFile
|
|
|
- * @param int $recordId
|
|
|
- * @param string $recordType ('IN' or 'OUT')
|
|
|
- * @return string
|
|
|
- * @throws Exception
|
|
|
+ * Store file temporarily with client structure
|
|
|
*/
|
|
|
- public function uploadAttachment(UploadedFile $attachmentFile, int $recordId, string $recordType = 'OUT'): string
|
|
|
+ public function storeTemporarily($uploadedFile)
|
|
|
{
|
|
|
try {
|
|
|
- $currentClient = session('currentClient', 'iao');
|
|
|
+ $clientName = $this->getClientName();
|
|
|
+ $extension = $uploadedFile->getClientOriginalExtension();
|
|
|
+ $fileName = time() . '_' . Str::random(10) . '.' . $extension;
|
|
|
+ $tempPath = "{$clientName}/temp/uploads/{$fileName}";
|
|
|
+
|
|
|
+ Log::info("=== STORING FILE TEMPORARILY ===");
|
|
|
+ Log::info("Client: {$clientName}");
|
|
|
+ Log::info("Original filename: " . $uploadedFile->getClientOriginalName());
|
|
|
+ Log::info("File size: " . $uploadedFile->getSize() . " bytes");
|
|
|
+ Log::info("Temp path: {$tempPath}");
|
|
|
|
|
|
- // Validate file
|
|
|
- $this->validateAttachmentFile($attachmentFile);
|
|
|
+ try {
|
|
|
+ $storedPath = Storage::disk('s3')->putFileAs("{$clientName}/temp/uploads", $uploadedFile, $fileName);
|
|
|
+ Log::info("Method 1 success - putFileAs returned: {$storedPath}");
|
|
|
+
|
|
|
+ if (Storage::disk('s3')->exists($tempPath)) {
|
|
|
+ $storedSize = Storage::disk('s3')->size($tempPath);
|
|
|
+ Log::info("File verification successful - size: {$storedSize} bytes");
|
|
|
+
|
|
|
+ if ($storedSize === $uploadedFile->getSize()) {
|
|
|
+ Log::info("File sizes match perfectly");
|
|
|
+ return $tempPath;
|
|
|
+ } else {
|
|
|
+ Log::warning("⚠ File size mismatch - Original: {$uploadedFile->getSize()}, Stored: {$storedSize}");
|
|
|
+ return $tempPath;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new \Exception("File not found after putFileAs");
|
|
|
+ }
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::warning("Method 1 failed: " . $e->getMessage());
|
|
|
+ }
|
|
|
|
|
|
- // Create filename
|
|
|
- $originalName = pathinfo($attachmentFile->getClientOriginalName(), PATHINFO_FILENAME);
|
|
|
- $extension = strtolower($attachmentFile->getClientOriginalExtension());
|
|
|
- $timestamp = time();
|
|
|
- $filename = 'attachment_' . $recordId . '_' . $timestamp . '_' . substr(md5($originalName), 0, 8) . '.' . $extension;
|
|
|
+ try {
|
|
|
+ Log::info("Trying Method 2: put with file contents");
|
|
|
+ $fileContent = file_get_contents($uploadedFile->getRealPath());
|
|
|
|
|
|
- // Upload to S3
|
|
|
- $s3Path = $currentClient . '/records/' . strtolower($recordType) . '/' . $recordId . '/attachments/' . $filename;
|
|
|
+ if (!$fileContent) {
|
|
|
+ throw new \Exception("Could not read file contents");
|
|
|
+ }
|
|
|
|
|
|
- $uploaded = $this->disk->putFileAs(
|
|
|
- $currentClient . '/records/' . strtolower($recordType) . '/' . $recordId . '/attachments',
|
|
|
- $attachmentFile,
|
|
|
- $filename,
|
|
|
- 'private'
|
|
|
- );
|
|
|
+ $stored = Storage::disk('s3')->put($tempPath, $fileContent);
|
|
|
|
|
|
- if (!$uploaded) {
|
|
|
- throw new Exception('Failed to upload attachment to S3: ' . $originalName);
|
|
|
+ if ($stored && Storage::disk('s3')->exists($tempPath)) {
|
|
|
+ Log::info("Method 2 success - put with contents");
|
|
|
+ return $tempPath;
|
|
|
+ } else {
|
|
|
+ throw new \Exception("Put method failed");
|
|
|
+ }
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::warning("Method 2 failed: " . $e->getMessage());
|
|
|
}
|
|
|
|
|
|
- Log::info("Record attachment uploaded", [
|
|
|
- 'record_id' => $recordId,
|
|
|
- 'record_type' => $recordType,
|
|
|
- 'path' => $s3Path,
|
|
|
- 'original_name' => $attachmentFile->getClientOriginalName(),
|
|
|
- 'size' => $attachmentFile->getSize()
|
|
|
- ]);
|
|
|
-
|
|
|
- return $s3Path;
|
|
|
-
|
|
|
- } catch (Exception $e) {
|
|
|
- Log::error("Error uploading record attachment", [
|
|
|
- 'record_id' => $recordId,
|
|
|
- 'record_type' => $recordType,
|
|
|
- 'error' => $e->getMessage()
|
|
|
- ]);
|
|
|
+ throw new \Exception("All temp storage methods failed");
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error("Error storing file temporarily: " . $e->getMessage());
|
|
|
+ Log::error("Stack trace: " . $e->getTraceAsString());
|
|
|
throw $e;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Upload XML receipt file (for electronic invoice imports)
|
|
|
- *
|
|
|
- * @param UploadedFile $xmlFile
|
|
|
- * @param int $recordId
|
|
|
- * @param string $recordType
|
|
|
- * @return string
|
|
|
- * @throws Exception
|
|
|
+ * Upload attachment directly to final S3 location with client structure
|
|
|
*/
|
|
|
- public function uploadXmlReceipt(UploadedFile $xmlFile, int $recordId, string $recordType = 'OUT'): string
|
|
|
+ public function uploadAttachment($file, $recordId, $type)
|
|
|
{
|
|
|
try {
|
|
|
- $currentClient = session('currentClient', 'iao');
|
|
|
+ $clientName = $this->getClientName();
|
|
|
+ $type = strtolower($type);
|
|
|
+ $extension = $file->getClientOriginalExtension();
|
|
|
+ $fileName = time() . '_' . Str::random(10) . '.' . $extension;
|
|
|
+ $finalPath = "{$clientName}/records/{$type}/{$recordId}/attachments/{$fileName}";
|
|
|
+
|
|
|
+ Log::info("Uploading attachment to S3:");
|
|
|
+ Log::info("- Client: {$clientName}");
|
|
|
+ Log::info("- Record ID: {$recordId}");
|
|
|
+ Log::info("- Type: {$type}");
|
|
|
+ Log::info("- File path: {$finalPath}");
|
|
|
+ Log::info("- File size: " . $file->getSize() . " bytes");
|
|
|
+
|
|
|
+ $storedPath = Storage::disk('s3')->putFileAs(
|
|
|
+ "{$clientName}/records/{$type}/{$recordId}/attachments",
|
|
|
+ $file,
|
|
|
+ $fileName
|
|
|
+ );
|
|
|
|
|
|
- // Validate XML file
|
|
|
- $this->validateXmlFile($xmlFile);
|
|
|
+ Log::info("File uploaded successfully to S3: {$storedPath}");
|
|
|
|
|
|
- // Create filename
|
|
|
- $originalName = pathinfo($xmlFile->getClientOriginalName(), PATHINFO_FILENAME);
|
|
|
- $extension = strtolower($xmlFile->getClientOriginalExtension());
|
|
|
- $timestamp = time();
|
|
|
- $filename = 'xml_receipt_' . $recordId . '_' . $timestamp . '_' . substr(md5($originalName), 0, 8) . '.' . $extension;
|
|
|
+ if (Storage::disk('s3')->exists($finalPath)) {
|
|
|
+ Log::info("S3 upload verified successfully");
|
|
|
+ return $finalPath;
|
|
|
+ } else {
|
|
|
+ throw new \Exception("File verification failed - not found on S3");
|
|
|
+ }
|
|
|
|
|
|
- // Upload to S3
|
|
|
- $s3Path = $currentClient . '/records/' . strtolower($recordType) . '/' . $recordId . '/xml/' . $filename;
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error("Error uploading attachment to S3: " . $e->getMessage());
|
|
|
+ throw $e;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- $uploaded = $this->disk->putFileAs(
|
|
|
- $currentClient . '/records/' . strtolower($recordType) . '/' . $recordId . '/xml',
|
|
|
- $xmlFile,
|
|
|
- $filename,
|
|
|
- 'private'
|
|
|
+ /**
|
|
|
+ * Upload XML receipt for import functionality with client structure
|
|
|
+ */
|
|
|
+ public function uploadXmlReceipt($file, $recordId, $type)
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $clientName = $this->getClientName();
|
|
|
+ $type = strtolower($type);
|
|
|
+ $extension = $file->getClientOriginalExtension() ?: 'xml';
|
|
|
+ $fileName = 'receipt_' . time() . '_' . Str::random(8) . '.' . $extension;
|
|
|
+ $finalPath = "{$clientName}/records/{$type}/{$recordId}/attachments/{$fileName}";
|
|
|
+
|
|
|
+ Log::info("Uploading XML receipt to S3:");
|
|
|
+ Log::info("- Client: {$clientName}");
|
|
|
+ Log::info("- Path: {$finalPath}");
|
|
|
+
|
|
|
+ $storedPath = Storage::disk('s3')->putFileAs(
|
|
|
+ "{$clientName}/records/{$type}/{$recordId}/attachments",
|
|
|
+ $file,
|
|
|
+ $fileName
|
|
|
);
|
|
|
|
|
|
- if (!$uploaded) {
|
|
|
- throw new Exception('Failed to upload XML receipt to S3: ' . $originalName);
|
|
|
- }
|
|
|
+ Log::info("XML receipt uploaded to S3: {$storedPath}");
|
|
|
+ return $finalPath;
|
|
|
|
|
|
- Log::info("XML receipt uploaded", [
|
|
|
- 'record_id' => $recordId,
|
|
|
- 'record_type' => $recordType,
|
|
|
- 'path' => $s3Path,
|
|
|
- 'original_name' => $xmlFile->getClientOriginalName(),
|
|
|
- 'size' => $xmlFile->getSize()
|
|
|
- ]);
|
|
|
-
|
|
|
- return $s3Path;
|
|
|
-
|
|
|
- } catch (Exception $e) {
|
|
|
- Log::error("Error uploading XML receipt", [
|
|
|
- 'record_id' => $recordId,
|
|
|
- 'record_type' => $recordType,
|
|
|
- 'error' => $e->getMessage()
|
|
|
- ]);
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error("Error uploading XML receipt to S3: " . $e->getMessage());
|
|
|
throw $e;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Get attachment file URL for display/download
|
|
|
- *
|
|
|
- * @param string $filePath
|
|
|
- * @param string $expiresIn
|
|
|
- * @return string|null
|
|
|
+ * Get S3 attachment URL
|
|
|
*/
|
|
|
- public function getAttachmentUrl(string $filePath, string $expiresIn = '+1 hour'): ?string
|
|
|
+ public function getAttachmentUrl($filePath)
|
|
|
{
|
|
|
- if (!$filePath) {
|
|
|
- return null;
|
|
|
- }
|
|
|
+ try {
|
|
|
+ if (!$filePath) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
|
|
|
- // Handle legacy local paths - return asset URL
|
|
|
- if (!$this->isS3Path($filePath)) {
|
|
|
- return asset('storage/' . $filePath);
|
|
|
- }
|
|
|
+ Log::info("Getting S3 attachment URL for: {$filePath}");
|
|
|
+
|
|
|
+ if (!Storage::disk('s3')->exists($filePath)) {
|
|
|
+ Log::warning("S3 attachment file not found: {$filePath}");
|
|
|
+
|
|
|
+ $directory = dirname($filePath);
|
|
|
+ try {
|
|
|
+ $files = Storage::disk('s3')->files($directory);
|
|
|
+ Log::info("Files in S3 directory {$directory}: " . json_encode($files));
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::warning("Could not list S3 directory {$directory}: " . $e->getMessage());
|
|
|
+ }
|
|
|
|
|
|
- try {
|
|
|
- if (!$this->disk->exists($filePath)) {
|
|
|
- Log::warning("Attachment file not found", ['path' => $filePath]);
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- return $this->disk->temporaryUrl($filePath, now()->add($expiresIn));
|
|
|
+ $url = Storage::disk('s3')->temporaryUrl($filePath, now()->addHours(1));
|
|
|
+ Log::info("Generated S3 temporary URL for: {$filePath}");
|
|
|
+ return $url;
|
|
|
|
|
|
- } catch (Exception $e) {
|
|
|
- Log::error("Error generating attachment URL", [
|
|
|
- 'path' => $filePath,
|
|
|
- 'error' => $e->getMessage()
|
|
|
- ]);
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error("Error getting S3 attachment URL for {$filePath}: " . $e->getMessage());
|
|
|
return null;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Delete attachment file from S3
|
|
|
- *
|
|
|
- * @param string $filePath
|
|
|
- * @return bool
|
|
|
+ * Delete attachment from S3
|
|
|
*/
|
|
|
- public function deleteAttachment(string $filePath): bool
|
|
|
+ public function deleteAttachment($filePath)
|
|
|
{
|
|
|
- // Don't try to delete local files
|
|
|
- if (!$this->isS3Path($filePath)) {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
try {
|
|
|
- if ($this->disk->exists($filePath)) {
|
|
|
- $this->disk->delete($filePath);
|
|
|
- Log::info("Attachment file deleted", ['path' => $filePath]);
|
|
|
- return true;
|
|
|
+ if (!$filePath) {
|
|
|
+ return false;
|
|
|
}
|
|
|
- return false;
|
|
|
|
|
|
- } catch (Exception $e) {
|
|
|
- Log::error("Error deleting attachment file", [
|
|
|
- 'path' => $filePath,
|
|
|
- 'error' => $e->getMessage()
|
|
|
- ]);
|
|
|
- return false;
|
|
|
+ Log::info("Deleting S3 attachment: {$filePath}");
|
|
|
+
|
|
|
+ if (Storage::disk('s3')->exists($filePath)) {
|
|
|
+ $deleted = Storage::disk('s3')->delete($filePath);
|
|
|
+ if ($deleted) {
|
|
|
+ Log::info("S3 attachment deleted successfully: {$filePath}");
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ Log::error("Failed to delete S3 attachment: {$filePath}");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ Log::warning("S3 attachment not found for deletion: {$filePath}");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error("Error deleting S3 attachment: " . $e->getMessage());
|
|
|
+ throw $e;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Check if path is S3 path
|
|
|
+ * Debug S3 configuration and connectivity
|
|
|
*/
|
|
|
- private function isS3Path(string $path): bool
|
|
|
+ public function debugFileSystem()
|
|
|
{
|
|
|
- return strpos($path, '/records/') !== false ||
|
|
|
- strpos($path, session('currentClient', 'iao')) === 0;
|
|
|
- }
|
|
|
+ $clientName = $this->getClientName();
|
|
|
|
|
|
- /**
|
|
|
- * Validate the uploaded attachment file
|
|
|
- *
|
|
|
- * @param UploadedFile $attachmentFile
|
|
|
- * @throws Exception
|
|
|
- */
|
|
|
- private function validateAttachmentFile(UploadedFile $attachmentFile): void
|
|
|
- {
|
|
|
- // Check if file is valid
|
|
|
- if (!$attachmentFile->isValid()) {
|
|
|
- throw new Exception('Invalid file upload');
|
|
|
- }
|
|
|
+ Log::info("=== S3 DEBUG ===");
|
|
|
+ Log::info("Client Name: {$clientName}");
|
|
|
+ Log::info("S3 Configuration:");
|
|
|
+ Log::info("- Bucket: " . config('filesystems.disks.s3.bucket'));
|
|
|
+ Log::info("- Region: " . config('filesystems.disks.s3.region'));
|
|
|
+ Log::info("- URL: " . config('filesystems.disks.s3.url'));
|
|
|
+ Log::info("- Key: " . (config('filesystems.disks.s3.key') ? 'Set' : 'Not set'));
|
|
|
+ Log::info("- Secret: " . (config('filesystems.disks.s3.secret') ? 'Set' : 'Not set'));
|
|
|
|
|
|
- // Check file size
|
|
|
- if ($attachmentFile->getSize() > (self::MAX_FILE_SIZE * 1024)) {
|
|
|
- throw new Exception('File size exceeds maximum allowed size of ' . self::MAX_FILE_SIZE . 'KB');
|
|
|
- }
|
|
|
+ try {
|
|
|
+ $testFile = "{$clientName}/test_connection_" . time() . '.txt';
|
|
|
+ $testContent = 'S3 connection test: ' . now();
|
|
|
|
|
|
- // Check file extension
|
|
|
- $extension = strtolower($attachmentFile->getClientOriginalExtension());
|
|
|
- if (!in_array($extension, self::ALLOWED_EXTENSIONS)) {
|
|
|
- throw new Exception('File type not allowed. Allowed types: ' . implode(', ', self::ALLOWED_EXTENSIONS));
|
|
|
- }
|
|
|
+ Log::info("Testing S3 connection with client structure...");
|
|
|
+ Storage::disk('s3')->put($testFile, $testContent);
|
|
|
|
|
|
- // Check mime type for additional security
|
|
|
- $mimeType = $attachmentFile->getMimeType();
|
|
|
- $allowedMimeTypes = [
|
|
|
- 'image/jpeg', 'image/png',
|
|
|
- 'application/pdf',
|
|
|
- 'application/msword',
|
|
|
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
|
- 'application/xml', 'text/xml',
|
|
|
- 'text/plain',
|
|
|
- 'text/csv',
|
|
|
- 'application/vnd.ms-excel',
|
|
|
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
|
- ];
|
|
|
-
|
|
|
- if (!in_array($mimeType, $allowedMimeTypes)) {
|
|
|
- throw new Exception('Invalid file type');
|
|
|
+ if (Storage::disk('s3')->exists($testFile)) {
|
|
|
+ Log::info("S3 connection test: SUCCESS");
|
|
|
+ Storage::disk('s3')->delete($testFile);
|
|
|
+ } else {
|
|
|
+ Log::error("S3 connection test: FAILED - file not found after upload");
|
|
|
+ }
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error("S3 connection test: FAILED - " . $e->getMessage());
|
|
|
}
|
|
|
+
|
|
|
+ Log::info("=== END S3 DEBUG ===");
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Validate XML file specifically
|
|
|
- *
|
|
|
- * @param UploadedFile $xmlFile
|
|
|
- * @throws Exception
|
|
|
+ * Clean up old temp files from S3 for specific client or all clients
|
|
|
*/
|
|
|
- private function validateXmlFile(UploadedFile $xmlFile): void
|
|
|
+ public function cleanupTempFiles($olderThanHours = 24, $specificClient = null)
|
|
|
{
|
|
|
- // Check if file is valid
|
|
|
- if (!$xmlFile->isValid()) {
|
|
|
- throw new Exception('Invalid XML file upload');
|
|
|
- }
|
|
|
-
|
|
|
- // Check file size
|
|
|
- if ($xmlFile->getSize() > (self::MAX_FILE_SIZE * 1024)) {
|
|
|
- throw new Exception('XML file size exceeds maximum allowed size of ' . self::MAX_FILE_SIZE . 'KB');
|
|
|
- }
|
|
|
-
|
|
|
- // Check file extension
|
|
|
- $extension = strtolower($xmlFile->getClientOriginalExtension());
|
|
|
- if ($extension !== 'xml') {
|
|
|
- throw new Exception('Only XML files are allowed for receipt uploads');
|
|
|
- }
|
|
|
+ try {
|
|
|
+ $clientName = $specificClient ?: $this->getClientName();
|
|
|
+ $tempPath = $specificClient ? "{$specificClient}/temp/uploads" : "{$clientName}/temp/uploads";
|
|
|
+
|
|
|
+ $tempFiles = Storage::disk('s3')->files($tempPath);
|
|
|
+ $cutoffTime = now()->subHours($olderThanHours);
|
|
|
+ $deletedCount = 0;
|
|
|
+
|
|
|
+ Log::info("Cleaning up S3 temp files for client '{$clientName}' older than {$olderThanHours} hours");
|
|
|
+
|
|
|
+ foreach ($tempFiles as $file) {
|
|
|
+ $fileTime = Storage::disk('s3')->lastModified($file);
|
|
|
+ if ($fileTime < $cutoffTime->timestamp) {
|
|
|
+ if (Storage::disk('s3')->delete($file)) {
|
|
|
+ $deletedCount++;
|
|
|
+ Log::info("Cleaned up old S3 temp file: {$file}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // Check mime type
|
|
|
- $mimeType = $xmlFile->getMimeType();
|
|
|
- $allowedMimeTypes = ['application/xml', 'text/xml'];
|
|
|
+ Log::info("S3 cleanup completed for client '{$clientName}'. Deleted {$deletedCount} temp files.");
|
|
|
|
|
|
- if (!in_array($mimeType, $allowedMimeTypes)) {
|
|
|
- throw new Exception('Invalid XML file type');
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error("Error cleaning up S3 temp files: " . $e->getMessage());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Create record folder structure in S3
|
|
|
- *
|
|
|
- * @param int $recordId
|
|
|
- * @param string $recordType
|
|
|
+ * Clean up temp files for all clients
|
|
|
*/
|
|
|
- public function createRecordFolders(int $recordId, string $recordType = 'OUT'): void
|
|
|
+ public function cleanupAllClientTempFiles($olderThanHours = 24)
|
|
|
{
|
|
|
try {
|
|
|
- $currentClient = session('currentClient', 'iao');
|
|
|
-
|
|
|
- $folders = [
|
|
|
- $currentClient . '/records/' . strtolower($recordType) . '/' . $recordId . '/attachments/.gitkeep',
|
|
|
- $currentClient . '/records/' . strtolower($recordType) . '/' . $recordId . '/xml/.gitkeep'
|
|
|
- ];
|
|
|
-
|
|
|
- foreach ($folders as $folder) {
|
|
|
- if (!$this->disk->exists($folder)) {
|
|
|
- $this->disk->put($folder, '');
|
|
|
+ $allDirectories = Storage::disk('s3')->directories('');
|
|
|
+ $deletedCount = 0;
|
|
|
+
|
|
|
+ Log::info("Cleaning up temp files for all clients older than {$olderThanHours} hours");
|
|
|
+
|
|
|
+ foreach ($allDirectories as $clientDir) {
|
|
|
+ $tempPath = "{$clientDir}/temp/uploads";
|
|
|
+
|
|
|
+ if (Storage::disk('s3')->exists($tempPath)) {
|
|
|
+ $tempFiles = Storage::disk('s3')->files($tempPath);
|
|
|
+ $cutoffTime = now()->subHours($olderThanHours);
|
|
|
+
|
|
|
+ foreach ($tempFiles as $file) {
|
|
|
+ $fileTime = Storage::disk('s3')->lastModified($file);
|
|
|
+ if ($fileTime < $cutoffTime->timestamp) {
|
|
|
+ if (Storage::disk('s3')->delete($file)) {
|
|
|
+ $deletedCount++;
|
|
|
+ Log::info("Cleaned up old S3 temp file: {$file}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- Log::info("Created record folder structure", [
|
|
|
- 'record_id' => $recordId,
|
|
|
- 'record_type' => $recordType
|
|
|
- ]);
|
|
|
-
|
|
|
- } catch (Exception $e) {
|
|
|
- Log::error("Error creating record folders", [
|
|
|
- 'record_id' => $recordId,
|
|
|
- 'record_type' => $recordType,
|
|
|
- 'error' => $e->getMessage()
|
|
|
- ]);
|
|
|
+ Log::info("S3 cleanup completed for all clients. Deleted {$deletedCount} temp files.");
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error("Error cleaning up all client temp files: " . $e->getMessage());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Get attachment file info
|
|
|
- *
|
|
|
- * @param string $filePath
|
|
|
- * @return array|null
|
|
|
+ * Check if a file exists on S3
|
|
|
*/
|
|
|
- public function getAttachmentInfo(string $filePath): ?array
|
|
|
+ public function fileExists($filePath)
|
|
|
{
|
|
|
- if (!$filePath) {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- // Handle legacy local paths
|
|
|
- if (!$this->isS3Path($filePath)) {
|
|
|
- $localPath = storage_path('app/public/' . $filePath);
|
|
|
- if (file_exists($localPath)) {
|
|
|
- return [
|
|
|
- 'path' => $filePath,
|
|
|
- 'name' => basename($filePath),
|
|
|
- 'size' => filesize($localPath),
|
|
|
- 'last_modified' => filemtime($localPath),
|
|
|
- 'url' => $this->getAttachmentUrl($filePath),
|
|
|
- 'exists' => true,
|
|
|
- 'storage_type' => 'local'
|
|
|
- ];
|
|
|
- }
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- if (!$this->disk->exists($filePath)) {
|
|
|
- return null;
|
|
|
+ try {
|
|
|
+ return Storage::disk('s3')->exists($filePath);
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error("Error checking if S3 file exists: " . $e->getMessage());
|
|
|
+ return false;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
+ /**
|
|
|
+ * Get file size from S3
|
|
|
+ */
|
|
|
+ public function getFileSize($filePath)
|
|
|
+ {
|
|
|
try {
|
|
|
- return [
|
|
|
- 'path' => $filePath,
|
|
|
- 'name' => basename($filePath),
|
|
|
- 'size' => $this->disk->size($filePath),
|
|
|
- 'last_modified' => $this->disk->lastModified($filePath),
|
|
|
- 'url' => $this->getAttachmentUrl($filePath),
|
|
|
- 'exists' => true,
|
|
|
- 'storage_type' => 's3'
|
|
|
- ];
|
|
|
-
|
|
|
- } catch (Exception $e) {
|
|
|
- Log::error("Error getting attachment info", [
|
|
|
- 'path' => $filePath,
|
|
|
- 'error' => $e->getMessage()
|
|
|
- ]);
|
|
|
- return null;
|
|
|
+ if (Storage::disk('s3')->exists($filePath)) {
|
|
|
+ return Storage::disk('s3')->size($filePath);
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error("Error getting S3 file size: " . $e->getMessage());
|
|
|
+ return 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * List all attachments for a record
|
|
|
- *
|
|
|
- * @param int $recordId
|
|
|
- * @param string $recordType
|
|
|
- * @return array
|
|
|
+ * Get file last modified time from S3
|
|
|
*/
|
|
|
- public function listRecordAttachments(int $recordId, string $recordType = 'OUT'): array
|
|
|
+ public function getFileLastModified($filePath)
|
|
|
{
|
|
|
try {
|
|
|
- $currentClient = session('currentClient', 'iao');
|
|
|
- $attachmentPath = $currentClient . '/records/' . strtolower($recordType) . '/' . $recordId . '/attachments';
|
|
|
-
|
|
|
- $files = $this->disk->files($attachmentPath);
|
|
|
-
|
|
|
- return array_filter($files, function($file) {
|
|
|
- $filename = basename($file);
|
|
|
- return strpos($filename, 'attachment_') === 0;
|
|
|
- });
|
|
|
-
|
|
|
- } catch (Exception $e) {
|
|
|
- Log::error("Error listing record attachments", [
|
|
|
- 'record_id' => $recordId,
|
|
|
- 'record_type' => $recordType,
|
|
|
- 'error' => $e->getMessage()
|
|
|
- ]);
|
|
|
- return [];
|
|
|
+ if (Storage::disk('s3')->exists($filePath)) {
|
|
|
+ return Storage::disk('s3')->lastModified($filePath);
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error("Error getting S3 file last modified: " . $e->getMessage());
|
|
|
+ return null;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Store XML receipt files for batch import
|
|
|
- *
|
|
|
- * @param array $xmlFiles
|
|
|
- * @param string $batchId
|
|
|
- * @return array
|
|
|
+ * Get all files for a specific client and type
|
|
|
*/
|
|
|
- public function storeXmlBatch(array $xmlFiles, string $batchId): array
|
|
|
+ public function getClientFiles($type = null, $clientName = null)
|
|
|
{
|
|
|
- $storedFiles = [];
|
|
|
- $currentClient = session('currentClient', 'iao');
|
|
|
+ try {
|
|
|
+ $clientName = $clientName ?: $this->getClientName();
|
|
|
+ $type = $type ? strtolower($type) : '*';
|
|
|
|
|
|
- foreach ($xmlFiles as $index => $xmlFile) {
|
|
|
- try {
|
|
|
- $this->validateXmlFile($xmlFile);
|
|
|
-
|
|
|
- $originalName = pathinfo($xmlFile->getClientOriginalName(), PATHINFO_FILENAME);
|
|
|
- $extension = strtolower($xmlFile->getClientOriginalExtension());
|
|
|
- $timestamp = time();
|
|
|
- $filename = 'batch_' . $batchId . '_' . $index . '_' . $timestamp . '_' . substr(md5($originalName), 0, 8) . '.' . $extension;
|
|
|
-
|
|
|
- $s3Path = $currentClient . '/imports/xml_batch/' . $batchId . '/' . $filename;
|
|
|
-
|
|
|
- $uploaded = $this->disk->putFileAs(
|
|
|
- $currentClient . '/imports/xml_batch/' . $batchId,
|
|
|
- $xmlFile,
|
|
|
- $filename,
|
|
|
- 'private'
|
|
|
- );
|
|
|
-
|
|
|
- if ($uploaded) {
|
|
|
- $storedFiles[] = [
|
|
|
- 'original_name' => $xmlFile->getClientOriginalName(),
|
|
|
- 's3_path' => $s3Path,
|
|
|
- 'local_path' => $xmlFile->getRealPath(),
|
|
|
- 'index' => $index
|
|
|
- ];
|
|
|
-
|
|
|
- Log::info("XML file stored for batch processing", [
|
|
|
- 'batch_id' => $batchId,
|
|
|
- 'original_name' => $xmlFile->getClientOriginalName(),
|
|
|
- 's3_path' => $s3Path
|
|
|
- ]);
|
|
|
- }
|
|
|
+ $basePath = $type === '*' ? "{$clientName}/records" : "{$clientName}/records/{$type}";
|
|
|
|
|
|
- } catch (Exception $e) {
|
|
|
- Log::error("Error storing XML file for batch", [
|
|
|
- 'batch_id' => $batchId,
|
|
|
- 'file_name' => $xmlFile->getClientOriginalName(),
|
|
|
- 'error' => $e->getMessage()
|
|
|
- ]);
|
|
|
- }
|
|
|
- }
|
|
|
+ $files = Storage::disk('s3')->allFiles($basePath);
|
|
|
+ Log::info("Found " . count($files) . " files for client '{$clientName}' and type '{$type}'");
|
|
|
|
|
|
- return $storedFiles;
|
|
|
+ return $files;
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error("Error getting client files: " . $e->getMessage());
|
|
|
+ return [];
|
|
|
+ }
|
|
|
}
|
|
|
}
|