- 급여 관리 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>
313 lines
13 KiB
PHP
313 lines
13 KiB
PHP
<?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 [];
|
|
}
|
|
}
|
|
}
|