feat(API): 직책/직원/근태 관리 API 개선
- Position 모델: key 필드 추가 및 마이그레이션 - PositionSeeder: 기본 직책 시더 추가 - TenantUserProfile: 프로필 이미지 관련 필드 추가 - Employee Request: 직원 등록/수정 요청 검증 강화 - EmployeeService: 직원 관리 서비스 로직 개선 - AttendanceService: 근태 관리 서비스 개선 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -48,7 +48,7 @@ public function rules(): array
|
||||
'bank_account.bankName' => 'nullable|string|max:50',
|
||||
'bank_account.accountNumber' => 'nullable|string|max:50',
|
||||
'bank_account.accountHolder' => 'nullable|string|max:50',
|
||||
'work_type' => 'nullable|in:regular,daily,temporary,external',
|
||||
'work_type' => 'nullable|in:regular,contract,parttime,intern',
|
||||
'contract_info' => 'nullable|array',
|
||||
'contract_info.start_date' => 'nullable|date',
|
||||
'contract_info.end_date' => 'nullable|date',
|
||||
|
||||
@@ -54,7 +54,7 @@ public function rules(): array
|
||||
'bank_account.bankName' => 'nullable|string|max:50',
|
||||
'bank_account.accountNumber' => 'nullable|string|max:50',
|
||||
'bank_account.accountHolder' => 'nullable|string|max:50',
|
||||
'work_type' => 'nullable|in:regular,daily,temporary,external',
|
||||
'work_type' => 'nullable|in:regular,contract,parttime,intern',
|
||||
'contract_info' => 'nullable|array',
|
||||
'contract_info.start_date' => 'nullable|date',
|
||||
'contract_info.end_date' => 'nullable|date',
|
||||
|
||||
@@ -45,6 +45,15 @@ class Attendance extends Model
|
||||
'deleted_by',
|
||||
];
|
||||
|
||||
/**
|
||||
* JSON 응답에 포함할 accessor
|
||||
*/
|
||||
protected $appends = [
|
||||
'check_in',
|
||||
'check_out',
|
||||
'break_minutes',
|
||||
];
|
||||
|
||||
/**
|
||||
* 기본값 설정
|
||||
*/
|
||||
@@ -85,18 +94,44 @@ public function updater(): BelongsTo
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* 출근 시간
|
||||
* 출근 시간 (가장 빠른 출근)
|
||||
* check_ins 배열이 있으면 가장 빠른 시간 반환, 없으면 레거시 check_in 사용
|
||||
*/
|
||||
public function getCheckInAttribute(): ?string
|
||||
{
|
||||
$checkIns = $this->json_details['check_ins'] ?? [];
|
||||
|
||||
if (! empty($checkIns)) {
|
||||
$times = array_filter(array_map(fn ($entry) => $entry['time'] ?? null, $checkIns));
|
||||
if (! empty($times)) {
|
||||
sort($times);
|
||||
|
||||
return $times[0];
|
||||
}
|
||||
}
|
||||
|
||||
// 레거시 호환: 기존 check_in 필드
|
||||
return $this->json_details['check_in'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 퇴근 시간
|
||||
* 퇴근 시간 (가장 늦은 퇴근)
|
||||
* check_outs 배열이 있으면 가장 늦은 시간 반환, 없으면 레거시 check_out 사용
|
||||
*/
|
||||
public function getCheckOutAttribute(): ?string
|
||||
{
|
||||
$checkOuts = $this->json_details['check_outs'] ?? [];
|
||||
|
||||
if (! empty($checkOuts)) {
|
||||
$times = array_filter(array_map(fn ($entry) => $entry['time'] ?? null, $checkOuts));
|
||||
if (! empty($times)) {
|
||||
rsort($times);
|
||||
|
||||
return $times[0];
|
||||
}
|
||||
}
|
||||
|
||||
// 레거시 호환: 기존 check_out 필드
|
||||
return $this->json_details['check_out'] ?? null;
|
||||
}
|
||||
|
||||
@@ -134,6 +169,50 @@ public function getWorkMinutesAttribute(): ?int
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 휴게 시간 (분 단위)
|
||||
ㅣ * 1. json_details에 저장된 값이 있으면 반환
|
||||
* 2. 없으면 check_in/check_out 기준으로 WorkSetting에서 실시간 계산
|
||||
*/
|
||||
public function getBreakMinutesAttribute(): ?int
|
||||
{
|
||||
// 1. 이미 계산된 값이 있으면 사용
|
||||
if (isset($this->json_details['break_minutes'])) {
|
||||
return (int) $this->json_details['break_minutes'];
|
||||
}
|
||||
|
||||
// 2. 레거시 데이터: check_in, check_out이 있으면 실시간 계산
|
||||
$checkIn = $this->check_in;
|
||||
$checkOut = $this->check_out;
|
||||
|
||||
if (! $checkIn || ! $checkOut) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// WorkSetting에서 휴게시간 설정 조회
|
||||
$workSetting = WorkSetting::where('tenant_id', $this->tenant_id)->first();
|
||||
|
||||
if (! $workSetting || ! $workSetting->break_start || ! $workSetting->break_end) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$checkInCarbon = \Carbon\Carbon::createFromFormat('H:i:s', $checkIn);
|
||||
$checkOutCarbon = \Carbon\Carbon::createFromFormat('H:i:s', $checkOut);
|
||||
$breakStart = \Carbon\Carbon::createFromFormat('H:i:s', $workSetting->break_start);
|
||||
$breakEnd = \Carbon\Carbon::createFromFormat('H:i:s', $workSetting->break_end);
|
||||
|
||||
// 출근~퇴근 시간이 휴게시간을 포함하면 휴게시간 적용
|
||||
if ($checkInCarbon->lte($breakStart) && $checkOutCarbon->gte($breakEnd)) {
|
||||
return (int) $breakStart->diffInMinutes($breakEnd);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 초과 근무 시간 (분 단위)
|
||||
*/
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* @property int $id
|
||||
* @property int $tenant_id
|
||||
* @property string $type rank(직급) | title(직책)
|
||||
* @property string|null $key 영문 키 (tenant_user_profiles 연동용)
|
||||
* @property string $name 명칭
|
||||
* @property int $sort_order 정렬 순서
|
||||
* @property bool $is_active 활성화 여부
|
||||
@@ -26,6 +27,7 @@ class Position extends Model
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'type',
|
||||
'key',
|
||||
'name',
|
||||
'sort_order',
|
||||
'is_active',
|
||||
|
||||
@@ -65,6 +65,26 @@ public function manager(): BelongsTo
|
||||
return $this->belongsTo(User::class, 'manager_user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 직급 (positions 테이블 type=rank)
|
||||
*/
|
||||
public function rankPosition(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Position::class, 'position_key', 'key')
|
||||
->where('type', Position::TYPE_RANK)
|
||||
->where('tenant_id', $this->tenant_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 직책 (positions 테이블 type=title)
|
||||
*/
|
||||
public function titlePosition(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Position::class, 'job_title_key', 'key')
|
||||
->where('type', Position::TYPE_TITLE)
|
||||
->where('tenant_id', $this->tenant_id);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// json_extra 헬퍼 메서드
|
||||
// =========================================================================
|
||||
@@ -151,36 +171,37 @@ public function getEmergencyContactAttribute(): ?string
|
||||
}
|
||||
|
||||
/**
|
||||
* 직책 레이블 (position_key → 한글)
|
||||
* 직급 레이블 (position_key → positions 테이블에서 name 조회)
|
||||
*/
|
||||
public function getPositionLabelAttribute(): ?string
|
||||
{
|
||||
$labels = [
|
||||
'EXECUTIVE' => '임원',
|
||||
'DIRECTOR' => '부장',
|
||||
'MANAGER' => '과장',
|
||||
'SENIOR' => '대리',
|
||||
'STAFF' => '사원',
|
||||
'INTERN' => '인턴',
|
||||
];
|
||||
if (! $this->position_key || ! $this->tenant_id) {
|
||||
return $this->position_key;
|
||||
}
|
||||
|
||||
return $labels[$this->position_key] ?? $this->position_key;
|
||||
$position = Position::where('tenant_id', $this->tenant_id)
|
||||
->where('type', Position::TYPE_RANK)
|
||||
->where('key', $this->position_key)
|
||||
->first();
|
||||
|
||||
return $position?->name ?? $this->position_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 직급 레이블 (job_title_key → 한글)
|
||||
* 직책 레이블 (job_title_key → positions 테이블에서 name 조회)
|
||||
*/
|
||||
public function getJobTitleLabelAttribute(): ?string
|
||||
{
|
||||
$labels = [
|
||||
'CEO' => '대표이사',
|
||||
'CTO' => '기술이사',
|
||||
'CFO' => '재무이사',
|
||||
'TEAM_LEAD' => '팀장',
|
||||
'TEAM_MEMBER' => '팀원',
|
||||
];
|
||||
if (! $this->job_title_key || ! $this->tenant_id) {
|
||||
return $this->job_title_key;
|
||||
}
|
||||
|
||||
return $labels[$this->job_title_key] ?? $this->job_title_key;
|
||||
$position = Position::where('tenant_id', $this->tenant_id)
|
||||
->where('type', Position::TYPE_TITLE)
|
||||
->where('key', $this->job_title_key)
|
||||
->first();
|
||||
|
||||
return $position?->name ?? $this->job_title_key;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -212,6 +212,8 @@ public function bulkDelete(array $ids): array
|
||||
|
||||
/**
|
||||
* 출근 기록 (체크인)
|
||||
* - 모든 출근 기록을 check_ins 배열에 히스토리로 저장
|
||||
* - check_in accessor는 가장 빠른 출근 시간 반환
|
||||
*/
|
||||
public function checkIn(array $data): Attendance
|
||||
{
|
||||
@@ -231,25 +233,32 @@ public function checkIn(array $data): Attendance
|
||||
$checkInTime = $data['check_in'] ?? now()->format('H:i:s');
|
||||
$gpsData = $data['gps_data'] ?? null;
|
||||
|
||||
// 출근 엔트리 생성
|
||||
$entry = [
|
||||
'time' => $checkInTime,
|
||||
'recorded_at' => now()->toIso8601String(),
|
||||
];
|
||||
if ($gpsData) {
|
||||
$entry['gps'] = $gpsData;
|
||||
}
|
||||
|
||||
if ($attendance) {
|
||||
// 기존 기록 업데이트
|
||||
// 기존 기록에 출근 추가
|
||||
$jsonDetails = $attendance->json_details ?? [];
|
||||
$jsonDetails['check_in'] = $checkInTime;
|
||||
if ($gpsData) {
|
||||
$jsonDetails['gps_data'] = array_merge(
|
||||
$jsonDetails['gps_data'] ?? [],
|
||||
['check_in' => $gpsData]
|
||||
);
|
||||
}
|
||||
$checkIns = $jsonDetails['check_ins'] ?? [];
|
||||
$checkIns[] = $entry;
|
||||
$jsonDetails['check_ins'] = $checkIns;
|
||||
|
||||
$attendance->json_details = $jsonDetails;
|
||||
$attendance->status = $this->determineStatusFromEntries($jsonDetails);
|
||||
$attendance->updated_by = $userId;
|
||||
$attendance->save();
|
||||
} else {
|
||||
// 새 기록 생성
|
||||
$jsonDetails = ['check_in' => $checkInTime];
|
||||
if ($gpsData) {
|
||||
$jsonDetails['gps_data'] = ['check_in' => $gpsData];
|
||||
}
|
||||
$jsonDetails = [
|
||||
'check_ins' => [$entry],
|
||||
'check_outs' => [],
|
||||
];
|
||||
|
||||
$attendance = Attendance::create([
|
||||
'tenant_id' => $tenantId,
|
||||
@@ -268,6 +277,8 @@ public function checkIn(array $data): Attendance
|
||||
|
||||
/**
|
||||
* 퇴근 기록 (체크아웃)
|
||||
* - 모든 퇴근 기록을 check_outs 배열에 히스토리로 저장
|
||||
* - check_out accessor는 가장 늦은 퇴근 시간 반환
|
||||
*/
|
||||
public function checkOut(array $data): Attendance
|
||||
{
|
||||
@@ -290,28 +301,54 @@ public function checkOut(array $data): Attendance
|
||||
$checkOutTime = $data['check_out'] ?? now()->format('H:i:s');
|
||||
$gpsData = $data['gps_data'] ?? null;
|
||||
|
||||
$jsonDetails = $attendance->json_details ?? [];
|
||||
$jsonDetails['check_out'] = $checkOutTime;
|
||||
|
||||
// 근무 시간 계산
|
||||
if (isset($jsonDetails['check_in'])) {
|
||||
$checkIn = \Carbon\Carbon::createFromFormat('H:i:s', $jsonDetails['check_in']);
|
||||
$checkOut = \Carbon\Carbon::createFromFormat('H:i:s', $checkOutTime);
|
||||
$jsonDetails['work_minutes'] = $checkOut->diffInMinutes($checkIn);
|
||||
// 퇴근 엔트리 생성
|
||||
$entry = [
|
||||
'time' => $checkOutTime,
|
||||
'recorded_at' => now()->toIso8601String(),
|
||||
];
|
||||
if ($gpsData) {
|
||||
$entry['gps'] = $gpsData;
|
||||
}
|
||||
|
||||
if ($gpsData) {
|
||||
$jsonDetails['gps_data'] = array_merge(
|
||||
$jsonDetails['gps_data'] ?? [],
|
||||
['check_out' => $gpsData]
|
||||
);
|
||||
$jsonDetails = $attendance->json_details ?? [];
|
||||
$checkOuts = $jsonDetails['check_outs'] ?? [];
|
||||
$checkOuts[] = $entry;
|
||||
$jsonDetails['check_outs'] = $checkOuts;
|
||||
|
||||
// 근무 시간 계산 (가장 빠른 출근 ~ 가장 늦은 퇴근)
|
||||
$checkIns = $jsonDetails['check_ins'] ?? [];
|
||||
if (! empty($checkIns)) {
|
||||
$earliestIn = $this->getEarliestTime($checkIns);
|
||||
$latestOut = $this->getLatestTime($checkOuts);
|
||||
if ($earliestIn && $latestOut) {
|
||||
$checkInCarbon = \Carbon\Carbon::createFromFormat('H:i:s', $earliestIn);
|
||||
$checkOutCarbon = \Carbon\Carbon::createFromFormat('H:i:s', $latestOut);
|
||||
$totalMinutes = $checkOutCarbon->diffInMinutes($checkInCarbon);
|
||||
|
||||
// 휴게시간 계산 (근무 설정에서 조회)
|
||||
$workSettingService = app(WorkSettingService::class);
|
||||
$workSetting = $workSettingService->getWorkSetting();
|
||||
$breakMinutes = 0;
|
||||
|
||||
// 설정된 휴게시간이 있으면 적용
|
||||
if ($workSetting->break_start && $workSetting->break_end) {
|
||||
$breakStart = \Carbon\Carbon::createFromFormat('H:i:s', $workSetting->break_start);
|
||||
$breakEnd = \Carbon\Carbon::createFromFormat('H:i:s', $workSetting->break_end);
|
||||
|
||||
// 출근~퇴근 시간이 휴게시간을 포함하면 휴게시간 적용
|
||||
if ($checkInCarbon->lte($breakStart) && $checkOutCarbon->gte($breakEnd)) {
|
||||
$breakMinutes = $breakEnd->diffInMinutes($breakStart);
|
||||
$jsonDetails['break_minutes'] = $breakMinutes;
|
||||
}
|
||||
}
|
||||
|
||||
// 실제 근무시간 = 총 시간 - 휴게시간
|
||||
$jsonDetails['work_minutes'] = $totalMinutes - $breakMinutes;
|
||||
}
|
||||
}
|
||||
|
||||
$attendance->json_details = $jsonDetails;
|
||||
$attendance->status = $this->determineStatus(
|
||||
$jsonDetails['check_in'] ?? null,
|
||||
$checkOutTime
|
||||
);
|
||||
$attendance->status = $this->determineStatusFromEntries($jsonDetails);
|
||||
$attendance->updated_by = $userId;
|
||||
$attendance->save();
|
||||
|
||||
@@ -398,7 +435,8 @@ private function buildJsonDetails(array $data): array
|
||||
|
||||
/**
|
||||
* 상태 자동 결정
|
||||
* 실제 업무에서는 회사별 출근 시간 설정을 참조해야 함
|
||||
* 근무 설정의 출근 시간 기준으로 지각 여부 판단
|
||||
* 출근 시간 설정이 없으면 지각 개념 없음 (항상 정시)
|
||||
*/
|
||||
private function determineStatus(?string $checkIn, ?string $checkOut): string
|
||||
{
|
||||
@@ -406,13 +444,86 @@ private function determineStatus(?string $checkIn, ?string $checkOut): string
|
||||
return 'absent';
|
||||
}
|
||||
|
||||
// 기본 출근 시간: 09:00
|
||||
$standardCheckIn = '09:00:00';
|
||||
// 근무 설정에서 출근 시간 조회
|
||||
$workSettingService = app(WorkSettingService::class);
|
||||
$workSetting = $workSettingService->getWorkSetting();
|
||||
|
||||
if ($checkIn > $standardCheckIn) {
|
||||
// 출근 시간 설정이 없으면 지각 판정 안함
|
||||
if (! $workSetting->start_time) {
|
||||
return 'onTime';
|
||||
}
|
||||
|
||||
if ($checkIn > $workSetting->start_time) {
|
||||
return 'late';
|
||||
}
|
||||
|
||||
return 'onTime';
|
||||
}
|
||||
|
||||
/**
|
||||
* 엔트리 기반 상태 결정
|
||||
* check_ins 배열에서 가장 빠른 시간을 기준으로 상태 판단
|
||||
*/
|
||||
private function determineStatusFromEntries(array $jsonDetails): string
|
||||
{
|
||||
$checkIns = $jsonDetails['check_ins'] ?? [];
|
||||
$checkOuts = $jsonDetails['check_outs'] ?? [];
|
||||
|
||||
if (empty($checkIns)) {
|
||||
return 'absent';
|
||||
}
|
||||
|
||||
$earliestIn = $this->getEarliestTime($checkIns);
|
||||
$latestOut = ! empty($checkOuts) ? $this->getLatestTime($checkOuts) : null;
|
||||
|
||||
return $this->determineStatus($earliestIn, $latestOut);
|
||||
}
|
||||
|
||||
/**
|
||||
* 엔트리 배열에서 가장 빠른 시간 추출
|
||||
*/
|
||||
private function getEarliestTime(array $entries): ?string
|
||||
{
|
||||
if (empty($entries)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$times = array_map(function ($entry) {
|
||||
return $entry['time'] ?? null;
|
||||
}, $entries);
|
||||
|
||||
$times = array_filter($times);
|
||||
|
||||
if (empty($times)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
sort($times);
|
||||
|
||||
return $times[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 엔트리 배열에서 가장 늦은 시간 추출
|
||||
*/
|
||||
private function getLatestTime(array $entries): ?string
|
||||
{
|
||||
if (empty($entries)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$times = array_map(function ($entry) {
|
||||
return $entry['time'] ?? null;
|
||||
}, $entries);
|
||||
|
||||
$times = array_filter($times);
|
||||
|
||||
if (empty($times)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
rsort($times);
|
||||
|
||||
return $times[0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public function index(array $params): LengthAwarePaginator
|
||||
|
||||
$query = TenantUserProfile::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->with(['user', 'department', 'manager']);
|
||||
->with(['user', 'department', 'manager', 'rankPosition', 'titlePosition']);
|
||||
|
||||
// 검색 (이름, 이메일, 사원코드)
|
||||
if (! empty($params['q'])) {
|
||||
@@ -77,7 +77,7 @@ public function show(int $id): TenantUserProfile
|
||||
|
||||
$profile = TenantUserProfile::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->with(['user', 'department', 'manager'])
|
||||
->with(['user', 'department', 'manager', 'rankPosition', 'titlePosition'])
|
||||
->find($id);
|
||||
|
||||
if (! $profile) {
|
||||
@@ -155,7 +155,7 @@ public function store(array $data): TenantUserProfile
|
||||
]);
|
||||
$profile->save();
|
||||
|
||||
return $profile->fresh(['user', 'department', 'manager']);
|
||||
return $profile->fresh(['user', 'department', 'manager', 'rankPosition', 'titlePosition']);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ public function update(int $id, array $data): TenantUserProfile
|
||||
$profile->save();
|
||||
}
|
||||
|
||||
return $profile->fresh(['user', 'department', 'manager']);
|
||||
return $profile->fresh(['user', 'department', 'manager', 'rankPosition', 'titlePosition']);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -339,7 +339,7 @@ public function createAccount(int $id, string $password): TenantUserProfile
|
||||
'updated_by' => $userId,
|
||||
]);
|
||||
|
||||
return $profile->fresh(['user', 'department', 'manager']);
|
||||
return $profile->fresh(['user', 'department', 'manager', 'rankPosition', 'titlePosition']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -376,7 +376,7 @@ public function revokeAccount(int $id): TenantUserProfile
|
||||
// 2. 기존 토큰 무효화 (로그아웃 처리)
|
||||
$user->tokens()->delete();
|
||||
|
||||
return $profile->fresh(['user', 'department', 'manager']);
|
||||
return $profile->fresh(['user', 'department', 'manager', 'rankPosition', 'titlePosition']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('positions', function (Blueprint $table) {
|
||||
$table->string('key', 64)->nullable()->after('type')->comment('영문 키 (tenant_user_profiles 연동용)');
|
||||
$table->unique(['tenant_id', 'type', 'key'], 'positions_tenant_type_key_unique');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('positions', function (Blueprint $table) {
|
||||
$table->dropUnique('positions_tenant_type_key_unique');
|
||||
$table->dropColumn('key');
|
||||
});
|
||||
}
|
||||
};
|
||||
76
database/seeders/PositionSeeder.php
Normal file
76
database/seeders/PositionSeeder.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Tenants\Position;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class PositionSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
* 직급(rank)과 직책(title) 기본 데이터 생성
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$tenantId = 287; // 프론트_테스트회사
|
||||
|
||||
// 직급 (rank) - 기존 tenant_user_profiles.position_key와 매핑
|
||||
$ranks = [
|
||||
['key' => 'STAFF', 'name' => '사원', 'sort_order' => 1],
|
||||
['key' => 'SENIOR', 'name' => '주임', 'sort_order' => 2],
|
||||
['key' => 'ASSISTANT_MANAGER', 'name' => '대리', 'sort_order' => 3],
|
||||
['key' => 'MANAGER', 'name' => '과장', 'sort_order' => 4],
|
||||
['key' => 'DEPUTY_MANAGER', 'name' => '차장', 'sort_order' => 5],
|
||||
['key' => 'DIRECTOR', 'name' => '부장', 'sort_order' => 6],
|
||||
['key' => 'EXECUTIVE', 'name' => '이사', 'sort_order' => 7],
|
||||
['key' => 'SENIOR_EXECUTIVE', 'name' => '상무', 'sort_order' => 8],
|
||||
['key' => 'VICE_PRESIDENT', 'name' => '전무', 'sort_order' => 9],
|
||||
['key' => 'CEO', 'name' => '대표', 'sort_order' => 10],
|
||||
];
|
||||
|
||||
// 직책 (title)
|
||||
$titles = [
|
||||
['key' => 'MEMBER', 'name' => '팀원', 'sort_order' => 1],
|
||||
['key' => 'PART_LEADER', 'name' => '파트장', 'sort_order' => 2],
|
||||
['key' => 'TEAM_LEADER', 'name' => '팀장', 'sort_order' => 3],
|
||||
['key' => 'DEPARTMENT_HEAD', 'name' => '실장', 'sort_order' => 4],
|
||||
['key' => 'DIVISION_HEAD', 'name' => '본부장', 'sort_order' => 5],
|
||||
['key' => 'CEO', 'name' => '대표이사', 'sort_order' => 6],
|
||||
];
|
||||
|
||||
// 직급 생성
|
||||
foreach ($ranks as $rank) {
|
||||
Position::updateOrCreate(
|
||||
[
|
||||
'tenant_id' => $tenantId,
|
||||
'type' => 'rank',
|
||||
'key' => $rank['key'],
|
||||
],
|
||||
[
|
||||
'name' => $rank['name'],
|
||||
'sort_order' => $rank['sort_order'],
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// 직책 생성
|
||||
foreach ($titles as $title) {
|
||||
Position::updateOrCreate(
|
||||
[
|
||||
'tenant_id' => $tenantId,
|
||||
'type' => 'title',
|
||||
'key' => $title['key'],
|
||||
],
|
||||
[
|
||||
'name' => $title['name'],
|
||||
'sort_order' => $title['sort_order'],
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$this->command->info('Positions seeded: ' . count($ranks) . ' ranks, ' . count($titles) . ' titles');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user