234 lines
8.4 KiB
PHP
234 lines
8.4 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,firstname,lastname')
|
|
->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,firstname,lastname')->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,firstname,lastname'),
|
|
]);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|