feat: 급여 관리 API 및 더미 시더 확장
- 급여 관리 API 추가 (SalaryController, SalaryService, Salary 모델) - 급여 목록/상세/등록/수정/삭제 - 상태 변경 (scheduled/completed) - 일괄 상태 변경 - 통계 조회 - 더미 시더 확장 - 근태, 휴가, 부서, 사용자 시더 추가 - 기존 시더 tenant_id/created_by/updated_by 필드 추가 - 부서 서비스 개선 (tree 조회 기능 추가) - 카드 서비스 수정 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
<?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::create('salaries', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
|
||||
$table->unsignedBigInteger('employee_id')->comment('직원 ID');
|
||||
$table->unsignedSmallInteger('year')->comment('년도');
|
||||
$table->unsignedTinyInteger('month')->comment('월');
|
||||
|
||||
// 급여 금액
|
||||
$table->decimal('base_salary', 15, 2)->default(0)->comment('기본급');
|
||||
$table->decimal('total_allowance', 15, 2)->default(0)->comment('총 수당');
|
||||
$table->decimal('total_overtime', 15, 2)->default(0)->comment('초과근무수당');
|
||||
$table->decimal('total_bonus', 15, 2)->default(0)->comment('상여');
|
||||
$table->decimal('total_deduction', 15, 2)->default(0)->comment('총 공제');
|
||||
$table->decimal('net_payment', 15, 2)->default(0)->comment('실지급액');
|
||||
|
||||
// 상세 내역 (JSON)
|
||||
$table->json('allowance_details')->nullable()->comment('수당 상세 내역');
|
||||
$table->json('deduction_details')->nullable()->comment('공제 상세 내역');
|
||||
|
||||
// 지급 정보
|
||||
$table->date('payment_date')->nullable()->comment('지급일');
|
||||
$table->enum('status', ['scheduled', 'completed'])->default('scheduled')->comment('상태: 지급예정/지급완료');
|
||||
|
||||
// 감사 필드
|
||||
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자');
|
||||
$table->unsignedBigInteger('updated_by')->nullable()->comment('수정자');
|
||||
$table->unsignedBigInteger('deleted_by')->nullable()->comment('삭제자');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
// 인덱스
|
||||
$table->index('tenant_id');
|
||||
$table->index('employee_id');
|
||||
$table->index(['year', 'month']);
|
||||
$table->index('status');
|
||||
$table->unique(['tenant_id', 'employee_id', 'year', 'month'], 'unique_salary_per_employee_month');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('salaries');
|
||||
}
|
||||
};
|
||||
312
database/seeders/ApprovalTestDataSeeder.php
Normal file
312
database/seeders/ApprovalTestDataSeeder.php
Normal file
@@ -0,0 +1,312 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class ApprovalTestDataSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* 결재 시스템 테스트 데이터 시더
|
||||
* - 기안함: 15건 (draft 5건, pending 10건)
|
||||
* - 결재함: 15건 (결재 대기 상태)
|
||||
* - 참조함: 10건 (열람 대기 상태)
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$tenantId = 1;
|
||||
$now = Carbon::now();
|
||||
|
||||
// 사용자 ID 가져오기
|
||||
$users = DB::table('users')->pluck('id')->toArray();
|
||||
if (count($users) < 3) {
|
||||
$this->command->error('최소 3명의 사용자가 필요합니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
$mainUser = $users[0]; // 기안자 겸 참조 대상
|
||||
$approver1 = $users[1] ?? $users[0];
|
||||
$approver2 = $users[2] ?? $users[0];
|
||||
|
||||
// 1. 결재 양식 생성
|
||||
$this->command->info('결재 양식 생성 중...');
|
||||
$forms = $this->createApprovalForms($tenantId, $mainUser, $now);
|
||||
|
||||
// 2. 결재 문서 생성
|
||||
$this->command->info('결재 문서 생성 중...');
|
||||
$this->createApprovals($tenantId, $forms, $mainUser, $approver1, $approver2, $now);
|
||||
|
||||
$this->command->info('✅ 결재 테스트 데이터 생성 완료!');
|
||||
$this->command->info(' - 기안함: 15건');
|
||||
$this->command->info(' - 결재함: 15건');
|
||||
$this->command->info(' - 참조함: 10건');
|
||||
}
|
||||
|
||||
private function createApprovalForms(int $tenantId, int $userId, Carbon $now): array
|
||||
{
|
||||
$forms = [
|
||||
[
|
||||
'tenant_id' => $tenantId,
|
||||
'name' => '품의서',
|
||||
'code' => 'proposal',
|
||||
'category' => '일반',
|
||||
'template' => json_encode([
|
||||
'fields' => [
|
||||
['name' => 'title', 'type' => 'text', 'label' => '제목', 'required' => true],
|
||||
['name' => 'vendor', 'type' => 'text', 'label' => '거래처', 'required' => false],
|
||||
['name' => 'description', 'type' => 'textarea', 'label' => '내용', 'required' => true],
|
||||
['name' => 'reason', 'type' => 'textarea', 'label' => '사유', 'required' => true],
|
||||
['name' => 'estimatedCost', 'type' => 'number', 'label' => '예상비용', 'required' => false],
|
||||
]
|
||||
]),
|
||||
'is_active' => true,
|
||||
'created_by' => $userId,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
[
|
||||
'tenant_id' => $tenantId,
|
||||
'name' => '지출결의서',
|
||||
'code' => 'expenseReport',
|
||||
'category' => '경비',
|
||||
'template' => json_encode([
|
||||
'fields' => [
|
||||
['name' => 'requestDate', 'type' => 'date', 'label' => '신청일', 'required' => true],
|
||||
['name' => 'paymentDate', 'type' => 'date', 'label' => '지급일', 'required' => true],
|
||||
['name' => 'items', 'type' => 'array', 'label' => '지출항목', 'required' => true],
|
||||
['name' => 'totalAmount', 'type' => 'number', 'label' => '총액', 'required' => true],
|
||||
]
|
||||
]),
|
||||
'is_active' => true,
|
||||
'created_by' => $userId,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
[
|
||||
'tenant_id' => $tenantId,
|
||||
'name' => '비용견적서',
|
||||
'code' => 'expenseEstimate',
|
||||
'category' => '경비',
|
||||
'template' => json_encode([
|
||||
'fields' => [
|
||||
['name' => 'items', 'type' => 'array', 'label' => '비용항목', 'required' => true],
|
||||
['name' => 'totalExpense', 'type' => 'number', 'label' => '총지출', 'required' => true],
|
||||
['name' => 'accountBalance', 'type' => 'number', 'label' => '계좌잔액', 'required' => true],
|
||||
]
|
||||
]),
|
||||
'is_active' => true,
|
||||
'created_by' => $userId,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
];
|
||||
|
||||
$formIds = [];
|
||||
foreach ($forms as $form) {
|
||||
// 기존 양식 확인
|
||||
$existing = DB::table('approval_forms')
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('code', $form['code'])
|
||||
->first();
|
||||
|
||||
if ($existing) {
|
||||
$formIds[$form['code']] = $existing->id;
|
||||
} else {
|
||||
$formIds[$form['code']] = DB::table('approval_forms')->insertGetId($form);
|
||||
}
|
||||
}
|
||||
|
||||
return $formIds;
|
||||
}
|
||||
|
||||
private function createApprovals(
|
||||
int $tenantId,
|
||||
array $forms,
|
||||
int $mainUser,
|
||||
int $approver1,
|
||||
int $approver2,
|
||||
Carbon $now
|
||||
): void {
|
||||
$proposalTitles = [
|
||||
'신규 장비 구매 품의', '사무용품 구매 요청', '소프트웨어 라이선스 갱신',
|
||||
'출장 경비 지원 요청', '교육 프로그램 신청', '복지시설 개선 제안',
|
||||
'마케팅 예산 증액 품의', '시스템 업그레이드 제안', '인력 충원 요청',
|
||||
'사무실 이전 품의', '연구개발 예산 신청', '고객 세미나 개최 품의',
|
||||
'협력업체 계약 갱신', '보안 시스템 도입 품의', '업무 차량 구매 요청',
|
||||
];
|
||||
|
||||
$expenseItems = [
|
||||
'교통비', '식비', '숙박비', '소모품비', '통신비', '유류비', '접대비', '회의비'
|
||||
];
|
||||
|
||||
$vendors = [
|
||||
'삼성전자', 'LG전자', 'SK하이닉스', '현대자동차', '네이버', '카카오',
|
||||
'쿠팡', '배달의민족', '토스', '당근마켓'
|
||||
];
|
||||
|
||||
$docNumber = 1;
|
||||
|
||||
// 기안함용 문서 15건 (mainUser가 기안자)
|
||||
for ($i = 0; $i < 15; $i++) {
|
||||
$status = $i < 5 ? 'draft' : 'pending';
|
||||
$formCode = ['proposal', 'expenseReport', 'expenseEstimate'][$i % 3];
|
||||
$formId = $forms[$formCode];
|
||||
|
||||
$content = $this->generateContent($formCode, $proposalTitles[$i], $vendors, $expenseItems);
|
||||
|
||||
$approvalId = DB::table('approvals')->insertGetId([
|
||||
'tenant_id' => $tenantId,
|
||||
'document_number' => sprintf('DOC-%s-%04d', $now->format('Ymd'), $docNumber++),
|
||||
'form_id' => $formId,
|
||||
'title' => $proposalTitles[$i],
|
||||
'content' => json_encode($content),
|
||||
'status' => $status,
|
||||
'drafter_id' => $mainUser,
|
||||
'drafted_at' => $status === 'pending' ? $now->copy()->subDays(rand(1, 10)) : null,
|
||||
'current_step' => $status === 'pending' ? 1 : 0,
|
||||
'created_by' => $mainUser,
|
||||
'created_at' => $now->copy()->subDays(rand(1, 15)),
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
|
||||
// 결재선 추가 (pending 상태인 경우)
|
||||
if ($status === 'pending') {
|
||||
// 결재자 1 (approver1이 결재 대기)
|
||||
DB::table('approval_steps')->insert([
|
||||
'approval_id' => $approvalId,
|
||||
'step_order' => 1,
|
||||
'step_type' => 'approval',
|
||||
'approver_id' => $approver1,
|
||||
'status' => 'pending',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
|
||||
// 결재자 2
|
||||
DB::table('approval_steps')->insert([
|
||||
'approval_id' => $approvalId,
|
||||
'step_order' => 2,
|
||||
'step_type' => 'approval',
|
||||
'approver_id' => $approver2,
|
||||
'status' => 'pending',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
|
||||
// 참조 (mainUser에게 참조)
|
||||
if ($i < 10) {
|
||||
DB::table('approval_steps')->insert([
|
||||
'approval_id' => $approvalId,
|
||||
'step_order' => 3,
|
||||
'step_type' => 'reference',
|
||||
'approver_id' => $mainUser,
|
||||
'status' => 'pending',
|
||||
'is_read' => false,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 결재함용 추가 문서 (approver1/approver2가 기안, mainUser가 결재자)
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$formCode = ['proposal', 'expenseReport'][$i % 2];
|
||||
$formId = $forms[$formCode];
|
||||
$drafter = $i < 3 ? $approver1 : $approver2;
|
||||
|
||||
$title = '추가 결재 요청 문서 ' . ($i + 1);
|
||||
$content = $this->generateContent($formCode, $title, $vendors, $expenseItems);
|
||||
|
||||
$approvalId = DB::table('approvals')->insertGetId([
|
||||
'tenant_id' => $tenantId,
|
||||
'document_number' => sprintf('DOC-%s-%04d', $now->format('Ymd'), $docNumber++),
|
||||
'form_id' => $formId,
|
||||
'title' => $title,
|
||||
'content' => json_encode($content),
|
||||
'status' => 'pending',
|
||||
'drafter_id' => $drafter,
|
||||
'drafted_at' => $now->copy()->subDays(rand(1, 5)),
|
||||
'current_step' => 1,
|
||||
'created_by' => $drafter,
|
||||
'created_at' => $now->copy()->subDays(rand(1, 10)),
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
|
||||
// mainUser가 결재자
|
||||
DB::table('approval_steps')->insert([
|
||||
'approval_id' => $approvalId,
|
||||
'step_order' => 1,
|
||||
'step_type' => 'approval',
|
||||
'approver_id' => $mainUser,
|
||||
'status' => 'pending',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function generateContent(string $formCode, string $title, array $vendors, array $expenseItems): array
|
||||
{
|
||||
switch ($formCode) {
|
||||
case 'proposal':
|
||||
return [
|
||||
'title' => $title,
|
||||
'vendor' => $vendors[array_rand($vendors)],
|
||||
'vendorPaymentDate' => Carbon::now()->addDays(rand(7, 30))->format('Y-m-d'),
|
||||
'description' => $title . '에 대한 상세 설명입니다. 업무 효율성 향상과 비용 절감을 위해 필요합니다.',
|
||||
'reason' => '업무 효율성 향상 및 경쟁력 강화를 위해 필수적으로 진행해야 합니다.',
|
||||
'estimatedCost' => rand(100, 5000) * 10000,
|
||||
];
|
||||
|
||||
case 'expenseReport':
|
||||
$items = [];
|
||||
$total = 0;
|
||||
for ($j = 0; $j < rand(2, 5); $j++) {
|
||||
$amount = rand(10, 200) * 1000;
|
||||
$total += $amount;
|
||||
$items[] = [
|
||||
'id' => (string) ($j + 1),
|
||||
'description' => $expenseItems[array_rand($expenseItems)],
|
||||
'amount' => $amount,
|
||||
'note' => '업무 관련 지출',
|
||||
];
|
||||
}
|
||||
return [
|
||||
'requestDate' => Carbon::now()->subDays(rand(1, 7))->format('Y-m-d'),
|
||||
'paymentDate' => Carbon::now()->addDays(rand(1, 14))->format('Y-m-d'),
|
||||
'items' => $items,
|
||||
'cardId' => 'CARD-' . rand(1000, 9999),
|
||||
'totalAmount' => $total,
|
||||
];
|
||||
|
||||
case 'expenseEstimate':
|
||||
$items = [];
|
||||
$total = 0;
|
||||
for ($j = 0; $j < rand(3, 8); $j++) {
|
||||
$amount = rand(50, 500) * 10000;
|
||||
$total += $amount;
|
||||
$items[] = [
|
||||
'id' => (string) ($j + 1),
|
||||
'expectedPaymentDate' => Carbon::now()->addDays(rand(1, 60))->format('Y-m-d'),
|
||||
'category' => $expenseItems[array_rand($expenseItems)],
|
||||
'amount' => $amount,
|
||||
'vendor' => $vendors[array_rand($vendors)],
|
||||
'memo' => '예정 지출',
|
||||
'checked' => false,
|
||||
];
|
||||
}
|
||||
return [
|
||||
'items' => $items,
|
||||
'totalExpense' => $total,
|
||||
'accountBalance' => rand(5000, 20000) * 10000,
|
||||
'finalDifference' => rand(5000, 20000) * 10000 - $total,
|
||||
];
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
191
database/seeders/Dummy/DummyAttendanceSeeder.php
Normal file
191
database/seeders/Dummy/DummyAttendanceSeeder.php
Normal file
@@ -0,0 +1,191 @@
|
||||
<?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,
|
||||
};
|
||||
}
|
||||
}
|
||||
34
database/seeders/Dummy/DummyAttendanceSettingSeeder.php
Normal file
34
database/seeders/Dummy/DummyAttendanceSettingSeeder.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders\Dummy;
|
||||
|
||||
use App\Models\Tenants\AttendanceSetting;
|
||||
use Database\Seeders\DummyDataSeeder;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DummyAttendanceSettingSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$tenantId = DummyDataSeeder::TENANT_ID;
|
||||
$userId = DummyDataSeeder::USER_ID;
|
||||
|
||||
// 기존 설정이 있으면 스킵
|
||||
$existing = AttendanceSetting::where('tenant_id', $tenantId)->first();
|
||||
if ($existing) {
|
||||
$this->command->info(' ⚠ attendance_settings: 이미 존재 (스킵)');
|
||||
return;
|
||||
}
|
||||
|
||||
AttendanceSetting::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'use_gps' => true,
|
||||
'allowed_radius' => 500, // 500m
|
||||
'hq_address' => '서울시 강남구 테헤란로 123',
|
||||
'hq_latitude' => 37.5012,
|
||||
'hq_longitude' => 127.0396,
|
||||
]);
|
||||
|
||||
$this->command->info(' ✓ attendance_settings: 1건 생성');
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,13 @@ public function run(): void
|
||||
$tenantId = DummyDataSeeder::TENANT_ID;
|
||||
$userId = DummyDataSeeder::USER_ID;
|
||||
|
||||
// 기존 데이터 있으면 스킵
|
||||
$existing = BadDebt::where('tenant_id', $tenantId)->count();
|
||||
if ($existing > 0) {
|
||||
$this->command->info(' ⚠ bad_debts: 이미 ' . $existing . '건 존재 (스킵)');
|
||||
return;
|
||||
}
|
||||
|
||||
// SALES 또는 BOTH 타입의 거래처 조회
|
||||
$clients = Client::where('tenant_id', $tenantId)
|
||||
->whereIn('client_type', ['SALES', 'BOTH'])
|
||||
|
||||
@@ -13,6 +13,13 @@ public function run(): void
|
||||
$tenantId = DummyDataSeeder::TENANT_ID;
|
||||
$userId = DummyDataSeeder::USER_ID;
|
||||
|
||||
// 기존 데이터 있으면 스킵
|
||||
$existing = BankAccount::where('tenant_id', $tenantId)->count();
|
||||
if ($existing > 0) {
|
||||
$this->command->info(' ⚠ bank_accounts: 이미 ' . $existing . '개 존재 (스킵)');
|
||||
return;
|
||||
}
|
||||
|
||||
$accounts = [
|
||||
['bank_code' => '004', 'bank_name' => 'KB국민은행', 'account_number' => '123-45-6789012', 'account_holder' => '프론트테스트', 'account_name' => '운영계좌', 'is_primary' => true],
|
||||
['bank_code' => '088', 'bank_name' => '신한은행', 'account_number' => '110-123-456789', 'account_holder' => '프론트테스트', 'account_name' => '급여계좌', 'is_primary' => false],
|
||||
|
||||
@@ -16,6 +16,13 @@ public function run(): void
|
||||
$tenantId = DummyDataSeeder::TENANT_ID;
|
||||
$userId = DummyDataSeeder::USER_ID;
|
||||
|
||||
// 기존 데이터 있으면 스킵
|
||||
$existing = Bill::where('tenant_id', $tenantId)->count();
|
||||
if ($existing > 0) {
|
||||
$this->command->info(' ⚠ bills: 이미 ' . $existing . '건 존재 (스킵)');
|
||||
return;
|
||||
}
|
||||
|
||||
// 거래처 매핑
|
||||
$clients = Client::where('tenant_id', $tenantId)->get()->keyBy('name');
|
||||
|
||||
|
||||
@@ -13,6 +13,13 @@ public function run(): void
|
||||
$tenantId = DummyDataSeeder::TENANT_ID;
|
||||
$userId = DummyDataSeeder::USER_ID;
|
||||
|
||||
// 기존 데이터 있으면 스킵
|
||||
$existing = ClientGroup::where('tenant_id', $tenantId)->count();
|
||||
if ($existing > 0) {
|
||||
$this->command->info(' ⚠ client_groups: 이미 ' . $existing . '개 존재 (스킵)');
|
||||
return;
|
||||
}
|
||||
|
||||
$groups = [
|
||||
['group_code' => 'VIP', 'group_name' => 'VIP 고객', 'price_rate' => 0.95],
|
||||
['group_code' => 'GOLD', 'group_name' => '골드 고객', 'price_rate' => 0.97],
|
||||
|
||||
@@ -14,6 +14,13 @@ public function run(): void
|
||||
$tenantId = DummyDataSeeder::TENANT_ID;
|
||||
$userId = DummyDataSeeder::USER_ID;
|
||||
|
||||
// 기존 데이터 있으면 스킵
|
||||
$existing = Client::where('tenant_id', $tenantId)->count();
|
||||
if ($existing > 0) {
|
||||
$this->command->info(' ⚠ clients: 이미 ' . $existing . '개 존재 (스킵)');
|
||||
return;
|
||||
}
|
||||
|
||||
// 그룹 ID 조회
|
||||
$groups = ClientGroup::where('tenant_id', $tenantId)
|
||||
->pluck('id', 'group_code')
|
||||
|
||||
133
database/seeders/Dummy/DummyDepartmentSeeder.php
Normal file
133
database/seeders/Dummy/DummyDepartmentSeeder.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders\Dummy;
|
||||
|
||||
use App\Models\Tenants\Department;
|
||||
use Database\Seeders\DummyDataSeeder;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DummyDepartmentSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$tenantId = DummyDataSeeder::TENANT_ID;
|
||||
$userId = DummyDataSeeder::USER_ID;
|
||||
|
||||
// 기존 부서가 있으면 스킵
|
||||
$existing = Department::where('tenant_id', $tenantId)->count();
|
||||
if ($existing > 0) {
|
||||
$this->command->info(' ⚠ departments: 이미 ' . $existing . '개 존재 (스킵)');
|
||||
return;
|
||||
}
|
||||
|
||||
// 1레벨: 본부 (3개)
|
||||
$divisions = [
|
||||
['code' => 'DIV01', 'name' => '경영본부', 'description' => '경영 및 기획 업무'],
|
||||
['code' => 'DIV02', 'name' => '기술본부', 'description' => '기술 개발 및 연구'],
|
||||
['code' => 'DIV03', 'name' => '영업본부', 'description' => '영업 및 마케팅'],
|
||||
];
|
||||
|
||||
// 2레벨: 부서 (본부별 2~3개씩)
|
||||
$departments = [
|
||||
'DIV01' => [
|
||||
['code' => 'HR', 'name' => '인사팀', 'description' => '인사 및 채용 관리'],
|
||||
['code' => 'FIN', 'name' => '재무팀', 'description' => '재무 및 회계 관리'],
|
||||
['code' => 'ADM', 'name' => '총무팀', 'description' => '총무 및 시설 관리'],
|
||||
],
|
||||
'DIV02' => [
|
||||
['code' => 'DEV', 'name' => '개발팀', 'description' => '소프트웨어 개발'],
|
||||
['code' => 'QA', 'name' => 'QA팀', 'description' => '품질 보증 및 테스트'],
|
||||
['code' => 'INFRA', 'name' => '인프라팀', 'description' => '서버 및 인프라 관리'],
|
||||
],
|
||||
'DIV03' => [
|
||||
['code' => 'SALES', 'name' => '영업팀', 'description' => '영업 및 고객 관리'],
|
||||
['code' => 'MKT', 'name' => '마케팅팀', 'description' => '마케팅 및 홍보'],
|
||||
],
|
||||
];
|
||||
|
||||
$count = 0;
|
||||
$divisionIds = [];
|
||||
|
||||
// 본부 생성
|
||||
foreach ($divisions as $index => $division) {
|
||||
$dept = Department::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'parent_id' => null,
|
||||
'code' => $division['code'],
|
||||
'name' => $division['name'],
|
||||
'description' => $division['description'],
|
||||
'is_active' => true,
|
||||
'sort_order' => $index + 1,
|
||||
'created_by' => $userId,
|
||||
]);
|
||||
$divisionIds[$division['code']] = $dept->id;
|
||||
$count++;
|
||||
}
|
||||
|
||||
// 부서 생성
|
||||
foreach ($departments as $divCode => $depts) {
|
||||
$parentId = $divisionIds[$divCode] ?? null;
|
||||
foreach ($depts as $index => $dept) {
|
||||
Department::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'parent_id' => $parentId,
|
||||
'code' => $dept['code'],
|
||||
'name' => $dept['name'],
|
||||
'description' => $dept['description'],
|
||||
'is_active' => true,
|
||||
'sort_order' => $index + 1,
|
||||
'created_by' => $userId,
|
||||
]);
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
// 사용자-부서 연결
|
||||
$this->assignUsersToDepartments($tenantId);
|
||||
|
||||
$this->command->info(' ✓ departments: ' . $count . '개 생성 (본부 3개, 부서 8개)');
|
||||
}
|
||||
|
||||
private function assignUsersToDepartments(int $tenantId): void
|
||||
{
|
||||
// 테넌트 소속 사용자 조회
|
||||
$userIds = DB::table('user_tenants')
|
||||
->where('tenant_id', $tenantId)
|
||||
->pluck('user_id')
|
||||
->toArray();
|
||||
|
||||
// 부서 조회 (2레벨만)
|
||||
$departments = Department::where('tenant_id', $tenantId)
|
||||
->whereNotNull('parent_id')
|
||||
->get();
|
||||
|
||||
if ($departments->isEmpty() || empty($userIds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 사용자를 부서에 분배
|
||||
foreach ($userIds as $index => $userId) {
|
||||
$dept = $departments[$index % $departments->count()];
|
||||
$isPrimary = ($index % $departments->count() === 0); // 첫 번째만 primary
|
||||
|
||||
// 이미 연결되어 있는지 확인
|
||||
$exists = DB::table('department_user')
|
||||
->where('user_id', $userId)
|
||||
->where('department_id', $dept->id)
|
||||
->exists();
|
||||
|
||||
if (!$exists) {
|
||||
DB::table('department_user')->insert([
|
||||
'tenant_id' => $tenantId,
|
||||
'user_id' => $userId,
|
||||
'department_id' => $dept->id,
|
||||
'is_primary' => $isPrimary,
|
||||
'joined_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,13 @@ public function run(): void
|
||||
$tenantId = DummyDataSeeder::TENANT_ID;
|
||||
$userId = DummyDataSeeder::USER_ID;
|
||||
|
||||
// 기존 데이터 있으면 스킵
|
||||
$existing = Deposit::where('tenant_id', $tenantId)->count();
|
||||
if ($existing > 0) {
|
||||
$this->command->info(' ⚠ deposits: 이미 ' . $existing . '건 존재 (스킵)');
|
||||
return;
|
||||
}
|
||||
|
||||
// 거래처 매핑 (SALES, BOTH만)
|
||||
$clients = Client::where('tenant_id', $tenantId)
|
||||
->whereIn('client_type', ['SALES', 'BOTH'])
|
||||
|
||||
95
database/seeders/Dummy/DummyLeaveGrantSeeder.php
Normal file
95
database/seeders/Dummy/DummyLeaveGrantSeeder.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders\Dummy;
|
||||
|
||||
use App\Models\Tenants\LeaveGrant;
|
||||
use Database\Seeders\DummyDataSeeder;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DummyLeaveGrantSeeder 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(' ⚠ leave_grants: 사용자가 없습니다');
|
||||
return;
|
||||
}
|
||||
|
||||
$year = 2025;
|
||||
$count = 0;
|
||||
|
||||
// 각 사용자별 연차/월차 부여
|
||||
foreach ($userIds as $uId) {
|
||||
// 연차 부여 (1월 1일)
|
||||
$existing = LeaveGrant::where('tenant_id', $tenantId)
|
||||
->where('user_id', $uId)
|
||||
->where('grant_type', 'annual')
|
||||
->whereYear('grant_date', $year)
|
||||
->exists();
|
||||
|
||||
if (!$existing) {
|
||||
LeaveGrant::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'user_id' => $uId,
|
||||
'grant_type' => 'annual',
|
||||
'grant_date' => sprintf('%04d-01-01', $year),
|
||||
'grant_days' => rand(12, 20), // 12~20일
|
||||
'reason' => sprintf('%d년 연차 부여', $year),
|
||||
'created_by' => $userId,
|
||||
]);
|
||||
$count++;
|
||||
}
|
||||
|
||||
// 월차 부여 (월별, 12건)
|
||||
for ($month = 1; $month <= 12; $month++) {
|
||||
$grantDate = sprintf('%04d-%02d-01', $year, $month);
|
||||
|
||||
$monthlyExists = LeaveGrant::where('tenant_id', $tenantId)
|
||||
->where('user_id', $uId)
|
||||
->where('grant_type', 'monthly')
|
||||
->where('grant_date', $grantDate)
|
||||
->exists();
|
||||
|
||||
if (!$monthlyExists) {
|
||||
LeaveGrant::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'user_id' => $uId,
|
||||
'grant_type' => 'monthly',
|
||||
'grant_date' => $grantDate,
|
||||
'grant_days' => 1,
|
||||
'reason' => sprintf('%d년 %d월 월차', $year, $month),
|
||||
'created_by' => $userId,
|
||||
]);
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
// 포상 휴가 (일부 사용자에게 랜덤)
|
||||
if (rand(1, 5) === 1) { // 20% 확률
|
||||
$rewardMonth = rand(3, 11);
|
||||
LeaveGrant::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'user_id' => $uId,
|
||||
'grant_type' => 'reward',
|
||||
'grant_date' => sprintf('%04d-%02d-15', $year, $rewardMonth),
|
||||
'grant_days' => rand(1, 3),
|
||||
'reason' => '우수 사원 포상 휴가',
|
||||
'created_by' => $userId,
|
||||
]);
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->command->info(' ✓ leave_grants: ' . $count . '건 생성');
|
||||
}
|
||||
}
|
||||
135
database/seeders/Dummy/DummyLeaveSeeder.php
Normal file
135
database/seeders/Dummy/DummyLeaveSeeder.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders\Dummy;
|
||||
|
||||
use App\Models\Tenants\Leave;
|
||||
use Database\Seeders\DummyDataSeeder;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DummyLeaveSeeder 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(' ⚠ leaves: 사용자가 없습니다');
|
||||
return;
|
||||
}
|
||||
|
||||
// 휴가 유형 (가중치)
|
||||
$leaveTypes = [
|
||||
'annual' => 50, // 연차 50%
|
||||
'half_am' => 15, // 오전 반차 15%
|
||||
'half_pm' => 15, // 오후 반차 15%
|
||||
'sick' => 10, // 병가 10%
|
||||
'family' => 5, // 경조사 5%
|
||||
'other' => 5, // 기타 5%
|
||||
];
|
||||
|
||||
// 상태 (가중치)
|
||||
$statuses = [
|
||||
'approved' => 60, // 승인 60%
|
||||
'pending' => 25, // 대기 25%
|
||||
'rejected' => 10, // 반려 10%
|
||||
'cancelled' => 5, // 취소 5%
|
||||
];
|
||||
|
||||
$count = 0;
|
||||
$year = 2025;
|
||||
|
||||
// 사용자별로 1~4건의 휴가 생성
|
||||
foreach ($userIds as $uId) {
|
||||
$leaveCount = rand(1, 4);
|
||||
|
||||
for ($i = 0; $i < $leaveCount; $i++) {
|
||||
$month = rand(1, 12);
|
||||
$day = rand(1, 28);
|
||||
$startDate = sprintf('%04d-%02d-%02d', $year, $month, $day);
|
||||
|
||||
$leaveType = $this->getRandomWeighted($leaveTypes);
|
||||
$status = $this->getRandomWeighted($statuses);
|
||||
|
||||
// 휴가 일수 결정
|
||||
if (in_array($leaveType, ['half_am', 'half_pm'])) {
|
||||
$days = 0.5;
|
||||
$endDate = $startDate;
|
||||
} else {
|
||||
$days = rand(1, 3);
|
||||
$endDate = date('Y-m-d', strtotime($startDate . ' + ' . ($days - 1) . ' days'));
|
||||
}
|
||||
|
||||
// 승인자 정보
|
||||
$approvedBy = null;
|
||||
$approvedAt = null;
|
||||
$rejectReason = null;
|
||||
|
||||
if ($status === 'approved') {
|
||||
$approvedBy = $userId;
|
||||
$approvedAt = date('Y-m-d H:i:s', strtotime($startDate . ' - 2 days'));
|
||||
} elseif ($status === 'rejected') {
|
||||
$approvedBy = $userId;
|
||||
$approvedAt = date('Y-m-d H:i:s', strtotime($startDate . ' - 2 days'));
|
||||
$rejectReason = '업무 일정 상 불가';
|
||||
}
|
||||
|
||||
Leave::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'user_id' => $uId,
|
||||
'leave_type' => $leaveType,
|
||||
'start_date' => $startDate,
|
||||
'end_date' => $endDate,
|
||||
'days' => $days,
|
||||
'reason' => $this->getLeaveReason($leaveType),
|
||||
'status' => $status,
|
||||
'approved_by' => $approvedBy,
|
||||
'approved_at' => $approvedAt,
|
||||
'reject_reason' => $rejectReason,
|
||||
'created_by' => $uId,
|
||||
]);
|
||||
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->command->info(' ✓ leaves: ' . $count . '건 생성');
|
||||
}
|
||||
|
||||
private function getRandomWeighted(array $weights): string
|
||||
{
|
||||
$total = array_sum($weights);
|
||||
$rand = rand(1, $total);
|
||||
$cumulative = 0;
|
||||
|
||||
foreach ($weights as $key => $weight) {
|
||||
$cumulative += $weight;
|
||||
if ($rand <= $cumulative) {
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
|
||||
return array_key_first($weights);
|
||||
}
|
||||
|
||||
private function getLeaveReason(string $type): string
|
||||
{
|
||||
return match ($type) {
|
||||
'annual' => '개인 휴가',
|
||||
'half_am' => '오전 병원 방문',
|
||||
'half_pm' => '오후 개인 일정',
|
||||
'sick' => '건강 사유',
|
||||
'family' => '가족 행사',
|
||||
'maternity' => '출산 휴가',
|
||||
'parental' => '육아 휴직',
|
||||
default => '개인 사유',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,15 @@ public function run(): void
|
||||
$tenantId = DummyDataSeeder::TENANT_ID;
|
||||
$userId = DummyDataSeeder::USER_ID;
|
||||
|
||||
// 기존 데이터 있으면 스킵
|
||||
$existing = Payment::whereHas('subscription', function ($q) use ($tenantId) {
|
||||
$q->where('tenant_id', $tenantId);
|
||||
})->count();
|
||||
if ($existing > 0) {
|
||||
$this->command->info(' ⚠ payments: 이미 ' . $existing . '건 존재 (스킵)');
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 요금제 생성 (없으면)
|
||||
$plans = $this->createPlans($userId);
|
||||
|
||||
|
||||
@@ -13,6 +13,13 @@ public function run(): void
|
||||
$tenantId = DummyDataSeeder::TENANT_ID;
|
||||
$userId = DummyDataSeeder::USER_ID;
|
||||
|
||||
// 기존 데이터 있으면 스킵
|
||||
$existing = Popup::where('tenant_id', $tenantId)->count();
|
||||
if ($existing > 0) {
|
||||
$this->command->info(' ⚠ popups: 이미 ' . $existing . '개 존재 (스킵)');
|
||||
return;
|
||||
}
|
||||
|
||||
$popups = [
|
||||
[
|
||||
'target_type' => 'all',
|
||||
|
||||
@@ -14,6 +14,13 @@ public function run(): void
|
||||
$tenantId = DummyDataSeeder::TENANT_ID;
|
||||
$userId = DummyDataSeeder::USER_ID;
|
||||
|
||||
// 기존 데이터 있으면 스킵
|
||||
$existing = Purchase::where('tenant_id', $tenantId)->count();
|
||||
if ($existing > 0) {
|
||||
$this->command->info(' ⚠ purchases: 이미 ' . $existing . '건 존재 (스킵)');
|
||||
return;
|
||||
}
|
||||
|
||||
// 거래처 매핑 (PURCHASE, BOTH만)
|
||||
$clients = Client::where('tenant_id', $tenantId)
|
||||
->whereIn('client_type', ['PURCHASE', 'BOTH'])
|
||||
|
||||
@@ -14,6 +14,13 @@ public function run(): void
|
||||
$tenantId = DummyDataSeeder::TENANT_ID;
|
||||
$userId = DummyDataSeeder::USER_ID;
|
||||
|
||||
// 기존 데이터 있으면 스킵
|
||||
$existing = Sale::where('tenant_id', $tenantId)->count();
|
||||
if ($existing > 0) {
|
||||
$this->command->info(' ⚠ sales: 이미 ' . $existing . '건 존재 (스킵)');
|
||||
return;
|
||||
}
|
||||
|
||||
// 거래처 매핑 (SALES, BOTH만)
|
||||
$clients = Client::where('tenant_id', $tenantId)
|
||||
->whereIn('client_type', ['SALES', 'BOTH'])
|
||||
|
||||
89
database/seeders/Dummy/DummyUserSeeder.php
Normal file
89
database/seeders/Dummy/DummyUserSeeder.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders\Dummy;
|
||||
|
||||
use App\Models\Members\User;
|
||||
use Database\Seeders\DummyDataSeeder;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class DummyUserSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$tenantId = DummyDataSeeder::TENANT_ID;
|
||||
$userId = DummyDataSeeder::USER_ID;
|
||||
|
||||
// 더미 직원 데이터 (15명)
|
||||
$employees = [
|
||||
['user_id' => 'EMP001', 'name' => '김철수', 'email' => 'chulsoo.kim@test.com', 'phone' => '010-1234-5678'],
|
||||
['user_id' => 'EMP002', 'name' => '이영희', 'email' => 'younghee.lee@test.com', 'phone' => '010-2345-6789'],
|
||||
['user_id' => 'EMP003', 'name' => '박민수', 'email' => 'minsoo.park@test.com', 'phone' => '010-3456-7890'],
|
||||
['user_id' => 'EMP004', 'name' => '정은지', 'email' => 'eunji.jung@test.com', 'phone' => '010-4567-8901'],
|
||||
['user_id' => 'EMP005', 'name' => '최준호', 'email' => 'junho.choi@test.com', 'phone' => '010-5678-9012'],
|
||||
['user_id' => 'EMP006', 'name' => '강미래', 'email' => 'mirae.kang@test.com', 'phone' => '010-6789-0123'],
|
||||
['user_id' => 'EMP007', 'name' => '임도현', 'email' => 'dohyun.lim@test.com', 'phone' => '010-7890-1234'],
|
||||
['user_id' => 'EMP008', 'name' => '윤서연', 'email' => 'seoyeon.yoon@test.com', 'phone' => '010-8901-2345'],
|
||||
['user_id' => 'EMP009', 'name' => '한지민', 'email' => 'jimin.han@test.com', 'phone' => '010-9012-3456'],
|
||||
['user_id' => 'EMP010', 'name' => '오태양', 'email' => 'taeyang.oh@test.com', 'phone' => '010-0123-4567'],
|
||||
['user_id' => 'EMP011', 'name' => '신동욱', 'email' => 'dongwook.shin@test.com', 'phone' => '010-1111-2222'],
|
||||
['user_id' => 'EMP012', 'name' => '권나래', 'email' => 'narae.kwon@test.com', 'phone' => '010-2222-3333'],
|
||||
['user_id' => 'EMP013', 'name' => '조성민', 'email' => 'sungmin.cho@test.com', 'phone' => '010-3333-4444'],
|
||||
['user_id' => 'EMP014', 'name' => '백지훈', 'email' => 'jihun.baek@test.com', 'phone' => '010-4444-5555'],
|
||||
['user_id' => 'EMP015', 'name' => '송하늘', 'email' => 'haneul.song@test.com', 'phone' => '010-5555-6666'],
|
||||
];
|
||||
|
||||
$count = 0;
|
||||
|
||||
foreach ($employees as $employee) {
|
||||
// 이미 존재하는지 확인
|
||||
$existing = User::where('email', $employee['email'])->first();
|
||||
if ($existing) {
|
||||
// 이미 tenant에 연결되어 있는지 확인
|
||||
$linked = DB::table('user_tenants')
|
||||
->where('user_id', $existing->id)
|
||||
->where('tenant_id', $tenantId)
|
||||
->exists();
|
||||
|
||||
if (!$linked) {
|
||||
DB::table('user_tenants')->insert([
|
||||
'user_id' => $existing->id,
|
||||
'tenant_id' => $tenantId,
|
||||
'is_active' => true,
|
||||
'is_default' => false,
|
||||
'joined_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$user = User::create([
|
||||
'user_id' => $employee['user_id'],
|
||||
'name' => $employee['name'],
|
||||
'email' => $employee['email'],
|
||||
'phone' => $employee['phone'],
|
||||
'password' => Hash::make('password123'),
|
||||
'is_active' => true,
|
||||
'created_by' => $userId,
|
||||
]);
|
||||
|
||||
// 테넌트에 연결
|
||||
DB::table('user_tenants')->insert([
|
||||
'user_id' => $user->id,
|
||||
'tenant_id' => $tenantId,
|
||||
'is_active' => true,
|
||||
'is_default' => true,
|
||||
'joined_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$count++;
|
||||
}
|
||||
|
||||
$this->command->info(' ✓ users: ' . $count . '명 생성 (테넌트 연결 완료)');
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,13 @@ public function run(): void
|
||||
$tenantId = DummyDataSeeder::TENANT_ID;
|
||||
$userId = DummyDataSeeder::USER_ID;
|
||||
|
||||
// 기존 데이터 있으면 스킵
|
||||
$existing = Withdrawal::where('tenant_id', $tenantId)->count();
|
||||
if ($existing > 0) {
|
||||
$this->command->info(' ⚠ withdrawals: 이미 ' . $existing . '건 존재 (스킵)');
|
||||
return;
|
||||
}
|
||||
|
||||
// 거래처 매핑 (PURCHASE, BOTH만)
|
||||
$clients = Client::where('tenant_id', $tenantId)
|
||||
->whereIn('client_type', ['PURCHASE', 'BOTH'])
|
||||
|
||||
39
database/seeders/Dummy/DummyWorkSettingSeeder.php
Normal file
39
database/seeders/Dummy/DummyWorkSettingSeeder.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders\Dummy;
|
||||
|
||||
use App\Models\Tenants\WorkSetting;
|
||||
use Database\Seeders\DummyDataSeeder;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DummyWorkSettingSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$tenantId = DummyDataSeeder::TENANT_ID;
|
||||
$userId = DummyDataSeeder::USER_ID;
|
||||
|
||||
// 기존 설정이 있으면 스킵
|
||||
$existing = WorkSetting::where('tenant_id', $tenantId)->first();
|
||||
if ($existing) {
|
||||
$this->command->info(' ⚠ work_settings: 이미 존재 (스킵)');
|
||||
return;
|
||||
}
|
||||
|
||||
WorkSetting::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'work_type' => 'fixed',
|
||||
'standard_hours' => 8,
|
||||
'overtime_hours' => 4,
|
||||
'overtime_limit' => 52,
|
||||
'work_days' => [1, 2, 3, 4, 5], // 월~금
|
||||
'start_time' => '09:00:00',
|
||||
'end_time' => '18:00:00',
|
||||
'break_minutes' => 60,
|
||||
'break_start' => '12:00:00',
|
||||
'break_end' => '13:00:00',
|
||||
]);
|
||||
|
||||
$this->command->info(' ✓ work_settings: 1건 생성');
|
||||
}
|
||||
}
|
||||
@@ -19,14 +19,44 @@ public function run(): void
|
||||
$this->command->info('🌱 더미 데이터 시딩 시작...');
|
||||
$this->command->info(' 대상 테넌트: ID '.self::TENANT_ID);
|
||||
|
||||
// 1. 기본 데이터 (순서 중요)
|
||||
$this->command->info('');
|
||||
$this->command->info('📦 기본 데이터 생성...');
|
||||
$this->call([
|
||||
Dummy\DummyUserSeeder::class, // HR용 사용자
|
||||
Dummy\DummyDepartmentSeeder::class, // 부서
|
||||
Dummy\DummyClientGroupSeeder::class,
|
||||
Dummy\DummyBankAccountSeeder::class,
|
||||
Dummy\DummyClientSeeder::class,
|
||||
]);
|
||||
|
||||
// 2. 회계 데이터
|
||||
$this->command->info('');
|
||||
$this->command->info('💰 회계 데이터 생성...');
|
||||
$this->call([
|
||||
Dummy\DummyDepositSeeder::class,
|
||||
Dummy\DummyWithdrawalSeeder::class,
|
||||
Dummy\DummySaleSeeder::class,
|
||||
Dummy\DummyPurchaseSeeder::class,
|
||||
Dummy\DummyBadDebtSeeder::class, // 악성채권
|
||||
Dummy\DummyBillSeeder::class, // 어음
|
||||
]);
|
||||
|
||||
// 3. HR 데이터
|
||||
$this->command->info('');
|
||||
$this->command->info('👥 HR 데이터 생성...');
|
||||
$this->call([
|
||||
Dummy\DummyWorkSettingSeeder::class, // 근무 설정
|
||||
Dummy\DummyAttendanceSettingSeeder::class, // 근태 설정
|
||||
Dummy\DummyAttendanceSeeder::class, // 근태
|
||||
Dummy\DummyLeaveGrantSeeder::class, // 휴가 부여
|
||||
Dummy\DummyLeaveSeeder::class, // 휴가
|
||||
]);
|
||||
|
||||
// 4. 기타 데이터
|
||||
$this->command->info('');
|
||||
$this->command->info('📋 기타 데이터 생성...');
|
||||
$this->call([
|
||||
Dummy\DummyPopupSeeder::class,
|
||||
Dummy\DummyPaymentSeeder::class,
|
||||
]);
|
||||
@@ -34,20 +64,27 @@ public function run(): void
|
||||
$this->command->info('');
|
||||
$this->command->info('✅ 더미 데이터 시딩 완료!');
|
||||
$this->command->table(
|
||||
['테이블', '생성 수량'],
|
||||
['카테고리', '테이블', '생성 수량'],
|
||||
[
|
||||
['client_groups', '5'],
|
||||
['bank_accounts', '5'],
|
||||
['clients', '20'],
|
||||
['deposits', '60'],
|
||||
['withdrawals', '60'],
|
||||
['sales', '80'],
|
||||
['purchases', '70'],
|
||||
['popups', '8'],
|
||||
['plans', '6'],
|
||||
['subscriptions', '1'],
|
||||
['payments', '13'],
|
||||
['총계', '~328'],
|
||||
['기본', 'users', '15'],
|
||||
['기본', 'departments', '11'],
|
||||
['기본', 'client_groups', '5'],
|
||||
['기본', 'bank_accounts', '5'],
|
||||
['기본', 'clients', '20'],
|
||||
['회계', 'deposits', '60'],
|
||||
['회계', 'withdrawals', '60'],
|
||||
['회계', 'sales', '80'],
|
||||
['회계', 'purchases', '70'],
|
||||
['회계', 'bad_debts', '18'],
|
||||
['회계', 'bills', '30'],
|
||||
['HR', 'work_settings', '1'],
|
||||
['HR', 'attendance_settings', '1'],
|
||||
['HR', 'attendances', '~300'],
|
||||
['HR', 'leave_grants', '~200'],
|
||||
['HR', 'leaves', '~50'],
|
||||
['기타', 'popups', '8'],
|
||||
['기타', 'payments', '13'],
|
||||
['', '총계', '~950'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user