docs: plans 폴더 추가 및 HR API 규칙 문서 정리
- plans/ 폴더 신규 생성 (개발 계획 임시 문서용) - hr-api-react-sync-plan.md를 specs → plans로 이동 - INDEX.md 업데이트 (폴더 구조, 워크플로우) - rules/ HR API 규칙 문서 추가 (employee, attendance, department-tree) - pricing API 요청 문서 업데이트
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
|
||||
| 문서 | 설명 |
|
||||
|------|------|
|
||||
| *(작성 예정)* | |
|
||||
| [pricing-policy.md](pricing-policy.md) | 단가 정책 (원가/판매가 계산, 리비전 관리) |
|
||||
|
||||
## 관련 폴더
|
||||
- [standards/](../standards/) - 개발 표준 (어떻게 코드를 작성할 것인가)
|
||||
|
||||
220
rules/attendance-api.md
Normal file
220
rules/attendance-api.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# Attendance API (근태관리 API) 규칙
|
||||
|
||||
## 개요
|
||||
|
||||
근태관리 API는 테넌트 내 사용자의 출퇴근 및 근태 정보를 관리하는 API입니다.
|
||||
`attendances` 테이블을 사용하며, 상세 출퇴근 정보는 `json_details` 필드에 저장합니다.
|
||||
|
||||
## 핵심 모델
|
||||
|
||||
### Attendance
|
||||
|
||||
- **위치**: `App\Models\Tenants\Attendance`
|
||||
- **역할**: 일별 근태 기록
|
||||
- **특징**:
|
||||
- `BelongsToTenant` 트레이트 사용 (멀티테넌트 자동 스코핑)
|
||||
- `SoftDeletes` 적용
|
||||
- `json_details` 필드에 상세 출퇴근 정보 저장
|
||||
|
||||
## 엔드포인트
|
||||
|
||||
| Method | Path | 설명 |
|
||||
|--------|------|------|
|
||||
| GET | `/v1/attendances` | 근태 목록 조회 |
|
||||
| GET | `/v1/attendances/{id}` | 근태 상세 조회 |
|
||||
| POST | `/v1/attendances` | 근태 등록 |
|
||||
| PATCH | `/v1/attendances/{id}` | 근태 수정 |
|
||||
| DELETE | `/v1/attendances/{id}` | 근태 삭제 |
|
||||
| DELETE | `/v1/attendances/bulk` | 근태 일괄 삭제 |
|
||||
| POST | `/v1/attendances/check-in` | 출근 기록 |
|
||||
| POST | `/v1/attendances/check-out` | 퇴근 기록 |
|
||||
| GET | `/v1/attendances/monthly-stats` | 월간 통계 |
|
||||
|
||||
## 데이터 구조
|
||||
|
||||
### 기본 필드
|
||||
|
||||
| 필드 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | int | PK |
|
||||
| `tenant_id` | int | 테넌트 ID |
|
||||
| `user_id` | int | 사용자 ID (FK → users) |
|
||||
| `base_date` | date | 기준 일자 |
|
||||
| `status` | string | 근태 상태 |
|
||||
| `json_details` | json | 상세 출퇴근 정보 |
|
||||
| `remarks` | string | 비고 (500자 제한) |
|
||||
| `created_by` | int | 생성자 |
|
||||
| `updated_by` | int | 수정자 |
|
||||
| `deleted_by` | int | 삭제자 |
|
||||
| `deleted_at` | timestamp | Soft Delete |
|
||||
|
||||
### 근태 상태 (status)
|
||||
|
||||
| 상태 | 설명 |
|
||||
|------|------|
|
||||
| `onTime` | 정상 출근 (기본값) |
|
||||
| `late` | 지각 |
|
||||
| `absent` | 결근 |
|
||||
| `vacation` | 휴가 |
|
||||
| `businessTrip` | 출장 |
|
||||
| `fieldWork` | 외근 |
|
||||
| `overtime` | 야근 |
|
||||
| `remote` | 재택근무 |
|
||||
|
||||
### json_details 필드 구조
|
||||
|
||||
```json
|
||||
{
|
||||
"check_in": "09:00:00",
|
||||
"check_out": "18:00:00",
|
||||
"gps_data": {
|
||||
"check_in": {
|
||||
"lat": 37.5665,
|
||||
"lng": 126.9780,
|
||||
"accuracy": 10
|
||||
},
|
||||
"check_out": {
|
||||
"lat": 37.5665,
|
||||
"lng": 126.9780,
|
||||
"accuracy": 10
|
||||
}
|
||||
},
|
||||
"external_work": {
|
||||
"location": "고객사",
|
||||
"purpose": "미팅",
|
||||
"start_time": "14:00:00",
|
||||
"end_time": "16:00:00"
|
||||
},
|
||||
"multiple_entries": [
|
||||
{ "in": "09:00:00", "out": "12:00:00" },
|
||||
{ "in": "13:00:00", "out": "18:00:00" }
|
||||
],
|
||||
"work_minutes": 480,
|
||||
"overtime_minutes": 60,
|
||||
"late_minutes": 30,
|
||||
"early_leave_minutes": 0,
|
||||
"vacation_type": "annual|half|sick"
|
||||
}
|
||||
```
|
||||
|
||||
### 허용된 json_details 키
|
||||
|
||||
```php
|
||||
$allowedKeys = [
|
||||
'check_in', // 출근 시간 (HH:MM:SS)
|
||||
'check_out', // 퇴근 시간 (HH:MM:SS)
|
||||
'gps_data', // GPS 데이터 (출퇴근 위치)
|
||||
'external_work', // 외근 정보
|
||||
'multiple_entries', // 다중 출퇴근 기록
|
||||
'work_minutes', // 총 근무 시간 (분)
|
||||
'overtime_minutes', // 초과 근무 시간 (분)
|
||||
'late_minutes', // 지각 시간 (분)
|
||||
'early_leave_minutes',// 조퇴 시간 (분)
|
||||
'vacation_type', // 휴가 유형
|
||||
];
|
||||
```
|
||||
|
||||
## 비즈니스 규칙
|
||||
|
||||
### 출근 기록 (check-in)
|
||||
|
||||
1. 오늘 기록이 있으면 업데이트, 없으면 새로 생성
|
||||
2. `check_in` 시간과 GPS 데이터 저장
|
||||
3. 출근 시간 기준으로 상태 자동 결정 (09:00 기준 지각 판단)
|
||||
|
||||
```php
|
||||
// 상태 자동 결정 로직
|
||||
if ($checkIn > '09:00:00') {
|
||||
$status = 'late';
|
||||
} else {
|
||||
$status = 'onTime';
|
||||
}
|
||||
```
|
||||
|
||||
### 퇴근 기록 (check-out)
|
||||
|
||||
1. 오늘 출근 기록이 없으면 에러 반환
|
||||
2. `check_out` 시간과 GPS 데이터 저장
|
||||
3. 근무 시간(work_minutes) 자동 계산
|
||||
|
||||
```php
|
||||
// 근무 시간 계산
|
||||
$checkIn = Carbon::createFromFormat('H:i:s', $jsonDetails['check_in']);
|
||||
$checkOut = Carbon::createFromFormat('H:i:s', $checkOutTime);
|
||||
$jsonDetails['work_minutes'] = $checkOut->diffInMinutes($checkIn);
|
||||
```
|
||||
|
||||
### 근태 등록 (store)
|
||||
|
||||
1. 같은 날 같은 사용자 기록이 있으면 에러 반환
|
||||
2. `json_details` 직접 전달 또는 개별 필드에서 구성
|
||||
|
||||
```php
|
||||
// json_details 처리 방식
|
||||
$jsonDetails = isset($data['json_details']) && is_array($data['json_details'])
|
||||
? $data['json_details']
|
||||
: $this->buildJsonDetails($data);
|
||||
```
|
||||
|
||||
### 월간 통계 (monthly-stats)
|
||||
|
||||
통계 항목:
|
||||
- 총 근무일수
|
||||
- 상태별 일수 (정상, 지각, 결근, 휴가, 출장, 외근, 야근, 재택)
|
||||
- 총 근무 시간 (분)
|
||||
- 총 초과 근무 시간 (분)
|
||||
|
||||
## 검색/필터 파라미터
|
||||
|
||||
| 파라미터 | 타입 | 설명 |
|
||||
|----------|------|------|
|
||||
| `user_id` | int | 사용자 필터 |
|
||||
| `date` | date | 특정 날짜 필터 |
|
||||
| `date_from` | date | 시작 날짜 |
|
||||
| `date_to` | date | 종료 날짜 |
|
||||
| `status` | string | 근태 상태 필터 |
|
||||
| `department_id` | int | 부서 필터 (사용자의 부서) |
|
||||
| `sort_by` | string | 정렬 기준 (기본: base_date) |
|
||||
| `sort_dir` | string | 정렬 방향 (기본: desc) |
|
||||
| `per_page` | int | 페이지당 항목 수 (기본: 20) |
|
||||
|
||||
## 관계 (Relationships)
|
||||
|
||||
```php
|
||||
public function user(): BelongsTo // 사용자 정보
|
||||
public function creator(): BelongsTo // 생성자
|
||||
public function updater(): BelongsTo // 수정자
|
||||
```
|
||||
|
||||
## 스코프 (Scopes)
|
||||
|
||||
```php
|
||||
$query->onDate('2024-01-15'); // 특정 날짜
|
||||
$query->betweenDates('2024-01-01', '2024-01-31'); // 날짜 범위
|
||||
$query->forUser(123); // 특정 사용자
|
||||
$query->withStatus('late'); // 특정 상태
|
||||
```
|
||||
|
||||
## Accessor
|
||||
|
||||
```php
|
||||
$attendance->check_in; // json_details['check_in']
|
||||
$attendance->check_out; // json_details['check_out']
|
||||
$attendance->gps_data; // json_details['gps_data']
|
||||
$attendance->external_work; // json_details['external_work']
|
||||
$attendance->multiple_entries; // json_details['multiple_entries']
|
||||
$attendance->work_minutes; // json_details['work_minutes']
|
||||
$attendance->overtime_minutes; // json_details['overtime_minutes']
|
||||
$attendance->late_minutes; // json_details['late_minutes']
|
||||
$attendance->early_leave_minutes;// json_details['early_leave_minutes']
|
||||
$attendance->vacation_type; // json_details['vacation_type']
|
||||
```
|
||||
|
||||
## 주의사항
|
||||
|
||||
1. **중복 방지**: 같은 날짜 + 같은 사용자 조합은 유일해야 함
|
||||
2. **멀티테넌트**: BelongsToTenant 트레이트로 자동 스코핑
|
||||
3. **Soft Delete**: deleted_by 기록 후 삭제
|
||||
4. **Audit**: created_by/updated_by 자동 기록
|
||||
5. **시간 형식**: check_in/check_out은 HH:MM:SS 형식
|
||||
6. **표준 출근 시간**: 기본 09:00:00 (회사별 설정 필요)
|
||||
258
rules/department-tree-api.md
Normal file
258
rules/department-tree-api.md
Normal file
@@ -0,0 +1,258 @@
|
||||
# Department Tree API (부서트리 조회 API) 규칙
|
||||
|
||||
## 개요
|
||||
|
||||
부서트리 API는 테넌트 내 조직도를 계층 구조로 조회하는 API입니다.
|
||||
`departments` 테이블의 `parent_id`를 통한 자기참조 관계로 무한 depth 계층 구조를 지원합니다.
|
||||
|
||||
## 핵심 모델
|
||||
|
||||
### Department
|
||||
|
||||
- **위치**: `App\Models\Tenants\Department`
|
||||
- **역할**: 부서/조직 정보
|
||||
- **특징**:
|
||||
- `parent_id` 자기참조로 계층 구조
|
||||
- `HasRoles` 트레이트 (부서도 권한/역할 보유 가능)
|
||||
- `ModelTrait` 적용 (is_active, 날짜 처리)
|
||||
|
||||
## 엔드포인트
|
||||
|
||||
### 부서 트리 전용
|
||||
|
||||
| Method | Path | 설명 |
|
||||
|--------|------|------|
|
||||
| GET | `/v1/departments/tree` | 부서 트리 조회 |
|
||||
|
||||
### 기본 CRUD (참고)
|
||||
|
||||
| Method | Path | 설명 |
|
||||
|--------|------|------|
|
||||
| GET | `/v1/departments` | 부서 목록 조회 |
|
||||
| GET | `/v1/departments/{id}` | 부서 상세 조회 |
|
||||
| POST | `/v1/departments` | 부서 생성 |
|
||||
| PATCH | `/v1/departments/{id}` | 부서 수정 |
|
||||
| DELETE | `/v1/departments/{id}` | 부서 삭제 |
|
||||
|
||||
### 부서-사용자 관리
|
||||
|
||||
| Method | Path | 설명 |
|
||||
|--------|------|------|
|
||||
| GET | `/v1/departments/{id}/users` | 부서 사용자 목록 |
|
||||
| POST | `/v1/departments/{id}/users` | 사용자 배정 |
|
||||
| DELETE | `/v1/departments/{id}/users/{user}` | 사용자 제거 |
|
||||
| PATCH | `/v1/departments/{id}/users/{user}/primary` | 주부서 설정 |
|
||||
|
||||
## 데이터 구조
|
||||
|
||||
### 기본 필드
|
||||
|
||||
| 필드 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `id` | int | PK |
|
||||
| `tenant_id` | int | 테넌트 ID |
|
||||
| `parent_id` | int | 상위 부서 ID (nullable, 최상위는 null) |
|
||||
| `code` | string | 부서 코드 (unique) |
|
||||
| `name` | string | 부서명 |
|
||||
| `description` | string | 부서 설명 |
|
||||
| `is_active` | bool | 활성화 상태 |
|
||||
| `sort_order` | int | 정렬 순서 |
|
||||
| `created_by` | int | 생성자 |
|
||||
| `updated_by` | int | 수정자 |
|
||||
| `deleted_by` | int | 삭제자 |
|
||||
|
||||
### 트리 응답 구조
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"tenant_id": 1,
|
||||
"parent_id": null,
|
||||
"code": "DEPT001",
|
||||
"name": "경영지원본부",
|
||||
"is_active": true,
|
||||
"sort_order": 1,
|
||||
"children": [
|
||||
{
|
||||
"id": 2,
|
||||
"tenant_id": 1,
|
||||
"parent_id": 1,
|
||||
"code": "DEPT002",
|
||||
"name": "인사팀",
|
||||
"is_active": true,
|
||||
"sort_order": 1,
|
||||
"children": [],
|
||||
"users": []
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"tenant_id": 1,
|
||||
"parent_id": 1,
|
||||
"code": "DEPT003",
|
||||
"name": "재무팀",
|
||||
"is_active": true,
|
||||
"sort_order": 2,
|
||||
"children": [],
|
||||
"users": []
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
{ "id": 1, "name": "홍길동", "email": "hong@example.com" }
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 트리 조회 로직
|
||||
|
||||
### tree() 메서드 구현
|
||||
|
||||
```php
|
||||
public function tree(array $params = []): array
|
||||
{
|
||||
// 1. 파라미터 검증
|
||||
$withUsers = filter_var($params['with_users'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
// 2. 최상위 부서 조회 (parent_id가 null)
|
||||
$query = Department::query()
|
||||
->whereNull('parent_id')
|
||||
->orderBy('sort_order')
|
||||
->orderBy('name');
|
||||
|
||||
// 3. 재귀적으로 자식 부서 로드
|
||||
$query->with(['children' => function ($q) use ($withUsers) {
|
||||
$q->orderBy('sort_order')->orderBy('name');
|
||||
$this->loadChildrenRecursive($q, $withUsers);
|
||||
}]);
|
||||
|
||||
// 4. 사용자 포함 옵션
|
||||
if ($withUsers) {
|
||||
$query->with(['users:id,name,email']);
|
||||
}
|
||||
|
||||
return $query->get()->toArray();
|
||||
}
|
||||
|
||||
// 재귀 로딩 헬퍼
|
||||
private function loadChildrenRecursive($query, bool $withUsers): void
|
||||
{
|
||||
$query->with(['children' => function ($q) use ($withUsers) {
|
||||
$q->orderBy('sort_order')->orderBy('name');
|
||||
$this->loadChildrenRecursive($q, $withUsers);
|
||||
}]);
|
||||
|
||||
if ($withUsers) {
|
||||
$query->with(['users:id,name,email']);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 정렬 규칙
|
||||
|
||||
1. `sort_order` 오름차순
|
||||
2. `name` 오름차순 (동일 sort_order일 때)
|
||||
|
||||
## 요청 파라미터
|
||||
|
||||
### GET /v1/departments/tree
|
||||
|
||||
| 파라미터 | 타입 | 기본값 | 설명 |
|
||||
|----------|------|--------|------|
|
||||
| `with_users` | bool | false | 부서별 사용자 목록 포함 |
|
||||
|
||||
### 예시
|
||||
|
||||
```bash
|
||||
# 기본 트리 조회
|
||||
GET /v1/departments/tree
|
||||
|
||||
# 사용자 포함 트리 조회
|
||||
GET /v1/departments/tree?with_users=1
|
||||
```
|
||||
|
||||
## 관계 (Relationships)
|
||||
|
||||
```php
|
||||
public function parent(): BelongsTo // 상위 부서
|
||||
public function children() // 하위 부서들 (HasMany)
|
||||
public function users() // 소속 사용자들 (BelongsToMany)
|
||||
public function departmentUsers() // 부서-사용자 pivot (HasMany)
|
||||
public function permissionOverrides() // 권한 오버라이드 (MorphMany)
|
||||
```
|
||||
|
||||
## 부서-사용자 관계 (Pivot)
|
||||
|
||||
### department_user 테이블
|
||||
|
||||
| 필드 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `department_id` | int | 부서 ID |
|
||||
| `user_id` | int | 사용자 ID |
|
||||
| `tenant_id` | int | 테넌트 ID |
|
||||
| `is_primary` | bool | 주부서 여부 |
|
||||
| `joined_at` | timestamp | 배정일 |
|
||||
| `left_at` | timestamp | 해제일 |
|
||||
| `deleted_at` | timestamp | Soft Delete |
|
||||
|
||||
### 주부서 규칙
|
||||
|
||||
- 한 사용자는 여러 부서에 소속 가능
|
||||
- 주부서(`is_primary`)는 사용자당 1개만 가능
|
||||
- 주부서 설정 시 기존 주부서는 자동 해제
|
||||
|
||||
## 권한 관리
|
||||
|
||||
### 부서 권한 시스템
|
||||
|
||||
부서는 Spatie Permission과 연동되어 권한을 가질 수 있습니다.
|
||||
|
||||
- **ALLOW**: `model_has_permissions` 테이블
|
||||
- **DENY**: `permission_overrides` 테이블 (effect: -1)
|
||||
|
||||
### 관련 엔드포인트
|
||||
|
||||
| Method | Path | 설명 |
|
||||
|--------|------|------|
|
||||
| GET | `/v1/departments/{id}/permissions` | 부서 권한 목록 |
|
||||
| POST | `/v1/departments/{id}/permissions` | 권한 부여/차단 |
|
||||
| DELETE | `/v1/departments/{id}/permissions/{permission}` | 권한 제거 |
|
||||
|
||||
## 주의사항
|
||||
|
||||
1. **무한 재귀 방지**: Eloquent eager loading으로 처리, 별도 depth 제한 없음
|
||||
2. **성능 고려**: 대규모 조직도의 경우 `with_users` 사용 시 응답 시간 증가
|
||||
3. **정렬 일관성**: 모든 레벨에서 동일한 정렬 규칙 적용
|
||||
4. **멀티테넌트**: tenant_id 기반 자동 스코핑
|
||||
5. **주부서 제약**: 사용자당 주부서 1개만 허용
|
||||
6. **Soft Delete**: department_user pivot도 Soft Delete 적용
|
||||
|
||||
## 트리 구축 예시
|
||||
|
||||
### 조직도 예시
|
||||
|
||||
```
|
||||
경영지원본부 (parent_id: null)
|
||||
├── 인사팀 (parent_id: 1)
|
||||
│ ├── 채용파트 (parent_id: 2)
|
||||
│ └── 교육파트 (parent_id: 2)
|
||||
├── 재무팀 (parent_id: 1)
|
||||
└── 총무팀 (parent_id: 1)
|
||||
|
||||
개발본부 (parent_id: null)
|
||||
├── 프론트엔드팀 (parent_id: 4)
|
||||
├── 백엔드팀 (parent_id: 4)
|
||||
└── QA팀 (parent_id: 4)
|
||||
```
|
||||
|
||||
### SQL 예시 (데이터 삽입)
|
||||
|
||||
```sql
|
||||
-- 최상위 부서
|
||||
INSERT INTO departments (tenant_id, parent_id, code, name, sort_order)
|
||||
VALUES (1, NULL, 'HQ', '경영지원본부', 1);
|
||||
|
||||
-- 하위 부서
|
||||
INSERT INTO departments (tenant_id, parent_id, code, name, sort_order)
|
||||
VALUES (1, 1, 'HR', '인사팀', 1);
|
||||
```
|
||||
181
rules/employee-api.md
Normal file
181
rules/employee-api.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# Employee API (사원관리 API) 규칙
|
||||
|
||||
## 개요
|
||||
|
||||
사원관리 API는 테넌트 내 사원 정보를 관리하는 API입니다.
|
||||
`users` 테이블과 `tenant_user_profiles` 테이블을 조합하여 사원 정보를 구성합니다.
|
||||
|
||||
## 핵심 모델
|
||||
|
||||
### TenantUserProfile
|
||||
|
||||
- **위치**: `App\Models\Tenants\TenantUserProfile`
|
||||
- **역할**: 테넌트별 사용자 프로필 (사원 정보)
|
||||
- **특징**: `json_extra` 필드에 사원 상세 정보 저장
|
||||
|
||||
### User
|
||||
|
||||
- **위치**: `App\Models\Members\User`
|
||||
- **역할**: 기본 사용자 계정 (이름, 이메일, 비밀번호)
|
||||
|
||||
## 엔드포인트
|
||||
|
||||
| Method | Path | 설명 |
|
||||
|--------|------|------|
|
||||
| GET | `/v1/employees` | 사원 목록 조회 |
|
||||
| GET | `/v1/employees/{id}` | 사원 상세 조회 |
|
||||
| POST | `/v1/employees` | 사원 등록 |
|
||||
| PATCH | `/v1/employees/{id}` | 사원 수정 |
|
||||
| DELETE | `/v1/employees/{id}` | 사원 삭제 (상태 변경) |
|
||||
| DELETE | `/v1/employees/bulk` | 사원 일괄 삭제 |
|
||||
| GET | `/v1/employees/stats` | 사원 통계 |
|
||||
| POST | `/v1/employees/{id}/account` | 시스템 계정 생성 |
|
||||
|
||||
## 데이터 구조
|
||||
|
||||
### 기본 필드 (TenantUserProfile)
|
||||
|
||||
| 필드 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| `tenant_id` | int | 테넌트 ID |
|
||||
| `user_id` | int | 사용자 ID (FK → users) |
|
||||
| `department_id` | int | 부서 ID (nullable) |
|
||||
| `position_key` | string | 직위 코드 |
|
||||
| `job_title_key` | string | 직책 코드 |
|
||||
| `work_location_key` | string | 근무지 코드 |
|
||||
| `employment_type_key` | string | 고용 형태 코드 |
|
||||
| `employee_status` | string | 고용 상태 (active/leave/resigned) |
|
||||
| `manager_user_id` | int | 상위 관리자 ID (nullable) |
|
||||
| `profile_photo_path` | string | 프로필 사진 경로 |
|
||||
| `display_name` | string | 표시명 |
|
||||
| `json_extra` | json | 확장 사원 정보 |
|
||||
|
||||
### json_extra 필드 구조
|
||||
|
||||
```json
|
||||
{
|
||||
"employee_code": "EMP001",
|
||||
"resident_number": "encrypted_value",
|
||||
"gender": "male|female",
|
||||
"address": "서울시 강남구...",
|
||||
"salary": 5000000,
|
||||
"hire_date": "2024-01-15",
|
||||
"rank": "대리",
|
||||
"bank_account": {
|
||||
"bank": "국민은행",
|
||||
"account": "123-456-789",
|
||||
"holder": "홍길동"
|
||||
},
|
||||
"work_type": "regular|contract|part_time",
|
||||
"contract_info": {
|
||||
"start_date": "2024-01-15",
|
||||
"end_date": "2025-01-14"
|
||||
},
|
||||
"emergency_contact": {
|
||||
"name": "김부모",
|
||||
"phone": "010-1234-5678",
|
||||
"relation": "부모"
|
||||
},
|
||||
"education": [],
|
||||
"certifications": []
|
||||
}
|
||||
```
|
||||
|
||||
### 허용된 json_extra 키
|
||||
|
||||
```php
|
||||
$allowedKeys = [
|
||||
'employee_code', // 사원번호
|
||||
'resident_number', // 주민등록번호 (암호화 필수)
|
||||
'gender', // 성별
|
||||
'address', // 주소
|
||||
'salary', // 급여
|
||||
'hire_date', // 입사일
|
||||
'rank', // 직급
|
||||
'bank_account', // 급여계좌
|
||||
'work_type', // 근무유형
|
||||
'contract_info', // 계약 정보
|
||||
'emergency_contact', // 비상연락처
|
||||
'education', // 학력
|
||||
'certifications', // 자격증
|
||||
];
|
||||
```
|
||||
|
||||
## 비즈니스 규칙
|
||||
|
||||
### 사원 등록 (store)
|
||||
|
||||
1. `users` 테이블에 사용자 생성
|
||||
2. `user_tenants` pivot에 관계 추가 (is_default: true)
|
||||
3. `tenant_user_profiles` 생성
|
||||
4. `json_extra`에 사원 정보 설정
|
||||
|
||||
```php
|
||||
// 자동 생성되는 user_id 형식
|
||||
$userId = strtolower(explode('@', $email)[0] . '_' . Str::random(4));
|
||||
```
|
||||
|
||||
### 사원 삭제 (destroy)
|
||||
|
||||
- **Hard Delete 하지 않음**
|
||||
- `employee_status`를 `resigned`로 변경
|
||||
- 사용자 계정은 유지됨
|
||||
|
||||
### 사원 상태 (employee_status)
|
||||
|
||||
| 상태 | 설명 |
|
||||
|------|------|
|
||||
| `active` | 재직 중 |
|
||||
| `leave` | 휴직 |
|
||||
| `resigned` | 퇴사 |
|
||||
|
||||
### 시스템 계정 (has_account)
|
||||
|
||||
- 시스템 계정 = `users.password`가 NULL이 아닌 경우
|
||||
- `POST /employees/{id}/account`로 비밀번호 설정 시 계정 생성
|
||||
- 첫 로그인 시 비밀번호 변경 필요 (`must_change_password: true`)
|
||||
|
||||
## 검색/필터 파라미터
|
||||
|
||||
| 파라미터 | 타입 | 설명 |
|
||||
|----------|------|------|
|
||||
| `q` | string | 이름/이메일/사원코드 검색 |
|
||||
| `status` | string | 고용 상태 필터 |
|
||||
| `department_id` | int | 부서 필터 |
|
||||
| `has_account` | bool | 시스템 계정 보유 여부 |
|
||||
| `sort_by` | string | 정렬 기준 (기본: created_at) |
|
||||
| `sort_dir` | string | 정렬 방향 (asc/desc) |
|
||||
| `per_page` | int | 페이지당 항목 수 (기본: 20) |
|
||||
|
||||
## 관계 (Relationships)
|
||||
|
||||
```php
|
||||
// TenantUserProfile
|
||||
public function user(): BelongsTo // 기본 사용자 정보
|
||||
public function department(): BelongsTo // 소속 부서
|
||||
public function manager(): BelongsTo // 상위 관리자
|
||||
```
|
||||
|
||||
## 스코프 (Scopes)
|
||||
|
||||
```php
|
||||
$query->active(); // employee_status = 'active'
|
||||
$query->onLeave(); // employee_status = 'leave'
|
||||
$query->resigned(); // employee_status = 'resigned'
|
||||
```
|
||||
|
||||
## Accessor
|
||||
|
||||
```php
|
||||
$profile->employee_code; // json_extra['employee_code']
|
||||
$profile->hire_date; // json_extra['hire_date']
|
||||
$profile->address; // json_extra['address']
|
||||
$profile->emergency_contact; // json_extra['emergency_contact']
|
||||
```
|
||||
|
||||
## 주의사항
|
||||
|
||||
1. **주민등록번호**: 반드시 암호화하여 저장
|
||||
2. **멀티테넌트**: tenant_id 자동 스코핑
|
||||
3. **Audit**: created_by/updated_by 자동 기록
|
||||
4. **삭제**: Hard Delete 금지, employee_status 변경으로 처리
|
||||
Reference in New Issue
Block a user