refactor: [hr] 사번 필드 제거, 비밀번호 제거, 퇴직일 추가

This commit is contained in:
김보곤
2026-02-26 19:27:15 +09:00
parent 72a5c096a2
commit 39061c244d
7 changed files with 48 additions and 91 deletions

View File

@@ -76,7 +76,6 @@ public function store(Request $request): JsonResponse
'name' => 'required|string|max:50',
'email' => 'nullable|email|max:100',
'phone' => 'nullable|string|max:20',
'password' => 'nullable|string|min:6',
'department_id' => 'nullable|integer|exists:departments,id',
'position_key' => 'nullable|string|max:50',
'job_title_key' => 'nullable|string|max:50',
@@ -85,8 +84,8 @@ public function store(Request $request): JsonResponse
'employee_status' => 'nullable|string|in:active,leave,resigned',
'manager_user_id' => 'nullable|integer|exists:users,id',
'display_name' => 'nullable|string|max:50',
'employee_code' => 'nullable|string|max:30',
'hire_date' => 'nullable|date',
'resign_date' => 'nullable|date',
'address' => 'nullable|string|max:200',
'emergency_contact' => 'nullable|string|max:100',
];
@@ -154,8 +153,8 @@ public function update(Request $request, int $id): JsonResponse
'employee_status' => 'nullable|string|in:active,leave,resigned',
'manager_user_id' => 'nullable|integer|exists:users,id',
'display_name' => 'nullable|string|max:50',
'employee_code' => 'nullable|string|max:30',
'hire_date' => 'nullable|date',
'resign_date' => 'nullable|date',
'address' => 'nullable|string|max:200',
'emergency_contact' => 'nullable|string|max:100',
]);

View File

