feat:카드 사용내역 공제/증빙/내역 필드 수정 기능 추가

- 공제 필드: 공제/불공 선택 가능 (불공은 적색 표시)
- 증빙/판매자상호 필드: 텍스트 입력으로 수정 가능
- 내역 필드: 텍스트 입력으로 수정 가능
- CardTransaction 모델에 deduction_type, evidence_name, description 필드 추가
- 마이그레이션 추가
- 컨트롤러에서 새 필드 저장/로드 처리

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
pro
2026-01-23 15:16:17 +09:00
parent 6221e84ea6
commit 7ad8b00a47
4 changed files with 91 additions and 12 deletions

View File

@@ -548,6 +548,10 @@ private function parseTransactionLogs($resultData, $savedData = null): array
// 저장된 계정과목 정보 병합
'accountCode' => $savedItem?->account_code ?? '',
'accountName' => $savedItem?->account_name ?? '',
// 수정 가능한 필드들 (저장된 값 또는 기본값)
'deductionType' => $savedItem?->deduction_type ?? ($log->UseStoreCorpNum ? 'deductible' : 'non_deductible'),
'evidenceName' => $savedItem?->evidence_name ?? ($log->UseStoreName ?? ''),
'description' => $savedItem?->description ?? ($log->UseStoreBizType ?? $log->Memo ?? ''),
'isSaved' => $savedItem !== null,
];
@@ -716,6 +720,9 @@ public function save(Request $request): JsonResponse
'use_key' => $trans['useKey'] ?? '',
'account_code' => $trans['accountCode'] ?? null,
'account_name' => $trans['accountName'] ?? null,
'deduction_type' => $trans['deductionType'] ?? null,
'evidence_name' => $trans['evidenceName'] ?? null,
'description' => $trans['description'] ?? null,
];
// Upsert: 있으면 업데이트, 없으면 생성
@@ -727,10 +734,13 @@ public function save(Request $request): JsonResponse
->first();
if ($existing) {
// 계정과목 업데이트
// 계정과목 및 수정 가능한 필드들 업데이트
$existing->update([
'account_code' => $data['account_code'],
'account_name' => $data['account_name'],
'deduction_type' => $data['deduction_type'],
'evidence_name' => $data['evidence_name'],
'description' => $data['description'],
]);
$updated++;
} else {

View File

@@ -38,6 +38,9 @@ class CardTransaction extends Model
'use_key',
'account_code',
'account_name',
'deduction_type',
'evidence_name',
'description',
];
protected $casts = [

View File

@@ -0,0 +1,34 @@
<?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_transactions', function (Blueprint $table) {
// 공제 유형: 공제(deductible) / 불공(non_deductible)
$table->string('deduction_type', 20)->nullable()->after('account_name')->comment('공제유형: deductible/non_deductible');
// 증빙/판매자상호 (사용자 수정용)
$table->string('evidence_name', 255)->nullable()->after('deduction_type')->comment('증빙/판매자상호 (수정용)');
// 내역 (사용자 수정용)
$table->string('description', 500)->nullable()->after('evidence_name')->comment('내역 (수정용)');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('barobill_card_transactions', function (Blueprint $table) {
$table->dropColumn(['deduction_type', 'evidence_name', 'description']);
});
}
};

View File

@@ -471,6 +471,7 @@ className="flex-1 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 t
totalCount,
accountCodes,
onAccountCodeChange,
onFieldChange,
onSave,
onExport,
saving,
@@ -644,22 +645,39 @@ className="p-1.5 text-purple-500 hover:bg-purple-100 rounded-lg transition-color
</div>
</td>
<td className="px-4 py-3 text-center">
<span className={`px-2 py-1 rounded text-xs font-medium ${
log.merchantBizNum
? 'bg-green-100 text-green-700'
: 'bg-stone-100 text-stone-500'
}`}>
{log.merchantBizNum ? '공제' : '불공제'}
</span>
<select
value={log.deductionType || (log.merchantBizNum ? 'deductible' : 'non_deductible')}
onChange={(e) => onFieldChange(index, 'deductionType', e.target.value)}
className={`px-2 py-1 rounded text-xs font-medium border-0 cursor-pointer ${
(log.deductionType || (log.merchantBizNum ? 'deductible' : 'non_deductible')) === 'deductible'
? 'bg-green-100 text-green-700'
: 'bg-red-100 text-red-600'
}`}
>
<option value="deductible">공제</option>
<option value="non_deductible">불공</option>
</select>
</td>
<td className="px-4 py-3">
<div className="font-medium text-stone-900">{log.merchantName || '-'}</div>
<input
type="text"
value={log.evidenceName ?? log.merchantName ?? ''}
onChange={(e) => onFieldChange(index, 'evidenceName', e.target.value)}
className="w-full px-2 py-1 text-sm border border-stone-200 rounded focus:outline-none focus:ring-1 focus:ring-purple-500 focus:border-purple-500"
placeholder="판매자상호"
/>
{log.merchantBizNum && (
<div className="text-xs text-stone-400">{log.merchantBizNum}</div>
<div className="text-xs text-stone-400 mt-1">{log.merchantBizNum}</div>
)}
</td>
<td className="px-4 py-3 text-stone-600 text-sm">
{log.merchantBizType || log.memo || '-'}
<td className="px-4 py-3">
<input
type="text"
value={log.description ?? log.merchantBizType ?? log.memo ?? ''}
onChange={(e) => onFieldChange(index, 'description', e.target.value)}
className="w-full px-2 py-1 text-sm border border-stone-200 rounded focus:outline-none focus:ring-1 focus:ring-purple-500 focus:border-purple-500"
placeholder="내역"
/>
</td>
<td className="px-4 py-3 text-right font-medium">
<span className={log.approvalType === '1' ? 'text-purple-600' : 'text-red-600'}>
@@ -935,6 +953,19 @@ className="text-xs text-amber-600 hover:text-amber-700 underline"
setHasChanges(true);
}, []);
// 필드 변경 핸들러 (공제, 증빙/판매자상호, 내역)
const handleFieldChange = useCallback((index, field, value) => {
setLogs(prevLogs => {
const newLogs = [...prevLogs];
newLogs[index] = {
...newLogs[index],
[field]: value
};
return newLogs;
});
setHasChanges(true);
}, []);
// 저장 핸들러
const handleSave = async () => {
if (logs.length === 0) return;
@@ -1095,6 +1126,7 @@ className="text-xs text-amber-600 hover:text-amber-700 underline"
totalCount={summary.count || logs.length}
accountCodes={accountCodes}
onAccountCodeChange={handleAccountCodeChange}
onFieldChange={handleFieldChange}
onSave={handleSave}
onExport={handleExport}
saving={saving}