Feat: Заявки на курсы (Course Requests) - базовая структура
✅ Миграции: course_requests, course_request_items ✅ Models: CourseRequest, CourseRequestItem ✅ CourseRequestController (CRUD + approve/reject) ✅ CourseRequestPolicy ✅ Маршруты и регистрация Policy ✅ index.blade.php Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
21a836ef4d
commit
87f20ef702
|
|
@ -0,0 +1,282 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\CourseRequest;
|
||||
use App\Models\CourseRequestItem;
|
||||
use App\Models\Course;
|
||||
use App\Models\CourseAssignment;
|
||||
use App\Models\User;
|
||||
use App\Models\Group;
|
||||
use App\Models\Organization;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class CourseRequestController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
Gate::authorize('viewAny', CourseRequest::class);
|
||||
|
||||
$query = CourseRequest::with(['organization', 'requestedBy', 'approvedBy', 'items.course']);
|
||||
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
if ($request->filled('organization_id')) {
|
||||
$query->where('organization_id', $request->organization_id);
|
||||
}
|
||||
|
||||
$requests = $query->orderBy('created_at', 'desc')->paginate(20);
|
||||
$organizations = Organization::pluck('name', 'id');
|
||||
|
||||
return view('admin.course-requests.index', compact('requests', 'organizations'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
Gate::authorize('create', CourseRequest::class);
|
||||
|
||||
$courses = Course::pluck('title', 'id');
|
||||
$users = User::pluck('name', 'id');
|
||||
$groups = Group::pluck('name', 'id');
|
||||
$organizations = Organization::pluck('name', 'id');
|
||||
|
||||
return view('admin.course-requests.create', compact('courses', 'users', 'groups', 'organizations'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
Gate::authorize('create', CourseRequest::class);
|
||||
|
||||
$validated = $request->validate([
|
||||
'organization_id' => 'required|exists:organizations,id',
|
||||
'status' => 'nullable|in:pending,approved,rejected',
|
||||
'note' => 'nullable|string',
|
||||
'items' => 'required|array',
|
||||
'items.*.course_id' => 'required|exists:courses,id',
|
||||
'items.*.user_id' => 'nullable|exists:users,id',
|
||||
'items.*.group_id' => 'nullable|exists:groups,id',
|
||||
'items.*.start_date' => 'required|date',
|
||||
'items.*.end_date' => 'nullable|date|after:items.*.start_date',
|
||||
]);
|
||||
|
||||
// Определяем статус
|
||||
$user = auth()->user();
|
||||
if ($validated['status'] === 'approved' || $user->hasRole(['Administrator', 'Manager', 'Curator'])) {
|
||||
$status = 'approved';
|
||||
} else {
|
||||
$status = 'pending';
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
// Создаём заявку
|
||||
$courseRequest = CourseRequest::create([
|
||||
'organization_id' => $validated['organization_id'],
|
||||
'requested_by_user_id' => $user->id,
|
||||
'status' => $status,
|
||||
'note' => $validated['note'] ?? null,
|
||||
'approved_by' => $status === 'approved' ? $user->id : null,
|
||||
'approved_at' => $status === 'approved' ? now() : null,
|
||||
]);
|
||||
|
||||
// Создаём элементы заявки
|
||||
foreach ($validated['items'] as $itemData) {
|
||||
CourseRequestItem::create([
|
||||
'course_request_id' => $courseRequest->id,
|
||||
'course_id' => $itemData['course_id'],
|
||||
'user_id' => $itemData['user_id'] ?? null,
|
||||
'group_id' => $itemData['group_id'] ?? null,
|
||||
'start_date' => $itemData['start_date'],
|
||||
'end_date' => $itemData['end_date'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
// Если заявка одобрена - сразу создаём назначения
|
||||
if ($status === 'approved') {
|
||||
$this->createAssignments($courseRequest);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
return redirect()->route('admin.course-requests.index')
|
||||
->with('success', $status === 'approved' ? 'Заявка создана и одобрена. Назначения созданы.' : 'Заявка создана и ожидает подтверждения.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return back()->withErrors(['error' => 'Ошибка при создании заявки: ' . $e->getMessage()])->withInput();
|
||||
}
|
||||
}
|
||||
|
||||
public function show(CourseRequest $courseRequest)
|
||||
{
|
||||
Gate::authorize('view', $courseRequest);
|
||||
|
||||
$courseRequest->load(['organization', 'requestedBy', 'approvedBy', 'items.course', 'items.user', 'items.group']);
|
||||
|
||||
return view('admin.course-requests.show', compact('courseRequest'));
|
||||
}
|
||||
|
||||
public function edit(CourseRequest $courseRequest)
|
||||
{
|
||||
Gate::authorize('update', $courseRequest);
|
||||
|
||||
if ($courseRequest->isApproved() || $courseRequest->isRejected()) {
|
||||
return back()->with('error', 'Нельзя редактировать ' . ($courseRequest->isApproved() ? 'одобренную' : 'отклонённую') . ' заявку.');
|
||||
}
|
||||
|
||||
$courses = Course::pluck('title', 'id');
|
||||
$users = User::pluck('name', 'id');
|
||||
$groups = Group::pluck('name', 'id');
|
||||
|
||||
return view('admin.course-requests.edit', compact('courseRequest', 'courses', 'users', 'groups'));
|
||||
}
|
||||
|
||||
public function update(Request $request, CourseRequest $courseRequest)
|
||||
{
|
||||
Gate::authorize('update', $courseRequest);
|
||||
|
||||
if ($courseRequest->isApproved() || $courseRequest->isRejected()) {
|
||||
return back()->with('error', 'Нельзя редактировать ' . ($courseRequest->isApproved() ? 'одобренную' : 'отклонённую') . ' заявку.');
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'note' => 'nullable|string',
|
||||
'items' => 'required|array',
|
||||
'items.*.id' => 'nullable|exists:course_request_items,id',
|
||||
'items.*.course_id' => 'required|exists:courses,id',
|
||||
'items.*.user_id' => 'nullable|exists:users,id',
|
||||
'items.*.group_id' => 'nullable|exists:groups,id',
|
||||
'items.*.start_date' => 'required|date',
|
||||
'items.*.end_date' => 'nullable|date|after:items.*.start_date',
|
||||
]);
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
$courseRequest->update(['note' => $validated['note'] ?? null]);
|
||||
|
||||
// Обновляем элементы
|
||||
foreach ($validated['items'] as $itemData) {
|
||||
if (!empty($itemData['id'])) {
|
||||
// Обновляем существующий
|
||||
$item = CourseRequestItem::find($itemData['id']);
|
||||
$item->update([
|
||||
'course_id' => $itemData['course_id'],
|
||||
'user_id' => $itemData['user_id'] ?? null,
|
||||
'group_id' => $itemData['group_id'] ?? null,
|
||||
'start_date' => $itemData['start_date'],
|
||||
'end_date' => $itemData['end_date'] ?? null,
|
||||
]);
|
||||
} else {
|
||||
// Создаём новый
|
||||
CourseRequestItem::create([
|
||||
'course_request_id' => $courseRequest->id,
|
||||
'course_id' => $itemData['course_id'],
|
||||
'user_id' => $itemData['user_id'] ?? null,
|
||||
'group_id' => $itemData['group_id'] ?? null,
|
||||
'start_date' => $itemData['start_date'],
|
||||
'end_date' => $itemData['end_date'] ?? null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
return redirect()->route('admin.course-requests.show', $courseRequest)
|
||||
->with('success', 'Заявка обновлена.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return back()->withErrors(['error' => 'Ошибка при обновлении: ' . $e->getMessage()])->withInput();
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(CourseRequest $courseRequest)
|
||||
{
|
||||
Gate::authorize('delete', $courseRequest);
|
||||
|
||||
if ($courseRequest->isApproved()) {
|
||||
return back()->with('error', 'Нельзя удалить одобренную заявку.');
|
||||
}
|
||||
|
||||
$courseRequest->delete();
|
||||
|
||||
return redirect()->route('admin.course-requests.index')
|
||||
->with('success', 'Заявка удалена.');
|
||||
}
|
||||
|
||||
public function approve(CourseRequest $courseRequest, Request $request)
|
||||
{
|
||||
Gate::authorize('approve', $courseRequest);
|
||||
|
||||
if (!$courseRequest->isPending()) {
|
||||
return back()->with('error', 'Можно одобрить только заявку в статусе ожидания.');
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
$courseRequest->approve(auth()->user());
|
||||
$this->createAssignments($courseRequest);
|
||||
|
||||
DB::commit();
|
||||
|
||||
return back()->with('success', 'Заявка одобрена. Назначения созданы.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return back()->withErrors(['error' => 'Ошибка при одобрении: ' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
public function reject(CourseRequest $courseRequest, Request $request)
|
||||
{
|
||||
Gate::authorize('reject', $courseRequest);
|
||||
|
||||
if (!$courseRequest->isPending()) {
|
||||
return back()->with('error', 'Можно отклонить только заявку в статусе ожидания.');
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'reject_reason' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$courseRequest->reject(auth()->user());
|
||||
|
||||
return back()->with('success', 'Заявка отклонена.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Создаёт назначения из одобренной заявки
|
||||
*/
|
||||
private function createAssignments(CourseRequest $courseRequest): void
|
||||
{
|
||||
foreach ($courseRequest->items as $item) {
|
||||
$type = $item->type;
|
||||
|
||||
CourseAssignment::create([
|
||||
'course_id' => $item->course_id,
|
||||
'organization_id' => $type === 'organization' ? $courseRequest->organization_id : null,
|
||||
'group_id' => $item->group_id,
|
||||
'user_id' => $item->user_id,
|
||||
'type' => $type,
|
||||
'start_date' => $item->start_date,
|
||||
'end_date' => $item->end_date,
|
||||
'note' => $courseRequest->note,
|
||||
'created_by' => $courseRequest->approved_by,
|
||||
'is_active' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,10 +14,10 @@ class CourseRequest extends Model
|
|||
protected $fillable = [
|
||||
'organization_id',
|
||||
'requested_by_user_id',
|
||||
'approved_by_user_id',
|
||||
'status',
|
||||
'comment',
|
||||
'approved_by',
|
||||
'approved_at',
|
||||
'note',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
|
@ -36,11 +36,44 @@ class CourseRequest extends Model
|
|||
|
||||
public function approvedBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'approved_by_user_id');
|
||||
return $this->belongsTo(User::class, 'approved_by');
|
||||
}
|
||||
|
||||
public function items(): HasMany
|
||||
{
|
||||
return $this->hasMany(CourseRequestItem::class);
|
||||
}
|
||||
|
||||
public function isPending(): bool
|
||||
{
|
||||
return $this->status === 'pending';
|
||||
}
|
||||
|
||||
public function isApproved(): bool
|
||||
{
|
||||
return $this->status === 'approved';
|
||||
}
|
||||
|
||||
public function isRejected(): bool
|
||||
{
|
||||
return $this->status === 'rejected';
|
||||
}
|
||||
|
||||
public function approve(User $approvedBy): void
|
||||
{
|
||||
$this->update([
|
||||
'status' => 'approved',
|
||||
'approved_by' => $approvedBy->id,
|
||||
'approved_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function reject(User $approvedBy): void
|
||||
{
|
||||
$this->update([
|
||||
'status' => 'rejected',
|
||||
'approved_by' => $approvedBy->id,
|
||||
'approved_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,20 @@ class CourseRequestItem extends Model
|
|||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'request_id',
|
||||
'course_request_id',
|
||||
'course_id',
|
||||
'user_id',
|
||||
'group_id',
|
||||
'start_date',
|
||||
'end_date',
|
||||
];
|
||||
|
||||
public function request(): BelongsTo
|
||||
protected $casts = [
|
||||
'start_date' => 'date',
|
||||
'end_date' => 'date',
|
||||
];
|
||||
|
||||
public function courseRequest(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(CourseRequest::class);
|
||||
}
|
||||
|
|
@ -30,4 +38,19 @@ class CourseRequestItem extends Model
|
|||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
|
||||
public function getTypeAttribute(): string
|
||||
{
|
||||
if ($this->user_id) {
|
||||
return 'individual';
|
||||
} elseif ($this->group_id) {
|
||||
return 'group';
|
||||
}
|
||||
return 'organization';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\CourseRequest;
|
||||
use App\Models\User;
|
||||
|
||||
class CourseRequestPolicy
|
||||
{
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return $user->hasRole(['Administrator', 'Manager', 'Curator']);
|
||||
}
|
||||
|
||||
public function view(User $user, CourseRequest $courseRequest): bool
|
||||
{
|
||||
return $user->hasRole(['Administrator', 'Manager', 'Curator']);
|
||||
}
|
||||
|
||||
public function create(User $user): bool
|
||||
{
|
||||
return $user->hasRole(['Administrator', 'Manager', 'Curator']);
|
||||
}
|
||||
|
||||
public function update(User $user, CourseRequest $courseRequest): bool
|
||||
{
|
||||
return $user->hasRole(['Administrator', 'Manager', 'Curator']);
|
||||
}
|
||||
|
||||
public function delete(User $user, CourseRequest $courseRequest): bool
|
||||
{
|
||||
return $user->hasRole(['Administrator', 'Manager']);
|
||||
}
|
||||
|
||||
public function approve(User $user, CourseRequest $courseRequest): bool
|
||||
{
|
||||
return $user->hasRole(['Administrator', 'Manager', 'Curator']);
|
||||
}
|
||||
|
||||
public function reject(User $user, CourseRequest $courseRequest): bool
|
||||
{
|
||||
return $user->hasRole(['Administrator', 'Manager', 'Curator']);
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ class AuthServiceProvider extends ServiceProvider
|
|||
CourseCategory::class => CourseCategoryPolicy::class,
|
||||
Course::class => CoursePolicy::class,
|
||||
CourseAssignment::class => CourseAssignmentPolicy::class,
|
||||
CourseRequest::class => CourseRequestPolicy::class,
|
||||
Test::class => TestPolicy::class,
|
||||
Question::class => QuestionPolicy::class,
|
||||
Organization::class => OrganizationPolicy::class,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('course_requests_tables', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('course_requests_tables');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
@extends('layouts.app')
|
||||
@section('title', 'Заявки на курсы')
|
||||
@section('content')
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<nav class="col-md-3 col-lg-2 d-md-block sidebar"><div class="position-sticky pt-3">@include('partials._sidebar')</div></nav>
|
||||
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4 main-content">
|
||||
<div class="d-flex justify-content-between align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">Заявки на курсы</h1>
|
||||
<a href="{{ route('admin.course-requests.create') }}" class="btn btn-primary btn-sm"><i class="bi bi-plus-lg"></i> Создать заявку</a>
|
||||
</div>
|
||||
@if(session('success'))<div class="alert alert-success">{{ session('success') }}</div>@endif
|
||||
@if(session('error'))<div class="alert alert-danger">{{ session('error') }}</div>@endif
|
||||
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<form action="{{ route('admin.course-requests.index') }}" method="GET" class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<select name="status" class="form-select">
|
||||
<option value="">Все статусы</option>
|
||||
<option value="pending" {{ request('status') == 'pending' ? 'selected' : '' }}>Ожидают</option>
|
||||
<option value="approved" {{ request('status') == 'approved' ? 'selected' : '' }}>Одобренные</option>
|
||||
<option value="rejected" {{ request('status') == 'rejected' ? 'selected' : '' }}>Отклонённые</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<select name="organization_id" class="form-select">
|
||||
<option value="">Все организации</option>
|
||||
@foreach($organizations as $id => $name)
|
||||
<option value="{{ $id }}" {{ request('organization_id') == $id ? 'selected' : '' }}>{{ $name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="submit" class="btn btn-primary w-100"><i class="bi bi-search"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Организация</th>
|
||||
<th>Статус</th>
|
||||
<th>Курсов</th>
|
||||
<th>Создана</th>
|
||||
<th>Кем</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($requests as $request)
|
||||
<tr>
|
||||
<td><strong>{{ $request->organization->name }}</strong></td>
|
||||
<td>
|
||||
@if($request->isPending())
|
||||
<span class="badge bg-warning">Ожидает</span>
|
||||
@elseif($request->isApproved())
|
||||
<span class="badge bg-success">Одобрена</span>
|
||||
@else
|
||||
<span class="badge bg-danger">Отклонена</span>
|
||||
@endif
|
||||
</td>
|
||||
<td><span class="badge bg-info">{{ $request->items->count() }}</span></td>
|
||||
<td><small>{{ $request->created_at->format('d.m.Y H:i') }}</small></td>
|
||||
<td>{{ $request->requestedBy->name }}</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="{{ route('admin.course-requests.show', $request) }}" class="btn btn-outline-primary" title="Просмотр"><i class="bi bi-eye"></i></a>
|
||||
@if($request->isPending())
|
||||
<form action="{{ route('admin.course-requests.approve', $request) }}" method="POST" class="d-inline">
|
||||
@csrf
|
||||
<button class="btn btn-outline-success" title="Одобрить" onclick="return confirm('Одобрить заявку?')"><i class="bi bi-check-lg"></i></button>
|
||||
</form>
|
||||
<form action="{{ route('admin.course-requests.reject', $request) }}" method="POST" class="d-inline">
|
||||
@csrf
|
||||
<button class="btn btn-outline-danger" title="Отклонить" onclick="return confirm('Отклонить заявку?')"><i class="bi bi-x-lg"></i></button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted py-5">
|
||||
<i class="bi bi-inbox" style="font-size: 3rem;"></i>
|
||||
<p class="mt-3">Заявок пока нет</p>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ $requests->links() }}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
@ -11,6 +11,7 @@ use App\Http\Controllers\Admin\TestController;
|
|||
use App\Http\Controllers\Admin\QuestionController;
|
||||
use App\Http\Controllers\Admin\CourseAssignmentController;
|
||||
use App\Http\Controllers\Admin\GroupUserController;
|
||||
use App\Http\Controllers\Admin\CourseRequestController;
|
||||
use App\Http\Controllers\Api\OrganizationSearchController;
|
||||
use App\Http\Controllers\Api\GroupSearchController;
|
||||
use App\Http\Controllers\Api\UserSearchController;
|
||||
|
|
@ -53,6 +54,9 @@ Route::middleware('auth')->group(function () {
|
|||
Route::delete('/organizations/{organization}/users/{user}/remove', [OrganizationController::class, 'removeUser'])->name('organizations.users.remove');
|
||||
Route::post('/organizations/{organization}/groups/add', [OrganizationController::class, 'addGroup'])->name('organizations.groups.add');
|
||||
Route::delete('/organizations/{organization}/groups/{group}/remove', [OrganizationController::class, 'removeGroup'])->name('organizations.groups.remove');
|
||||
Route::resource('course-requests', CourseRequestController::class);
|
||||
Route::post('/course-requests/{courseRequest}/approve', [CourseRequestController::class, 'approve'])->name('course-requests.approve');
|
||||
Route::post('/course-requests/{courseRequest}/reject', [CourseRequestController::class, 'reject'])->name('course-requests.reject');
|
||||
Route::resource('users', UserController::class);
|
||||
Route::resource('course-categories', CourseCategoryController::class);
|
||||
Route::resource('courses', CourseController::class);
|
||||
|
|
|
|||
Loading…
Reference in New Issue