vendorInfluencerModel = new VendorInfluencerMappingModel(); $this->vendorPartnershipModel = new VendorPartnershipModel(); $this->statusHistoryModel = new VendorInfluencerStatusHistoryModel(); $this->vendorModel = new VendorModel(); $this->influencerModel = new InfluencerModel(); } /** * 벤더사의 인플루언서 요청 목록 조회 (히스토리 테이블 기반) */ public function getInfluencerRequests() { try { $request = $this->request->getJSON(); $vendorSeq = $request->vendorSeq ?? null; $status = $request->status ?? null; $page = $request->page ?? 1; $size = $request->size ?? 20; log_message('debug', 'getInfluencerRequests 호출: ' . json_encode([ 'vendorSeq' => $vendorSeq, 'status' => $status, 'page' => $page, 'size' => $size ])); if (!$vendorSeq) { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => '벤더사 SEQ는 필수입니다.' ]); } $result = $this->vendorPartnershipModel->getVendorRequestsWithPagination($vendorSeq, $page, $size, $status); // 통계 계산 (히스토리 테이블이 없을 경우를 대비한 안전장치) $statsFormatted = [ 'pending' => 0, 'approved' => 0, 'rejected' => 0, 'total' => 0 ]; try { $stats = $this->statusHistoryModel->getStatusStatsByVendor($vendorSeq); foreach ($stats as $stat) { $statsFormatted['total'] += $stat['count']; switch ($stat['STATUS']) { case 'PENDING': $statsFormatted['pending'] = $stat['count']; break; case 'APPROVED': $statsFormatted['approved'] = $stat['count']; break; case 'REJECTED': $statsFormatted['rejected'] = $stat['count']; break; } } } catch (\Exception $statsError) { log_message('warning', '통계 조회 실패 (히스토리 테이블 없음?): ' . $statsError->getMessage()); // 히스토리 테이블이 없으면 메인 테이블에서 대략적인 통계 계산 try { $mainStats = $this->vendorInfluencerModel ->where('VENDOR_SEQ', $vendorSeq) ->where('IS_ACT', 'Y') ->countAllResults(); $statsFormatted['total'] = $mainStats; $statsFormatted['pending'] = $mainStats; // 히스토리가 없으면 모두 PENDING으로 가정 } catch (\Exception $mainStatsError) { log_message('error', '메인 테이블 통계도 실패: ' . $mainStatsError->getMessage()); } } log_message('debug', 'API 응답 데이터: ' . json_encode([ 'items_count' => count($result['data']), 'pagination' => $result['pagination'], 'stats' => $statsFormatted ])); // 프론트엔드에서 기대하는 응답 구조에 맞춤 return $this->response->setJSON([ 'success' => true, 'data' => [ 'items' => $result['data'], // 프론트엔드에서 data.items로 접근 'total' => $result['pagination']['total'], 'page' => $result['pagination']['currentPage'], 'totalPages' => $result['pagination']['totalPages'], 'size' => $result['pagination']['limit'], 'stats' => $statsFormatted ] ]); } catch (\Exception $e) { log_message('error', '인플루언서 요청 목록 조회 오류: ' . $e->getMessage()); log_message('error', '스택 트레이스: ' . $e->getTraceAsString()); return $this->response->setStatusCode(500)->setJSON([ 'success' => false, 'message' => '요청 목록 조회 중 오류가 발생했습니다.', 'error' => $e->getMessage() ]); } } /** * 인플루언서 요청 승인/거절 처리 (히스토리 테이블 기반) */ public function processInfluencerRequest() { try { $request = $this->request->getJSON(); $mappingSeq = $request->mappingSeq ?? null; $action = $request->action ?? null; // 'approve' or 'reject' $processedBy = $request->processedBy ?? null; $responseMessage = $request->responseMessage ?? ''; log_message('debug', '승인 처리 요청: ' . json_encode([ 'mappingSeq' => $mappingSeq, 'action' => $action, 'processedBy' => $processedBy, 'responseMessage' => $responseMessage ])); if (!$mappingSeq || !$action || !$processedBy) { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => '필수 파라미터가 누락되었습니다. (mappingSeq, action, processedBy 필요)' ]); } // action 검증 if (!in_array($action, ['approve', 'reject'])) { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => 'action은 approve 또는 reject만 가능합니다.' ]); } // 매핑 정보와 현재 상태 확인 $mapping = $this->vendorInfluencerModel->getWithCurrentStatus($mappingSeq); if (!$mapping) { return $this->response->setStatusCode(404)->setJSON([ 'success' => false, 'message' => '요청을 찾을 수 없습니다.' ]); } // 현재 상태가 PENDING인지 확인 if ($mapping['CURRENT_STATUS'] !== 'PENDING') { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => '이미 처리된 요청입니다. 현재 상태: ' . $mapping['CURRENT_STATUS'] ]); } // 처리자 확인 $processingUser = $this->validateProcessor($processedBy); if (!$processingUser['success']) { return $this->response->setStatusCode(400)->setJSON($processingUser); } // 상태 변경 $newStatus = ($action === 'approve') ? 'APPROVED' : 'REJECTED'; $statusMessage = $responseMessage ?: ($action === 'approve' ? '승인 처리됨' : '거부 처리됨'); log_message('debug', "상태 변경: {$mapping['CURRENT_STATUS']} → {$newStatus}"); // 히스토리 테이블에 상태 변경 기록 $this->statusHistoryModel->changeStatus($mappingSeq, $newStatus, $statusMessage, $processedBy); // 메인 테이블 업데이트 (응답 관련 정보) $this->vendorInfluencerModel->update($mappingSeq, [ 'RESPONSE_MESSAGE' => $responseMessage, 'RESPONSE_DATE' => date('Y-m-d H:i:s'), 'APPROVED_BY' => $processedBy ]); // 승인인 경우 파트너십 시작일 설정 if ($action === 'approve') { $this->vendorInfluencerModel->update($mappingSeq, [ 'PARTNERSHIP_START_DATE' => date('Y-m-d H:i:s') ]); } log_message('debug', "승인 처리 완료: action={$action}, newStatus={$newStatus}"); return $this->response->setJSON([ 'success' => true, 'message' => $action === 'approve' ? '요청이 승인되었습니다.' : '요청이 거부되었습니다.', 'data' => [ 'mappingSeq' => $mappingSeq, 'action' => $action, 'status' => $newStatus, 'processedBy' => $processingUser['data']['name'], 'responseMessage' => $responseMessage ] ]); } catch (\Exception $e) { log_message('error', '승인 처리 중 예외 발생: ' . $e->getMessage()); log_message('error', '승인 처리 스택 트레이스: ' . $e->getTraceAsString()); return $this->response->setStatusCode(500)->setJSON([ 'success' => false, 'message' => '요청 처리 중 오류가 발생했습니다.', 'error' => $e->getMessage() ]); } } /** * 처리자 검증 (벤더사 또는 사용자) */ private function validateProcessor($processedBy) { // 1. 먼저 USER_LIST에서 확인 (인플루언서) $user = $this->influencerModel ->where('SEQ', $processedBy) ->where('IS_ACT', 'Y') ->first(); if ($user) { return [ 'success' => true, 'data' => [ 'type' => 'user', 'seq' => $user['SEQ'], 'name' => $user['NICK_NAME'] ?: $user['NAME'] ] ]; } // 2. VENDOR_LIST에서 확인 (벤더사) $vendor = $this->vendorModel ->where('SEQ', $processedBy) ->where('IS_ACT', 'Y') ->first(); if ($vendor) { return [ 'success' => true, 'data' => [ 'type' => 'vendor', 'seq' => $vendor['SEQ'], 'name' => $vendor['COMPANY_NAME'] . ' (벤더사)' ] ]; } return [ 'success' => false, 'message' => "처리자 SEQ {$processedBy}는 USER_LIST나 VENDOR_LIST에서 찾을 수 없습니다." ]; } /** * 파트너십 해지 (벤더사 권한) */ public function terminatePartnership() { try { $request = $this->request->getJSON(); $mappingSeq = $request->mappingSeq ?? null; $terminatedBy = $request->terminatedBy ?? null; $terminateReason = $request->terminateReason ?? ''; // 프론트엔드와 일치 log_message('debug', '파트너십 해지 요청: ' . json_encode([ 'mappingSeq' => $mappingSeq, 'terminatedBy' => $terminatedBy, 'terminateReason' => $terminateReason ])); if (!$mappingSeq || !$terminatedBy) { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => '필수 파라미터가 누락되었습니다.' ]); } // 매핑 정보와 현재 상태 확인 $mapping = $this->vendorInfluencerModel->getWithCurrentStatus($mappingSeq); if (!$mapping) { return $this->response->setStatusCode(404)->setJSON([ 'success' => false, 'message' => '파트너십을 찾을 수 없습니다.' ]); } log_message('debug', '현재 매핑 상태: ' . json_encode($mapping)); // 현재 상태가 APPROVED인지 확인 if ($mapping['CURRENT_STATUS'] !== 'APPROVED') { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => '승인된 파트너십만 해지할 수 있습니다. 현재 상태: ' . $mapping['CURRENT_STATUS'] ]); } // 처리자 확인 $processingUser = $this->validateProcessor($terminatedBy); if (!$processingUser['success']) { return $this->response->setStatusCode(400)->setJSON($processingUser); } log_message('debug', '처리자 검증 완료: ' . json_encode($processingUser['data'])); // VendorPartnershipModel을 통한 해지 처리 $statusMessage = '파트너십 해지: ' . $terminateReason; // CHANGED_BY 값을 확실하게 설정 (processingUser에서 가져온 실제 SEQ 사용) $actualChangedBy = $processingUser['data']['seq'] ?? $terminatedBy; // CHANGED_BY가 여전히 null이면 기본값 설정 if (!$actualChangedBy) { log_message('warning', 'CHANGED_BY가 여전히 null - 원본 terminatedBy 사용: ' . $terminatedBy); $actualChangedBy = $terminatedBy ?: 1; // 최종 기본값 1 } log_message('debug', "해지 처리 준비: mappingSeq={$mappingSeq}, changedBy={$actualChangedBy} (원본: {$terminatedBy})"); try { // 상태를 TERMINATED로 변경 $this->statusHistoryModel->changeStatus($mappingSeq, 'TERMINATED', $statusMessage, $actualChangedBy); // 해지 날짜 업데이트 $this->vendorInfluencerModel->update($mappingSeq, [ 'PARTNERSHIP_END_DATE' => date('Y-m-d H:i:s') ]); log_message('debug', '파트너십 해지 완료: mappingSeq=' . $mappingSeq); return $this->response->setJSON([ 'success' => true, 'message' => '파트너십이 해지되었습니다.', 'data' => [ 'mappingSeq' => $mappingSeq, 'status' => 'TERMINATED', 'terminatedBy' => $processingUser['data']['name'], 'terminateReason' => $terminateReason ] ]); } catch (\Exception $statusError) { log_message('error', '상태 변경 실패: ' . $statusError->getMessage()); log_message('error', '상태 변경 스택 트레이스: ' . $statusError->getTraceAsString()); return $this->response->setStatusCode(500)->setJSON([ 'success' => false, 'message' => '파트너십 해지 중 오류가 발생했습니다.', 'error' => $statusError->getMessage() ]); } } catch (\Exception $e) { log_message('error', '파트너십 해지 오류: ' . $e->getMessage()); log_message('error', '파트너십 해지 스택 트레이스: ' . $e->getTraceAsString()); return $this->response->setStatusCode(500)->setJSON([ 'success' => false, 'message' => '파트너십 해지 중 오류가 발생했습니다.', 'error' => $e->getMessage() ]); } } /** * 벤더사 상태 통계 조회 */ public function getStatusStats() { try { $request = $this->request->getJSON(); $vendorSeq = $request->vendorSeq ?? null; if (!$vendorSeq) { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => '벤더사 SEQ는 필수입니다.' ]); } $stats = $this->statusHistoryModel->getStatusStatsByVendor($vendorSeq); return $this->response->setJSON([ 'success' => true, 'data' => $stats ]); } catch (\Exception $e) { log_message('error', '상태 통계 조회 오류: ' . $e->getMessage()); return $this->response->setStatusCode(500)->setJSON([ 'success' => false, 'message' => '상태 통계 조회 중 오류가 발생했습니다.', 'error' => $e->getMessage() ]); } } /** * 인플루언서 요청 승인/거절 (프론트엔드 호환용) * 프론트엔드에서 /api/vendor-influencer/approve 호출에 대응 */ public function approveInfluencerRequest() { try { $request = $this->request->getJSON(); $mappingSeq = $request->mappingSeq ?? null; $action = $request->action ?? null; // 'APPROVE' or 'REJECT' $processedBy = $request->processedBy ?? null; $responseMessage = $request->responseMessage ?? ''; log_message('debug', '프론트엔드 승인 처리 요청: ' . json_encode([ 'mappingSeq' => $mappingSeq, 'action' => $action, 'processedBy' => $processedBy, 'responseMessage' => $responseMessage ])); if (!$mappingSeq || !$action || !$processedBy) { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => '필수 파라미터가 누락되었습니다. (mappingSeq, action, processedBy 필요)' ]); } // action 값 정규화 (프론트엔드에서는 대문자로 전송) $normalizedAction = strtolower($action); if (!in_array($normalizedAction, ['approve', 'reject'])) { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => 'action은 APPROVE 또는 REJECT만 가능합니다.' ]); } // 매핑 정보와 현재 상태 확인 $mapping = $this->vendorInfluencerModel->getWithCurrentStatus($mappingSeq); if (!$mapping) { return $this->response->setStatusCode(404)->setJSON([ 'success' => false, 'message' => '요청을 찾을 수 없습니다.' ]); } // 현재 상태가 PENDING인지 확인 if ($mapping['CURRENT_STATUS'] !== 'PENDING') { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => '이미 처리된 요청입니다. 현재 상태: ' . $mapping['CURRENT_STATUS'] ]); } // 처리자 확인 $processingUser = $this->validateProcessor($processedBy); if (!$processingUser['success']) { return $this->response->setStatusCode(400)->setJSON($processingUser); } // 상태 변경 $newStatus = ($normalizedAction === 'approve') ? 'APPROVED' : 'REJECTED'; $statusMessage = $responseMessage ?: ($normalizedAction === 'approve' ? '승인 처리됨' : '거부 처리됨'); log_message('debug', "프론트엔드 상태 변경: {$mapping['CURRENT_STATUS']} → {$newStatus}"); // 히스토리 테이블에 상태 변경 기록 $this->statusHistoryModel->changeStatus($mappingSeq, $newStatus, $statusMessage, $processedBy); // 메인 테이블 업데이트 (응답 관련 정보) $this->vendorInfluencerModel->update($mappingSeq, [ 'RESPONSE_MESSAGE' => $responseMessage, 'RESPONSE_DATE' => date('Y-m-d H:i:s'), 'APPROVED_BY' => $processedBy ]); // 승인인 경우 파트너십 시작일 설정 if ($normalizedAction === 'approve') { $this->vendorInfluencerModel->update($mappingSeq, [ 'PARTNERSHIP_START_DATE' => date('Y-m-d H:i:s') ]); } log_message('debug', "프론트엔드 승인 처리 완료: action={$normalizedAction}, newStatus={$newStatus}"); return $this->response->setJSON([ 'success' => true, 'message' => $normalizedAction === 'approve' ? '요청이 승인되었습니다.' : '요청이 거부되었습니다.', 'data' => [ 'mappingSeq' => $mappingSeq, 'action' => $action, 'status' => $newStatus, 'processedBy' => $processingUser['data']['name'], 'responseMessage' => $responseMessage ] ]); } catch (\Exception $e) { log_message('error', '프론트엔드 승인 처리 중 예외 발생: ' . $e->getMessage()); log_message('error', '프론트엔드 승인 처리 스택 트레이스: ' . $e->getTraceAsString()); return $this->response->setStatusCode(500)->setJSON([ 'success' => false, 'message' => '요청 처리 중 오류가 발생했습니다.', 'error' => $e->getMessage() ]); } } /** * 디버깅용: 특정 매핑의 상태 정보 확인 */ public function debugMappingStatus($mappingSeq = null) { try { $mappingSeq = $mappingSeq ?: $this->request->getGet('mappingSeq'); if (!$mappingSeq) { return $this->response->setJSON([ 'success' => false, 'message' => 'mappingSeq 파라미터가 필요합니다.' ]); } // 1. 메인 매핑 정보 $mapping = $this->vendorInfluencerModel->find($mappingSeq); // 2. 현재 상태 $currentStatus = $this->statusHistoryModel->getCurrentStatus($mappingSeq); // 3. 모든 히스토리 $allHistory = $this->statusHistoryModel->getStatusHistory($mappingSeq, 50); // 4. 현재 상태가 여러 개인지 확인 $currentStatusCount = $this->statusHistoryModel ->where('MAPPING_SEQ', $mappingSeq) ->where('IS_CURRENT', 'Y') ->countAllResults(); return $this->response->setJSON([ 'success' => true, 'data' => [ 'mappingSeq' => $mappingSeq, 'mapping' => $mapping, 'currentStatus' => $currentStatus, 'currentStatusCount' => $currentStatusCount, 'statusHistory' => $allHistory, 'historyCount' => count($allHistory) ] ]); } catch (\Exception $e) { return $this->response->setJSON([ 'success' => false, 'message' => '상태 확인 중 오류가 발생했습니다.', 'error' => $e->getMessage() ]); } } /** * 디버깅용: 히스토리 테이블 insert 테스트 */ public function debugHistoryInsert() { try { $request = $this->request->getJSON(); $mappingSeq = $request->mappingSeq ?? 1; // 최소한의 데이터로 테스트 insert $testData = [ 'MAPPING_SEQ' => (int)$mappingSeq, 'STATUS' => 'PENDING', 'PREVIOUS_STATUS' => null, 'STATUS_MESSAGE' => 'Test insert', 'CHANGED_BY' => 1, 'IS_CURRENT' => 'N', // 테스트용이므로 N으로 설정 'CHANGED_DATE' => date('Y-m-d H:i:s') ]; log_message('debug', '테스트 insert 데이터: ' . json_encode($testData)); // validation 체크 if (!$this->statusHistoryModel->validate($testData)) { $validationErrors = $this->statusHistoryModel->errors(); return $this->response->setJSON([ 'success' => false, 'message' => 'Validation 실패', 'errors' => $validationErrors, 'data' => $testData ]); } $result = $this->statusHistoryModel->insert($testData, false); if (!$result) { $dbError = $this->statusHistoryModel->db->error(); return $this->response->setJSON([ 'success' => false, 'message' => 'DB Insert 실패', 'dbError' => $dbError, 'data' => $testData ]); } return $this->response->setJSON([ 'success' => true, 'message' => '테스트 insert 성공', 'insertId' => $result, 'data' => $testData ]); } catch (\Exception $e) { return $this->response->setJSON([ 'success' => false, 'message' => '테스트 insert 중 오류', 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); } } }