feat: Phase 3.8 바로빌 세금계산서 연동 API 구현

- 마이그레이션: barobill_settings, tax_invoices 테이블 생성
- 모델: BarobillSetting (인증서 암호화), TaxInvoice (상태/유형 상수)
- 서비스: BarobillService (API 연동), TaxInvoiceService (CRUD, 발행/취소)
- 컨트롤러: BarobillSettingController, TaxInvoiceController
- FormRequest: 6개 요청 검증 클래스
- Swagger: API 문서 완성 (BarobillSettingApi, TaxInvoiceApi)
This commit is contained in:
2025-12-18 15:31:59 +09:00
parent 9b3dd2f4b8
commit 8ad4d7c0ce
17 changed files with 2425 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
<?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::create('barobill_settings', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id')->unique()->comment('테넌트 ID');
$table->string('corp_num', 20)->comment('사업자번호 (하이픈 제외)');
$table->string('cert_key')->nullable()->comment('바로빌 인증키 (암호화)');
$table->string('barobill_id', 100)->nullable()->comment('바로빌 아이디');
$table->string('corp_name', 100)->comment('상호');
$table->string('ceo_name', 50)->comment('대표자명');
$table->string('addr', 200)->nullable()->comment('사업장 주소');
$table->string('biz_type', 100)->nullable()->comment('업태');
$table->string('biz_class', 100)->nullable()->comment('종목');
$table->string('contact_id', 100)->nullable()->comment('담당자 이메일');
$table->string('contact_name', 50)->nullable()->comment('담당자명');
$table->string('contact_tel', 20)->nullable()->comment('담당자 전화번호');
$table->boolean('is_active')->default(false)->comment('활성화 여부');
$table->boolean('auto_issue')->default(false)->comment('자동 발행 여부');
$table->timestamp('verified_at')->nullable()->comment('연동 검증일시');
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자');
$table->unsignedBigInteger('updated_by')->nullable()->comment('수정자');
$table->timestamps();
$table->index('corp_num', 'idx_corp_num');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('barobill_settings');
}
};

View File

@@ -0,0 +1,89 @@
<?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::create('tax_invoices', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id')->comment('테넌트 ID');
$table->string('nts_confirm_num', 24)->nullable()->comment('국세청 승인번호');
$table->string('invoice_type', 20)->comment('세금계산서/계산서/수정세금계산서');
$table->string('issue_type', 20)->comment('정발행/역발행/위수탁');
$table->string('direction', 10)->comment('매출(sales)/매입(purchases)');
// 공급자 정보
$table->string('supplier_corp_num', 20)->comment('공급자 사업자번호');
$table->string('supplier_corp_name', 100)->comment('공급자 상호');
$table->string('supplier_ceo_name', 50)->nullable()->comment('공급자 대표자명');
$table->string('supplier_addr', 200)->nullable()->comment('공급자 주소');
$table->string('supplier_biz_type', 100)->nullable()->comment('공급자 업태');
$table->string('supplier_biz_class', 100)->nullable()->comment('공급자 종목');
$table->string('supplier_contact_id', 100)->nullable()->comment('공급자 담당자 이메일');
// 공급받는자 정보
$table->string('buyer_corp_num', 20)->comment('공급받는자 사업자번호');
$table->string('buyer_corp_name', 100)->comment('공급받는자 상호');
$table->string('buyer_ceo_name', 50)->nullable()->comment('공급받는자 대표자명');
$table->string('buyer_addr', 200)->nullable()->comment('공급받는자 주소');
$table->string('buyer_biz_type', 100)->nullable()->comment('공급받는자 업태');
$table->string('buyer_biz_class', 100)->nullable()->comment('공급받는자 종목');
$table->string('buyer_contact_id', 100)->nullable()->comment('공급받는자 담당자 이메일');
// 금액 정보
$table->date('issue_date')->comment('발행일자');
$table->decimal('supply_amount', 15, 2)->comment('공급가액');
$table->decimal('tax_amount', 15, 2)->comment('세액');
$table->decimal('total_amount', 15, 2)->comment('합계금액');
// 품목 정보 (JSON)
$table->json('items')->nullable()->comment('품목 목록 [{name, spec, qty, unit_price, supply_amt, tax_amt, remark}]');
// 상태 정보
$table->string('status', 20)->default('draft')->comment('상태: draft/issued/sent/cancelled/failed');
$table->string('nts_send_status', 20)->nullable()->comment('국세청 전송상태');
$table->timestamp('issued_at')->nullable()->comment('발행일시');
$table->timestamp('sent_at')->nullable()->comment('전송일시');
$table->timestamp('cancelled_at')->nullable()->comment('취소일시');
// 연동 정보
$table->string('barobill_invoice_id', 50)->nullable()->comment('바로빌 세금계산서 ID');
$table->text('description')->nullable()->comment('비고');
$table->text('error_message')->nullable()->comment('오류 메시지');
// 참조 정보
$table->string('reference_type', 50)->nullable()->comment('참조 유형 (sale/purchase)');
$table->unsignedBigInteger('reference_id')->nullable()->comment('참조 ID');
$table->unsignedBigInteger('created_by')->nullable()->comment('생성자');
$table->unsignedBigInteger('updated_by')->nullable()->comment('수정자');
$table->unsignedBigInteger('deleted_by')->nullable()->comment('삭제자');
$table->softDeletes();
$table->timestamps();
// 인덱스
$table->index(['tenant_id', 'issue_date'], 'idx_tenant_issue_date');
$table->index(['tenant_id', 'direction'], 'idx_tenant_direction');
$table->index(['tenant_id', 'status'], 'idx_tenant_status');
$table->index('nts_confirm_num', 'idx_nts_confirm_num');
$table->index(['reference_type', 'reference_id'], 'idx_reference');
$table->index('supplier_corp_num', 'idx_supplier');
$table->index('buyer_corp_num', 'idx_buyer');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('tax_invoices');
}
};