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:
54
app/Http/Controllers/Api/V1/BarobillSettingController.php
Normal file
54
app/Http/Controllers/Api/V1/BarobillSettingController.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\BarobillSetting\SaveBarobillSettingRequest;
|
||||
use App\Services\BarobillService;
|
||||
|
||||
class BarobillSettingController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private BarobillService $barobillService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 바로빌 설정 조회
|
||||
*/
|
||||
public function show()
|
||||
{
|
||||
$setting = $this->barobillService->getSetting();
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $setting,
|
||||
message: __('message.fetched')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 바로빌 설정 저장
|
||||
*/
|
||||
public function save(SaveBarobillSettingRequest $request)
|
||||
{
|
||||
$setting = $this->barobillService->saveSetting($request->validated());
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $setting,
|
||||
message: __('message.saved')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 연동 테스트
|
||||
*/
|
||||
public function testConnection()
|
||||
{
|
||||
$result = $this->barobillService->testConnection();
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $result,
|
||||
message: __('message.barobill.connection_success')
|
||||
);
|
||||
}
|
||||
}
|
||||
137
app/Http/Controllers/Api/V1/TaxInvoiceController.php
Normal file
137
app/Http/Controllers/Api/V1/TaxInvoiceController.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\TaxInvoice\CancelTaxInvoiceRequest;
|
||||
use App\Http\Requests\TaxInvoice\CreateTaxInvoiceRequest;
|
||||
use App\Http\Requests\TaxInvoice\TaxInvoiceListRequest;
|
||||
use App\Http\Requests\TaxInvoice\TaxInvoiceSummaryRequest;
|
||||
use App\Http\Requests\TaxInvoice\UpdateTaxInvoiceRequest;
|
||||
use App\Services\TaxInvoiceService;
|
||||
|
||||
class TaxInvoiceController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private TaxInvoiceService $taxInvoiceService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 세금계산서 목록 조회
|
||||
*/
|
||||
public function index(TaxInvoiceListRequest $request)
|
||||
{
|
||||
$taxInvoices = $this->taxInvoiceService->list($request->validated());
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $taxInvoices,
|
||||
message: __('message.fetched')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 세금계산서 상세 조회
|
||||
*/
|
||||
public function show(int $id)
|
||||
{
|
||||
$taxInvoice = $this->taxInvoiceService->show($id);
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $taxInvoice,
|
||||
message: __('message.fetched')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 세금계산서 생성
|
||||
*/
|
||||
public function store(CreateTaxInvoiceRequest $request)
|
||||
{
|
||||
$taxInvoice = $this->taxInvoiceService->create($request->validated());
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $taxInvoice,
|
||||
message: __('message.created'),
|
||||
status: 201
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 세금계산서 수정
|
||||
*/
|
||||
public function update(UpdateTaxInvoiceRequest $request, int $id)
|
||||
{
|
||||
$taxInvoice = $this->taxInvoiceService->update($id, $request->validated());
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $taxInvoice,
|
||||
message: __('message.updated')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 세금계산서 삭제
|
||||
*/
|
||||
public function destroy(int $id)
|
||||
{
|
||||
$this->taxInvoiceService->delete($id);
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: null,
|
||||
message: __('message.deleted')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 세금계산서 발행
|
||||
*/
|
||||
public function issue(int $id)
|
||||
{
|
||||
$taxInvoice = $this->taxInvoiceService->issue($id);
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $taxInvoice,
|
||||
message: __('message.tax_invoice.issued')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 세금계산서 취소
|
||||
*/
|
||||
public function cancel(CancelTaxInvoiceRequest $request, int $id)
|
||||
{
|
||||
$taxInvoice = $this->taxInvoiceService->cancel($id, $request->validated()['reason']);
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $taxInvoice,
|
||||
message: __('message.tax_invoice.cancelled')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 국세청 전송 상태 조회
|
||||
*/
|
||||
public function checkStatus(int $id)
|
||||
{
|
||||
$taxInvoice = $this->taxInvoiceService->checkStatus($id);
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $taxInvoice,
|
||||
message: __('message.fetched')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 세금계산서 요약 통계
|
||||
*/
|
||||
public function summary(TaxInvoiceSummaryRequest $request)
|
||||
{
|
||||
$summary = $this->taxInvoiceService->summary($request->validated());
|
||||
|
||||
return ApiResponse::handle(
|
||||
data: $summary,
|
||||
message: __('message.fetched')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\BarobillSetting;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class SaveBarobillSettingRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'corp_num' => ['required', 'string', 'size:10'],
|
||||
'cert_key' => ['nullable', 'string', 'max:500'],
|
||||
'barobill_id' => ['nullable', 'string', 'max:100'],
|
||||
'corp_name' => ['required', 'string', 'max:100'],
|
||||
'ceo_name' => ['required', 'string', 'max:50'],
|
||||
'addr' => ['nullable', 'string', 'max:200'],
|
||||
'biz_type' => ['nullable', 'string', 'max:100'],
|
||||
'biz_class' => ['nullable', 'string', 'max:100'],
|
||||
'contact_id' => ['nullable', 'string', 'email', 'max:100'],
|
||||
'contact_name' => ['nullable', 'string', 'max:50'],
|
||||
'contact_tel' => ['nullable', 'string', 'max:20'],
|
||||
'is_active' => ['boolean'],
|
||||
'auto_issue' => ['boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'corp_num' => __('validation.attributes.corp_num'),
|
||||
'cert_key' => __('validation.attributes.cert_key'),
|
||||
'barobill_id' => __('validation.attributes.barobill_id'),
|
||||
'corp_name' => __('validation.attributes.corp_name'),
|
||||
'ceo_name' => __('validation.attributes.ceo_name'),
|
||||
'addr' => __('validation.attributes.addr'),
|
||||
'biz_type' => __('validation.attributes.biz_type'),
|
||||
'biz_class' => __('validation.attributes.biz_class'),
|
||||
'contact_id' => __('validation.attributes.contact_id'),
|
||||
'contact_name' => __('validation.attributes.contact_name'),
|
||||
'contact_tel' => __('validation.attributes.contact_tel'),
|
||||
];
|
||||
}
|
||||
}
|
||||
27
app/Http/Requests/TaxInvoice/CancelTaxInvoiceRequest.php
Normal file
27
app/Http/Requests/TaxInvoice/CancelTaxInvoiceRequest.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\TaxInvoice;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CancelTaxInvoiceRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'reason' => ['required', 'string', 'max:500'],
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'reason' => __('validation.attributes.cancel_reason'),
|
||||
];
|
||||
}
|
||||
}
|
||||
78
app/Http/Requests/TaxInvoice/CreateTaxInvoiceRequest.php
Normal file
78
app/Http/Requests/TaxInvoice/CreateTaxInvoiceRequest.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\TaxInvoice;
|
||||
|
||||
use App\Models\Tenants\TaxInvoice;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class CreateTaxInvoiceRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'invoice_type' => ['required', 'string', Rule::in(TaxInvoice::INVOICE_TYPES)],
|
||||
'issue_type' => ['required', 'string', Rule::in(TaxInvoice::ISSUE_TYPES)],
|
||||
'direction' => ['required', 'string', Rule::in(TaxInvoice::DIRECTIONS)],
|
||||
|
||||
// 공급자 정보
|
||||
'supplier_corp_num' => ['required', 'string', 'max:20'],
|
||||
'supplier_corp_name' => ['required', 'string', 'max:100'],
|
||||
'supplier_ceo_name' => ['nullable', 'string', 'max:50'],
|
||||
'supplier_addr' => ['nullable', 'string', 'max:200'],
|
||||
'supplier_biz_type' => ['nullable', 'string', 'max:100'],
|
||||
'supplier_biz_class' => ['nullable', 'string', 'max:100'],
|
||||
'supplier_contact_id' => ['nullable', 'string', 'email', 'max:100'],
|
||||
|
||||
// 공급받는자 정보
|
||||
'buyer_corp_num' => ['required', 'string', 'max:20'],
|
||||
'buyer_corp_name' => ['required', 'string', 'max:100'],
|
||||
'buyer_ceo_name' => ['nullable', 'string', 'max:50'],
|
||||
'buyer_addr' => ['nullable', 'string', 'max:200'],
|
||||
'buyer_biz_type' => ['nullable', 'string', 'max:100'],
|
||||
'buyer_biz_class' => ['nullable', 'string', 'max:100'],
|
||||
'buyer_contact_id' => ['nullable', 'string', 'email', 'max:100'],
|
||||
|
||||
// 금액 정보
|
||||
'issue_date' => ['required', 'date'],
|
||||
'supply_amount' => ['required', 'numeric', 'min:0'],
|
||||
'tax_amount' => ['required', 'numeric', 'min:0'],
|
||||
|
||||
// 품목 정보
|
||||
'items' => ['nullable', 'array'],
|
||||
'items.*.name' => ['required_with:items', 'string', 'max:100'],
|
||||
'items.*.spec' => ['nullable', 'string', 'max:100'],
|
||||
'items.*.qty' => ['nullable', 'numeric', 'min:0'],
|
||||
'items.*.unit_price' => ['nullable', 'numeric', 'min:0'],
|
||||
'items.*.supply_amt' => ['nullable', 'numeric', 'min:0'],
|
||||
'items.*.tax_amt' => ['nullable', 'numeric', 'min:0'],
|
||||
'items.*.remark' => ['nullable', 'string', 'max:200'],
|
||||
|
||||
// 참조 정보
|
||||
'reference_type' => ['nullable', 'string', 'max:50'],
|
||||
'reference_id' => ['nullable', 'integer'],
|
||||
'description' => ['nullable', 'string', 'max:1000'],
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'invoice_type' => __('validation.attributes.invoice_type'),
|
||||
'issue_type' => __('validation.attributes.issue_type'),
|
||||
'direction' => __('validation.attributes.direction'),
|
||||
'supplier_corp_num' => __('validation.attributes.supplier_corp_num'),
|
||||
'supplier_corp_name' => __('validation.attributes.supplier_corp_name'),
|
||||
'buyer_corp_num' => __('validation.attributes.buyer_corp_num'),
|
||||
'buyer_corp_name' => __('validation.attributes.buyer_corp_name'),
|
||||
'issue_date' => __('validation.attributes.issue_date'),
|
||||
'supply_amount' => __('validation.attributes.supply_amount'),
|
||||
'tax_amount' => __('validation.attributes.tax_amount'),
|
||||
];
|
||||
}
|
||||
}
|
||||
31
app/Http/Requests/TaxInvoice/TaxInvoiceListRequest.php
Normal file
31
app/Http/Requests/TaxInvoice/TaxInvoiceListRequest.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\TaxInvoice;
|
||||
|
||||
use App\Models\Tenants\TaxInvoice;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class TaxInvoiceListRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
|
||||
'direction' => ['nullable', 'string', Rule::in(TaxInvoice::DIRECTIONS)],
|
||||
'status' => ['nullable', 'string', Rule::in(TaxInvoice::STATUSES)],
|
||||
'invoice_type' => ['nullable', 'string', Rule::in(TaxInvoice::INVOICE_TYPES)],
|
||||
'issue_type' => ['nullable', 'string', Rule::in(TaxInvoice::ISSUE_TYPES)],
|
||||
'issue_date_from' => ['nullable', 'date'],
|
||||
'issue_date_to' => ['nullable', 'date', 'after_or_equal:issue_date_from'],
|
||||
'corp_num' => ['nullable', 'string', 'max:20'],
|
||||
'corp_name' => ['nullable', 'string', 'max:100'],
|
||||
'nts_confirm_num' => ['nullable', 'string', 'max:24'],
|
||||
];
|
||||
}
|
||||
}
|
||||
21
app/Http/Requests/TaxInvoice/TaxInvoiceSummaryRequest.php
Normal file
21
app/Http/Requests/TaxInvoice/TaxInvoiceSummaryRequest.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\TaxInvoice;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class TaxInvoiceSummaryRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'issue_date_from' => ['nullable', 'date'],
|
||||
'issue_date_to' => ['nullable', 'date', 'after_or_equal:issue_date_from'],
|
||||
];
|
||||
}
|
||||
}
|
||||
62
app/Http/Requests/TaxInvoice/UpdateTaxInvoiceRequest.php
Normal file
62
app/Http/Requests/TaxInvoice/UpdateTaxInvoiceRequest.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\TaxInvoice;
|
||||
|
||||
use App\Models\Tenants\TaxInvoice;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateTaxInvoiceRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'invoice_type' => ['sometimes', 'string', Rule::in(TaxInvoice::INVOICE_TYPES)],
|
||||
'issue_type' => ['sometimes', 'string', Rule::in(TaxInvoice::ISSUE_TYPES)],
|
||||
'direction' => ['sometimes', 'string', Rule::in(TaxInvoice::DIRECTIONS)],
|
||||
|
||||
// 공급자 정보
|
||||
'supplier_corp_num' => ['sometimes', 'string', 'max:20'],
|
||||
'supplier_corp_name' => ['sometimes', 'string', 'max:100'],
|
||||
'supplier_ceo_name' => ['nullable', 'string', 'max:50'],
|
||||
'supplier_addr' => ['nullable', 'string', 'max:200'],
|
||||
'supplier_biz_type' => ['nullable', 'string', 'max:100'],
|
||||
'supplier_biz_class' => ['nullable', 'string', 'max:100'],
|
||||
'supplier_contact_id' => ['nullable', 'string', 'email', 'max:100'],
|
||||
|
||||
// 공급받는자 정보
|
||||
'buyer_corp_num' => ['sometimes', 'string', 'max:20'],
|
||||
'buyer_corp_name' => ['sometimes', 'string', 'max:100'],
|
||||
'buyer_ceo_name' => ['nullable', 'string', 'max:50'],
|
||||
'buyer_addr' => ['nullable', 'string', 'max:200'],
|
||||
'buyer_biz_type' => ['nullable', 'string', 'max:100'],
|
||||
'buyer_biz_class' => ['nullable', 'string', 'max:100'],
|
||||
'buyer_contact_id' => ['nullable', 'string', 'email', 'max:100'],
|
||||
|
||||
// 금액 정보
|
||||
'issue_date' => ['sometimes', 'date'],
|
||||
'supply_amount' => ['sometimes', 'numeric', 'min:0'],
|
||||
'tax_amount' => ['sometimes', 'numeric', 'min:0'],
|
||||
|
||||
// 품목 정보
|
||||
'items' => ['nullable', 'array'],
|
||||
'items.*.name' => ['required_with:items', 'string', 'max:100'],
|
||||
'items.*.spec' => ['nullable', 'string', 'max:100'],
|
||||
'items.*.qty' => ['nullable', 'numeric', 'min:0'],
|
||||
'items.*.unit_price' => ['nullable', 'numeric', 'min:0'],
|
||||
'items.*.supply_amt' => ['nullable', 'numeric', 'min:0'],
|
||||
'items.*.tax_amt' => ['nullable', 'numeric', 'min:0'],
|
||||
'items.*.remark' => ['nullable', 'string', 'max:200'],
|
||||
|
||||
// 참조 정보
|
||||
'reference_type' => ['nullable', 'string', 'max:50'],
|
||||
'reference_id' => ['nullable', 'integer'],
|
||||
'description' => ['nullable', 'string', 'max:1000'],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user