feat: [hr] 사원등록 기능 확장
- 기본정보에 주민등록번호 필드 추가 - 급여이체정보 섹션 추가 (이체은행, 예금주, 계좌번호) - 부양가족 정보 섹션 추가 (동적 행 추가/삭제) - 첨부파일 업로드/다운로드/삭제 기능 추가 - 은행 목록 config/banks.php 설정 파일 생성 - show 페이지 주민등록번호 뒷자리 마스킹 처리
This commit is contained in:
@@ -3,10 +3,13 @@
|
||||
namespace App\Http\Controllers\Api\Admin\HR;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Boards\File;
|
||||
use App\Services\HR\EmployeeService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class EmployeeController extends Controller
|
||||
{
|
||||
@@ -88,6 +91,18 @@ public function store(Request $request): JsonResponse
|
||||
'resign_date' => 'nullable|date',
|
||||
'address' => 'nullable|string|max:200',
|
||||
'emergency_contact' => 'nullable|string|max:100',
|
||||
'resident_number' => 'nullable|string|max:14',
|
||||
'bank_account.bank_code' => 'nullable|string|max:20',
|
||||
'bank_account.bank_name' => 'nullable|string|max:50',
|
||||
'bank_account.account_holder' => 'nullable|string|max:50',
|
||||
'bank_account.account_number' => 'nullable|string|max:30',
|
||||
'dependents' => 'nullable|array',
|
||||
'dependents.*.name' => 'required_with:dependents|string|max:50',
|
||||
'dependents.*.nationality' => 'nullable|string|in:korean,foreigner',
|
||||
'dependents.*.resident_number' => 'nullable|string|max:14',
|
||||
'dependents.*.relationship' => 'nullable|string|max:20',
|
||||
'dependents.*.is_disabled' => 'nullable|boolean',
|
||||
'dependents.*.is_dependent' => 'nullable|boolean',
|
||||
];
|
||||
|
||||
// 신규 사용자일 때만 이메일 unique 검증
|
||||
@@ -157,6 +172,18 @@ public function update(Request $request, int $id): JsonResponse
|
||||
'resign_date' => 'nullable|date',
|
||||
'address' => 'nullable|string|max:200',
|
||||
'emergency_contact' => 'nullable|string|max:100',
|
||||
'resident_number' => 'nullable|string|max:14',
|
||||
'bank_account.bank_code' => 'nullable|string|max:20',
|
||||
'bank_account.bank_name' => 'nullable|string|max:50',
|
||||
'bank_account.account_holder' => 'nullable|string|max:50',
|
||||
'bank_account.account_number' => 'nullable|string|max:30',
|
||||
'dependents' => 'nullable|array',
|
||||
'dependents.*.name' => 'required_with:dependents|string|max:50',
|
||||
'dependents.*.nationality' => 'nullable|string|in:korean,foreigner',
|
||||
'dependents.*.resident_number' => 'nullable|string|max:14',
|
||||
'dependents.*.relationship' => 'nullable|string|max:20',
|
||||
'dependents.*.is_disabled' => 'nullable|boolean',
|
||||
'dependents.*.is_dependent' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
try {
|
||||
@@ -228,6 +255,101 @@ public function destroy(Request $request, int $id): JsonResponse|Response
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 사원 첨부파일 업로드
|
||||
*/
|
||||
public function uploadFile(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$employee = $this->employeeService->getEmployeeById($id);
|
||||
if (! $employee) {
|
||||
return response()->json(['success' => false, 'message' => '사원 정보를 찾을 수 없습니다.'], 404);
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'files' => 'required|array|max:10',
|
||||
'files.*' => 'file|max:20480',
|
||||
]);
|
||||
|
||||
$tenantId = session('selected_tenant_id');
|
||||
$uploaded = [];
|
||||
|
||||
foreach ($request->file('files') as $file) {
|
||||
$storedName = Str::random(40).'.'.$file->getClientOriginalExtension();
|
||||
$path = "tenants/{$tenantId}/employees/{$employee->id}";
|
||||
$file->storeAs($path, $storedName, 'tenant');
|
||||
|
||||
$fileRecord = File::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'document_id' => $employee->id,
|
||||
'document_type' => 'employee_profile',
|
||||
'original_name' => $file->getClientOriginalName(),
|
||||
'stored_name' => $storedName,
|
||||
'file_path' => $path.'/'.$storedName,
|
||||
'mime_type' => $file->getMimeType(),
|
||||
'file_size' => $file->getSize(),
|
||||
'file_type' => strtolower($file->getClientOriginalExtension()),
|
||||
'uploaded_by' => auth()->id(),
|
||||
'created_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
$uploaded[] = $fileRecord;
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => count($uploaded).'개 파일이 업로드되었습니다.',
|
||||
'data' => $uploaded,
|
||||
], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* 사원 첨부파일 삭제
|
||||
*/
|
||||
public function deleteFile(int $id, int $fileId): JsonResponse
|
||||
{
|
||||
$tenantId = session('selected_tenant_id');
|
||||
|
||||
$file = File::where('id', $fileId)
|
||||
->where('document_id', $id)
|
||||
->where('document_type', 'employee_profile')
|
||||
->where('tenant_id', $tenantId)
|
||||
->first();
|
||||
|
||||
if (! $file) {
|
||||
return response()->json(['success' => false, 'message' => '파일을 찾을 수 없습니다.'], 404);
|
||||
}
|
||||
|
||||
Storage::disk('tenant')->delete($file->file_path);
|
||||
$file->delete();
|
||||
|
||||
return response()->json(['success' => true, 'message' => '파일이 삭제되었습니다.']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 사원 첨부파일 다운로드
|
||||
*/
|
||||
public function downloadFile(int $id, int $fileId)
|
||||
{
|
||||
$tenantId = session('selected_tenant_id');
|
||||
|
||||
$file = File::where('id', $fileId)
|
||||
->where('document_id', $id)
|
||||
->where('document_type', 'employee_profile')
|
||||
->where('tenant_id', $tenantId)
|
||||
->first();
|
||||
|
||||
if (! $file) {
|
||||
abort(404, '파일을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
$disk = Storage::disk('tenant');
|
||||
if (! $disk->exists($file->file_path)) {
|
||||
abort(404, '파일이 서버에 존재하지 않습니다.');
|
||||
}
|
||||
|
||||
return $disk->download($file->file_path, $file->original_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 직급/직책 추가
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers\HR;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Boards\File;
|
||||
use App\Services\HR\EmployeeService;
|
||||
use Illuminate\Contracts\View\View;
|
||||
|
||||
@@ -39,6 +40,7 @@ public function create(): View
|
||||
'departments' => $departments,
|
||||
'ranks' => $ranks,
|
||||
'titles' => $titles,
|
||||
'banks' => config('banks', []),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -53,8 +55,15 @@ public function show(int $id): View
|
||||
abort(404, '사원 정보를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
$files = File::where('document_type', 'employee_profile')
|
||||
->where('document_id', $employee->id)
|
||||
->where('tenant_id', session('selected_tenant_id'))
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
return view('hr.employees.show', [
|
||||
'employee' => $employee,
|
||||
'files' => $files,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -73,11 +82,19 @@ public function edit(int $id): View
|
||||
$ranks = $this->employeeService->getPositions('rank');
|
||||
$titles = $this->employeeService->getPositions('title');
|
||||
|
||||
$files = File::where('document_type', 'employee_profile')
|
||||
->where('document_id', $employee->id)
|
||||
->where('tenant_id', session('selected_tenant_id'))
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
return view('hr.employees.edit', [
|
||||
'employee' => $employee,
|
||||
'departments' => $departments,
|
||||
'ranks' => $ranks,
|
||||
'titles' => $titles,
|
||||
'banks' => config('banks', []),
|
||||
'files' => $files,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,21 @@ public function getEmergencyContactAttribute(): ?string
|
||||
return $this->json_extra['emergency_contact'] ?? null;
|
||||
}
|
||||
|
||||
public function getResidentNumberAttribute(): ?string
|
||||
{
|
||||
return $this->json_extra['resident_number'] ?? null;
|
||||
}
|
||||
|
||||
public function getBankAccountAttribute(): ?array
|
||||
{
|
||||
return $this->json_extra['bank_account'] ?? null;
|
||||
}
|
||||
|
||||
public function getDependentsAttribute(): array
|
||||
{
|
||||
return $this->json_extra['dependents'] ?? [];
|
||||
}
|
||||
|
||||
public function getPositionLabelAttribute(): ?string
|
||||
{
|
||||
if (! $this->position_key || ! $this->tenant_id) {
|
||||
|
||||
@@ -205,17 +205,28 @@ public function createEmployee(array $data): Employee
|
||||
|
||||
// json_extra 구성
|
||||
$jsonExtra = [];
|
||||
if (! empty($data['hire_date'])) {
|
||||
$jsonExtra['hire_date'] = $data['hire_date'];
|
||||
foreach (['hire_date', 'resign_date', 'address', 'emergency_contact', 'resident_number'] as $key) {
|
||||
if (! empty($data[$key])) {
|
||||
$jsonExtra[$key] = $data[$key];
|
||||
}
|
||||
}
|
||||
if (! empty($data['resign_date'])) {
|
||||
$jsonExtra['resign_date'] = $data['resign_date'];
|
||||
|
||||
// 급여이체정보 (bank_account 객체)
|
||||
if (! empty($data['bank_account']) && is_array($data['bank_account'])) {
|
||||
$bankAccount = array_filter($data['bank_account'], fn ($v) => $v !== null && $v !== '');
|
||||
if (! empty($bankAccount)) {
|
||||
$jsonExtra['bank_account'] = $bankAccount;
|
||||
}
|
||||
}
|
||||
if (! empty($data['address'])) {
|
||||
$jsonExtra['address'] = $data['address'];
|
||||
}
|
||||
if (! empty($data['emergency_contact'])) {
|
||||
$jsonExtra['emergency_contact'] = $data['emergency_contact'];
|
||||
|
||||
// 부양가족 정보 (dependents 배열)
|
||||
if (! empty($data['dependents']) && is_array($data['dependents'])) {
|
||||
$dependents = array_values(array_filter($data['dependents'], function ($dep) {
|
||||
return ! empty($dep['name']);
|
||||
}));
|
||||
if (! empty($dependents)) {
|
||||
$jsonExtra['dependents'] = $dependents;
|
||||
}
|
||||
}
|
||||
|
||||
// Employee(TenantUserProfile) 생성
|
||||
@@ -259,8 +270,8 @@ public function updateEmployee(int $id, array $data): ?Employee
|
||||
'display_name' => $data['display_name'] ?? null,
|
||||
], fn ($v) => $v !== null);
|
||||
|
||||
// json_extra 업데이트
|
||||
$jsonExtraKeys = ['hire_date', 'resign_date', 'address', 'emergency_contact', 'salary', 'bank_account'];
|
||||
// json_extra 업데이트 (스칼라 값)
|
||||
$jsonExtraKeys = ['hire_date', 'resign_date', 'address', 'emergency_contact', 'salary', 'resident_number'];
|
||||
$extra = $employee->json_extra ?? [];
|
||||
foreach ($jsonExtraKeys as $key) {
|
||||
if (array_key_exists($key, $data)) {
|
||||
@@ -271,6 +282,37 @@ public function updateEmployee(int $id, array $data): ?Employee
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 급여이체정보 (bank_account 객체)
|
||||
if (array_key_exists('bank_account', $data)) {
|
||||
if (! empty($data['bank_account']) && is_array($data['bank_account'])) {
|
||||
$bankAccount = array_filter($data['bank_account'], fn ($v) => $v !== null && $v !== '');
|
||||
if (! empty($bankAccount)) {
|
||||
$extra['bank_account'] = $bankAccount;
|
||||
} else {
|
||||
unset($extra['bank_account']);
|
||||
}
|
||||
} else {
|
||||
unset($extra['bank_account']);
|
||||
}
|
||||
}
|
||||
|
||||
// 부양가족 정보 (dependents 배열)
|
||||
if (array_key_exists('dependents', $data)) {
|
||||
if (! empty($data['dependents']) && is_array($data['dependents'])) {
|
||||
$dependents = array_values(array_filter($data['dependents'], function ($dep) {
|
||||
return ! empty($dep['name']);
|
||||
}));
|
||||
if (! empty($dependents)) {
|
||||
$extra['dependents'] = $dependents;
|
||||
} else {
|
||||
unset($extra['dependents']);
|
||||
}
|
||||
} else {
|
||||
unset($extra['dependents']);
|
||||
}
|
||||
}
|
||||
|
||||
$updateData['json_extra'] = ! empty($extra) ? $extra : null;
|
||||
|
||||
$employee->update($updateData);
|
||||
|
||||
Reference in New Issue
Block a user