first commit
This commit is contained in:
8
app/Http/Controllers/Controller.php
Normal file
8
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
70
app/Http/Controllers/References/DepartmentController.php
Normal file
70
app/Http/Controllers/References/DepartmentController.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\References;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\References\StoreDepartmentProfileRequest;
|
||||
use App\Http\Requests\References\StoreDepartmentRequest;
|
||||
use App\Models\Department;
|
||||
use App\Models\DepartmentProfile;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class DepartmentController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the departments reference page.
|
||||
*/
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
return Inertia::render('references/departments/Index', [
|
||||
'currentTeam' => $request->user()?->currentTeam
|
||||
? $request->user()?->toUserTeam($request->user()->currentTeam)
|
||||
: null,
|
||||
'departmentProfiles' => DepartmentProfile::query()
|
||||
->withCount('departments')
|
||||
->orderBy('name')
|
||||
->get()
|
||||
->map(fn (DepartmentProfile $departmentProfile) => [
|
||||
'id' => $departmentProfile->id,
|
||||
'name' => $departmentProfile->name,
|
||||
'departmentsCount' => $departmentProfile->departments_count,
|
||||
]),
|
||||
'departments' => Department::query()
|
||||
->with('departmentProfile:id,name')
|
||||
->orderBy('name')
|
||||
->get()
|
||||
->map(fn (Department $department) => [
|
||||
'id' => $department->id,
|
||||
'name' => $department->name,
|
||||
'isActive' => $department->is_active,
|
||||
'departmentProfile' => [
|
||||
'id' => $department->departmentProfile->id,
|
||||
'name' => $department->departmentProfile->name,
|
||||
],
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created department profile.
|
||||
*/
|
||||
public function storeProfile(StoreDepartmentProfileRequest $request): RedirectResponse
|
||||
{
|
||||
DepartmentProfile::create($request->validated());
|
||||
|
||||
return to_route('references.departments.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created department.
|
||||
*/
|
||||
public function store(StoreDepartmentRequest $request): RedirectResponse
|
||||
{
|
||||
Department::create($request->validated());
|
||||
|
||||
return to_route('references.departments.index');
|
||||
}
|
||||
}
|
||||
54
app/Http/Controllers/Reports/AnalysisController.php
Normal file
54
app/Http/Controllers/Reports/AnalysisController.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Reports;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ReportPeriod;
|
||||
use App\Models\Team;
|
||||
use App\Services\Reports\AnalysisReportService;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class AnalysisController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the analysis page.
|
||||
*/
|
||||
public function index(Request $request, Team $current_team, AnalysisReportService $analysisReportService): Response
|
||||
{
|
||||
$periods = ReportPeriod::query()
|
||||
->whereBelongsTo($current_team)
|
||||
->orderByDesc('year')
|
||||
->orderByDesc('month')
|
||||
->get();
|
||||
|
||||
/** @var ReportPeriod|null $selectedPeriod */
|
||||
$selectedPeriod = $periods->firstWhere('id', $request->integer('period', $periods->first()?->id));
|
||||
|
||||
$analysis = $selectedPeriod
|
||||
? $analysisReportService->build($current_team, $selectedPeriod->year, $selectedPeriod->month)
|
||||
: [
|
||||
'period' => null,
|
||||
'columns' => [],
|
||||
'rows' => [],
|
||||
'meta' => [
|
||||
'status' => null,
|
||||
'statusLabel' => null,
|
||||
'updatedAt' => null,
|
||||
'canEditSources' => false,
|
||||
],
|
||||
];
|
||||
|
||||
return Inertia::render('reports/analysis/Index', [
|
||||
'periods' => $periods->map(fn (ReportPeriod $period) => [
|
||||
'id' => $period->id,
|
||||
'label' => $period->label(),
|
||||
'status' => $period->status->value,
|
||||
'statusLabel' => $period->status->label(),
|
||||
])->values(),
|
||||
'selectedPeriodId' => $selectedPeriod?->id,
|
||||
...$analysis,
|
||||
]);
|
||||
}
|
||||
}
|
||||
111
app/Http/Controllers/Reports/MedicationExpenseController.php
Normal file
111
app/Http/Controllers/Reports/MedicationExpenseController.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Reports;
|
||||
|
||||
use App\Enums\ExpenseCategory;
|
||||
use App\Enums\FundingSource;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Reports\StoreMedicationExpenseValuesRequest;
|
||||
use App\Models\Department;
|
||||
use App\Models\MedicationExpenseRow;
|
||||
use App\Models\MedicationExpenseValue;
|
||||
use App\Models\ReportPeriod;
|
||||
use App\Models\Team;
|
||||
use App\Services\Reports\MedicationExpenseService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class MedicationExpenseController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the medication expense page.
|
||||
*/
|
||||
public function index(Request $request, Team $current_team, MedicationExpenseService $medicationExpenseService): Response
|
||||
{
|
||||
$periods = ReportPeriod::query()
|
||||
->whereBelongsTo($current_team)
|
||||
->orderByDesc('year')
|
||||
->orderByDesc('month')
|
||||
->get();
|
||||
|
||||
$departments = Department::query()
|
||||
->where('is_active', true)
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
/** @var ReportPeriod|null $selectedPeriod */
|
||||
$selectedPeriod = $periods->firstWhere('id', $request->integer('period', $periods->first()?->id));
|
||||
/** @var Department|null $selectedDepartment */
|
||||
$selectedDepartment = $departments->firstWhere('id', $request->integer('department', $departments->first()?->id));
|
||||
|
||||
$expense = $selectedPeriod && $selectedDepartment
|
||||
? $medicationExpenseService->calculateForDepartmentPeriod($selectedPeriod, $selectedDepartment)
|
||||
: ['matrix' => [], 'totals' => []];
|
||||
|
||||
return Inertia::render('reports/medication-expenses/Index', [
|
||||
'periods' => $periods->map(fn (ReportPeriod $period) => [
|
||||
'id' => $period->id,
|
||||
'label' => $period->label(),
|
||||
'status' => $period->status->value,
|
||||
'statusLabel' => $period->status->label(),
|
||||
'isEditable' => $period->isEditable(),
|
||||
])->values(),
|
||||
'departments' => $departments->map(fn (Department $department) => [
|
||||
'id' => $department->id,
|
||||
'name' => $department->name,
|
||||
])->values(),
|
||||
'selectedPeriodId' => $selectedPeriod?->id,
|
||||
'selectedDepartmentId' => $selectedDepartment?->id,
|
||||
'fundingSources' => collect(FundingSource::cases())->map(fn (FundingSource $source) => [
|
||||
'value' => $source->value,
|
||||
'label' => $source->label(),
|
||||
])->all(),
|
||||
'expenseCategories' => collect(ExpenseCategory::cases())->map(fn (ExpenseCategory $category) => [
|
||||
'value' => $category->value,
|
||||
'label' => $category->label(),
|
||||
])->all(),
|
||||
'values' => $expense['matrix'],
|
||||
'totals' => $expense['totals'],
|
||||
'canEdit' => $selectedPeriod?->isEditable() ?? false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store medication expense values for a department and period.
|
||||
*/
|
||||
public function storeValues(StoreMedicationExpenseValuesRequest $request, Team $current_team): RedirectResponse
|
||||
{
|
||||
$period = ReportPeriod::query()
|
||||
->whereBelongsTo($current_team)
|
||||
->findOrFail($request->integer('report_period_id'));
|
||||
|
||||
abort_unless($period->isEditable(), 422);
|
||||
|
||||
$department = Department::query()->findOrFail($request->integer('department_id'));
|
||||
|
||||
$row = MedicationExpenseRow::query()->firstOrCreate([
|
||||
'report_period_id' => $period->id,
|
||||
'department_id' => $department->id,
|
||||
]);
|
||||
|
||||
foreach (FundingSource::cases() as $fundingSource) {
|
||||
foreach (ExpenseCategory::cases() as $expenseCategory) {
|
||||
MedicationExpenseValue::updateOrCreate([
|
||||
'medication_expense_row_id' => $row->id,
|
||||
'funding_source' => $fundingSource,
|
||||
'expense_category' => $expenseCategory,
|
||||
], [
|
||||
'amount' => (float) ($request->input("values.{$fundingSource->value}.{$expenseCategory->value}") ?? 0),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return to_route('reports.medication-expenses.index', [
|
||||
'current_team' => $current_team,
|
||||
'period' => $period->id,
|
||||
'department' => $department->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
143
app/Http/Controllers/Reports/OperationalReportController.php
Normal file
143
app/Http/Controllers/Reports/OperationalReportController.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Reports;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Reports\StoreServiceCatalogRequest;
|
||||
use App\Http\Requests\Reports\StoreServiceEntriesRequest;
|
||||
use App\Models\Department;
|
||||
use App\Models\ReportPeriod;
|
||||
use App\Models\ServiceCatalog;
|
||||
use App\Models\ServiceEntry;
|
||||
use App\Models\Team;
|
||||
use App\Services\Reports\ServiceAllocationService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class OperationalReportController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the operational values page.
|
||||
*/
|
||||
public function index(Request $request, Team $current_team, ServiceAllocationService $serviceAllocationService): Response
|
||||
{
|
||||
$periods = ReportPeriod::query()
|
||||
->whereBelongsTo($current_team)
|
||||
->orderByDesc('year')
|
||||
->orderByDesc('month')
|
||||
->get();
|
||||
|
||||
$departments = Department::query()
|
||||
->where('is_active', true)
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
$serviceCatalogs = ServiceCatalog::query()
|
||||
->orderBy('sort_order')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
/** @var ReportPeriod|null $selectedPeriod */
|
||||
$selectedPeriod = $periods->firstWhere('id', $request->integer('period', $periods->first()?->id));
|
||||
/** @var Department|null $selectedProviderDepartment */
|
||||
$selectedProviderDepartment = $departments->firstWhere('id', $request->integer('provider_department', $departments->first()?->id));
|
||||
/** @var ServiceCatalog|null $selectedServiceCatalog */
|
||||
$selectedServiceCatalog = $serviceCatalogs->firstWhere('id', $request->integer('service', $serviceCatalogs->first()?->id));
|
||||
$recipientDepartments = $departments
|
||||
->reject(fn (Department $department) => $department->id === $selectedProviderDepartment?->id)
|
||||
->values();
|
||||
|
||||
return Inertia::render('reports/operations/Index', [
|
||||
'periods' => $periods->map(fn (ReportPeriod $period) => [
|
||||
'id' => $period->id,
|
||||
'label' => $period->label(),
|
||||
'status' => $period->status->value,
|
||||
'statusLabel' => $period->status->label(),
|
||||
'isEditable' => $period->isEditable(),
|
||||
])->values(),
|
||||
'departments' => $departments->map(fn (Department $department) => [
|
||||
'id' => $department->id,
|
||||
'name' => $department->name,
|
||||
])->values(),
|
||||
'serviceCatalogs' => $serviceCatalogs->map(fn (ServiceCatalog $serviceCatalog) => [
|
||||
'id' => $serviceCatalog->id,
|
||||
'code' => $serviceCatalog->code,
|
||||
'name' => $serviceCatalog->name,
|
||||
'unit' => $serviceCatalog->unit,
|
||||
'defaultPrice' => (float) $serviceCatalog->default_price,
|
||||
'sortOrder' => $serviceCatalog->sort_order,
|
||||
'isActive' => $serviceCatalog->is_active,
|
||||
])->values(),
|
||||
'selectedPeriodId' => $selectedPeriod?->id,
|
||||
'selectedProviderDepartmentId' => $selectedProviderDepartment?->id,
|
||||
'selectedServiceCatalogId' => $selectedServiceCatalog?->id,
|
||||
'recipientDepartments' => $recipientDepartments->map(fn (Department $department) => [
|
||||
'id' => $department->id,
|
||||
'name' => $department->name,
|
||||
])->values(),
|
||||
'entries' => $selectedPeriod && $selectedProviderDepartment && $selectedServiceCatalog
|
||||
? $serviceAllocationService->entriesForProviderPeriodService(
|
||||
$selectedPeriod,
|
||||
$selectedProviderDepartment,
|
||||
$selectedServiceCatalog,
|
||||
$recipientDepartments,
|
||||
)
|
||||
: [],
|
||||
'canEdit' => $selectedPeriod?->isEditable() ?? false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new service catalog item.
|
||||
*/
|
||||
public function storeService(StoreServiceCatalogRequest $request, Team $current_team): RedirectResponse
|
||||
{
|
||||
ServiceCatalog::create([
|
||||
...$request->validated(),
|
||||
'sort_order' => $request->integer('sort_order'),
|
||||
'is_active' => $request->boolean('is_active'),
|
||||
'default_price' => (float) $request->input('default_price', 0),
|
||||
]);
|
||||
|
||||
return to_route('reports.operations.index', $current_team);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store service entries for a provider department and period.
|
||||
*/
|
||||
public function storeEntries(StoreServiceEntriesRequest $request, Team $current_team): RedirectResponse
|
||||
{
|
||||
$period = ReportPeriod::query()
|
||||
->whereBelongsTo($current_team)
|
||||
->findOrFail($request->integer('report_period_id'));
|
||||
|
||||
abort_unless($period->isEditable(), 422);
|
||||
|
||||
$providerDepartment = Department::query()->findOrFail($request->integer('provider_department_id'));
|
||||
$serviceCatalog = ServiceCatalog::query()->findOrFail($request->integer('service_catalog_id'));
|
||||
|
||||
foreach ($request->array('entries') as $recipientDepartmentId => $entry) {
|
||||
$quantity = (float) ($entry['quantity'] ?? 0);
|
||||
$unitPrice = (float) ($entry['unit_price'] ?? $serviceCatalog->default_price);
|
||||
|
||||
ServiceEntry::updateOrCreate([
|
||||
'report_period_id' => $period->id,
|
||||
'service_catalog_id' => $serviceCatalog->id,
|
||||
'provider_department_id' => $providerDepartment->id,
|
||||
'recipient_department_id' => (int) $recipientDepartmentId,
|
||||
], [
|
||||
'quantity' => $quantity,
|
||||
'unit_price' => $unitPrice,
|
||||
]);
|
||||
}
|
||||
|
||||
return to_route('reports.operations.index', [
|
||||
'current_team' => $current_team,
|
||||
'period' => $period->id,
|
||||
'provider_department' => $providerDepartment->id,
|
||||
'service' => $serviceCatalog->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
78
app/Http/Controllers/Reports/ReportPeriodController.php
Normal file
78
app/Http/Controllers/Reports/ReportPeriodController.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Reports;
|
||||
|
||||
use App\Enums\ReportPeriodStatus;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Reports\StoreReportPeriodRequest;
|
||||
use App\Models\ReportPeriod;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ReportPeriodController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the report periods page.
|
||||
*/
|
||||
public function index(Request $request, Team $current_team): Response
|
||||
{
|
||||
$periods = ReportPeriod::query()
|
||||
->whereBelongsTo($current_team)
|
||||
->orderByDesc('year')
|
||||
->orderByDesc('month')
|
||||
->get();
|
||||
|
||||
return Inertia::render('reports/periods/Index', [
|
||||
'periods' => $periods->map(fn (ReportPeriod $period) => [
|
||||
'id' => $period->id,
|
||||
'year' => $period->year,
|
||||
'month' => $period->month,
|
||||
'label' => $period->label(),
|
||||
'status' => $period->status->value,
|
||||
'statusLabel' => $period->status->label(),
|
||||
'isEditable' => $period->isEditable(),
|
||||
'updatedAt' => $period->updated_at?->toIso8601String(),
|
||||
])->values(),
|
||||
'months' => collect(range(1, 12))->map(fn (int $month) => [
|
||||
'value' => $month,
|
||||
'label' => (new ReportPeriod([
|
||||
'month' => $month,
|
||||
'year' => now()->year,
|
||||
'status' => ReportPeriodStatus::Draft,
|
||||
]))->label(),
|
||||
])->all(),
|
||||
'years' => collect(range((int) now()->year - 1, (int) now()->year + 2))->values()->all(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created report period.
|
||||
*/
|
||||
public function store(StoreReportPeriodRequest $request, Team $current_team): RedirectResponse
|
||||
{
|
||||
ReportPeriod::create([
|
||||
...$request->validated(),
|
||||
'team_id' => $current_team->id,
|
||||
'status' => ReportPeriodStatus::Draft,
|
||||
]);
|
||||
|
||||
return to_route('reports.periods.index', $current_team);
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve the given report period.
|
||||
*/
|
||||
public function approve(Team $current_team, ReportPeriod $report_period): RedirectResponse
|
||||
{
|
||||
abort_unless($report_period->team?->is($current_team), 404);
|
||||
|
||||
$report_period->update([
|
||||
'status' => ReportPeriodStatus::Approved,
|
||||
]);
|
||||
|
||||
return to_route('reports.periods.index', $current_team);
|
||||
}
|
||||
}
|
||||
60
app/Http/Controllers/Settings/ProfileController.php
Normal file
60
app/Http/Controllers/Settings/ProfileController.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Settings;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Settings\ProfileDeleteRequest;
|
||||
use App\Http\Requests\Settings\ProfileUpdateRequest;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the user's profile settings page.
|
||||
*/
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
return Inertia::render('settings/Profile', [
|
||||
'mustVerifyEmail' => $request->user() instanceof MustVerifyEmail,
|
||||
'status' => $request->session()->get('status'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*/
|
||||
public function update(ProfileUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$request->user()->fill($request->validated());
|
||||
|
||||
if ($request->user()->isDirty('email')) {
|
||||
$request->user()->email_verified_at = null;
|
||||
}
|
||||
|
||||
$request->user()->save();
|
||||
|
||||
return to_route('profile.edit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the user's profile.
|
||||
*/
|
||||
public function destroy(ProfileDeleteRequest $request): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
Auth::logout();
|
||||
|
||||
$user->delete();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
||||
58
app/Http/Controllers/Settings/SecurityController.php
Normal file
58
app/Http/Controllers/Settings/SecurityController.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Settings;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Settings\PasswordUpdateRequest;
|
||||
use App\Http\Requests\Settings\TwoFactorAuthenticationRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Routing\Controllers\HasMiddleware;
|
||||
use Illuminate\Routing\Controllers\Middleware;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
use Laravel\Fortify\Features;
|
||||
|
||||
class SecurityController extends Controller implements HasMiddleware
|
||||
{
|
||||
/**
|
||||
* Get the middleware that should be assigned to the controller.
|
||||
*/
|
||||
public static function middleware(): array
|
||||
{
|
||||
return Features::canManageTwoFactorAuthentication()
|
||||
&& Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword')
|
||||
? [new Middleware('password.confirm', only: ['edit'])]
|
||||
: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the user's security settings page.
|
||||
*/
|
||||
public function edit(TwoFactorAuthenticationRequest $request): Response
|
||||
{
|
||||
$props = [
|
||||
'canManageTwoFactor' => Features::canManageTwoFactorAuthentication(),
|
||||
];
|
||||
|
||||
if (Features::canManageTwoFactorAuthentication()) {
|
||||
$request->ensureStateIsValid();
|
||||
|
||||
$props['twoFactorEnabled'] = $request->user()->hasEnabledTwoFactorAuthentication();
|
||||
$props['requiresConfirmation'] = Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm');
|
||||
}
|
||||
|
||||
return Inertia::render('settings/Security', $props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's password.
|
||||
*/
|
||||
public function update(PasswordUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$request->user()->update([
|
||||
'password' => $request->password,
|
||||
]);
|
||||
|
||||
return back();
|
||||
}
|
||||
}
|
||||
137
app/Http/Controllers/Teams/TeamController.php
Normal file
137
app/Http/Controllers/Teams/TeamController.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Teams;
|
||||
|
||||
use App\Actions\Teams\CreateTeam;
|
||||
use App\Enums\TeamRole;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Teams\DeleteTeamRequest;
|
||||
use App\Http\Requests\Teams\SaveTeamRequest;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class TeamController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the user's teams.
|
||||
*/
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
return Inertia::render('teams/Index', [
|
||||
'teams' => $user->toUserTeams(includeCurrent: true),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created team.
|
||||
*/
|
||||
public function store(SaveTeamRequest $request, CreateTeam $createTeam): RedirectResponse
|
||||
{
|
||||
$team = $createTeam->handle($request->user(), $request->validated('name'));
|
||||
|
||||
return to_route('teams.edit', ['team' => $team->slug]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the team edit page.
|
||||
*/
|
||||
public function edit(Request $request, Team $team): Response
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
return Inertia::render('teams/Edit', [
|
||||
'team' => [
|
||||
'id' => $team->id,
|
||||
'name' => $team->name,
|
||||
'slug' => $team->slug,
|
||||
'isPersonal' => $team->is_personal,
|
||||
],
|
||||
'members' => $team->members()->get()->map(fn ($member) => [
|
||||
'id' => $member->id,
|
||||
'name' => $member->name,
|
||||
'email' => $member->email,
|
||||
'avatar' => $member->avatar ?? null,
|
||||
'role' => $member->pivot->role->value,
|
||||
'role_label' => $member->pivot->role?->label(),
|
||||
]),
|
||||
'invitations' => $team->invitations()
|
||||
->whereNull('accepted_at')
|
||||
->get()
|
||||
->map(fn ($invitation) => [
|
||||
'code' => $invitation->code,
|
||||
'email' => $invitation->email,
|
||||
'role' => $invitation->role->value,
|
||||
'role_label' => $invitation->role->label(),
|
||||
'created_at' => $invitation->created_at->toISOString(),
|
||||
]),
|
||||
'permissions' => $user->toTeamPermissions($team),
|
||||
'availableRoles' => TeamRole::assignable(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified team.
|
||||
*/
|
||||
public function update(SaveTeamRequest $request, Team $team): RedirectResponse
|
||||
{
|
||||
Gate::authorize('update', $team);
|
||||
|
||||
$team = DB::transaction(function () use ($request, $team) {
|
||||
$team = Team::whereKey($team->id)->lockForUpdate()->firstOrFail();
|
||||
|
||||
$team->update(['name' => $request->validated('name')]);
|
||||
|
||||
return $team;
|
||||
});
|
||||
|
||||
return to_route('teams.edit', ['team' => $team->slug]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the user's current team.
|
||||
*/
|
||||
public function switch(Request $request, Team $team): RedirectResponse
|
||||
{
|
||||
abort_unless($request->user()->belongsToTeam($team), 403);
|
||||
|
||||
$request->user()->switchTeam($team);
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the specified team.
|
||||
*/
|
||||
public function destroy(DeleteTeamRequest $request, Team $team): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
$fallbackTeam = $user->isCurrentTeam($team)
|
||||
? $user->fallbackTeam($team)
|
||||
: null;
|
||||
|
||||
DB::transaction(function () use ($user, $team) {
|
||||
User::where('current_team_id', $team->id)
|
||||
->where('id', '!=', $user->id)
|
||||
->each(fn (User $affectedUser) => $affectedUser->switchTeam($affectedUser->personalTeam()));
|
||||
|
||||
$team->invitations()->delete();
|
||||
$team->memberships()->delete();
|
||||
$team->delete();
|
||||
});
|
||||
|
||||
if ($fallbackTeam) {
|
||||
$user->switchTeam($fallbackTeam);
|
||||
|
||||
}
|
||||
|
||||
return to_route('teams.index');
|
||||
}
|
||||
}
|
||||
77
app/Http/Controllers/Teams/TeamInvitationController.php
Normal file
77
app/Http/Controllers/Teams/TeamInvitationController.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Teams;
|
||||
|
||||
use App\Enums\TeamRole;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Teams\AcceptTeamInvitationRequest;
|
||||
use App\Http\Requests\Teams\CreateTeamInvitationRequest;
|
||||
use App\Models\Team;
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Notifications\Teams\TeamInvitation as TeamInvitationNotification;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
class TeamInvitationController extends Controller
|
||||
{
|
||||
/**
|
||||
* Store a newly created invitation.
|
||||
*/
|
||||
public function store(CreateTeamInvitationRequest $request, Team $team): RedirectResponse
|
||||
{
|
||||
Gate::authorize('inviteMember', $team);
|
||||
|
||||
$invitation = $team->invitations()->create([
|
||||
'email' => $request->validated('email'),
|
||||
'role' => TeamRole::from($request->validated('role')),
|
||||
'invited_by' => $request->user()->id,
|
||||
'expires_at' => now()->addDays(3),
|
||||
]);
|
||||
|
||||
Notification::route('mail', $invitation->email)
|
||||
->notify(new TeamInvitationNotification($invitation));
|
||||
|
||||
return to_route('teams.edit', ['team' => $team->slug]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the specified invitation.
|
||||
*/
|
||||
public function destroy(Team $team, TeamInvitation $invitation): RedirectResponse
|
||||
{
|
||||
abort_unless($invitation->team_id === $team->id, 404);
|
||||
|
||||
Gate::authorize('cancelInvitation', $team);
|
||||
|
||||
$invitation->delete();
|
||||
|
||||
return to_route('teams.edit', ['team' => $team->slug]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept the invitation.
|
||||
*/
|
||||
public function accept(AcceptTeamInvitationRequest $request, TeamInvitation $invitation): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
DB::transaction(function () use ($user, $invitation) {
|
||||
$team = $invitation->team;
|
||||
|
||||
$membership = $team->memberships()->firstOrCreate(
|
||||
['user_id' => $user->id],
|
||||
['role' => $invitation->role],
|
||||
);
|
||||
|
||||
$wasRecentlyCreated = $membership->wasRecentlyCreated;
|
||||
|
||||
$invitation->update(['accepted_at' => now()]);
|
||||
|
||||
$user->switchTeam($team);
|
||||
});
|
||||
|
||||
return to_route('dashboard');
|
||||
}
|
||||
}
|
||||
51
app/Http/Controllers/Teams/TeamMemberController.php
Normal file
51
app/Http/Controllers/Teams/TeamMemberController.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Teams;
|
||||
|
||||
use App\Enums\TeamRole;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Teams\UpdateTeamMemberRequest;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class TeamMemberController extends Controller
|
||||
{
|
||||
/**
|
||||
* Update the specified team member's role.
|
||||
*/
|
||||
public function update(UpdateTeamMemberRequest $request, Team $team, User $user): RedirectResponse
|
||||
{
|
||||
Gate::authorize('updateMember', $team);
|
||||
|
||||
$newRole = TeamRole::from($request->validated('role'));
|
||||
|
||||
$team->memberships()
|
||||
->where('user_id', $user->id)
|
||||
->firstOrFail()
|
||||
->update(['role' => $newRole]);
|
||||
|
||||
return to_route('teams.edit', ['team' => $team->slug]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified team member.
|
||||
*/
|
||||
public function destroy(Team $team, User $user): RedirectResponse
|
||||
{
|
||||
Gate::authorize('removeMember', $team);
|
||||
|
||||
abort_if($team->owner()?->is($user), 403, 'The team owner cannot be removed.');
|
||||
|
||||
$team->memberships()
|
||||
->where('user_id', $user->id)
|
||||
->delete();
|
||||
|
||||
if ($user->isCurrentTeam($team)) {
|
||||
$user->switchTeam($user->personalTeam());
|
||||
}
|
||||
|
||||
return to_route('teams.edit', ['team' => $team->slug]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user