feat: 어음 관리(Bill Management) API 추가
- BillController: 어음 CRUD + 상태변경 + 요약 API - BillService: 비즈니스 로직 (멀티테넌트 지원) - Bill, BillInstallment 모델: 날짜 포맷(Y-m-d) toArray 오버라이드 - FormRequest: Store/Update/UpdateStatus 유효성 검사 - Swagger 문서: BillApi.php - 마이그레이션: bills, bill_installments 테이블 - DummyBillSeeder: 테스트 데이터 30건 + 차수 12건 - API Routes: /api/v1/bills 엔드포인트 7개
This commit is contained in:
59
database/migrations/2025_12_23_100000_create_bills_table.php
Normal file
59
database/migrations/2025_12_23_100000_create_bills_table.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('bills', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
|
||||
$table->string('bill_number', 50)->comment('어음번호');
|
||||
$table->enum('bill_type', ['received', 'issued'])->comment('어음 구분: received=수취, issued=발행');
|
||||
$table->unsignedBigInteger('client_id')->nullable()->comment('거래처 ID');
|
||||
$table->string('client_name', 100)->nullable()->comment('비회원 거래처명');
|
||||
$table->decimal('amount', 15, 2)->comment('금액');
|
||||
$table->date('issue_date')->comment('발행일');
|
||||
$table->date('maturity_date')->comment('만기일');
|
||||
$table->string('status', 30)->default('stored')->comment('상태: stored/maturityAlert/maturityResult/paymentComplete/dishonored/collectionRequest/collectionComplete/suing');
|
||||
$table->string('reason', 255)->nullable()->comment('사유');
|
||||
$table->unsignedInteger('installment_count')->default(0)->comment('차수');
|
||||
$table->text('note')->nullable()->comment('메모/비고');
|
||||
$table->boolean('is_electronic')->default(false)->comment('전자어음 여부');
|
||||
$table->unsignedBigInteger('bank_account_id')->nullable()->comment('입금/출금 계좌 ID');
|
||||
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자 ID');
|
||||
$table->unsignedBigInteger('updated_by')->nullable()->comment('수정자 ID');
|
||||
$table->unsignedBigInteger('deleted_by')->nullable()->comment('삭제자 ID');
|
||||
$table->softDeletes();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['tenant_id', 'bill_type', 'status'], 'idx_tenant_type_status');
|
||||
$table->index(['tenant_id', 'maturity_date'], 'idx_tenant_maturity');
|
||||
$table->index('client_id', 'idx_client');
|
||||
$table->unique(['tenant_id', 'bill_number'], 'uk_tenant_bill_number');
|
||||
});
|
||||
|
||||
// 어음 차수 관리 테이블 (installments)
|
||||
Schema::create('bill_installments', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('bill_id')->comment('어음 ID');
|
||||
$table->date('installment_date')->comment('차수 일자');
|
||||
$table->decimal('amount', 15, 2)->comment('차수 금액');
|
||||
$table->text('note')->nullable()->comment('비고');
|
||||
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자 ID');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('bill_id')->references('id')->on('bills')->onDelete('cascade');
|
||||
$table->index('bill_id', 'idx_bill');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('bill_installments');
|
||||
Schema::dropIfExists('bills');
|
||||
}
|
||||
};
|
||||
167
database/seeders/Dummy/DummyBillSeeder.php
Normal file
167
database/seeders/Dummy/DummyBillSeeder.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders\Dummy;
|
||||
|
||||
use App\Models\Orders\Client;
|
||||
use App\Models\Tenants\BankAccount;
|
||||
use App\Models\Tenants\Bill;
|
||||
use App\Models\Tenants\BillInstallment;
|
||||
use Database\Seeders\DummyDataSeeder;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DummyBillSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$tenantId = DummyDataSeeder::TENANT_ID;
|
||||
$userId = DummyDataSeeder::USER_ID;
|
||||
|
||||
// 거래처 매핑
|
||||
$clients = Client::where('tenant_id', $tenantId)->get()->keyBy('name');
|
||||
|
||||
// 은행계좌 (대표계좌)
|
||||
$primaryBankId = BankAccount::where('tenant_id', $tenantId)
|
||||
->where('is_primary', true)
|
||||
->value('id');
|
||||
|
||||
// 수취 어음 데이터 (received) - 15건
|
||||
$receivedBills = [
|
||||
['bill_number' => '202501000001', 'client' => '삼성전자', 'amount' => 50000000, 'issue_date' => '2025-01-15', 'maturity_date' => '2025-04-15', 'status' => 'paymentComplete'],
|
||||
['bill_number' => '202501000002', 'client' => 'LG전자', 'amount' => 35000000, 'issue_date' => '2025-02-10', 'maturity_date' => '2025-05-10', 'status' => 'paymentComplete'],
|
||||
['bill_number' => '202502000001', 'client' => 'SK하이닉스', 'amount' => 80000000, 'issue_date' => '2025-02-20', 'maturity_date' => '2025-05-20', 'status' => 'paymentComplete'],
|
||||
['bill_number' => '202503000001', 'client' => '현대자동차', 'amount' => 45000000, 'issue_date' => '2025-03-05', 'maturity_date' => '2025-06-05', 'status' => 'maturityResult'],
|
||||
['bill_number' => '202504000001', 'client' => '네이버', 'amount' => 25000000, 'issue_date' => '2025-04-12', 'maturity_date' => '2025-07-12', 'status' => 'maturityResult'],
|
||||
['bill_number' => '202505000001', 'client' => '카카오', 'amount' => 18000000, 'issue_date' => '2025-05-08', 'maturity_date' => '2025-08-08', 'status' => 'stored'],
|
||||
['bill_number' => '202506000001', 'client' => '쿠팡', 'amount' => 32000000, 'issue_date' => '2025-06-15', 'maturity_date' => '2025-09-15', 'status' => 'stored'],
|
||||
['bill_number' => '202507000001', 'client' => '삼성SDS', 'amount' => 65000000, 'issue_date' => '2025-07-20', 'maturity_date' => '2025-10-20', 'status' => 'stored'],
|
||||
['bill_number' => '202508000001', 'client' => '토스', 'amount' => 15000000, 'issue_date' => '2025-08-10', 'maturity_date' => '2025-11-10', 'status' => 'stored'],
|
||||
['bill_number' => '202509000001', 'client' => '두산에너빌리티', 'amount' => 55000000, 'issue_date' => '2025-09-05', 'maturity_date' => '2025-12-05', 'status' => 'maturityAlert'],
|
||||
['bill_number' => '202510000001', 'client' => '삼성전자', 'amount' => 42000000, 'issue_date' => '2025-10-15', 'maturity_date' => '2026-01-15', 'status' => 'stored'],
|
||||
['bill_number' => '202511000001', 'client' => 'LG전자', 'amount' => 28000000, 'issue_date' => '2025-11-08', 'maturity_date' => '2026-02-08', 'status' => 'stored'],
|
||||
['bill_number' => '202511000002', 'client' => '네이버', 'amount' => 38000000, 'issue_date' => '2025-11-20', 'maturity_date' => '2026-02-20', 'status' => 'stored'],
|
||||
['bill_number' => '202512000001', 'client' => '현대자동차', 'amount' => 52000000, 'issue_date' => '2025-12-10', 'maturity_date' => '2026-03-10', 'status' => 'stored'],
|
||||
['bill_number' => '202512000002', 'client' => 'SK하이닉스', 'amount' => 70000000, 'issue_date' => '2025-12-18', 'maturity_date' => '2026-03-18', 'status' => 'stored'],
|
||||
];
|
||||
|
||||
// 발행 어음 데이터 (issued) - 15건
|
||||
$issuedBills = [
|
||||
['bill_number' => '202501100001', 'client' => '한화솔루션', 'amount' => 40000000, 'issue_date' => '2025-01-20', 'maturity_date' => '2025-04-20', 'status' => 'collectionComplete'],
|
||||
['bill_number' => '202502100001', 'client' => '포스코', 'amount' => 55000000, 'issue_date' => '2025-02-15', 'maturity_date' => '2025-05-15', 'status' => 'collectionComplete'],
|
||||
['bill_number' => '202503100001', 'client' => '롯데케미칼', 'amount' => 30000000, 'issue_date' => '2025-03-10', 'maturity_date' => '2025-06-10', 'status' => 'collectionComplete'],
|
||||
['bill_number' => '202504100001', 'client' => 'GS칼텍스', 'amount' => 22000000, 'issue_date' => '2025-04-18', 'maturity_date' => '2025-07-18', 'status' => 'collectionComplete'],
|
||||
['bill_number' => '202505100001', 'client' => '대한항공', 'amount' => 18000000, 'issue_date' => '2025-05-12', 'maturity_date' => '2025-08-12', 'status' => 'collectionRequest'],
|
||||
['bill_number' => '202506100001', 'client' => '현대제철', 'amount' => 48000000, 'issue_date' => '2025-06-20', 'maturity_date' => '2025-09-20', 'status' => 'collectionRequest'],
|
||||
['bill_number' => '202507100001', 'client' => 'SK이노베이션', 'amount' => 35000000, 'issue_date' => '2025-07-15', 'maturity_date' => '2025-10-15', 'status' => 'stored'],
|
||||
['bill_number' => '202508100001', 'client' => 'CJ대한통운', 'amount' => 25000000, 'issue_date' => '2025-08-22', 'maturity_date' => '2025-11-22', 'status' => 'stored'],
|
||||
['bill_number' => '202509100001', 'client' => '두산에너빌리티', 'amount' => 60000000, 'issue_date' => '2025-09-10', 'maturity_date' => '2025-12-10', 'status' => 'maturityAlert'],
|
||||
['bill_number' => '202510100001', 'client' => '한화솔루션', 'amount' => 45000000, 'issue_date' => '2025-10-08', 'maturity_date' => '2026-01-08', 'status' => 'stored'],
|
||||
['bill_number' => '202511100001', 'client' => '포스코', 'amount' => 58000000, 'issue_date' => '2025-11-05', 'maturity_date' => '2026-02-05', 'status' => 'stored'],
|
||||
['bill_number' => '202511100002', 'client' => '롯데케미칼', 'amount' => 32000000, 'issue_date' => '2025-11-18', 'maturity_date' => '2026-02-18', 'status' => 'stored'],
|
||||
['bill_number' => '202512100001', 'client' => 'GS칼텍스', 'amount' => 28000000, 'issue_date' => '2025-12-05', 'maturity_date' => '2026-03-05', 'status' => 'stored'],
|
||||
['bill_number' => '202512100002', 'client' => '현대제철', 'amount' => 42000000, 'issue_date' => '2025-12-15', 'maturity_date' => '2026-03-15', 'status' => 'stored'],
|
||||
['bill_number' => '202512100003', 'client' => 'SK이노베이션', 'amount' => 38000000, 'issue_date' => '2025-12-22', 'maturity_date' => '2026-03-22', 'status' => 'stored'],
|
||||
];
|
||||
|
||||
// 차수 관리 데이터
|
||||
$installmentsData = [
|
||||
'202501000001' => [
|
||||
['date' => '2025-02-15', 'amount' => 25000000, 'note' => '1차 분할 입금'],
|
||||
['date' => '2025-03-15', 'amount' => 25000000, 'note' => '2차 분할 입금'],
|
||||
],
|
||||
'202502000001' => [
|
||||
['date' => '2025-03-20', 'amount' => 40000000, 'note' => '1차 분할 입금'],
|
||||
['date' => '2025-04-20', 'amount' => 40000000, 'note' => '2차 분할 입금'],
|
||||
],
|
||||
'202507000001' => [
|
||||
['date' => '2025-08-20', 'amount' => 30000000, 'note' => '1차 분할 입금'],
|
||||
['date' => '2025-09-20', 'amount' => 35000000, 'note' => '2차 분할 입금'],
|
||||
],
|
||||
'202501100001' => [
|
||||
['date' => '2025-02-20', 'amount' => 20000000, 'note' => '1차 분할 지급'],
|
||||
['date' => '2025-03-20', 'amount' => 20000000, 'note' => '2차 분할 지급'],
|
||||
],
|
||||
'202502100001' => [
|
||||
['date' => '2025-03-15', 'amount' => 27500000, 'note' => '1차 분할 지급'],
|
||||
['date' => '2025-04-15', 'amount' => 27500000, 'note' => '2차 분할 지급'],
|
||||
],
|
||||
'202506100001' => [
|
||||
['date' => '2025-07-20', 'amount' => 24000000, 'note' => '1차 분할 지급'],
|
||||
['date' => '2025-08-20', 'amount' => 24000000, 'note' => '2차 분할 지급'],
|
||||
],
|
||||
];
|
||||
|
||||
$billCount = 0;
|
||||
$installmentCount = 0;
|
||||
|
||||
// 수취 어음 생성
|
||||
foreach ($receivedBills as $data) {
|
||||
$client = $clients->get($data['client']);
|
||||
$bill = Bill::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'bill_number' => $data['bill_number'],
|
||||
'bill_type' => 'received',
|
||||
'client_id' => $client?->id,
|
||||
'client_name' => $client ? null : $data['client'],
|
||||
'amount' => $data['amount'],
|
||||
'issue_date' => $data['issue_date'],
|
||||
'maturity_date' => $data['maturity_date'],
|
||||
'status' => $data['status'],
|
||||
'is_electronic' => rand(0, 1) === 1,
|
||||
'bank_account_id' => $primaryBankId,
|
||||
'installment_count' => isset($installmentsData[$data['bill_number']]) ? count($installmentsData[$data['bill_number']]) : 0,
|
||||
'created_by' => $userId,
|
||||
]);
|
||||
$billCount++;
|
||||
|
||||
// 차수 관리 데이터 추가
|
||||
if (isset($installmentsData[$data['bill_number']])) {
|
||||
foreach ($installmentsData[$data['bill_number']] as $instData) {
|
||||
BillInstallment::create([
|
||||
'bill_id' => $bill->id,
|
||||
'installment_date' => $instData['date'],
|
||||
'amount' => $instData['amount'],
|
||||
'note' => $instData['note'],
|
||||
]);
|
||||
$installmentCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 발행 어음 생성
|
||||
foreach ($issuedBills as $data) {
|
||||
$client = $clients->get($data['client']);
|
||||
$bill = Bill::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'bill_number' => $data['bill_number'],
|
||||
'bill_type' => 'issued',
|
||||
'client_id' => $client?->id,
|
||||
'client_name' => $client ? null : $data['client'],
|
||||
'amount' => $data['amount'],
|
||||
'issue_date' => $data['issue_date'],
|
||||
'maturity_date' => $data['maturity_date'],
|
||||
'status' => $data['status'],
|
||||
'is_electronic' => rand(0, 1) === 1,
|
||||
'bank_account_id' => $primaryBankId,
|
||||
'installment_count' => isset($installmentsData[$data['bill_number']]) ? count($installmentsData[$data['bill_number']]) : 0,
|
||||
'created_by' => $userId,
|
||||
]);
|
||||
$billCount++;
|
||||
|
||||
// 차수 관리 데이터 추가
|
||||
if (isset($installmentsData[$data['bill_number']])) {
|
||||
foreach ($installmentsData[$data['bill_number']] as $instData) {
|
||||
BillInstallment::create([
|
||||
'bill_id' => $bill->id,
|
||||
'installment_date' => $instData['date'],
|
||||
'amount' => $instData['amount'],
|
||||
'note' => $instData['note'],
|
||||
]);
|
||||
$installmentCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->command->info(' ✓ bills: '.$billCount.'건 생성');
|
||||
$this->command->info(' ✓ bill_installments: '.$installmentCount.'건 생성');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user