- Dummy Seeder 파일들 정리 및 개선 - ApprovalTestDataSeeder 수정 - PositionSeeder, StockReceivingSeeder 수정 Co-Authored-By: Claude <noreply@anthropic.com>
195 lines
6.6 KiB
PHP
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,
|
|
};
|
|
}
|
|
}
|