feat: [재무] 어음 V8 + 상품권 접대비 연동 + 일반전표/계정과목 API

- Bill 확장 필드 (V8), Loan 상품권 카테고리/접대비 자동 연동
- GeneralJournalEntry CRUD, AccountSubject API
- 접대비/복리후생비 날짜 필터, 매출채권 soft delete 제외
- 바로빌 연동 API 엔드포인트 추가
- 부가세 상세 조회 API

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 02:58:55 +09:00
parent 3d12687a2d
commit 1df34b2fa9
36 changed files with 3579 additions and 378 deletions

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* 가지급금 카테고리 컬럼 추가
* D1.7 기획서: 카드/경조사/상품권/접대비 4개 카테고리 분류
*/
public function up(): void
{
Schema::table('loans', function (Blueprint $table) {
$table->string('category', 30)
->default('card')
->after('status')
->comment('카테고리: card, congratulatory, gift_certificate, entertainment');
$table->index(['tenant_id', 'category'], 'idx_tenant_category');
});
}
public function down(): void
{
Schema::table('loans', function (Blueprint $table) {
$table->dropIndex('idx_tenant_category');
$table->dropColumn('category');
});
}
};

View File

@@ -0,0 +1,195 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* 어음/수표 V8 확장 마이그레이션
*
* 프로토타입 v8의 ~45개 필드 지원을 위해 bills 테이블에 신규 컬럼 추가.
* 기존 컬럼(bill_number, bill_type, client_id, amount 등)은 그대로 유지.
*
* 주요 변경:
* - 증권종류(instrument_type), 매체(medium), 어음구분(bill_category) 추가
* - 전자어음 관리번호, 등록기관 추가
* - 환어음 정보(지급인, 인수여부) 추가
* - 받을어음: 배서, 보관장소, 할인, 추심 관련 필드 추가
* - 지급어음: 결제방법, 실제결제일 추가
* - 지급장소, 개서, 소구, 환매, 부도 법적절차 필드 추가
* - 분할배서, 이력관리 확장 (bill_installments에 type, counterparty 추가)
*/
return new class extends Migration
{
public function up(): void
{
Schema::table('bills', function (Blueprint $table) {
// === 증권종류/매체/구분 ===
$table->string('instrument_type', 30)->default('promissory')->after('bill_type')
->comment('증권종류: promissory/exchange/cashierCheck/currentCheck');
$table->string('medium', 20)->default('paper')->after('instrument_type')
->comment('매체: electronic/paper');
$table->string('bill_category', 30)->nullable()->after('medium')
->comment('어음구분: commercial/other');
// === 전자어음 정보 ===
$table->string('electronic_bill_no', 100)->nullable()->after('is_electronic')
->comment('전자어음 관리번호');
$table->string('registration_org', 30)->nullable()->after('electronic_bill_no')
->comment('등록기관: kftc/bank');
// === 환어음 정보 ===
$table->string('drawee', 100)->nullable()->after('registration_org')
->comment('환어음 지급인 (Drawee)');
$table->string('acceptance_status', 20)->nullable()->after('drawee')
->comment('인수여부: accepted/pending/refused');
$table->date('acceptance_date')->nullable()->after('acceptance_status')
->comment('인수일자');
$table->date('acceptance_refusal_date')->nullable()->after('acceptance_date')
->comment('인수거절일');
$table->string('acceptance_refusal_reason', 50)->nullable()->after('acceptance_refusal_date')
->comment('인수거절사유');
// === 받을어음 전용 ===
$table->string('endorsement', 30)->nullable()->after('acceptance_refusal_reason')
->comment('배서여부: endorsable/nonEndorsable');
$table->string('endorsement_order', 5)->nullable()->after('endorsement')
->comment('배서차수: 1~20');
$table->string('storage_place', 30)->nullable()->after('endorsement_order')
->comment('보관장소: safe/bank/other');
$table->string('issuer_bank', 100)->nullable()->after('storage_place')
->comment('발행은행');
// 할인 정보
$table->boolean('is_discounted')->default(false)->after('issuer_bank')
->comment('할인여부');
$table->date('discount_date')->nullable()->after('is_discounted')
->comment('할인일자');
$table->string('discount_bank', 100)->nullable()->after('discount_date')
->comment('할인처 (은행)');
$table->decimal('discount_rate', 5, 2)->nullable()->after('discount_bank')
->comment('할인율 (%)');
$table->decimal('discount_amount', 15, 2)->nullable()->after('discount_rate')
->comment('할인금액');
// 배서양도 정보
$table->date('endorsement_date')->nullable()->after('discount_amount')
->comment('배서일자');
$table->string('endorsee', 100)->nullable()->after('endorsement_date')
->comment('피배서인 (양수인)');
$table->string('endorsement_reason', 30)->nullable()->after('endorsee')
->comment('배서사유: payment/guarantee/collection/other');
// 추심 정보
$table->string('collection_bank', 100)->nullable()->after('endorsement_reason')
->comment('추심은행');
$table->date('collection_request_date')->nullable()->after('collection_bank')
->comment('추심의뢰일');
$table->decimal('collection_fee', 15, 2)->nullable()->after('collection_request_date')
->comment('추심수수료');
$table->date('collection_complete_date')->nullable()->after('collection_fee')
->comment('추심완료일');
$table->string('collection_result', 20)->nullable()->after('collection_complete_date')
->comment('추심결과: success/partial/failed/pending');
$table->date('collection_deposit_date')->nullable()->after('collection_result')
->comment('추심입금일');
$table->decimal('collection_deposit_amount', 15, 2)->nullable()->after('collection_deposit_date')
->comment('추심입금액 (수수료 차감후)');
// === 지급어음 전용 ===
$table->string('settlement_bank', 100)->nullable()->after('collection_deposit_amount')
->comment('결제은행');
$table->string('payment_method', 30)->nullable()->after('settlement_bank')
->comment('결제방법: autoTransfer/currentAccount/other');
$table->date('actual_payment_date')->nullable()->after('payment_method')
->comment('실제결제일');
// === 공통 ===
$table->string('payment_place', 30)->nullable()->after('actual_payment_date')
->comment('지급장소: issuerBank/issuerBankBranch/payerAddress/designatedBank/other');
$table->string('payment_place_detail', 200)->nullable()->after('payment_place')
->comment('지급장소 상세 (기타 선택 시)');
// === 개서 정보 ===
$table->date('renewal_date')->nullable()->after('payment_place_detail')
->comment('개서일자');
$table->string('renewal_new_bill_no', 50)->nullable()->after('renewal_date')
->comment('신어음번호');
$table->string('renewal_reason', 30)->nullable()->after('renewal_new_bill_no')
->comment('개서사유: maturityExtension/amountChange/conditionChange/other');
// === 소구 정보 ===
$table->date('recourse_date')->nullable()->after('renewal_reason')
->comment('소구일자');
$table->decimal('recourse_amount', 15, 2)->nullable()->after('recourse_date')
->comment('소구금액');
$table->string('recourse_target', 100)->nullable()->after('recourse_amount')
->comment('소구대상 (청구인)');
$table->string('recourse_reason', 30)->nullable()->after('recourse_target')
->comment('소구사유: endorsedDishonor/discountDishonor/other');
// === 환매 정보 ===
$table->date('buyback_date')->nullable()->after('recourse_reason')
->comment('환매일자');
$table->decimal('buyback_amount', 15, 2)->nullable()->after('buyback_date')
->comment('환매금액');
$table->string('buyback_bank', 100)->nullable()->after('buyback_amount')
->comment('환매요청 은행');
// === 부도/법적절차 ===
$table->date('dishonored_date')->nullable()->after('buyback_bank')
->comment('부도일자');
$table->string('dishonored_reason', 30)->nullable()->after('dishonored_date')
->comment('부도사유');
$table->boolean('has_protest')->default(false)->after('dishonored_reason')
->comment('거절증서 작성 여부');
$table->date('protest_date')->nullable()->after('has_protest')
->comment('거절증서 작성일');
$table->date('recourse_notice_date')->nullable()->after('protest_date')
->comment('소구 통지일');
$table->date('recourse_notice_deadline')->nullable()->after('recourse_notice_date')
->comment('소구 통지 기한 (부도일+4영업일)');
// === 분할배서 ===
$table->boolean('is_split')->default(false)->after('recourse_notice_deadline')
->comment('분할배서 허용 여부');
});
// bill_installments 에 처리구분, 상대처 추가
Schema::table('bill_installments', function (Blueprint $table) {
$table->string('type', 30)->default('other')->after('bill_id')
->comment('처리구분: received/endorsement/splitEndorsement/collection/...');
$table->string('counterparty', 100)->nullable()->after('amount')
->comment('상대처 (거래처/은행)');
});
}
public function down(): void
{
Schema::table('bill_installments', function (Blueprint $table) {
$table->dropColumn(['type', 'counterparty']);
});
Schema::table('bills', function (Blueprint $table) {
$table->dropColumn([
'instrument_type', 'medium', 'bill_category',
'electronic_bill_no', 'registration_org',
'drawee', 'acceptance_status', 'acceptance_date',
'acceptance_refusal_date', 'acceptance_refusal_reason',
'endorsement', 'endorsement_order', 'storage_place', 'issuer_bank',
'is_discounted', 'discount_date', 'discount_bank', 'discount_rate', 'discount_amount',
'endorsement_date', 'endorsee', 'endorsement_reason',
'collection_bank', 'collection_request_date', 'collection_fee',
'collection_complete_date', 'collection_result', 'collection_deposit_date', 'collection_deposit_amount',
'settlement_bank', 'payment_method', 'actual_payment_date',
'payment_place', 'payment_place_detail',
'renewal_date', 'renewal_new_bill_no', 'renewal_reason',
'recourse_date', 'recourse_amount', 'recourse_target', 'recourse_reason',
'buyback_date', 'buyback_amount', 'buyback_bank',
'dishonored_date', 'dishonored_reason', 'has_protest', 'protest_date',
'recourse_notice_date', 'recourse_notice_deadline',
'is_split',
]);
});
}
};

View File

@@ -0,0 +1,28 @@
<?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('loans', function (Blueprint $table) {
$table->json('metadata')->nullable()->after('category');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('loans', function (Blueprint $table) {
$table->dropColumn('metadata');
});
}
};

View File

@@ -0,0 +1,32 @@
<?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('expense_accounts', function (Blueprint $table) {
$table->unsignedBigInteger('loan_id')->nullable()->after('card_no')
->comment('연결된 가지급금 ID (상품권→접대비 전환 시)');
$table->index('loan_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('expense_accounts', function (Blueprint $table) {
$table->dropIndex(['loan_id']);
$table->dropColumn('loan_id');
});
}
};