Files
sam-api/database/seeders/Dummy/DummyAttendanceSeeder.php
kent be90c351fa chore(API): Seeder 파일 정리
- Dummy Seeder 파일들 정리 및 개선
- ApprovalTestDataSeeder 수정
- PositionSeeder, StockReceivingSeeder 수정

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-13 19:49:28 +09:00

195 lines
6.6 KiB
PHP

<?php
namespace Database\Seeders\Dummy;
use App\Models\Tenants\Attendance;
use Database\Seeders\DummyDataSeeder;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class DummyAttendanceSeeder extends Seeder
{
public function run(): void
{
$tenantId = DummyDataSeeder::TENANT_ID;
$userId = DummyDataSeeder::USER_ID;
// 테넌트 소속 사용자 조회
$userIds = DB::table('user_tenants')
->where('tenant_id', $tenantId)
->pluck('user_id')
->toArray();
if (empty($userIds)) {
$this->command->warn(' ⚠ attendances: 사용자가 없습니다');
return;
}
// 최근 30일간의 근태 데이터 생성
$startDate = now()->subDays(30);
$endDate = now();
$count = 0;
// 상태 분포 (enum: onTime, late, absent, vacation, businessTrip, fieldWork, overtime, remote)
$statuses = [
'onTime' => 60, // 정시 출근 60%
'late' => 10, // 지각 10%
'vacation' => 10, // 휴가 10%
'absent' => 5, // 결근 5%
'businessTrip' => 5, // 출장 5%
'remote' => 5, // 재택 5%
'overtime' => 5, // 초과근무 5%
];
foreach ($userIds as $uId) {
$currentDate = clone $startDate;
while ($currentDate <= $endDate) {
// 주말 제외
if ($currentDate->isWeekend()) {
$currentDate->addDay();
continue;
}
$baseDate = $currentDate->format('Y-m-d');
// 이미 존재하는지 확인
$exists = Attendance::where('tenant_id', $tenantId)
->where('user_id', $uId)
->where('base_date', $baseDate)
->exists();
if ($exists) {
$currentDate->addDay();
continue;
}
// 상태 결정 (가중치 기반 랜덤)
$status = $this->getRandomStatus($statuses);
// 출퇴근 시간 생성
$jsonDetails = $this->generateTimeDetails($status);
Attendance::create([
'tenant_id' => $tenantId,
'user_id' => $uId,
'base_date' => $baseDate,
'status' => $status,
'json_details' => $jsonDetails,
'remarks' => $this->getRemarks($status),
'created_by' => $userId,
]);
$count++;
$currentDate->addDay();
}
}
$this->command->info(' ✓ attendances: '.$count.'건 생성');
}
private function getRandomStatus(array $weights): string
{
$total = array_sum($weights);
$rand = rand(1, $total);
$cumulative = 0;
foreach ($weights as $status => $weight) {
$cumulative += $weight;
if ($rand <= $cumulative) {
return $status;
}
}
return 'normal';
}
private function generateTimeDetails(string $status): array
{
$checkIn = null;
$checkOut = null;
$workMinutes = 0;
$lateMinutes = 0;
$earlyLeaveMinutes = 0;
$overtimeMinutes = 0;
$standardStart = 9 * 60; // 09:00 in minutes
$standardEnd = 18 * 60; // 18:00 in minutes
$breakMinutes = 60;
switch ($status) {
case 'onTime':
$startVariance = rand(-10, 10); // ±10분
$endVariance = rand(-5, 30); // -5분 ~ +30분
$checkIn = sprintf('%02d:%02d:00', 9, max(0, $startVariance));
$checkOut = sprintf('%02d:%02d:00', 18 + intdiv($endVariance, 60), abs($endVariance) % 60);
$workMinutes = ($standardEnd - $standardStart - $breakMinutes) + $endVariance;
break;
case 'late':
$lateMinutes = rand(10, 60); // 10분 ~ 1시간 지각
$checkIn = sprintf('%02d:%02d:00', 9 + intdiv($lateMinutes, 60), $lateMinutes % 60);
$checkOut = '18:00:00';
$workMinutes = ($standardEnd - $standardStart - $breakMinutes) - $lateMinutes;
break;
case 'overtime':
$checkIn = '09:00:00';
$overtimeMinutes = rand(60, 180); // 1시간 ~ 3시간 초과근무
$endTime = $standardEnd + $overtimeMinutes;
$checkOut = sprintf('%02d:%02d:00', intdiv($endTime, 60), $endTime % 60);
$workMinutes = ($standardEnd - $standardStart - $breakMinutes) + $overtimeMinutes;
break;
case 'remote':
$checkIn = sprintf('%02d:%02d:00', 9, rand(0, 10));
$checkOut = sprintf('%02d:%02d:00', 18, rand(0, 30));
$workMinutes = ($standardEnd - $standardStart - $breakMinutes);
break;
case 'businessTrip':
case 'fieldWork':
$checkIn = sprintf('%02d:%02d:00', 8, rand(0, 30)); // 출장은 일찍 시작
$checkOut = sprintf('%02d:%02d:00', 19, rand(0, 60)); // 늦게 종료
$workMinutes = 10 * 60 - $breakMinutes; // 10시간 근무
break;
case 'vacation':
case 'absent':
// 출퇴근 기록 없음
break;
}
return [
'check_in' => $checkIn,
'check_out' => $checkOut,
'work_minutes' => max(0, $workMinutes),
'late_minutes' => $lateMinutes,
'early_leave_minutes' => $earlyLeaveMinutes,
'overtime_minutes' => $overtimeMinutes,
'vacation_type' => $status === 'vacation' ? 'annual' : null,
'gps_data' => $checkIn ? [
'check_in' => ['lat' => 37.5012 + (rand(-10, 10) / 10000), 'lng' => 127.0396 + (rand(-10, 10) / 10000)],
'check_out' => $checkOut ? ['lat' => 37.5012 + (rand(-10, 10) / 10000), 'lng' => 127.0396 + (rand(-10, 10) / 10000)] : null,
] : null,
];
}
private function getRemarks(string $status): ?string
{
return match ($status) {
'late' => '지각 - 교통 체증',
'vacation' => '연차 휴가',
'absent' => '결근',
'businessTrip' => '출장 - 외부 미팅',
'fieldWork' => '외근 - 현장 방문',
'overtime' => '초과근무 - 프로젝트 마감',
'remote' => '재택근무',
default => null,
};
}
}