Files
sam-api/app/Models/Tenants/Attendance.php
hskwon f1f4c52c31 feat: 근태관리/직원관리 API 구현
- AttendanceController, AttendanceService 추가
- EmployeeController, EmployeeService 추가
- Attendance 모델 및 마이그레이션 추가
- TenantUserProfile에 employee_status 컬럼 추가
- DepartmentService 트리 조회 기능 개선
- Swagger 문서 추가 (AttendanceApi, EmployeeApi)
- API 라우트 등록
2025-12-09 20:27:44 +09:00

269 lines
6.5 KiB
PHP

<?php
namespace App\Models\Tenants;
use App\Models\Members\User;
use App\Traits\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* 근태 기록 모델
*
* @property int $id
* @property int $tenant_id
* @property int $user_id
* @property string $base_date
* @property string $status
* @property array|null $json_details
* @property string|null $remarks
* @property int|null $created_by
* @property int|null $updated_by
* @property int|null $deleted_by
*/
class Attendance extends Model
{
use BelongsToTenant, SoftDeletes;
protected $table = 'attendances';
protected $casts = [
'json_details' => 'array',
'base_date' => 'date',
];
protected $fillable = [
'tenant_id',
'user_id',
'base_date',
'status',
'json_details',
'remarks',
'created_by',
'updated_by',
'deleted_by',
];
/**
* 기본값 설정
*/
protected $attributes = [
'status' => 'onTime',
];
// =========================================================================
// 관계 정의
// =========================================================================
/**
* 사용자 관계
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
/**
* 생성자 관계
*/
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
/**
* 수정자 관계
*/
public function updater(): BelongsTo
{
return $this->belongsTo(User::class, 'updated_by');
}
// =========================================================================
// json_details 헬퍼 메서드 (Accessor)
// =========================================================================
/**
* 출근 시간
*/
public function getCheckInAttribute(): ?string
{
return $this->json_details['check_in'] ?? null;
}
/**
* 퇴근 시간
*/
public function getCheckOutAttribute(): ?string
{
return $this->json_details['check_out'] ?? null;
}
/**
* GPS 데이터
*/
public function getGpsDataAttribute(): ?array
{
return $this->json_details['gps_data'] ?? null;
}
/**
* 외근 정보
*/
public function getExternalWorkAttribute(): ?array
{
return $this->json_details['external_work'] ?? null;
}
/**
* 다중 출퇴근 기록 (여러 번 출퇴근)
*/
public function getMultipleEntriesAttribute(): ?array
{
return $this->json_details['multiple_entries'] ?? null;
}
/**
* 근무 시간 (분 단위)
*/
public function getWorkMinutesAttribute(): ?int
{
return isset($this->json_details['work_minutes'])
? (int) $this->json_details['work_minutes']
: null;
}
/**
* 초과 근무 시간 (분 단위)
*/
public function getOvertimeMinutesAttribute(): ?int
{
return isset($this->json_details['overtime_minutes'])
? (int) $this->json_details['overtime_minutes']
: null;
}
/**
* 지각 시간 (분 단위)
*/
public function getLateMinutesAttribute(): ?int
{
return isset($this->json_details['late_minutes'])
? (int) $this->json_details['late_minutes']
: null;
}
/**
* 조퇴 시간 (분 단위)
*/
public function getEarlyLeaveMinutesAttribute(): ?int
{
return isset($this->json_details['early_leave_minutes'])
? (int) $this->json_details['early_leave_minutes']
: null;
}
/**
* 휴가 유형 (vacation 상태일 때)
*/
public function getVacationTypeAttribute(): ?string
{
return $this->json_details['vacation_type'] ?? null;
}
// =========================================================================
// json_details 업데이트 메서드
// =========================================================================
/**
* json_details에서 특정 키 값 설정
*/
public function setJsonDetailsValue(string $key, mixed $value): void
{
$jsonDetails = $this->json_details ?? [];
if ($value === null) {
unset($jsonDetails[$key]);
} else {
$jsonDetails[$key] = $value;
}
$this->json_details = $jsonDetails;
}
/**
* json_details에서 특정 키 값 가져오기
*/
public function getJsonDetailsValue(string $key, mixed $default = null): mixed
{
return $this->json_details[$key] ?? $default;
}
/**
* 출퇴근 정보 일괄 업데이트
*/
public function updateAttendanceDetails(array $data): void
{
$jsonDetails = $this->json_details ?? [];
$allowedKeys = [
'check_in',
'check_out',
'gps_data',
'external_work',
'multiple_entries',
'work_minutes',
'overtime_minutes',
'late_minutes',
'early_leave_minutes',
'vacation_type',
];
foreach ($allowedKeys as $key) {
if (array_key_exists($key, $data)) {
if ($data[$key] === null) {
unset($jsonDetails[$key]);
} else {
$jsonDetails[$key] = $data[$key];
}
}
}
$this->json_details = $jsonDetails;
}
// =========================================================================
// 스코프
// =========================================================================
/**
* 특정 날짜의 근태 조회
*/
public function scopeOnDate($query, string $date)
{
return $query->whereDate('base_date', $date);
}
/**
* 특정 기간의 근태 조회
*/
public function scopeBetweenDates($query, string $startDate, string $endDate)
{
return $query->whereBetween('base_date', [$startDate, $endDate]);
}
/**
* 특정 사용자의 근태 조회
*/
public function scopeForUser($query, int $userId)
{
return $query->where('user_id', $userId);
}
/**
* 특정 상태의 근태 조회
*/
public function scopeWithStatus($query, string $status)
{
return $query->where('status', $status);
}
}