fix: 종합분석 오늘의 이슈 승인/반려 버그 수정
- ComprehensiveAnalysisService::getTodayIssue() 수정 - 현재 사용자가 결재자인 문서만 조회하도록 whereHas 조건 추가 - 이전: 테넌트의 모든 대기 결재 표시 → "결재 순서가 아닙니다" 오류 - 수정: 현재 로그인 사용자가 approver_id인 문서만 표시 - ComprehensiveAnalysisSeeder 테스트 데이터 수정 - Tenant 287 (프론트_테스트회사) 기준 - User 33 (홍킬동) 기준으로 결재 단계 생성 - Client 모델 재무 컬럼 추가 (마이그레이션 포함) - outstanding_balance: 미수금 - credit_limit: 여신한도 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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',
|
||||
];
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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::table('clients', function (Blueprint $table) {
|
||||
$table->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']);
|
||||
});
|
||||
}
|
||||
};
|
||||
311
database/seeders/ComprehensiveAnalysisSeeder.php
Normal file
311
database/seeders/ComprehensiveAnalysisSeeder.php
Normal file
@@ -0,0 +1,311 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\BadDebts\BadDebt;
|
||||
use App\Models\Orders\Client;
|
||||
use App\Models\Tenants\Approval;
|
||||
use App\Models\Tenants\ApprovalForm;
|
||||
use App\Models\Tenants\ApprovalStep;
|
||||
use App\Models\Tenants\Deposit;
|
||||
use App\Models\Tenants\ExpectedExpense;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* 종합분석 페이지 테스트 데이터 Seeder
|
||||
*
|
||||
* 다음 테이블에 샘플 데이터를 생성합니다:
|
||||
* - ApprovalForm, Approval (결재 대기)
|
||||
* - ExpectedExpense (예상 지출)
|
||||
* - Deposit (입금)
|
||||
* - BadDebt (채권추심)
|
||||
* - Client (미수금/여신한도 업데이트)
|
||||
*/
|
||||
class ComprehensiveAnalysisSeeder extends Seeder
|
||||
{
|
||||
private int $tenantId = 287; // 프론트_테스트회사
|
||||
|
||||
private int $userId = 33; // 홍킬동 (Tenant 287 기본 사용자)
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$this->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).'건');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user