feat:거래 분개 모달에 공제/불공 선택 필드 추가

- SplitModal에 공제/불공 선택 드롭다운 추가
- 공제: 녹색 배경, 불공: 붉은색 배경
- CardTransactionSplit 모델에 deduction_type 필드 추가
- 마이그레이션으로 splits 테이블에 deduction_type 컬럼 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
pro
2026-01-23 15:55:35 +09:00
parent 4562e5c754
commit b6f3bcceb8
3 changed files with 51 additions and 0 deletions

View File

@@ -20,6 +20,7 @@ class CardTransactionSplit extends Model
'split_amount',
'account_code',
'account_name',
'deduction_type',
'evidence_name',
'description',
'memo',
@@ -100,6 +101,7 @@ public static function saveSplits(int $tenantId, string $uniqueKey, array $origi
'split_amount' => $split['amount'] ?? 0,
'account_code' => $split['accountCode'] ?? null,
'account_name' => $split['accountName'] ?? null,
'deduction_type' => $split['deductionType'] ?? null,
'evidence_name' => $split['evidenceName'] ?? null,
'description' => $split['description'] ?? null,
'memo' => $split['memo'] ?? null,

View File

@@ -0,0 +1,29 @@
<?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::table('barobill_card_transaction_splits', function (Blueprint $table) {
$table->string('deduction_type', 20)->nullable()->after('account_name')->comment('공제유형: deductible/non_deductible');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('barobill_card_transaction_splits', function (Blueprint $table) {
$table->dropColumn('deduction_type');
});
}
};

View File

@@ -284,12 +284,14 @@ className={`px-3 py-1.5 text-xs cursor-pointer hover:bg-purple-50 ${
useEffect(() => {
if (isOpen && log) {
const defaultDeductionType = log.deductionType || (log.merchantBizNum ? 'deductible' : 'non_deductible');
if (existingSplits && existingSplits.length > 0) {
// 기존 분개 로드
setSplits(existingSplits.map(s => ({
amount: parseFloat(s.split_amount || s.amount || 0),
accountCode: s.account_code || s.accountCode || '',
accountName: s.account_name || s.accountName || '',
deductionType: s.deduction_type || s.deductionType || defaultDeductionType,
evidenceName: s.evidence_name || s.evidenceName || log.evidenceName || log.merchantName || '',
description: s.description || log.description || log.merchantBizType || log.memo || '',
memo: s.memo || ''
@@ -300,6 +302,7 @@ className={`px-3 py-1.5 text-xs cursor-pointer hover:bg-purple-50 ${
amount: log.approvalAmount || 0,
accountCode: log.accountCode || '',
accountName: log.accountName || '',
deductionType: defaultDeductionType,
evidenceName: log.evidenceName || log.merchantName || '',
description: log.description || log.merchantBizType || log.memo || '',
memo: ''
@@ -316,10 +319,12 @@ className={`px-3 py-1.5 text-xs cursor-pointer hover:bg-purple-50 ${
const addSplit = () => {
const remaining = originalAmount - splitTotal;
const defaultDeductionType = log.deductionType || (log.merchantBizNum ? 'deductible' : 'non_deductible');
setSplits([...splits, {
amount: remaining > 0 ? remaining : 0,
accountCode: '',
accountName: '',
deductionType: defaultDeductionType,
evidenceName: log.evidenceName || log.merchantName || '',
description: log.description || log.merchantBizType || log.memo || '',
memo: ''
@@ -400,6 +405,21 @@ className="w-full px-3 py-2 border border-stone-200 rounded-lg text-sm focus:rin
accountCodes={accountCodes}
/>
</div>
<div>
<label className="block text-xs text-stone-500 mb-1">공제</label>
<select
value={split.deductionType || 'deductible'}
onChange={(e) => updateSplit(index, { deductionType: e.target.value })}
className={`w-full px-3 py-2 border border-stone-200 rounded-lg text-sm font-bold focus:ring-2 focus:ring-purple-500 outline-none ${
split.deductionType === 'non_deductible'
? 'bg-red-500 text-white'
: 'bg-green-100 text-green-700'
}`}
>
<option value="deductible">공제</option>
<option value="non_deductible">불공</option>
</select>
</div>
<div>
<label className="block text-xs text-stone-500 mb-1">증빙/판매자상호</label>
<input