validate([ 'start_date' => 'nullable|date', 'end_date' => 'nullable|date|after_or_equal:start_date', 'card_id' => 'nullable|integer', 'search' => 'nullable|string|max:100', 'sort_by' => 'nullable|in:used_at,amount,merchant_name,created_at', 'sort_dir' => 'nullable|in:asc,desc', 'per_page' => 'nullable|integer|min:1|max:100', 'page' => 'nullable|integer|min:1', ]); return $this->service->index($params); }, __('message.fetched')); } /** * 카드 거래 요약 통계 */ public function summary(Request $request): JsonResponse { return ApiResponse::handle(function () use ($request) { $params = $request->validate([ 'start_date' => 'nullable|date', 'end_date' => 'nullable|date|after_or_equal:start_date', ]); return $this->service->summary($params); }, __('message.fetched')); } /** * 카드 거래 대시보드 데이터 * * CEO 대시보드 카드/가지급금 관리 섹션의 cm1 모달용 상세 데이터 */ public function dashboard(): JsonResponse { return ApiResponse::handle(function () { return $this->service->dashboard(); }, __('message.fetched')); } /** * 계정과목 일괄 수정 */ public function bulkUpdateAccountCode(Request $request): JsonResponse { return ApiResponse::handle(function () use ($request) { $validated = $request->validate([ 'ids' => 'required|array|min:1', 'ids.*' => 'required|integer', 'account_code' => 'required|string|max:20', ]); $updatedCount = $this->service->bulkUpdateAccountCode( $validated['ids'], $validated['account_code'] ); return ['updated_count' => $updatedCount]; }, __('message.updated')); } /** * 단일 카드 거래 조회 */ public function show(int $id): JsonResponse { return ApiResponse::handle(function () use ($id) { $transaction = $this->service->show($id); if (! $transaction) { throw new \Illuminate\Database\Eloquent\ModelNotFoundException; } return $transaction; }, __('message.fetched')); } /** * 카드 거래 등록 */ public function store(Request $request): JsonResponse { return ApiResponse::handle(function () use ($request) { $validated = $request->validate([ 'card_id' => 'nullable|integer|exists:cards,id', 'used_at' => 'required|date', 'merchant_name' => 'required|string|max:100', 'amount' => 'required|numeric|min:0', 'description' => 'nullable|string|max:500', 'account_code' => 'nullable|string|max:20', ]); return $this->service->store($validated); }, __('message.created')); } /** * 카드 거래 수정 */ public function update(Request $request, int $id): JsonResponse { return ApiResponse::handle(function () use ($request, $id) { $validated = $request->validate([ 'used_at' => 'nullable|date', 'merchant_name' => 'nullable|string|max:100', 'amount' => 'nullable|numeric|min:0', 'description' => 'nullable|string|max:500', 'account_code' => 'nullable|string|max:20', ]); return $this->service->update($id, $validated); }, __('message.updated')); } /** * 카드 거래 삭제 */ public function destroy(int $id): JsonResponse { return ApiResponse::handle(function () use ($id) { return $this->service->destroy($id); }, __('message.deleted')); } // ========================================================================= // 분개 (Journal Entries) // ========================================================================= /** * 카드 거래 분개 조회 */ public function getJournalEntries(int $id): JsonResponse { return ApiResponse::handle(function () use ($id) { $sourceKey = "card_{$id}"; $data = $this->journalSyncService->getForSource( JournalEntry::SOURCE_CARD_TRANSACTION, $sourceKey ); if (! $data) { return ['items' => []]; } // 프론트엔드가 기대하는 items 형식으로 변환 $items = array_map(fn ($row) => [ 'id' => $row['id'], 'supply_amount' => $row['debit_amount'], 'tax_amount' => 0, 'account_code' => $row['account_code'], 'deduction_type' => 'deductible', 'vendor_name' => $row['vendor_name'], 'description' => $row['memo'], 'memo' => '', ], $data['rows']); return ['items' => $items]; }, __('message.fetched')); } /** * 카드 거래 분개 저장 */ public function storeJournalEntries(Request $request, int $id): JsonResponse { return ApiResponse::handle(function () use ($request, $id) { $validated = $request->validate([ 'items' => 'required|array|min:1', 'items.*.supply_amount' => 'required|integer|min:0', 'items.*.tax_amount' => 'required|integer|min:0', 'items.*.account_code' => 'required|string|max:20', 'items.*.deduction_type' => 'nullable|string|max:20', 'items.*.vendor_name' => 'nullable|string|max:200', 'items.*.description' => 'nullable|string|max:500', 'items.*.memo' => 'nullable|string|max:500', ]); // 카드 거래 정보 조회 (날짜용) $transaction = $this->service->show($id); if (! $transaction) { throw new \Illuminate\Database\Eloquent\ModelNotFoundException; } $entryDate = $transaction->used_at ? $transaction->used_at->format('Y-m-d') : ($transaction->withdrawal_date?->format('Y-m-d') ?? now()->format('Y-m-d')); // items → journal rows 변환 (각 item을 차변 행으로) $rows = []; foreach ($validated['items'] as $item) { $amount = ($item['supply_amount'] ?? 0) + ($item['tax_amount'] ?? 0); $rows[] = [ 'side' => 'debit', 'account_code' => $item['account_code'], 'debit_amount' => $amount, 'credit_amount' => 0, 'vendor_name' => $item['vendor_name'] ?? '', 'memo' => $item['description'] ?? $item['memo'] ?? '', ]; } // 대변 합계 행 (카드미지급금) $totalAmount = array_sum(array_column($rows, 'debit_amount')); $rows[] = [ 'side' => 'credit', 'account_code' => '25300', // 미지급금 (표준 코드) 'account_name' => '미지급금', 'debit_amount' => 0, 'credit_amount' => $totalAmount, 'vendor_name' => $transaction->merchant_name ?? '', 'memo' => '카드결제', ]; $sourceKey = "card_{$id}"; return $this->journalSyncService->saveForSource( JournalEntry::SOURCE_CARD_TRANSACTION, $sourceKey, $entryDate, "카드거래 분개 (#{$id})", $rows, ); }, __('message.created')); } }