feat: [finance] 계정과목 확장 및 전표 연동 시스템 구현

- AccountCode 모델/서비스 확장 (업데이트, 기본 계정과목 시딩)
- JournalSyncService 추가 (전표 자동 연동)
- SyncsExpenseAccounts 트레이트 추가
- CardTransactionController, TaxInvoiceController 기능 확장
- expense_accounts 테이블에 전표 연결 컬럼 마이그레이션
- account_codes 테이블 확장 마이그레이션
- 전체 테넌트 기본 계정과목 시딩 마이그레이션

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-03-08 10:32:20 +09:00
parent 3ac64d5b76
commit 0044779eb4
16 changed files with 1247 additions and 6 deletions

View File

@@ -10,12 +10,17 @@
use App\Http\Requests\TaxInvoice\TaxInvoiceSummaryRequest;
use App\Http\Requests\TaxInvoice\UpdateTaxInvoiceRequest;
use App\Http\Requests\V1\TaxInvoice\BulkIssueRequest;
use App\Models\Tenants\JournalEntry;
use App\Services\JournalSyncService;
use App\Services\TaxInvoiceService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class TaxInvoiceController extends Controller
{
public function __construct(
private TaxInvoiceService $taxInvoiceService
private TaxInvoiceService $taxInvoiceService,
private JournalSyncService $journalSyncService,
) {}
/**
@@ -148,4 +153,75 @@ public function summary(TaxInvoiceSummaryRequest $request)
message: __('message.fetched')
);
}
// =========================================================================
// 분개 (Journal Entries)
// =========================================================================
/**
* 세금계산서 분개 조회
*/
public function getJournalEntries(int $id): JsonResponse
{
return ApiResponse::handle(function () use ($id) {
$sourceKey = "tax_invoice_{$id}";
$data = $this->journalSyncService->getForSource(
JournalEntry::SOURCE_TAX_INVOICE,
$sourceKey
);
return $data ?? ['rows' => []];
}, __('message.fetched'));
}
/**
* 세금계산서 분개 저장/수정
*/
public function storeJournalEntries(Request $request, int $id): JsonResponse
{
return ApiResponse::handle(function () use ($request, $id) {
$validated = $request->validate([
'rows' => 'required|array|min:1',
'rows.*.side' => 'required|in:debit,credit',
'rows.*.account_subject' => 'required|string|max:20',
'rows.*.debit_amount' => 'required|integer|min:0',
'rows.*.credit_amount' => 'required|integer|min:0',
]);
// 세금계산서 정보 조회 (entry_date용)
$taxInvoice = $this->taxInvoiceService->show($id);
$rows = array_map(fn ($row) => [
'side' => $row['side'],
'account_code' => $row['account_subject'],
'debit_amount' => $row['debit_amount'],
'credit_amount' => $row['credit_amount'],
], $validated['rows']);
$sourceKey = "tax_invoice_{$id}";
return $this->journalSyncService->saveForSource(
JournalEntry::SOURCE_TAX_INVOICE,
$sourceKey,
$taxInvoice->issue_date?->format('Y-m-d') ?? now()->format('Y-m-d'),
"세금계산서 분개 (#{$id})",
$rows,
);
}, __('message.created'));
}
/**
* 세금계산서 분개 삭제
*/
public function deleteJournalEntries(int $id): JsonResponse
{
return ApiResponse::handle(function () use ($id) {
$sourceKey = "tax_invoice_{$id}";
return $this->journalSyncService->deleteForSource(
JournalEntry::SOURCE_TAX_INVOICE,
$sourceKey
);
}, __('message.deleted'));
}
}