@@ -38,8 +38,8 @@ class Employee extends Model
];
protected $appends = [
'employee_code',
'hire_date',
'resign_date',
'position_label',
'job_title_label',
];
@@ -67,16 +67,16 @@ public function manager(): BelongsTo
// json_extra Accessor
// =========================================================================
public function getEmployeeCodeAttribute(): ?string
{
return $this->json_extra['employee_code'] ?? null;
}
public function getHireDateAttribute(): ?string
{
return $this->json_extra['hire_date'] ?? null;
}
public function getResignDateAttribute(): ?string
{
return $this->json_extra['resign_date'] ?? null;
}
public function getAddressAttribute(): ?string
{
return $this->json_extra['address'] ?? null;

View File

@@ -24,7 +24,7 @@ public function getEmployees(array $filters = [], int $perPage = 20): LengthAwar
->with(['user', 'department'])
->forTenant($tenantId);
// 검색 필터 (이름, 사번, 이메일)
// 검색 필터 (이름, 이메일, 연락처)
if (! empty($filters['q'])) {
$search = $filters['q'];
$query->where(function ($q) use ($search) {
@@ -33,8 +33,7 @@ public function getEmployees(array $filters = [], int $perPage = 20): LengthAwar
$uq->where('name', 'like', "%{$search}%")
->orWhere('email', 'like', "%{$search}%")
->orWhere('phone', 'like', "%{$search}%");
})
->orWhereRaw("JSON_UNQUOTE(JSON_EXTRACT(json_extra, '$.employee_code')) LIKE ?", ["%{$search}%"]);
});
});
}
@@ -171,7 +170,7 @@ public function createEmployee(array $data): Employee
'name' => $data['name'],
'email' => $email,
'phone' => $data['phone'] ?? null,
'password' => Hash::make($data['password'] ?? 'sam1234!'),
'password' => Hash::make('sam1234!'),
'role' => 'ops',
'is_active' => true,
'must_change_password' => true,
@@ -189,12 +188,12 @@ public function createEmployee(array $data): Employee
// json_extra 구성
$jsonExtra = [];
if (! empty($data['employee_code'])) {
$jsonExtra['employee_code'] = $data['employee_code'];
}
if (! empty($data['hire_date'])) {
$jsonExtra['hire_date'] = $data['hire_date'];
}
if (! empty($data['resign_date'])) {
$jsonExtra['resign_date'] = $data['resign_date'];
}
if (! empty($data['address'])) {
$jsonExtra['address'] = $data['address'];
}
@@ -244,7 +243,7 @@ public function updateEmployee(int $id, array $data): ?Employee
], fn ($v) => $v !== null);
// json_extra 업데이트
$jsonExtraKeys = ['employee_code', 'hire_date', 'address', 'emergency_contact', 'salary', 'bank_account'];
$jsonExtraKeys = ['hire_date', 'resign_date', 'address', 'emergency_contact', 'salary', 'bank_account'];
$extra = $employee->json_extra ?? [];
foreach ($jsonExtraKeys as $key) {
if (array_key_exists($key, $data)) {

View File

@@ -109,16 +109,6 @@ class="text-xs text-red-500 hover:text-red-700 font-medium shrink-0">
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
{{-- 사번 --}}
<div>
<label for="employee_code" class="block text-sm font-medium text-gray-700 mb-1">
사번
</label>
<input type="text" name="employee_code" id="employee_code"
placeholder="EMP-001"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
{{-- 이메일 / 연락처 --}}
<div class="flex gap-4" style="flex-wrap: wrap;">
<div style="flex: 1 1 200px;">
@@ -135,17 +125,6 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin
</div>
</div>
{{-- 비밀번호 --}}
<div>
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">
비밀번호
</label>
<input type="password" name="password" id="password"
placeholder="미입력 시 기본 비밀번호(sam1234!) 설정"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<p class="text-xs text-gray-400 mt-1">미입력 기본 비밀번호가 설정됩니다.</p>
</div>
{{-- 근무 정보 --}}
<div class="border-b border-gray-200 pb-4 mb-4 mt-8">
<h2 class="text-lg font-semibold text-gray-700">근무 정보</h2>
@@ -205,14 +184,19 @@ class="shrink-0 w-9 h-9 flex items-center justify-center border border-gray-300
</div>
</div>
{{-- 입사일 / 상태 --}}
{{-- 입사일 / 퇴직일 / 상태 --}}
<div class="flex gap-4" style="flex-wrap: wrap;">
<div style="flex: 1 1 200px;">
<div style="flex: 1 1 150px;">
<label for="hire_date" class="block text-sm font-medium text-gray-700 mb-1">입사일</label>
<input type="date" name="hire_date" id="hire_date"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div style="flex: 1 1 200px;">
<div style="flex: 1 1 150px;">
<label for="resign_date" class="block text-sm font-medium text-gray-700 mb-1">퇴직일</label>
<input type="date" name="resign_date" id="resign_date"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div style="flex: 1 1 150px;">
<label for="employee_status" class="block text-sm font-medium text-gray-700 mb-1">재직상태</label>
<select name="employee_status" id="employee_status"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
@@ -293,25 +277,16 @@ function userSearch() {
this.showDropdown = false;
this.query = '';
// 폼 필드 채우기
const nameEl = document.getElementById('name');
const emailEl = document.getElementById('email');
const phoneEl = document.getElementById('phone');
const passwordEl = document.getElementById('password');
nameEl.value = user.name || '';
emailEl.value = user.email || '';
phoneEl.value = user.phone || '';
// readonly 설정
nameEl.readOnly = true;
emailEl.readOnly = true;
phoneEl.readOnly = true;
passwordEl.readOnly = true;
passwordEl.placeholder = '기존 사용자는 비밀번호를 변경하지 않습니다';
// 시각적 구분
[nameEl, emailEl, phoneEl, passwordEl].forEach(el => {
[nameEl, emailEl, phoneEl].forEach(el => {
el.readOnly = true;
el.classList.add('bg-gray-100', 'text-gray-500');
});
},
@@ -322,23 +297,10 @@ function userSearch() {
const nameEl = document.getElementById('name');
const emailEl = document.getElementById('email');
const phoneEl = document.getElementById('phone');
const passwordEl = document.getElementById('password');
// 값 초기화
nameEl.value = '';
emailEl.value = '';
phoneEl.value = '';
passwordEl.value = '';
// readonly 해제
nameEl.readOnly = false;
emailEl.readOnly = false;
phoneEl.readOnly = false;
passwordEl.readOnly = false;
passwordEl.placeholder = '미입력 시 기본 비밀번호(sam1234!) 설정';
// 시각적 구분 해제
[nameEl, emailEl, phoneEl, passwordEl].forEach(el => {
[nameEl, emailEl, phoneEl].forEach(el => {
el.value = '';
el.readOnly = false;
el.classList.remove('bg-gray-100', 'text-gray-500');
});
}

View File

@@ -50,14 +50,6 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
{{-- 사번 --}}
<div>
<label for="employee_code" class="block text-sm font-medium text-gray-700 mb-1">사번</label>
<input type="text" name="employee_code" id="employee_code"
value="{{ $employee->employee_code }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
{{-- 이메일 / 연락처 --}}
<div class="flex gap-4" style="flex-wrap: wrap;">
<div style="flex: 1 1 200px;">
@@ -139,15 +131,21 @@ class="shrink-0 w-9 h-9 flex items-center justify-center border border-gray-300
</div>
</div>
{{-- 입사일 / 상태 --}}
{{-- 입사일 / 퇴직일 / 상태 --}}
<div class="flex gap-4" style="flex-wrap: wrap;">
<div style="flex: 1 1 200px;">
<div style="flex: 1 1 150px;">
<label for="hire_date" class="block text-sm font-medium text-gray-700 mb-1">입사일</label>
<input type="date" name="hire_date" id="hire_date"
value="{{ $employee->hire_date }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div style="flex: 1 1 200px;">
<div style="flex: 1 1 150px;">
<label for="resign_date" class="block text-sm font-medium text-gray-700 mb-1">퇴직일</label>
<input type="date" name="resign_date" id="resign_date"
value="{{ $employee->resign_date }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div style="flex: 1 1 150px;">
<label for="employee_status" class="block text-sm font-medium text-gray-700 mb-1">재직상태</label>
<select name="employee_status" id="employee_status"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">

View File

@@ -8,6 +8,7 @@
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-600">직급/직책</th>
<th class="px-6 py-3 text-center text-sm font-semibold text-gray-600">상태</th>
<th class="px-6 py-3 text-center text-sm font-semibold text-gray-600">입사일</th>
<th class="px-6 py-3 text-center text-sm font-semibold text-gray-600">퇴직일</th>
<th class="px-6 py-3 text-center text-sm font-semibold text-gray-600">연락처</th>
<th class="px-6 py-3 text-center text-sm font-semibold text-gray-600">작업</th>
</tr>
@@ -26,9 +27,6 @@ class="flex items-center gap-3 group">
<div class="text-sm font-medium text-gray-900 group-hover:text-blue-600">
{{ $employee->display_name ?? $employee->user?->name ?? '-' }}
</div>
@if($employee->employee_code)
<div class="text-xs text-gray-400">{{ $employee->employee_code }}</div>
@endif
</div>
</a>
</td>
@@ -76,6 +74,11 @@ class="flex items-center gap-3 group">
{{ $employee->hire_date ?? '-' }}
</td>
{{-- 퇴직일 --}}
<td class="px-6 py-4 whitespace-nowrap text-center text-sm text-gray-500">
{{ $employee->resign_date ?? '-' }}
</td>
{{-- 연락처 --}}
<td class="px-6 py-4 whitespace-nowrap text-center text-sm text-gray-500">
{{ $employee->user?->phone ?? $employee->user?->email ?? '-' }}
@@ -120,7 +123,7 @@ class="text-red-600 hover:text-red-800" title="퇴직처리">
</tr>
@empty
<tr>
<td colspan="7" class="px-6 py-12 text-center">
<td colspan="8" class="px-6 py-12 text-center">
<div class="flex flex-col items-center gap-2">
<svg class="w-12 h-12 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>

View File

@@ -35,10 +35,6 @@ class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 te
{{ $employee->display_name ?? $employee->user?->name ?? '-' }}
</h2>
<div class="flex flex-wrap items-center gap-2 mt-1">
@if($employee->employee_code)
<span class="text-sm text-gray-500">{{ $employee->employee_code }}</span>
<span class="text-gray-300">|</span>
@endif
@if($employee->department)
<span class="text-sm text-gray-500">{{ $employee->department->name }}</span>
<span class="text-gray-300">|</span>
@@ -81,10 +77,6 @@ class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 te
<div class="shrink-0 text-sm font-medium text-gray-500" style="width: 140px;">표시 이름</div>
<div class="text-sm text-gray-900">{{ $employee->display_name ?? '-' }}</div>
</div>
<div class="px-6 py-3 flex" style="flex-wrap: wrap;">
<div class="shrink-0 text-sm font-medium text-gray-500" style="width: 140px;">사번</div>
<div class="text-sm text-gray-900">{{ $employee->employee_code ?? '-' }}</div>
</div>
<div class="px-6 py-3 flex" style="flex-wrap: wrap;">
<div class="shrink-0 text-sm font-medium text-gray-500" style="width: 140px;">이메일</div>
<div class="text-sm text-gray-900">{{ $employee->user?->email ?? '-' }}</div>
@@ -114,6 +106,10 @@ class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 te
<div class="shrink-0 text-sm font-medium text-gray-500" style="width: 140px;">입사일</div>
<div class="text-sm text-gray-900">{{ $employee->hire_date ?? '-' }}</div>
</div>
<div class="px-6 py-3 flex" style="flex-wrap: wrap;">
<div class="shrink-0 text-sm font-medium text-gray-500" style="width: 140px;">퇴직일</div>
<div class="text-sm text-gray-900">{{ $employee->resign_date ?? '-' }}</div>
</div>
<div class="px-6 py-3 flex" style="flex-wrap: wrap;">
<div class="shrink-0 text-sm font-medium text-gray-500" style="width: 140px;">재직상태</div>
<div class="text-sm text-gray-900">