- StoreManualJournalRequest/UpdateJournalRequest에 receipt_no 필드 추가 - GeneralJournalEntryService: store/update 시 receipt_no 전달 - JournalSyncService: saveForSource에 receiptNo 파라미터 추가 - SyncsExpenseAccounts: 증빙번호 결정 우선순위 (명시 전달 > 바로빌 승인번호 > null) - SOURCE_BAROBILL_CARD를 카드결제 payment_method에 추가 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
124 lines
4.2 KiB
PHP
124 lines
4.2 KiB
PHP
<?php
|
|
|
|
namespace App\Traits;
|
|
|
|
use App\Models\Barobill\BarobillCardTransaction;
|
|
use App\Models\Tenants\ExpenseAccount;
|
|
use App\Models\Tenants\JournalEntry;
|
|
|
|
/**
|
|
* 전표 저장/수정/삭제 시 expense_accounts 동기화 (복리후생비/접대비 → CEO 대시보드)
|
|
*
|
|
* 사용처: GeneralJournalEntryService, JournalSyncService
|
|
*/
|
|
trait SyncsExpenseAccounts
|
|
{
|
|
/**
|
|
* 계정과목명 → expense_accounts account_type 매핑
|
|
*/
|
|
private static array $expenseAccountMap = [
|
|
'복리후생비' => ExpenseAccount::TYPE_WELFARE,
|
|
'접대비' => ExpenseAccount::TYPE_ENTERTAINMENT,
|
|
];
|
|
|
|
/**
|
|
* 전표 저장/수정 후 expense_accounts 동기화
|
|
* 복리후생비/접대비 계정과목이 포함된 lines → expense_accounts에 반영
|
|
*
|
|
* @param string|null $receiptNo 수기 전표 등에서 직접 전달하는 증빙번호
|
|
*/
|
|
protected function syncExpenseAccounts(JournalEntry $entry, ?string $receiptNo = null): void
|
|
{
|
|
$tenantId = $entry->tenant_id;
|
|
|
|
// 1. 기존 이 전표에서 생성된 expense_accounts 삭제
|
|
ExpenseAccount::where('tenant_id', $tenantId)
|
|
->where('journal_entry_id', $entry->id)
|
|
->forceDelete();
|
|
|
|
// 2. 증빙번호 결정: 명시 전달 > 바로빌 승인번호 > null
|
|
$resolvedReceiptNo = $receiptNo ?? $this->resolveReceiptNo($entry);
|
|
|
|
// 3. 현재 lines 중 복리후생비/접대비 해당하는 것만 insert
|
|
$lines = $entry->lines()->get();
|
|
|
|
foreach ($lines as $line) {
|
|
$accountType = $this->getExpenseAccountType($line->account_name);
|
|
if (! $accountType) {
|
|
continue;
|
|
}
|
|
|
|
// 차변(debit)이 대변보다 큰 경우만 비용 발생으로 처리
|
|
$amount = $line->debit_amount - $line->credit_amount;
|
|
if ($amount <= 0) {
|
|
continue;
|
|
}
|
|
|
|
// source_type에 따라 payment_method 결정
|
|
$paymentMethod = match ($entry->source_type) {
|
|
JournalEntry::SOURCE_CARD_TRANSACTION,
|
|
JournalEntry::SOURCE_BAROBILL_CARD => ExpenseAccount::PAYMENT_CARD,
|
|
default => ExpenseAccount::PAYMENT_TRANSFER,
|
|
};
|
|
|
|
ExpenseAccount::create([
|
|
'tenant_id' => $tenantId,
|
|
'account_type' => $accountType,
|
|
'sub_type' => null,
|
|
'expense_date' => $entry->entry_date,
|
|
'amount' => $amount,
|
|
'description' => $line->description ?? $entry->description,
|
|
'receipt_no' => $resolvedReceiptNo,
|
|
'vendor_id' => $line->trading_partner_id,
|
|
'vendor_name' => $line->trading_partner_name,
|
|
'payment_method' => $paymentMethod,
|
|
'card_no' => null,
|
|
'journal_entry_id' => $entry->id,
|
|
'journal_entry_line_id' => $line->id,
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* source_type에 따라 증빙번호(receipt_no) 자동 조회
|
|
* - 바로빌 카드 거래: approval_num (카드 승인번호)
|
|
*/
|
|
private function resolveReceiptNo(JournalEntry $entry): ?string
|
|
{
|
|
if ($entry->source_type === JournalEntry::SOURCE_BAROBILL_CARD && $entry->source_key) {
|
|
// source_key = "barobill_card_{id}" → BarobillCardTransaction.approval_num
|
|
if (preg_match('/barobill_card_(\d+)/', $entry->source_key, $matches)) {
|
|
$tx = BarobillCardTransaction::find((int) $matches[1]);
|
|
|
|
return $tx?->approval_num;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 전표 삭제 시 expense_accounts 정리
|
|
*/
|
|
protected function cleanupExpenseAccounts(int $tenantId, int $entryId): void
|
|
{
|
|
ExpenseAccount::where('tenant_id', $tenantId)
|
|
->where('journal_entry_id', $entryId)
|
|
->forceDelete();
|
|
}
|
|
|
|
/**
|
|
* 계정과목명에서 비용 유형 판별
|
|
*/
|
|
private function getExpenseAccountType(string $accountName): ?string
|
|
{
|
|
foreach (self::$expenseAccountMap as $keyword => $type) {
|
|
if (str_contains($accountName, $keyword)) {
|
|
return $type;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|