feat:카드 거래 숨김(삭제) 및 복원 기능 추가
- CardTransactionHide 모델 생성 (숨김 테이블 연동) - EcardController에 hide/restore/hidden 메서드 추가 - 기존 transactions/getAllCardsTransactions에 숨김 키 필터링 적용 - 프론트엔드에 숨김 버튼, 삭제데이터 보기 토글, 복원 기능 추가 - web.php에 숨김 관련 라우트 3개 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
use App\Models\Barobill\BarobillMember;
|
||||
use App\Models\Barobill\CardTransaction;
|
||||
use App\Models\Barobill\CardTransactionAmountLog;
|
||||
use App\Models\Barobill\CardTransactionHide;
|
||||
use App\Models\Barobill\CardTransactionSplit;
|
||||
use App\Models\Tenants\Tenant;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -365,6 +366,13 @@ public function transactions(Request $request): JsonResponse
|
||||
// 데이터 파싱 (저장된 계정과목 병합)
|
||||
$logs = $this->parseTransactionLogs($resultData, $savedData);
|
||||
|
||||
// 숨김 키 필터링
|
||||
$hiddenKeys = CardTransactionHide::getHiddenKeys($tenantId, $startDate, $endDate);
|
||||
if ($hiddenKeys->isNotEmpty()) {
|
||||
$hiddenSet = $hiddenKeys->flip();
|
||||
$logs = $this->filterHiddenLogs($logs, $hiddenSet);
|
||||
}
|
||||
|
||||
// 수동 입력 건 병합
|
||||
$manualLogs = $this->convertManualToLogs($manualTransactions);
|
||||
$mergedLogs = array_merge($logs['logs'], $manualLogs['logs']);
|
||||
@@ -446,6 +454,11 @@ private function getAllCardsTransactions(string $userId, string $startDate, stri
|
||||
], $cardList),
|
||||
]);
|
||||
|
||||
// 숨김 키 조회
|
||||
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
|
||||
$hiddenKeys = CardTransactionHide::getHiddenKeys($tenantId, $startDate, $endDate);
|
||||
$hiddenSet = $hiddenKeys->isNotEmpty() ? $hiddenKeys->flip() : collect();
|
||||
|
||||
$allLogs = [];
|
||||
$totalAmount = 0;
|
||||
$approvalCount = 0;
|
||||
@@ -497,6 +510,12 @@ private function getAllCardsTransactions(string $userId, string $startDate, stri
|
||||
|
||||
if (!$errorCode || in_array($errorCode, [-24005, -24001])) {
|
||||
$parsed = $this->parseTransactionLogs($cardData, $savedData);
|
||||
|
||||
// 숨김 키 필터링
|
||||
if ($hiddenSet->isNotEmpty()) {
|
||||
$parsed = $this->filterHiddenLogs($parsed, $hiddenSet);
|
||||
}
|
||||
|
||||
Log::info('[ECard] 카드별 파싱 결과', [
|
||||
'cardNum' => $cardNum,
|
||||
'logs_count' => count($parsed['logs']),
|
||||
@@ -1577,6 +1596,195 @@ private function mergeSummaries(array $a, array $b): array
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 숨김 키로 로그 필터링
|
||||
*/
|
||||
private function filterHiddenLogs(array $parsed, $hiddenSet): array
|
||||
{
|
||||
$filteredLogs = [];
|
||||
$totalAmount = 0;
|
||||
$approvalCount = 0;
|
||||
$cancelCount = 0;
|
||||
$totalTax = 0;
|
||||
$deductibleAmount = 0;
|
||||
$deductibleCount = 0;
|
||||
$nonDeductibleAmount = 0;
|
||||
$nonDeductibleCount = 0;
|
||||
|
||||
foreach ($parsed['logs'] as $log) {
|
||||
$uniqueKey = implode('|', [
|
||||
$log['cardNum'],
|
||||
$log['useDt'],
|
||||
$log['approvalNum'],
|
||||
(int) $log['approvalAmount'],
|
||||
]);
|
||||
|
||||
if ($hiddenSet->has($uniqueKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filteredLogs[] = $log;
|
||||
|
||||
$amount = floatval($log['approvalAmount']);
|
||||
$isApproval = $log['approvalType'] !== '2';
|
||||
if ($isApproval) {
|
||||
$totalAmount += $amount;
|
||||
$approvalCount++;
|
||||
} else {
|
||||
$cancelCount++;
|
||||
}
|
||||
|
||||
$deductionType = $log['deductionType'] ?? 'non_deductible';
|
||||
$absAmount = abs($amount);
|
||||
if ($deductionType === 'deductible') {
|
||||
$deductibleAmount += $absAmount;
|
||||
$deductibleCount++;
|
||||
$totalTax += abs($log['effectiveTax'] ?? $log['tax'] ?? 0);
|
||||
} else {
|
||||
$nonDeductibleAmount += $absAmount;
|
||||
$nonDeductibleCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'logs' => $filteredLogs,
|
||||
'summary' => [
|
||||
'totalAmount' => $totalAmount,
|
||||
'count' => count($filteredLogs),
|
||||
'approvalCount' => $approvalCount,
|
||||
'cancelCount' => $cancelCount,
|
||||
'totalTax' => $totalTax,
|
||||
'deductibleAmount' => $deductibleAmount,
|
||||
'deductibleCount' => $deductibleCount,
|
||||
'nonDeductibleAmount' => $nonDeductibleAmount,
|
||||
'nonDeductibleCount' => $nonDeductibleCount,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래 숨김 처리
|
||||
*/
|
||||
public function hideTransaction(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
|
||||
$uniqueKey = $request->input('uniqueKey');
|
||||
$originalData = $request->input('originalData', []);
|
||||
|
||||
if (empty($uniqueKey)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '고유키가 없습니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
// 이미 숨김 처리된 건인지 확인
|
||||
$exists = CardTransactionHide::where('tenant_id', $tenantId)
|
||||
->where('original_unique_key', $uniqueKey)
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '이미 숨김 처리된 거래입니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
CardTransactionHide::hideTransaction($tenantId, $uniqueKey, $originalData, auth()->id());
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '거래가 숨김 처리되었습니다.'
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('거래 숨김 오류: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '숨김 처리 오류: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래 복원 (숨김 해제)
|
||||
*/
|
||||
public function restoreTransaction(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
|
||||
$uniqueKey = $request->input('uniqueKey');
|
||||
|
||||
if (empty($uniqueKey)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '고유키가 없습니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
$deleted = CardTransactionHide::restoreTransaction($tenantId, $uniqueKey);
|
||||
|
||||
if ($deleted === 0) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '숨김 데이터를 찾을 수 없습니다.'
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '거래가 복원되었습니다.'
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('거래 복원 오류: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '복원 오류: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 숨김 처리된 거래 목록 조회
|
||||
*/
|
||||
public function hiddenTransactions(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
|
||||
$startDate = $request->input('startDate', date('Ymd'));
|
||||
$endDate = $request->input('endDate', date('Ymd'));
|
||||
|
||||
$hidden = CardTransactionHide::where('tenant_id', $tenantId)
|
||||
->where('use_date', '>=', $startDate)
|
||||
->where('use_date', '<=', $endDate)
|
||||
->orderBy('use_date', 'desc')
|
||||
->get()
|
||||
->map(fn($h) => [
|
||||
'id' => $h->id,
|
||||
'uniqueKey' => $h->original_unique_key,
|
||||
'cardNum' => $h->card_num,
|
||||
'useDate' => $h->use_date,
|
||||
'approvalNum' => $h->approval_num,
|
||||
'originalAmount' => (float) $h->original_amount,
|
||||
'originalAmountFormatted' => number_format((float) $h->original_amount),
|
||||
'merchantName' => $h->merchant_name,
|
||||
'hiddenAt' => $h->created_at?->format('Y-m-d H:i'),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $hidden,
|
||||
'count' => $hidden->count()
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('숨김 목록 조회 오류: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '조회 오류: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SOAP 호출
|
||||
*/
|
||||
|
||||
60
app/Models/Barobill/CardTransactionHide.php
Normal file
60
app/Models/Barobill/CardTransactionHide.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Barobill;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class CardTransactionHide extends Model
|
||||
{
|
||||
protected $table = 'barobill_card_transaction_hides';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'original_unique_key',
|
||||
'card_num',
|
||||
'use_date',
|
||||
'approval_num',
|
||||
'original_amount',
|
||||
'merchant_name',
|
||||
'hidden_by',
|
||||
];
|
||||
|
||||
/**
|
||||
* 기간 내 숨김된 키 목록 조회
|
||||
*/
|
||||
public static function getHiddenKeys(int $tenantId, string $startDate, string $endDate): Collection
|
||||
{
|
||||
return static::where('tenant_id', $tenantId)
|
||||
->where('use_date', '>=', $startDate)
|
||||
->where('use_date', '<=', $endDate)
|
||||
->pluck('original_unique_key');
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래 숨김 처리
|
||||
*/
|
||||
public static function hideTransaction(int $tenantId, string $uniqueKey, array $originalData, ?int $userId = null): static
|
||||
{
|
||||
return static::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'original_unique_key' => $uniqueKey,
|
||||
'card_num' => $originalData['cardNum'] ?? '',
|
||||
'use_date' => $originalData['useDate'] ?? '',
|
||||
'approval_num' => $originalData['approvalNum'] ?? '',
|
||||
'original_amount' => floatval($originalData['approvalAmount'] ?? 0),
|
||||
'merchant_name' => $originalData['merchantName'] ?? '',
|
||||
'hidden_by' => $userId,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래 복원 (숨김 해제)
|
||||
*/
|
||||
public static function restoreTransaction(int $tenantId, string $uniqueKey): int
|
||||
{
|
||||
return static::where('tenant_id', $tenantId)
|
||||
->where('original_unique_key', $uniqueKey)
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,9 @@
|
||||
manualStore: '{{ route("barobill.ecard.manual.store") }}',
|
||||
manualUpdate: '{{ route("barobill.ecard.manual.update", ":id") }}',
|
||||
manualDestroy: '{{ route("barobill.ecard.manual.destroy", ":id") }}',
|
||||
hide: '{{ route("barobill.ecard.hide") }}',
|
||||
restore: '{{ route("barobill.ecard.restore") }}',
|
||||
hidden: '{{ route("barobill.ecard.hidden") }}',
|
||||
};
|
||||
|
||||
const CSRF_TOKEN = '{{ csrf_token() }}';
|
||||
@@ -1070,6 +1073,12 @@ className="flex-1 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 t
|
||||
onManualNew,
|
||||
onManualEdit,
|
||||
onManualDelete,
|
||||
onHide,
|
||||
showHidden,
|
||||
hiddenLogs,
|
||||
onToggleHidden,
|
||||
onRestore,
|
||||
loadingHidden,
|
||||
}) => {
|
||||
const formatCurrency = (val) => new Intl.NumberFormat('ko-KR').format(val || 0) + '원';
|
||||
|
||||
@@ -1177,6 +1186,22 @@ className="flex items-center gap-2 px-4 py-2 bg-blue-100 text-blue-700 rounded-l
|
||||
</svg>
|
||||
엑셀 다운로드
|
||||
</button>
|
||||
<button
|
||||
onClick={onToggleHidden}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
||||
showHidden
|
||||
? 'bg-red-600 text-white hover:bg-red-700'
|
||||
: 'bg-red-100 text-red-700 hover:bg-red-200'
|
||||
}`}
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21" />
|
||||
</svg>
|
||||
{showHidden ? '삭제데이터 닫기' : '삭제데이터 보기'}
|
||||
{showHidden && hiddenLogs.length > 0 && (
|
||||
<span className="bg-white text-red-600 text-xs px-1.5 py-0.5 rounded-full font-bold">{hiddenLogs.length}</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="overflow-x-auto" style={ {minHeight: '500px', overflowY: 'auto'} }>
|
||||
@@ -1407,28 +1432,39 @@ className="text-xs text-amber-600 hover:text-amber-700 underline"
|
||||
)}
|
||||
</td>
|
||||
<td className="px-3 py-3 text-center">
|
||||
{log.isManual && (
|
||||
<div className="flex items-center gap-1 justify-center">
|
||||
<button
|
||||
onClick={() => onManualEdit(log)}
|
||||
className="p-1 text-blue-500 hover:bg-blue-50 rounded transition-colors"
|
||||
title="수정"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onManualDelete(log.dbId)}
|
||||
className="p-1 text-red-500 hover:bg-red-50 rounded transition-colors"
|
||||
title="삭제"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-1 justify-center">
|
||||
{log.isManual && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => onManualEdit(log)}
|
||||
className="p-1 text-blue-500 hover:bg-blue-50 rounded transition-colors"
|
||||
title="수정"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onManualDelete(log.dbId)}
|
||||
className="p-1 text-red-500 hover:bg-red-50 rounded transition-colors"
|
||||
title="삭제"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
onClick={() => onHide(log, uniqueKey)}
|
||||
className="p-1 text-stone-400 hover:text-red-500 hover:bg-red-50 rounded transition-colors"
|
||||
title="숨기기"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/* 분개 행들 */}
|
||||
@@ -1494,6 +1530,66 @@ className="p-1 text-red-500 hover:bg-red-50 rounded transition-colors"
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* 숨김 데이터 영역 */}
|
||||
{showHidden && (
|
||||
<div className="border-t-2 border-red-200 bg-red-50/50">
|
||||
<div className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<svg className="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21" />
|
||||
</svg>
|
||||
<h3 className="text-sm font-bold text-red-700">숨김 처리된 거래 ({hiddenLogs.length}건)</h3>
|
||||
</div>
|
||||
{loadingHidden ? (
|
||||
<div className="flex items-center justify-center py-6">
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-red-500"></div>
|
||||
</div>
|
||||
) : hiddenLogs.length === 0 ? (
|
||||
<p className="text-sm text-red-400 py-4 text-center">숨김 처리된 거래가 없습니다.</p>
|
||||
) : (
|
||||
<table className="w-full text-left text-sm text-stone-600">
|
||||
<thead className="bg-red-100/50 text-xs uppercase font-medium text-red-500">
|
||||
<tr>
|
||||
<th className="px-4 py-2">사용일</th>
|
||||
<th className="px-4 py-2">카드번호</th>
|
||||
<th className="px-4 py-2">승인번호</th>
|
||||
<th className="px-4 py-2">가맹점명</th>
|
||||
<th className="px-4 py-2 text-right">금액</th>
|
||||
<th className="px-4 py-2">숨김일시</th>
|
||||
<th className="px-4 py-2 text-center">복원</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-red-100">
|
||||
{hiddenLogs.map((h, i) => {
|
||||
const dateStr = h.useDate
|
||||
? `${h.useDate.substring(0,4)}-${h.useDate.substring(4,6)}-${h.useDate.substring(6,8)}`
|
||||
: '-';
|
||||
return (
|
||||
<tr key={h.id || i} className="bg-red-50 hover:bg-red-100/50">
|
||||
<td className="px-4 py-2 text-sm">{dateStr}</td>
|
||||
<td className="px-4 py-2 text-sm font-mono">{h.cardNum}</td>
|
||||
<td className="px-4 py-2 text-sm">{h.approvalNum || '-'}</td>
|
||||
<td className="px-4 py-2 text-sm">{h.merchantName || '-'}</td>
|
||||
<td className="px-4 py-2 text-sm text-right font-medium">{h.originalAmountFormatted}원</td>
|
||||
<td className="px-4 py-2 text-xs text-stone-400">{h.hiddenAt}</td>
|
||||
<td className="px-4 py-2 text-center">
|
||||
<button
|
||||
onClick={() => onRestore(h.uniqueKey)}
|
||||
className="px-3 py-1 bg-green-500 text-white text-xs rounded-lg hover:bg-green-600 transition-colors font-medium"
|
||||
>
|
||||
복원
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1522,6 +1618,11 @@ className="p-1 text-red-500 hover:bg-red-50 rounded transition-colors"
|
||||
const [manualModalOpen, setManualModalOpen] = useState(false);
|
||||
const [manualEditData, setManualEditData] = useState(null);
|
||||
|
||||
// 숨김 관련 상태
|
||||
const [showHidden, setShowHidden] = useState(false);
|
||||
const [hiddenLogs, setHiddenLogs] = useState([]);
|
||||
const [loadingHidden, setLoadingHidden] = useState(false);
|
||||
|
||||
// 가맹점명 모달
|
||||
const [merchantNameModal, setMerchantNameModal] = useState(null);
|
||||
|
||||
@@ -1879,6 +1980,100 @@ className="p-1 text-red-500 hover:bg-red-50 rounded transition-colors"
|
||||
}
|
||||
};
|
||||
|
||||
// 거래 숨김 처리
|
||||
const handleHide = async (log, uniqueKey) => {
|
||||
if (!confirm('이 거래를 숨기시겠습니까?\n(삭제데이터 보기에서 복원 가능)')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(API.hide, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': CSRF_TOKEN,
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
uniqueKey,
|
||||
originalData: {
|
||||
cardNum: log.cardNum,
|
||||
useDate: log.useDate,
|
||||
approvalNum: log.approvalNum,
|
||||
approvalAmount: log.approvalAmount,
|
||||
merchantName: log.merchantName,
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
notify(data.message, 'success');
|
||||
loadTransactions();
|
||||
if (showHidden) loadHidden();
|
||||
} else {
|
||||
notify(data.error || '숨김 처리 실패', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
notify('숨김 처리 오류: ' + err.message, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
// 거래 복원
|
||||
const handleRestore = async (uniqueKey) => {
|
||||
if (!confirm('이 거래를 복원하시겠습니까?')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(API.restore, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': CSRF_TOKEN,
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ uniqueKey })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
notify(data.message, 'success');
|
||||
loadTransactions();
|
||||
loadHidden();
|
||||
} else {
|
||||
notify(data.error || '복원 실패', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
notify('복원 오류: ' + err.message, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
// 숨김 데이터 로드
|
||||
const loadHidden = async () => {
|
||||
setLoadingHidden(true);
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
startDate: dateFrom.replace(/-/g, ''),
|
||||
endDate: dateTo.replace(/-/g, '')
|
||||
});
|
||||
const response = await fetch(`${API.hidden}?${params}`);
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
setHiddenLogs(data.data || []);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('숨김 데이터 로드 오류:', err);
|
||||
} finally {
|
||||
setLoadingHidden(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 삭제데이터 보기 토글
|
||||
const handleToggleHidden = () => {
|
||||
const newVal = !showHidden;
|
||||
setShowHidden(newVal);
|
||||
if (newVal) {
|
||||
loadHidden();
|
||||
}
|
||||
};
|
||||
|
||||
// 계정과목 변경 핸들러
|
||||
const handleAccountCodeChange = useCallback((index, code, name) => {
|
||||
setLogs(prevLogs => {
|
||||
@@ -2132,6 +2327,12 @@ className="p-1 text-red-500 hover:bg-red-50 rounded transition-colors"
|
||||
onManualNew={handleManualNew}
|
||||
onManualEdit={handleManualEdit}
|
||||
onManualDelete={handleManualDelete}
|
||||
onHide={handleHide}
|
||||
showHidden={showHidden}
|
||||
hiddenLogs={hiddenLogs}
|
||||
onToggleHidden={handleToggleHidden}
|
||||
onRestore={handleRestore}
|
||||
loadingHidden={loadingHidden}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -466,6 +466,10 @@
|
||||
Route::post('/manual', [\App\Http\Controllers\Barobill\EcardController::class, 'storeManual'])->name('manual.store');
|
||||
Route::put('/manual/{id}', [\App\Http\Controllers\Barobill\EcardController::class, 'updateManual'])->name('manual.update');
|
||||
Route::delete('/manual/{id}', [\App\Http\Controllers\Barobill\EcardController::class, 'destroyManual'])->name('manual.destroy');
|
||||
// 거래 숨김/복원 관련
|
||||
Route::post('/hide', [\App\Http\Controllers\Barobill\EcardController::class, 'hideTransaction'])->name('hide');
|
||||
Route::post('/restore', [\App\Http\Controllers\Barobill\EcardController::class, 'restoreTransaction'])->name('restore');
|
||||
Route::get('/hidden', [\App\Http\Controllers\Barobill\EcardController::class, 'hiddenTransactions'])->name('hidden');
|
||||
});
|
||||
|
||||
// 홈택스 매입/매출 (React 페이지)
|
||||
|
||||
Reference in New Issue
Block a user