urls->forReimbursementStorage($filename); } public function expenseReceiptUrl(?string $filename): ?string { return $this->urls->forReceiptStorage($filename); } public function adminFileUrl(string $filename, string $mode = 'inline'): string { return $this->urls->forReimbursementAdminCheckFile($filename, $mode); } public function storeReceipt(?UploadedFile $file): ?string { if (!$file || !$file->isValid()) { return null; } if (($file->getSize() ?? 0) <= 0) { return null; } $dir = $this->ensureUploadDir(); $extension = $file->getClientOriginalExtension(); $extension = $extension !== '' ? '.' . strtolower($extension) : ''; $filename = 'reimb_' . date('Ymd_His') . '_' . Str::random(8) . $extension; if (!$file->move($dir, $filename)) { throw new RuntimeException('Unable to save uploaded file.'); } return $filename; } public function storeAdminFile(int $batchId, int $adminId, UploadedFile $file, int $userId): array { $dir = $this->ensureUploadDir(); $extension = $file->getClientOriginalExtension(); $extension = $extension !== '' ? '.' . strtolower($extension) : ''; $filename = 'batch_check_' . date('Ymd_His') . '_' . Str::random(8) . $extension; if (!$file->move($dir, $filename)) { throw new RuntimeException('Unable to save uploaded file.'); } $existing = ReimbursementBatchAdminFile::query() ->where('batch_id', $batchId) ->where('admin_id', $adminId) ->first(); if ($existing && !empty($existing->filename)) { $oldPath = $dir . DIRECTORY_SEPARATOR . $existing->filename; if (is_file($oldPath)) { @unlink($oldPath); } } $payload = [ 'batch_id' => $batchId, 'admin_id' => $adminId, 'filename' => $filename, 'original_filename' => $file->getClientOriginalName(), 'uploaded_at' => now(), 'uploaded_by' => $userId ?: null, ]; if ($existing) { $existing->fill($payload); $existing->save(); } else { ReimbursementBatchAdminFile::query()->create($payload); } return [ 'filename' => $filename, 'original_filename' => $file->getClientOriginalName(), 'url' => $this->adminFileUrl($filename, 'inline'), ]; } private function ensureUploadDir(): string { $dir = storage_path('uploads/reimbursements'); if (!is_dir($dir)) { if (!@mkdir($dir, 0755, true) && !is_dir($dir)) { throw new RuntimeException('Upload directory is not available.'); } } if (!is_writable($dir)) { throw new RuntimeException('Upload directory is not writable.'); } return $dir; } }