refactor: [hr] 사번 필드 제거, 비밀번호 제거, 퇴직일 추가
This commit is contained in:
@@ -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',
|
||||
]);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user