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

@@ -48,6 +48,16 @@ public function index(array $params): LengthAwarePaginator
$query->where('client_id', $params['client_id']);
}
// 증권종류 필터
if (! empty($params['instrument_type'])) {
$query->where('instrument_type', $params['instrument_type']);
}
// 매체 필터
if (! empty($params['medium'])) {
$query->where('medium', $params['medium']);
}
// 전자어음 필터
if (isset($params['is_electronic']) && $params['is_electronic'] !== '') {
$query->where('is_electronic', (bool) $params['is_electronic']);
@@ -113,32 +123,23 @@ public function store(array $data): Bill
$bill->client_name = $data['client_name'] ?? null;
$bill->amount = $data['amount'];
$bill->issue_date = $data['issue_date'];
$bill->maturity_date = $data['maturity_date'];
$bill->maturity_date = $data['maturity_date'] ?? null;
$bill->status = $data['status'] ?? 'stored';
$bill->reason = $data['reason'] ?? null;
$bill->installment_count = $data['installment_count'] ?? 0;
$bill->note = $data['note'] ?? null;
$bill->is_electronic = $data['is_electronic'] ?? false;
$bill->bank_account_id = $data['bank_account_id'] ?? null;
// V8 확장 필드
$this->assignV8Fields($bill, $data);
$bill->created_by = $userId;
$bill->updated_by = $userId;
$bill->save();
// 차수 관리 저장
if (! empty($data['installments'])) {
foreach ($data['installments'] as $installment) {
BillInstallment::create([
'bill_id' => $bill->id,
'installment_date' => $installment['date'],
'amount' => $installment['amount'],
'note' => $installment['note'] ?? null,
'created_by' => $userId,
]);
}
// 차수 카운트 업데이트
$bill->installment_count = count($data['installments']);
$bill->save();
}
$this->syncInstallments($bill, $data['installments'] ?? [], $userId);
return $bill->load(['client:id,name', 'bankAccount:id,bank_name,account_name', 'installments']);
});
@@ -157,6 +158,7 @@ public function update(int $id, array $data): Bill
->where('tenant_id', $tenantId)
->findOrFail($id);
// 기존 필드
if (isset($data['bill_number'])) {
$bill->bill_number = $data['bill_number'];
}
@@ -175,7 +177,7 @@ public function update(int $id, array $data): Bill
if (isset($data['issue_date'])) {
$bill->issue_date = $data['issue_date'];
}
if (isset($data['maturity_date'])) {
if (array_key_exists('maturity_date', $data)) {
$bill->maturity_date = $data['maturity_date'];
}
if (isset($data['status'])) {
@@ -194,27 +196,15 @@ public function update(int $id, array $data): Bill
$bill->bank_account_id = $data['bank_account_id'];
}
// V8 확장 필드
$this->assignV8Fields($bill, $data);
$bill->updated_by = $userId;
$bill->save();
// 차수 관리 업데이트 (전체 교체)
if (isset($data['installments'])) {
// 기존 차수 삭제
$bill->installments()->delete();
// 새 차수 추가
foreach ($data['installments'] as $installment) {
BillInstallment::create([
'bill_id' => $bill->id,
'installment_date' => $installment['date'],
'amount' => $installment['amount'],
'note' => $installment['note'] ?? null,
'created_by' => $userId,
]);
}
// 차수 카운트 업데이트
$bill->installment_count = count($data['installments']);
$bill->save();
$this->syncInstallments($bill, $data['installments'], $userId);
}
return $bill->fresh(['client:id,name', 'bankAccount:id,bank_name,account_name', 'installments']);
@@ -440,6 +430,68 @@ public function dashboardDetail(): array
];
}
/**
* V8 확장 필드를 Bill 모델에 할당
*/
private function assignV8Fields(Bill $bill, array $data): void
{
$v8Fields = [
'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',
];
foreach ($v8Fields as $field) {
if (array_key_exists($field, $data)) {
$bill->{$field} = $data[$field];
}
}
}
/**
* 차수(이력) 동기화 — 기존 삭제 후 새로 생성
*/
private function syncInstallments(Bill $bill, array $installments, int $userId): void
{
if (empty($installments)) {
return;
}
// 기존 차수 삭제
$bill->installments()->delete();
// 새 차수 추가
foreach ($installments as $installment) {
BillInstallment::create([
'bill_id' => $bill->id,
'type' => $installment['type'] ?? 'other',
'installment_date' => $installment['date'],
'amount' => $installment['amount'],
'counterparty' => $installment['counterparty'] ?? null,
'note' => $installment['note'] ?? null,
'created_by' => $userId,
]);
}
// 차수 카운트 업데이트
$bill->installment_count = count($installments);
$bill->save();
}
/**
* 어음번호 자동 생성
*/