Files
baodev0andClaude Sonnet 5.5 b44a88bb52 feat(service): huỷ dịch vụ, SSL, đổi mật khẩu hosting, lịch sử thanh toán theo hình thức
- Lịch sử thanh toán hoá đơn trả thêm `method` (tự động gia hạn / số dư / admin / chuyển khoản / hoàn tiền), bỏ đuôi `(method)` khỏi ghi chú
- `DnsServer::panelUrl()` = https://<domain>/ dùng cho email cấp hosting và nút Plesk của khách
- Order item trả thêm `uuid`/`type` của dịch vụ để FE dẫn tới trang quản lý
- Yêu cầu huỷ dịch vụ, chứng chỉ SSL, lịch sử gia hạn, đổi mật khẩu hosting cho khách

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 16:40:24 +07:00

87 lines
3.5 KiB
PHP

<?php
namespace App\Http\Controllers\Api\Admin\Service\Concerns;
use App\Models\Invoice;
use App\Models\InvoiceAttachment;
use App\Models\Service;
use App\Models\ServiceAttachment;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
trait HandlesServiceAttachments
{
public function listAttachments(int $id): JsonResponse
{
$service = Service::with('orderItem')->findOrFail($id);
return response()->json([
'error' => false,
'data' => [
'attachments' => ServiceAttachment::where('service_id', $id)->latest()->get(),
'invoices' => $this->linkedInvoices(collect([$service])),
],
]);
}
public function uploadAttachment(Request $request, int $id): JsonResponse
{
Service::findOrFail($id);
$audience = $request->validate([
'file' => ServiceAttachment::UPLOAD_RULE,
'audience' => 'nullable|in:' . ServiceAttachment::AUDIENCE_ADMIN . ',' . ServiceAttachment::AUDIENCE_CUSTOMER,
])['audience'] ?? ServiceAttachment::AUDIENCE_ADMIN;
$file = $request->file('file');
$attachment = ServiceAttachment::create([
'service_id' => $id,
'audience' => $audience,
'filename' => $file->getClientOriginalName(),
'path' => $file->store("service-attachments/{$id}", 'public'),
'disk' => 'public',
'mime_type' => $file->getMimeType(),
'size' => $file->getSize(),
'uploaded_by' => $request->user()?->username ?? $request->user()?->name ?? 'admin',
]);
return response()->json(['error' => false, 'message' => 'File uploaded successfully', 'data' => $attachment], 201);
}
public function deleteAttachment(int $id, int $attachmentId): JsonResponse
{
$attachment = ServiceAttachment::where('service_id', $id)->findOrFail($attachmentId);
Storage::disk($attachment->disk)->delete($attachment->path);
$attachment->delete();
return response()->json(['error' => false, 'message' => 'Attachment deleted']);
}
public function copyAttachmentToInvoice(Request $request, int $id, int $attachmentId): JsonResponse
{
$service = Service::with('orderItem')->findOrFail($id);
$attachment = ServiceAttachment::where('service_id', $id)->findOrFail($attachmentId);
$invoiceId = (int) $request->validate(['invoice_id' => 'required|integer'])['invoice_id'];
$invoice = Invoice::ofServices([$service])->findOrFail($invoiceId);
$path = "invoice-attachments/{$invoice->id}/" . basename($attachment->path);
if (!Storage::disk('public')->exists($path)) {
Storage::disk('public')->put($path, Storage::disk($attachment->disk)->readStream($attachment->path));
}
$copy = InvoiceAttachment::firstOrCreate(
['invoice_id' => $invoice->id, 'path' => $path],
[
'type' => InvoiceAttachment::TYPE_DEPLOYMENT,
'filename' => $attachment->filename,
'disk' => 'public',
'mime_type' => $attachment->mime_type,
'size' => $attachment->size,
'uploaded_by' => $request->user()?->username ?? $request->user()?->name ?? 'admin',
],
);
return response()->json(['error' => false, 'message' => 'Attachment copied to invoice', 'data' => $copy], 201);
}
}