feat: [payroll] 급여관리 페이지 접근 제한 (이름 기반)

- 허용 사용자: 이경호, 전진선, 김보곤
- 웹 컨트롤러: 미허용 시 안내 뷰 반환
- API 컨트롤러: 모든 엔드포인트에 403 반환
- restricted.blade.php 안내 페이지 생성
This commit is contained in:
김보곤
2026-02-27 17:59:50 +09:00
parent 732d021b6d
commit 53c7f00340
3 changed files with 90 additions and 1 deletions

View File

@@ -12,15 +12,33 @@
class PayrollController extends Controller
{
private const ALLOWED_PAYROLL_USERS = ['이경호', '전진선', '김보곤'];
public function __construct(
private PayrollService $payrollService
) {}
private function checkPayrollAccess(): ?JsonResponse
{
if (! in_array(auth()->user()->name, self::ALLOWED_PAYROLL_USERS)) {
return response()->json([
'success' => false,
'message' => '급여관리는 관계자만 볼 수 있습니다.',
], 403);
}
return null;
}
/**
* 급여 목록 조회 (HTMX → HTML / 일반 → JSON)
*/
public function index(Request $request): JsonResponse|Response
{
if ($denied = $this->checkPayrollAccess()) {
return $denied;
}
$payrolls = $this->payrollService->getPayrolls(
$request->all(),
$request->integer('per_page', 20)
@@ -47,6 +65,10 @@ public function index(Request $request): JsonResponse|Response
*/
public function stats(Request $request): JsonResponse|Response
{
if ($denied = $this->checkPayrollAccess()) {
return $denied;
}
$stats = $this->payrollService->getMonthlyStats(
$request->integer('year') ?: null,
$request->integer('month') ?: null
@@ -67,6 +89,10 @@ public function stats(Request $request): JsonResponse|Response
*/
public function store(Request $request): JsonResponse
{
if ($denied = $this->checkPayrollAccess()) {
return $denied;
}
$validated = $request->validate([
'user_id' => 'required|integer|exists:users,id',
'pay_year' => 'required|integer|min:2020|max:2100',
@@ -124,6 +150,10 @@ public function store(Request $request): JsonResponse
*/
public function update(Request $request, int $id): JsonResponse
{
if ($denied = $this->checkPayrollAccess()) {
return $denied;
}
$validated = $request->validate([
'base_salary' => 'sometimes|required|numeric|min:0',
'overtime_pay' => 'nullable|numeric|min:0',
@@ -175,6 +205,10 @@ public function update(Request $request, int $id): JsonResponse
*/
public function destroy(Request $request, int $id): JsonResponse|Response
{
if ($denied = $this->checkPayrollAccess()) {
return $denied;
}
try {
$result = $this->payrollService->deletePayroll($id);
@@ -214,6 +248,10 @@ public function destroy(Request $request, int $id): JsonResponse|Response
*/
public function confirm(Request $request, int $id): JsonResponse
{
if ($denied = $this->checkPayrollAccess()) {
return $denied;
}
try {
$payroll = $this->payrollService->confirmPayroll($id);
@@ -245,6 +283,10 @@ public function confirm(Request $request, int $id): JsonResponse
*/
public function pay(Request $request, int $id): JsonResponse
{
if ($denied = $this->checkPayrollAccess()) {
return $denied;
}
try {
$payroll = $this->payrollService->payPayroll($id);
@@ -276,6 +318,10 @@ public function pay(Request $request, int $id): JsonResponse
*/
public function copyFromPrevious(Request $request): JsonResponse
{
if ($denied = $this->checkPayrollAccess()) {
return $denied;
}
$validated = $request->validate([
'pay_year' => 'required|integer|min:2020|max:2100',
'pay_month' => 'required|integer|min:1|max:12',
@@ -315,6 +361,10 @@ public function copyFromPrevious(Request $request): JsonResponse
*/
public function bulkGenerate(Request $request): JsonResponse
{
if ($denied = $this->checkPayrollAccess()) {
return $denied;
}
$validated = $request->validate([
'year' => 'required|integer|min:2020|max:2100',
'month' => 'required|integer|min:1|max:12',
@@ -342,8 +392,12 @@ public function bulkGenerate(Request $request): JsonResponse
/**
* 엑셀(CSV) 내보내기
*/
public function export(Request $request): StreamedResponse
public function export(Request $request): StreamedResponse|JsonResponse
{
if ($denied = $this->checkPayrollAccess()) {
return $denied;
}
$payrolls = $this->payrollService->getExportData($request->all());
$year = $request->input('year', now()->year);
$month = $request->input('month', now()->month);
@@ -392,6 +446,10 @@ public function export(Request $request): StreamedResponse
*/
public function settingsIndex(Request $request): JsonResponse|Response
{
if ($denied = $this->checkPayrollAccess()) {
return $denied;
}
$settings = $this->payrollService->getSettings();
if ($request->header('HX-Request')) {
@@ -409,6 +467,10 @@ public function settingsIndex(Request $request): JsonResponse|Response
*/
public function settingsUpdate(Request $request): JsonResponse
{
if ($denied = $this->checkPayrollAccess()) {
return $denied;
}
$validated = $request->validate([
'health_insurance_rate' => 'nullable|numeric|min:0|max:100',
'long_term_care_rate' => 'nullable|numeric|min:0|max:100',
@@ -444,6 +506,10 @@ public function settingsUpdate(Request $request): JsonResponse
*/
public function calculate(Request $request): JsonResponse
{
if ($denied = $this->checkPayrollAccess()) {
return $denied;
}
$validated = $request->validate([
'base_salary' => 'required|numeric|min:0',
'overtime_pay' => 'nullable|numeric|min:0',

View File

@@ -11,6 +11,8 @@
class PayrollController extends Controller
{
private const ALLOWED_PAYROLL_USERS = ['이경호', '전진선', '김보곤'];
public function __construct(
private PayrollService $payrollService
) {}
@@ -24,6 +26,10 @@ public function index(Request $request): View|Response
return response('', 200)->header('HX-Redirect', route('hr.payrolls.index'));
}
if (! in_array(auth()->user()->name, self::ALLOWED_PAYROLL_USERS)) {
return view('hr.payrolls.restricted');
}
$stats = $this->payrollService->getMonthlyStats();
$departments = $this->payrollService->getDepartments();
$employees = $this->payrollService->getActiveEmployees();

View File

@@ -0,0 +1,17 @@
@extends('layouts.app')
@section('title', '급여관리 - 접근 제한')
@section('content')
<div class="flex items-center justify-center" style="min-height: 60vh;">
<div class="text-center">
<div class="text-gray-300 mb-6">
<svg xmlns="http://www.w3.org/2000/svg" class="mx-auto" style="width: 80px; height: 80px;" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 1 0-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 0 0 2.25-2.25v-6.75a2.25 2.25 0 0 0-2.25-2.25H6.75a2.25 2.25 0 0 0-2.25 2.25v6.75a2.25 2.25 0 0 0 2.25 2.25Z" />
</svg>
</div>
<h2 class="text-xl font-semibold text-gray-700 mb-2">접근이 제한된 페이지입니다</h2>
<p class="text-gray-500">급여관리는 관계자만 있습니다.</p>
</div>
</div>
@endsection