Add stubs for various Laravel components and scaffolding

- Created stubs for JavaScript and SASS assets.
- Added stubs for channel, command, component, controller, event, factory, job, listener, mail, middleware, migration, model, notification, observer, policy, request, resource, route provider, seeder, and validation rule.
- Implemented scaffolding for configuration, service provider, and Vite integration.
- Included example views for module structure.
- Introduced a module loader for Vite to handle asset paths dynamically.
This commit is contained in:
baodev0
2026-05-29 09:28:08 +07:00
parent fccb0c353a
commit 6bb4a47bfb
89 changed files with 3355 additions and 4560 deletions
@@ -124,6 +124,8 @@ class InvoiceTestController extends Controller
'companyAddress' => $settingValue->address ?? '',
'companyPhone' => $settingValue->phone ?? '',
'companyEmail' => $settingValue->email ?? '',
])->stream($invoice->invoice_number . '.pdf');
])
->setPaper('a4', 'portrait')
->stream($invoice->invoice_number . '.pdf');
}
}
}
@@ -11,6 +11,7 @@ use App\Support\Cache\CacheKey;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
class SettingController extends Controller
@@ -99,4 +100,19 @@ class SettingController extends Controller
'message' => 'Cập nhật cài đặt thành công.',
]);
}
public function uploadMedia(Request $request): JsonResponse
{
$request->validate([
'file' => 'required|file|mimes:jpg,jpeg,png,webp,svg|max:5120',
]);
$path = $request->file('file')->store('system-config', 'public');
return response()->json([
'error' => false,
'url' => Storage::disk('public')->url($path),
'path' => $path,
]);
}
}
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\Admin\Transaction;
use App\Enums\TransactionStatus;
use App\Http\Controllers\Controller;
use App\Mail\Customer\ManualDepositStatus;
use App\Models\Notification;
use App\Models\Log;
use App\Models\Transaction;
@@ -11,6 +12,7 @@ use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log as FacadesLog;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
class AdminTransactionController extends Controller
@@ -126,6 +128,28 @@ class AdminTransactionController extends Controller
]);
DB::commit();
try {
Mail::to($customer->email)->send(new ManualDepositStatus([
'customer_id' => $customer->id,
'customer_email' => $customer->email,
'customer_name' => $customer->fullname,
'transaction_code' => $transaction->public_code,
'amount' => $transaction->amount,
'status_label' => 'Đã duyệt',
'status_color' => '#15803d',
'status_bg' => '#f0fdf4',
'subject' => 'Yêu cầu nạp tiền đã được duyệt - ' . $transaction->public_code,
'status_message' => 'Yêu cầu nạp tiền của bạn đã được duyệt. Số tiền đã được cộng vào tài khoản và có thể sử dụng ngay.',
'deposit_date' => $transaction->created_at?->format('d/m/Y H:i:s') ?? now()->format('d/m/Y H:i:s'),
'processed_date' => now()->format('d/m/Y H:i:s'),
]));
} catch (\Throwable $e) {
FacadesLog::error('Không thể gửi email duyệt nạp tiền thủ công: ' . $e->getMessage(), [
'transaction_code' => $transaction->public_code,
'customer_email' => $customer->email,
]);
}
} catch (\Exception $e) {
DB::rollBack();
FacadesLog::error('AdminTransactionController::approve error: ' . $e->getMessage());
@@ -172,6 +196,28 @@ class AdminTransactionController extends Controller
]);
DB::commit();
try {
Mail::to($customer->email)->send(new ManualDepositStatus([
'customer_id' => $customer->id,
'customer_email' => $customer->email,
'customer_name' => $customer->fullname,
'transaction_code' => $transaction->public_code,
'amount' => $transaction->amount,
'status_label' => 'Không phù hợp',
'status_color' => '#b91c1c',
'status_bg' => '#fef2f2',
'subject' => 'Yêu cầu nạp tiền không phù hợp - ' . $transaction->public_code,
'status_message' => 'Yêu cầu nạp tiền của bạn chưa phù hợp để được duyệt. Vui lòng kiểm tra lại chứng từ hoặc liên hệ bộ phận hỗ trợ để được hướng dẫn thêm.',
'deposit_date' => $transaction->created_at?->format('d/m/Y H:i:s') ?? now()->format('d/m/Y H:i:s'),
'processed_date' => now()->format('d/m/Y H:i:s'),
]));
} catch (\Throwable $e) {
FacadesLog::error('Không thể gửi email từ chối nạp tiền thủ công: ' . $e->getMessage(), [
'transaction_code' => $transaction->public_code,
'customer_email' => $customer->email,
]);
}
} catch (\Exception $e) {
DB::rollBack();
FacadesLog::error('AdminTransactionController::reject error: ' . $e->getMessage());
@@ -5,7 +5,6 @@ namespace App\Http\Controllers\Api\Service;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Api\Service\Concerns\ResolvesVm;
use App\Http\Services\vCenter;
use App\Models\ModuleVmware;
use App\Services\ActivityLogger;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -19,20 +18,18 @@ class VmRebuildController extends Controller
public function options(Request $request): JsonResponse
{
$productId = (int) $request->query('product');
$productId = $request->query('product');
if (!$productId) {
return response()->json(['error' => true, 'message' => 'Thiếu product id'], 422);
}
$vm = ModuleVmware::with(['order.orderItems.product.osVersions.osGroup'])
->find($productId);
if (!$vm) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy VM'], 404);
}
if ((int) $vm->customer_id !== (int) $request->user()->id) {
return response()->json(['error' => true, 'message' => 'Bạn không có quyền thao tác VM này'], 403);
$vm = $this->resolveVmById($productId, $request);
if ($vm instanceof JsonResponse) {
return $vm;
}
$vm->loadMissing(['order.orderItems.product.osVersions.osGroup']);
$product = optional($vm->order?->orderItems?->first())->product;
if (!$product) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy product của dịch vụ hiện tại'], 404);
@@ -63,19 +60,17 @@ class VmRebuildController extends Controller
{
$customer = $request->user();
$payload = $request->validate([
'product' => ['required', 'integer'],
'product' => ['required'],
'os_id' => ['required', 'integer', 'exists:os,id'],
]);
$vm = ModuleVmware::with(['order.orderItems.product', 'vCenter', 'ipAddress.host.datacenter.vCenter'])
->find($payload['product']);
if (!$vm) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy VM'], 404);
}
if ((int) $vm->customer_id !== (int) $customer->id) {
return response()->json(['error' => true, 'message' => 'Bạn không có quyền thao tác VM này'], 403);
$vm = $this->resolveVmById($payload['product'], $request);
if ($vm instanceof JsonResponse) {
return $vm;
}
$vm->loadMissing(['order.orderItems.product', 'vCenter', 'ipAddress.host.datacenter.vCenter']);
$product = optional($vm->order?->orderItems?->first())->product;
if (!$product) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy product của dịch vụ hiện tại'], 404);
@@ -123,20 +118,18 @@ class VmRebuildController extends Controller
public function addonOptions(Request $request): JsonResponse
{
$productId = (int) $request->query('product');
$productId = $request->query('product');
if (!$productId) {
return response()->json(['error' => true, 'message' => 'Thiếu product id'], 422);
}
$vm = ModuleVmware::with(['order.orderItems.product.addons'])
->find($productId);
if (!$vm) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy VM'], 404);
}
if ((int) $vm->customer_id !== (int) $request->user()->id) {
return response()->json(['error' => true, 'message' => 'Bạn không có quyền thao tác VM này'], 403);
$vm = $this->resolveVmById($productId, $request);
if ($vm instanceof JsonResponse) {
return $vm;
}
$vm->loadMissing(['order.orderItems.product.addons']);
$product = optional($vm->order?->orderItems?->first())->product;
if (!$product) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy product của dịch vụ hiện tại'], 404);
@@ -145,18 +138,23 @@ class VmRebuildController extends Controller
$addons = $product->addons()->where('status', true)->get()->map(function ($addon) {
return [
'id' => $addon->id,
'type' => $addon->type instanceof \BackedEnum ? $addon->type->value : (string) $addon->type,
'code' => strtolower((string) $addon->code),
'name' => $addon->name,
'price' => (float) $addon->price,
'min' => (int) ($addon->min ?? 0),
'max' => (int) ($addon->max ?? 0),
'step' => (int) ($addon->step ?? 1),
'prefix' => $addon->prefix,
'input_type' => $addon->input_type ?? 'number',
'sort_order' => (int) ($addon->sort_order ?? 0),
];
});
return response()->json([
'error' => false,
'data' => [
'data' => $addons->values(),
'meta' => [
'product_id' => $product->id,
'product_name' => $product->name,
'base_specs' => [
@@ -166,7 +164,6 @@ class VmRebuildController extends Controller
'bandwidth' => (int) ($product->bandwidth ?? 0),
'gpu' => $product->gpu,
],
'addons' => $addons->values(),
],
]);
}
@@ -175,23 +172,21 @@ class VmRebuildController extends Controller
{
$customer = $request->user();
$payload = $request->validate([
'product' => ['required', 'integer'],
'product' => ['required'],
'cpu' => ['nullable', 'integer', 'min:0'],
'ram' => ['nullable', 'integer', 'min:0'],
'disk' => ['nullable', 'integer', 'min:0'],
'bandwidth' => ['nullable', 'integer', 'min:0'],
'gpu' => ['nullable', 'string', 'max:255'],
'gpu' => ['nullable'],
]);
$vm = ModuleVmware::with(['vCenter', 'ipAddress.host.datacenter.vCenter'])
->find($payload['product']);
if (!$vm) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy VM'], 404);
}
if ((int) $vm->customer_id !== (int) $customer->id) {
return response()->json(['error' => true, 'message' => 'Bạn không có quyền thao tác VM này'], 403);
$vm = $this->resolveVmById($payload['product'], $request);
if ($vm instanceof JsonResponse) {
return $vm;
}
$vm->loadMissing(['vCenter', 'ipAddress.host.datacenter.vCenter']);
$vcenter = $vm->vCenter ?? $vm->ipAddress?->host?->datacenter?->vCenter;
if (!$vcenter || !$serviceVCenter->loginServer($vcenter)) {
return response()->json(['error' => true, 'message' => 'Không thể đăng nhập vCenter'], 400);
@@ -235,4 +230,4 @@ class VmRebuildController extends Controller
],
]);
}
}
}
@@ -6,12 +6,16 @@ use App\Enums\TransactionStatus;
use App\Http\Controllers\Controller;
use App\Http\Requests\DepositRequest;
use App\Http\Services\Telegram;
use App\Mail\Admin\ManualDepositPendingNotification;
use App\Mail\Customer\ManualDepositStatus;
use App\Models\Log;
use App\Models\Setting;
use App\Models\Transaction;
use App\Services\ActivityLogger;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
@@ -196,6 +200,7 @@ class ManualDepositController extends Controller
try {
(new Telegram())->SendManualDepositPendingApproval($user, $transaction);
$this->sendManualDepositAdminEmail($user, $transaction, $request);
} catch (\Exception) {
// Không để lỗi Telegram ảnh hưởng response
}
@@ -214,6 +219,27 @@ class ManualDepositController extends Controller
newValues: ['public_code' => $transaction->public_code, 'amount' => $transaction->amount, 'has_proof' => true],
);
try {
Mail::to($user->email)->send(new ManualDepositStatus([
'customer_id' => $user->id,
'customer_email' => $user->email,
'customer_name' => $user->fullname,
'transaction_code' => $transaction->public_code,
'amount' => $transaction->amount,
'status_label' => 'Đang chờ duyệt',
'status_color' => '#f16723',
'status_bg' => '#fff7ed',
'subject' => 'Yêu cầu nạp tiền đang chờ duyệt - ' . $transaction->public_code,
'status_message' => 'Yêu cầu nạp tiền của bạn đã được ghi nhận. Chúng tôi sẽ kiểm tra chứng từ và phản hồi kết quả trong thời gian sớm nhất.',
'deposit_date' => $transaction->created_at?->format('d/m/Y H:i:s') ?? now()->format('d/m/Y H:i:s'),
]));
} catch (\Throwable $e) {
\Log::error('Không thể gửi email trạng thái nạp tiền thủ công: ' . $e->getMessage(), [
'transaction_code' => $transaction->public_code,
'customer_email' => $user->email,
]);
}
return response()->json([
'error' => false,
'message' => 'Yêu cầu nạp tiền đã được ghi nhận, vui lòng chờ admin xác nhận',
@@ -239,4 +265,37 @@ class ManualDepositController extends Controller
'path' => $path,
]);
}
private function sendManualDepositAdminEmail($customer, Transaction $transaction, Request $request): void
{
try {
$setting = Setting::where('key', 'setting')->first();
$settingValue = $setting ? json_decode((string) $setting->value, true) : [];
$adminEmail = $settingValue['admin_email'] ?? null;
if (empty($adminEmail)) {
\Log::warning('Manual deposit admin email not sent: admin_email not configured.', [
'transaction_code' => $transaction->public_code,
]);
return;
}
Mail::to($adminEmail)->send(new ManualDepositPendingNotification([
'company_name' => $settingValue['company'] ?? config('app.name', 'Portal Admin'),
'customer_name' => $customer->fullname ?: trim(($customer->firstname ?? '') . ' ' . ($customer->lastname ?? '')),
'customer_email' => $customer->email,
'customer_phone' => $customer->phone ?? null,
'client_ip' => $request->ip(),
'transaction_code' => $transaction->public_code,
'amount' => $transaction->amount,
'proof_image_url' => $transaction->proof_image ? Storage::disk('public')->url($transaction->proof_image) : null,
'created_at' => now()->format('d/m/Y H:i:s'),
]));
} catch (\Throwable $e) {
\Log::error('Không thể gửi email admin thông báo nạp tiền chờ duyệt: ' . $e->getMessage(), [
'transaction_code' => $transaction->public_code,
'customer_email' => $customer->email ?? null,
]);
}
}
}