option('type') ?: 'all'; $tenantId = $this->option('tenant'); $dryRun = $this->option('dry-run'); $this->info('=== Expected Expenses 동기화 시작 ==='); $this->info("유형: {$type}, 테넌트: ".($tenantId ?: '전체').', 모드: '.($dryRun ? 'DRY-RUN' : '실행')); $this->newLine(); if ($dryRun) { $this->warn('⚠️ DRY-RUN 모드: 실제 데이터는 저장되지 않습니다.'); $this->newLine(); } DB::beginTransaction(); try { if ($type === 'all' || $type === 'purchase') { $this->syncPurchases($tenantId, $dryRun); } if ($type === 'all' || $type === 'card') { $this->syncCardWithdrawals($tenantId, $dryRun); } if ($type === 'all' || $type === 'bill') { $this->syncBills($tenantId, $dryRun); } if ($dryRun) { DB::rollBack(); $this->warn('DRY-RUN 완료: 롤백됨'); } else { DB::commit(); $this->info('✅ 커밋 완료'); } } catch (\Exception $e) { DB::rollBack(); $this->error('❌ 오류 발생: '.$e->getMessage()); return Command::FAILURE; } $this->newLine(); $this->info('=== 동기화 결과 ==='); $this->table( ['항목', '건수'], [ ['생성', $this->created], ['업데이트', $this->updated], ['스킵 (이미 존재)', $this->skipped], ['합계', $this->created + $this->updated + $this->skipped], ] ); return Command::SUCCESS; } /** * 매입 → expected_expenses 동기화 */ private function syncPurchases(?int $tenantId, bool $dryRun): void { $this->info('📦 매입(purchases) 동기화 중...'); $query = Purchase::withoutGlobalScopes()->whereNull('deleted_at'); if ($tenantId) { $query->where('tenant_id', $tenantId); } $purchases = $query->get(); $bar = $this->output->createProgressBar($purchases->count()); foreach ($purchases as $purchase) { $this->syncPurchaseToExpense($purchase, $dryRun); $bar->advance(); } $bar->finish(); $this->newLine(); } /** * 카드 출금 → expected_expenses 동기화 */ private function syncCardWithdrawals(?int $tenantId, bool $dryRun): void { $this->info('💳 카드(withdrawals) 동기화 중...'); $query = Withdrawal::withoutGlobalScopes() ->whereNull('deleted_at') ->where('payment_method', 'card'); if ($tenantId) { $query->where('tenant_id', $tenantId); } $withdrawals = $query->get(); $bar = $this->output->createProgressBar($withdrawals->count()); foreach ($withdrawals as $withdrawal) { $this->syncWithdrawalToExpense($withdrawal, $dryRun); $bar->advance(); } $bar->finish(); $this->newLine(); } /** * 발행어음 → expected_expenses 동기화 */ private function syncBills(?int $tenantId, bool $dryRun): void { $this->info('📄 발행어음(bills) 동기화 중...'); $query = Bill::withoutGlobalScopes() ->whereNull('deleted_at') ->where('bill_type', 'issued'); // 발행어음만 (수령어음 제외) if ($tenantId) { $query->where('tenant_id', $tenantId); } $bills = $query->get(); $bar = $this->output->createProgressBar($bills->count()); foreach ($bills as $bill) { $this->syncBillToExpense($bill, $dryRun); $bar->advance(); } $bar->finish(); $this->newLine(); } /** * 매입 레코드 → expected_expense 동기화 */ private function syncPurchaseToExpense(Purchase $purchase, bool $dryRun): void { $existing = ExpectedExpense::withoutGlobalScopes() ->where('source_type', 'purchases') ->where('source_id', $purchase->id) ->first(); if ($existing) { // 이미 존재하면 업데이트 if (! $dryRun) { $existing->update([ 'expected_payment_date' => $purchase->purchase_date, 'amount' => $purchase->total_amount, 'client_id' => $purchase->client_id, 'description' => $purchase->description ?? "매입번호: {$purchase->purchase_number}", 'payment_status' => $purchase->withdrawal_id ? 'paid' : 'pending', 'updated_by' => $purchase->updated_by, ]); } $this->updated++; } else { // 새로 생성 if (! $dryRun) { ExpectedExpense::create([ 'tenant_id' => $purchase->tenant_id, 'expected_payment_date' => $purchase->purchase_date, 'transaction_type' => 'purchase', 'amount' => $purchase->total_amount, 'client_id' => $purchase->client_id, 'description' => $purchase->description ?? "매입번호: {$purchase->purchase_number}", 'payment_status' => $purchase->withdrawal_id ? 'paid' : 'pending', 'approval_status' => 'none', 'source_type' => 'purchases', 'source_id' => $purchase->id, 'created_by' => $purchase->created_by, 'updated_by' => $purchase->updated_by, ]); } $this->created++; } } /** * 카드 출금 레코드 → expected_expense 동기화 */ private function syncWithdrawalToExpense(Withdrawal $withdrawal, bool $dryRun): void { $existing = ExpectedExpense::withoutGlobalScopes() ->where('source_type', 'withdrawals') ->where('source_id', $withdrawal->id) ->first(); $clientName = $withdrawal->client_name ?: $withdrawal->merchant_name ?: '미지정'; if ($existing) { if (! $dryRun) { $existing->update([ 'expected_payment_date' => $withdrawal->withdrawal_date ?? $withdrawal->used_at, 'amount' => $withdrawal->amount, 'client_id' => $withdrawal->client_id, 'description' => $withdrawal->description ?? "카드결제: {$clientName}", 'payment_status' => 'paid', // 카드는 이미 결제됨 'updated_by' => $withdrawal->updated_by, ]); } $this->updated++; } else { if (! $dryRun) { ExpectedExpense::create([ 'tenant_id' => $withdrawal->tenant_id, 'expected_payment_date' => $withdrawal->withdrawal_date ?? $withdrawal->used_at, 'transaction_type' => 'card', 'amount' => $withdrawal->amount, 'client_id' => $withdrawal->client_id, 'description' => $withdrawal->description ?? "카드결제: {$clientName}", 'payment_status' => 'paid', 'approval_status' => 'none', 'source_type' => 'withdrawals', 'source_id' => $withdrawal->id, 'created_by' => $withdrawal->created_by, 'updated_by' => $withdrawal->updated_by, ]); } $this->created++; } } /** * 발행어음 레코드 → expected_expense 동기화 */ private function syncBillToExpense(Bill $bill, bool $dryRun): void { $existing = ExpectedExpense::withoutGlobalScopes() ->where('source_type', 'bills') ->where('source_id', $bill->id) ->first(); $clientName = $bill->client_name ?: '미지정'; if ($existing) { if (! $dryRun) { $existing->update([ 'expected_payment_date' => $bill->maturity_date, // 만기일 기준 'amount' => $bill->amount, 'client_id' => $bill->client_id, 'description' => $bill->note ?? "발행어음: {$bill->bill_number}", 'payment_status' => $bill->status === 'settled' ? 'paid' : 'pending', 'updated_by' => $bill->updated_by, ]); } $this->updated++; } else { if (! $dryRun) { ExpectedExpense::create([ 'tenant_id' => $bill->tenant_id, 'expected_payment_date' => $bill->maturity_date, 'transaction_type' => 'bill', 'amount' => $bill->amount, 'client_id' => $bill->client_id, 'description' => $bill->note ?? "발행어음: {$bill->bill_number}", 'payment_status' => $bill->status === 'settled' ? 'paid' : 'pending', 'approval_status' => 'none', 'source_type' => 'bills', 'source_id' => $bill->id, 'created_by' => $bill->created_by, 'updated_by' => $bill->updated_by, ]); } $this->created++; } } }