diff --git a/app/Models/Orders/Client.php b/app/Models/Orders/Client.php index 2d40dc0..fe9db06 100644 --- a/app/Models/Orders/Client.php +++ b/app/Models/Orders/Client.php @@ -35,6 +35,8 @@ class Client extends Model 'tax_start_date', 'tax_end_date', 'memo', + 'outstanding_balance', + 'credit_limit', 'is_active', 'client_type', 'manager_name', @@ -46,6 +48,8 @@ class Client extends Model 'is_active' => 'boolean', 'tax_agreement' => 'boolean', 'tax_amount' => 'decimal:2', + 'outstanding_balance' => 'decimal:2', + 'credit_limit' => 'decimal:2', 'tax_start_date' => 'date', 'tax_end_date' => 'date', ]; diff --git a/app/Services/ComprehensiveAnalysisService.php b/app/Services/ComprehensiveAnalysisService.php index cf82a30..0718f16 100644 --- a/app/Services/ComprehensiveAnalysisService.php +++ b/app/Services/ComprehensiveAnalysisService.php @@ -5,6 +5,7 @@ use App\Models\BadDebts\BadDebt; use App\Models\Orders\Client; use App\Models\Tenants\Approval; +use App\Models\Tenants\ApprovalStep; use App\Models\Tenants\Deposit; use App\Models\Tenants\ExpectedExpense; use Carbon\Carbon; @@ -35,14 +36,20 @@ public function getAnalysis(array $params): array } /** - * 오늘의 이슈 - 결재 대기 문서 + * 오늘의 이슈 - 현재 사용자가 결재할 수 있는 대기 문서 */ protected function getTodayIssue(Carbon $date): array { $tenantId = $this->tenantId(); + $userId = $this->apiUserId(); + // 현재 사용자가 결재자인 대기 문서만 조회 $pendingApprovals = Approval::where('tenant_id', $tenantId) ->pending() + ->whereHas('steps', function ($q) use ($userId) { + $q->where('approver_id', $userId) + ->where('status', ApprovalStep::STATUS_PENDING); + }) ->with(['form', 'drafter']) ->orderBy('drafted_at', 'desc') ->limit(10) diff --git a/database/migrations/2025_12_27_163021_add_financial_columns_to_clients_table.php b/database/migrations/2025_12_27_163021_add_financial_columns_to_clients_table.php new file mode 100644 index 0000000..ee07d4a --- /dev/null +++ b/database/migrations/2025_12_27_163021_add_financial_columns_to_clients_table.php @@ -0,0 +1,29 @@ +decimal('outstanding_balance', 15, 2)->default(0)->after('memo')->comment('미수금 잔액'); + $table->decimal('credit_limit', 15, 2)->default(0)->after('outstanding_balance')->comment('여신 한도'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('clients', function (Blueprint $table) { + $table->dropColumn(['outstanding_balance', 'credit_limit']); + }); + } +}; diff --git a/database/seeders/ComprehensiveAnalysisSeeder.php b/database/seeders/ComprehensiveAnalysisSeeder.php new file mode 100644 index 0000000..769d791 --- /dev/null +++ b/database/seeders/ComprehensiveAnalysisSeeder.php @@ -0,0 +1,311 @@ +command->info('종합분석 테스트 데이터 생성 시작...'); + + DB::transaction(function () { + $this->seedApprovalForms(); + $this->seedApprovals(); + $this->seedExpectedExpenses(); + $this->seedDeposits(); + $this->seedBadDebts(); + $this->updateClientBalances(); + }); + + $this->command->info('종합분석 테스트 데이터 생성 완료!'); + } + + /** + * 결재 양식 생성 + */ + private function seedApprovalForms(): void + { + $forms = [ + ['code' => 'REQ-001', 'name' => '품의서', 'category' => 'request'], + ['code' => 'EXP-001', 'name' => '지출결의서', 'category' => 'expense'], + ['code' => 'EST-001', 'name' => '지출 예상 내역서', 'category' => 'expense_estimate'], + ]; + + foreach ($forms as $form) { + ApprovalForm::firstOrCreate( + ['tenant_id' => $this->tenantId, 'code' => $form['code']], + [ + 'name' => $form['name'], + 'category' => $form['category'], + 'template' => ['fields' => []], + 'is_active' => true, + 'created_by' => $this->userId, + ] + ); + } + + $this->command->info(' - 결재 양식 3건 생성'); + } + + /** + * 결재 대기 문서 생성 + */ + private function seedApprovals(): void + { + $form = ApprovalForm::where('tenant_id', $this->tenantId)->first(); + + if (! $form) { + $this->command->warn(' - 결재 양식이 없어 결재 문서 생성 건너뜀'); + + return; + } + + $approvals = [ + ['title' => '12월 사무용품 구매 품의', 'time' => '09:15'], + ['title' => '연말 회식비 지출결의', 'time' => '10:30'], + ['title' => '출장비 정산 요청', 'time' => '11:45'], + ['title' => '서버 호스팅 비용 결재', 'time' => '14:20'], + ['title' => '신규 장비 구매 품의', 'time' => '15:00'], + ]; + + $today = Carbon::today(); + + foreach ($approvals as $i => $approval) { + $approvalRecord = Approval::firstOrCreate( + [ + 'tenant_id' => $this->tenantId, + 'document_number' => 'DOC-'.date('Ymd').'-'.str_pad($i + 1, 3, '0', STR_PAD_LEFT), + ], + [ + 'form_id' => $form->id, + 'title' => $approval['title'], + 'content' => ['description' => $approval['title'].' 내용입니다.'], + 'status' => Approval::STATUS_PENDING, + 'drafter_id' => $this->userId, + 'drafted_at' => $today->copy()->setTimeFromTimeString($approval['time']), + 'current_step' => 1, + 'created_by' => $this->userId, + ] + ); + + // 결재 단계 생성 (현재 로그인 사용자가 승인/반려할 수 있도록) + ApprovalStep::firstOrCreate( + [ + 'approval_id' => $approvalRecord->id, + 'step_order' => 1, + ], + [ + 'step_type' => 'approval', // ApprovalLine::STEP_TYPE_APPROVAL + 'approver_id' => $this->userId, + 'status' => ApprovalStep::STATUS_PENDING, + ] + ); + } + + $this->command->info(' - 결재 대기 문서 5건 + 결재 단계 생성'); + } + + /** + * 예상 지출 데이터 생성 + */ + private function seedExpectedExpenses(): void + { + $today = Carbon::today(); + $thisMonth = $today->copy()->startOfMonth(); + $lastMonth = $today->copy()->subMonth()->startOfMonth(); + + $expenses = [ + // 이번 달 지출 + ['type' => 'purchase', 'amount' => 5000000, 'status' => 'paid', 'date' => $thisMonth->copy()->addDays(5)], + ['type' => 'purchase', 'amount' => 3000000, 'status' => 'pending', 'date' => $thisMonth->copy()->addDays(15)], + ['type' => 'salary', 'amount' => 25000000, 'status' => 'paid', 'date' => $thisMonth->copy()->addDays(25)], + ['type' => 'insurance', 'amount' => 2500000, 'status' => 'pending', 'date' => $thisMonth->copy()->addDays(28)], + ['type' => 'rent', 'amount' => 3000000, 'status' => 'paid', 'date' => $thisMonth->copy()->addDays(1)], + ['type' => 'utilities', 'amount' => 500000, 'status' => 'pending', 'date' => $thisMonth->copy()->addDays(20)], + ['type' => 'tax', 'amount' => 1500000, 'status' => 'pending', 'date' => $thisMonth->copy()->addDays(25)], + ['type' => 'other', 'amount' => 2000000, 'status' => 'paid', 'date' => $thisMonth->copy()->addDays(10)], + // 가지급금/선급금 + ['type' => 'suspense', 'amount' => 5000000, 'status' => 'pending', 'date' => $thisMonth->copy()->addDays(3)], + ['type' => 'suspense', 'amount' => 3000000, 'status' => 'pending', 'date' => $thisMonth->copy()->addDays(12)], + ['type' => 'advance', 'amount' => 2000000, 'status' => 'pending', 'date' => $thisMonth->copy()->addDays(8)], + // 연체 (지난 달) + ['type' => 'purchase', 'amount' => 1500000, 'status' => 'pending', 'date' => $lastMonth->copy()->addDays(20)], + ]; + + foreach ($expenses as $expense) { + ExpectedExpense::create([ + 'tenant_id' => $this->tenantId, + 'expected_payment_date' => $expense['date'], + 'transaction_type' => $expense['type'], + 'amount' => $expense['amount'], + 'payment_status' => $expense['status'], + 'approval_status' => 'approved', + 'description' => ExpectedExpense::TRANSACTION_TYPES[$expense['type']].' 지출', + 'created_by' => $this->userId, + ]); + } + + $this->command->info(' - 예상 지출 12건 생성'); + } + + /** + * 입금 데이터 생성 + */ + private function seedDeposits(): void + { + $today = Carbon::today(); + $thisMonth = $today->copy()->startOfMonth(); + + $deposits = [ + // 오늘 입금 + ['amount' => 5000000, 'method' => 'transfer', 'date' => $today, 'client' => '(주)테스트고객A'], + ['amount' => 3000000, 'method' => 'transfer', 'date' => $today, 'client' => '(주)테스트고객B'], + // 이번 달 입금 + ['amount' => 10000000, 'method' => 'transfer', 'date' => $thisMonth->copy()->addDays(5), 'client' => '(주)테스트고객C'], + ['amount' => 7500000, 'method' => 'card', 'date' => $thisMonth->copy()->addDays(10), 'client' => '(주)테스트고객D'], + ['amount' => 15000000, 'method' => 'transfer', 'date' => $thisMonth->copy()->addDays(15), 'client' => '(주)테스트고객E'], + ['amount' => 8000000, 'method' => 'check', 'date' => $thisMonth->copy()->addDays(20), 'client' => '(주)테스트고객F'], + ]; + + foreach ($deposits as $deposit) { + Deposit::create([ + 'tenant_id' => $this->tenantId, + 'deposit_date' => $deposit['date'], + 'amount' => $deposit['amount'], + 'payment_method' => $deposit['method'], + 'client_name' => $deposit['client'], + 'description' => $deposit['client'].' 입금', + 'created_by' => $this->userId, + ]); + } + + $this->command->info(' - 입금 6건 생성'); + } + + /** + * 채권추심 데이터 생성 + */ + private function seedBadDebts(): void + { + // 먼저 테스트 거래처 생성 + $clients = $this->ensureTestClients(); + + $badDebts = [ + // 추심중 + ['client_idx' => 0, 'amount' => 5000000, 'status' => BadDebt::STATUS_COLLECTING, 'days' => 45], + ['client_idx' => 1, 'amount' => 3000000, 'status' => BadDebt::STATUS_COLLECTING, 'days' => 60], + // 법적조치 + ['client_idx' => 2, 'amount' => 8000000, 'status' => BadDebt::STATUS_LEGAL_ACTION, 'days' => 120], + // 회수완료 (올해) + ['client_idx' => 3, 'amount' => 2000000, 'status' => BadDebt::STATUS_RECOVERED, 'days' => 30, 'closed' => true], + ['client_idx' => 4, 'amount' => 1500000, 'status' => BadDebt::STATUS_RECOVERED, 'days' => 45, 'closed' => true], + // 대손처리 + ['client_idx' => 5, 'amount' => 10000000, 'status' => BadDebt::STATUS_BAD_DEBT, 'days' => 365, 'closed' => true], + ]; + + foreach ($badDebts as $debt) { + $occurredAt = Carbon::today()->subDays($debt['days']); + + BadDebt::create([ + 'tenant_id' => $this->tenantId, + 'client_id' => $clients[$debt['client_idx']]->id ?? null, + 'debt_amount' => $debt['amount'], + 'status' => $debt['status'], + 'overdue_days' => $debt['days'], + 'assigned_user_id' => $this->userId, + 'occurred_at' => $occurredAt, + 'closed_at' => isset($debt['closed']) ? Carbon::today() : null, + 'is_active' => ! isset($debt['closed']), + 'created_by' => $this->userId, + ]); + } + + $this->command->info(' - 채권추심 6건 생성'); + } + + /** + * 테스트 거래처 생성 + */ + private function ensureTestClients(): array + { + $clientNames = [ + '(주)문제거래처A', + '(주)문제거래처B', + '(주)문제거래처C', + '(주)정상거래처D', + '(주)정상거래처E', + '(주)대손거래처F', + ]; + + $clients = []; + + foreach ($clientNames as $i => $name) { + $clients[] = Client::firstOrCreate( + ['tenant_id' => $this->tenantId, 'client_code' => 'T'.$this->tenantId.'-'.str_pad($i + 1, 3, '0', STR_PAD_LEFT)], + [ + 'name' => $name, + 'is_active' => true, + 'client_type' => 'company', + ] + ); + } + + return $clients; + } + + /** + * 거래처 미수금/여신한도 업데이트 + */ + private function updateClientBalances(): void + { + // 기존 거래처 중 일부에 미수금/여신한도 설정 + $clients = Client::where('tenant_id', $this->tenantId) + ->where('is_active', true) + ->limit(5) + ->get(); + + $balances = [ + ['outstanding' => 15000000, 'limit' => 10000000], // 한도 초과 + ['outstanding' => 8000000, 'limit' => 10000000], // 정상 + ['outstanding' => 12000000, 'limit' => 10000000], // 한도 초과 + ['outstanding' => 5000000, 'limit' => 20000000], // 정상 + ['outstanding' => 25000000, 'limit' => 20000000], // 한도 초과 + ]; + + foreach ($clients as $i => $client) { + if (isset($balances[$i])) { + $client->update([ + 'outstanding_balance' => $balances[$i]['outstanding'], + 'credit_limit' => $balances[$i]['limit'], + ]); + } + } + + $this->command->info(' - 거래처 미수금/여신한도 업데이트 '.min(count($clients), 5).'건'); + } +}