Files
portal-api/app/Http/Controllers/Api/Admin/WebDesign/WebDesignQuoteController.php
T
2026-07-06 08:00:23 +07:00

234 lines
8.3 KiB
PHP

<?php
namespace App\Http\Controllers\Api\Admin\WebDesign;
use App\Enums\WebDesign\WebDesignPlatform;
use App\Enums\WebDesign\WebDesignQuoteStatus;
use App\Http\Controllers\Controller;
use App\Models\WebDesignQuote;
use App\Services\WebDesignQuotePdfService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Validation\Rule;
class WebDesignQuoteController extends Controller
{
public function index(Request $request): JsonResponse
{
$perPage = min((int) $request->get('per_page', 15), 100);
$query = WebDesignQuote::with('creator:id,name')
->when($request->get('filter'), fn($q, $f) =>
$q->where(fn($sub) => $sub
->where('quote_number', 'like', "%{$f}%")
->orWhere('customer_name', 'like', "%{$f}%")
->orWhere('website_name', 'like', "%{$f}%")
->orWhere('customer_email', 'like', "%{$f}%"))
)->when($request->filled('status'), fn($q) =>
$q->where('status', $request->get('status'))
)->when($request->filled('platform'), fn($q) =>
$q->where('platform', $request->get('platform'))
)->orderByDesc('id');
$paginator = $query->paginate($perPage);
return response()->json([
'error' => false,
'data' => [
'items' => $paginator->items(),
'total' => $paginator->total(),
'per_page' => $paginator->perPage(),
'current_page' => $paginator->currentPage(),
'last_page' => $paginator->lastPage(),
],
]);
}
public function show(int $id): JsonResponse
{
$quote = WebDesignQuote::with('creator:id,name')->find($id);
if (!$quote) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy báo giá.'], 404);
}
return response()->json([
'error' => false,
'data' => $quote,
]);
}
public function store(Request $request): JsonResponse
{
$validated = $this->validateQuote($request);
$validated['quote_number'] = WebDesignQuote::generateQuoteNumber();
$validated['created_by'] = $request->user()?->id;
$validated = $this->applyTotals($validated);
$quote = WebDesignQuote::create($validated);
return response()->json([
'error' => false,
'message' => 'Tạo báo giá thành công.',
'data' => $quote,
], 201);
}
public function update(Request $request, int $id): JsonResponse
{
$quote = WebDesignQuote::find($id);
if (!$quote) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy báo giá.'], 404);
}
$validated = $this->validateQuote($request);
$validated = $this->applyTotals($validated);
$quote->update($validated);
return response()->json([
'error' => false,
'message' => 'Cập nhật báo giá thành công.',
'data' => $quote->fresh('creator:id,name'),
]);
}
public function updateStatus(Request $request, int $id): JsonResponse
{
$quote = WebDesignQuote::find($id);
if (!$quote) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy báo giá.'], 404);
}
$validated = $request->validate([
'status' => ['required', 'string', Rule::in(WebDesignQuoteStatus::values())],
]);
$quote->update($validated);
return response()->json([
'error' => false,
'message' => 'Cập nhật trạng thái báo giá thành công.',
'data' => $quote->fresh(),
]);
}
public function destroy(int $id): JsonResponse
{
$quote = WebDesignQuote::find($id);
if (!$quote) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy báo giá.'], 404);
}
$quote->delete();
return response()->json([
'error' => false,
'message' => 'Xoá báo giá thành công.',
]);
}
public function downloadPdf(int $id, WebDesignQuotePdfService $pdfService): Response|JsonResponse
{
$quote = WebDesignQuote::find($id);
if (!$quote) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy báo giá.'], 404);
}
$fileName = $pdfService->generateFileName($quote);
return response($pdfService->generate($quote), 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="' . $fileName . '"',
]);
}
/**
* Danh sách cấu hình nền tảng cho form báo giá (label, frontend, custom admin, phí bản quyền).
*/
public function platformOptions(): JsonResponse
{
$platforms = array_map(fn(WebDesignPlatform $p) => [
'value' => $p->value,
'label' => $p->label(),
'frontend_options' => $p->frontendOptions(),
'default_frontend' => $p->defaultFrontend(),
'supports_custom_admin' => $p->supportsCustomAdmin(),
'annual_license_fee_usd' => $p->annualLicenseFeeUsd(),
], WebDesignPlatform::cases());
return response()->json([
'error' => false,
'data' => $platforms,
]);
}
private function validateQuote(Request $request): array
{
$platform = WebDesignPlatform::tryFrom((string) $request->get('platform'));
$validated = $request->validate([
'status' => ['nullable', 'string', Rule::in(WebDesignQuoteStatus::values())],
'customer_name' => 'required|string|max:150',
'customer_email' => 'nullable|email|max:150',
'customer_phone' => 'nullable|string|max:30',
'customer_company' => 'nullable|string|max:200',
'website_name' => 'required|string|max:200',
'website_category' => 'required|string|max:100',
'description' => 'nullable|string|max:5000',
'requirements' => 'nullable|string|max:5000',
'reference_websites' => 'nullable|array|max:20',
'reference_websites.*' => 'string|max:500',
'platform' => ['required', 'string', Rule::in(WebDesignPlatform::values())],
'frontend_framework' => [
Rule::requiredIf(fn() => $platform && count($platform->frontendOptions()) > 0),
'nullable',
'string',
Rule::in($platform?->frontendOptions() ?? []),
],
'custom_admin' => 'boolean',
'items' => 'required|array|min:1',
'items.*.name' => 'required|string|max:300',
'items.*.description' => 'nullable|string|max:1000',
'items.*.quantity' => 'required|numeric|min:0',
'items.*.unit_price' => 'required|numeric|min:0',
'discount' => 'nullable|numeric|min:0',
'vat_percent' => 'nullable|numeric|min:0|max:100',
'currency' => ['nullable', 'string', Rule::in(['VND', 'USD'])],
'valid_until' => 'nullable|date',
'note' => 'nullable|string|max:2000',
]);
if ($platform && !$platform->supportsCustomAdmin()) {
$validated['custom_admin'] = false;
}
if ($platform && count($platform->frontendOptions()) === 0) {
$validated['frontend_framework'] = null;
}
return $validated;
}
private function applyTotals(array $data): array
{
$subtotal = collect($data['items'])
->sum(fn($item) => (float) $item['quantity'] * (float) $item['unit_price']);
$discount = (float) ($data['discount'] ?? 0);
$vatPercent = (float) ($data['vat_percent'] ?? 0);
$taxable = max($subtotal - $discount, 0);
$data['subtotal'] = round($subtotal, 2);
$data['total'] = round($taxable + $taxable * $vatPercent / 100, 2);
return $data;
}
}