morphMany(Attachment::class, 'attachable'); } /** * Get receipts only. */ public function receipts() { return $this->morphMany(Attachment::class, 'attachable') ->where('category', 'receipt'); } /** * Get documents only. */ public function documents() { return $this->morphMany(Attachment::class, 'attachable') ->where('category', 'document'); } /** * Upload and attach a file. */ public function attachFile( UploadedFile $file, string $category = 'receipt', ?string $description = null, ?string $folder = null ): Attachment { $folder = $folder ?? $this->getAttachmentFolder(); $filePath = $file->store($folder, 'public'); return $this->attachments()->create([ 'tenant_id' => session('tenant_id') ?? $this->tenant_id, 'user_id' => Auth::id(), 'file_name' => $file->getClientOriginalName(), 'file_path' => $filePath, 'mime_type' => $file->getMimeType(), 'file_size' => $file->getSize(), 'category' => $category, 'description' => $description, ]); } /** * Upload multiple files at once. */ public function attachFiles(array $files, string $category = 'receipt', ?string $folder = null): array { $attachments = []; foreach ($files as $file) { if ($file instanceof UploadedFile && $file->isValid()) { $attachments[] = $this->attachFile($file, $category, null, $folder); } } return $attachments; } /** * Detach (soft delete) an attachment by ID. */ public function detachFile(int $attachmentId): bool { $attachment = $this->attachments()->find($attachmentId); if ($attachment) { $attachment->delete(); return true; } return false; } /** * Migrate legacy receipt_path to the attachment system. */ public function migrateLegacyReceipt(): ?Attachment { $receiptPath = $this->getOriginal('receipt_path') ?? $this->receipt_path ?? null; if (!$receiptPath) { return null; } // Check if already migrated $exists = $this->attachments() ->where('file_path', $receiptPath) ->exists(); if ($exists) { return null; } if (!Storage::disk('public')->exists($receiptPath)) { return null; } return $this->attachments()->create([ 'tenant_id' => $this->tenant_id, 'user_id' => null, 'file_name' => basename($receiptPath), 'file_path' => $receiptPath, 'mime_type' => Storage::disk('public')->mimeType($receiptPath), 'file_size' => Storage::disk('public')->size($receiptPath), 'category' => 'receipt', 'description' => null, ]); } /** * Get the default storage folder for this model's attachments. */ protected function getAttachmentFolder(): string { $className = strtolower(class_basename($this)); return "attachments/{$className}"; } }