vendorModel = new VendorModel(); $this->userModel = new UserModel(); $this->vendorInfluencerModel = new VendorInfluencerMappingModel(); } /** * 벤더사 검색 */ public function searchVendors() { try { $request = $this->request->getJSON(); $keyword = $request->keyword ?? ''; $category = $request->category ?? ''; $region = $request->region ?? ''; $sortBy = $request->sortBy ?? 'latest'; $page = intval($request->page ?? 1); $size = intval($request->size ?? 12); $influencerSeq = $request->influencerSeq ?? null; $builder = $this->vendorModel->builder(); $builder->where('IS_ACT', 'Y'); // 키워드 검색 if (!empty($keyword)) { $builder->groupStart() ->like('COMPANY_NAME', $keyword) ->orLike('DESCRIPTION', $keyword) ->orLike('TAGS', $keyword) ->groupEnd(); } // 카테고리 필터 if (!empty($category)) { $builder->where('CATEGORY', $category); } // 지역 필터 if (!empty($region)) { $builder->where('REGION', $region); } // 정렬 switch ($sortBy) { case 'partnership': $builder->orderBy('PARTNERSHIP_COUNT', 'DESC') ->orderBy('REG_DATE', 'DESC'); break; case 'name': $builder->orderBy('COMPANY_NAME', 'ASC'); break; case 'latest': default: $builder->orderBy('REG_DATE', 'DESC'); break; } // 전체 개수 $totalCount = $builder->countAllResults(false); // 페이징 $offset = ($page - 1) * $size; $vendors = $builder->limit($size, $offset)->get()->getResultArray(); // 파트너십 개수 추가 foreach ($vendors as &$vendor) { $partnershipCount = $this->vendorInfluencerModel ->where('VENDOR_SEQ', $vendor['SEQ']) ->where('STATUS', 'APPROVED') ->where('IS_ACT', 'Y') ->countAllResults(); $vendor['PARTNERSHIP_COUNT'] = $partnershipCount; // 인플루언서의 파트너십 상태 확인 if ($influencerSeq) { $partnershipStatus = $this->vendorInfluencerModel ->where('VENDOR_SEQ', $vendor['SEQ']) ->where('INFLUENCER_SEQ', $influencerSeq) ->where('IS_ACT', 'Y') ->first(); $vendor['PARTNERSHIP_STATUS'] = $partnershipStatus['STATUS'] ?? null; $vendor['REQUEST_TYPE'] = $partnershipStatus['REQUEST_TYPE'] ?? null; } } $totalPages = ceil($totalCount / $size); return $this->response->setJSON([ 'success' => true, 'data' => [ 'items' => $vendors, 'pagination' => [ 'currentPage' => $page, 'totalPages' => $totalPages, 'totalCount' => $totalCount, 'pageSize' => $size, 'hasNext' => $page < $totalPages, 'hasPrev' => $page > 1 ] ] ]); } catch (\Exception $e) { return $this->response->setStatusCode(500)->setJSON([ 'success' => false, 'message' => '벤더사 검색 중 오류가 발생했습니다.', 'error' => ENVIRONMENT === 'development' ? $e->getMessage() : null ]); } } /** * 승인 요청 생성 */ public function createRequest() { try { $request = $this->request->getJSON(); $vendorSeq = $request->vendorSeq ?? null; $influencerSeq = $request->influencerSeq ?? null; $requestType = $request->requestType ?? 'INFLUENCER_REQUEST'; $requestMessage = $request->requestMessage ?? ''; $requestedBy = $request->requestedBy ?? null; $commissionRate = $request->commissionRate ?? null; $specialConditions = $request->specialConditions ?? ''; // 필수 파라미터 검증 if (!$vendorSeq || !$influencerSeq || !$requestedBy) { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => '필수 파라미터가 누락되었습니다.' ]); } // 중복 요청 확인 $existingRequest = $this->vendorInfluencerModel ->where('VENDOR_SEQ', $vendorSeq) ->where('INFLUENCER_SEQ', $influencerSeq) ->where('STATUS', 'PENDING') ->where('IS_ACT', 'Y') ->first(); if ($existingRequest) { return $this->response->setStatusCode(409)->setJSON([ 'success' => false, 'message' => '이미 처리 중인 요청이 있습니다.' ]); } // 요청 생성 $data = [ 'VENDOR_SEQ' => $vendorSeq, 'INFLUENCER_SEQ' => $influencerSeq, 'REQUEST_TYPE' => $requestType, 'STATUS' => 'PENDING', 'REQUEST_MESSAGE' => $requestMessage, 'REQUESTED_BY' => $requestedBy, 'COMMISSION_RATE' => $commissionRate, 'SPECIAL_CONDITIONS' => $specialConditions, 'EXPIRED_DATE' => date('Y-m-d H:i:s', strtotime('+7 days')) ]; $insertId = $this->vendorInfluencerModel->insert($data); // 생성된 데이터 조회 $createdRequest = $this->vendorInfluencerModel ->select('vim.*, v.COMPANY_NAME as vendorName, u.NICK_NAME as influencerName, req_user.NICK_NAME as requestedByName') ->from('VENDOR_INFLUENCER_MAPPING vim') ->join('VENDOR_LIST v', 'vim.VENDOR_SEQ = v.SEQ', 'left') ->join('USER_LIST u', 'vim.INFLUENCER_SEQ = u.SEQ', 'left') ->join('USER_LIST req_user', 'vim.REQUESTED_BY = req_user.SEQ', 'left') ->where('vim.SEQ', $insertId) ->get() ->getRowArray(); return $this->response->setJSON([ 'success' => true, 'message' => '승인 요청이 성공적으로 생성되었습니다.', 'data' => $createdRequest ]); } catch (\Exception $e) { return $this->response->setStatusCode(500)->setJSON([ 'success' => false, 'message' => '승인 요청 생성 중 오류가 발생했습니다.', 'error' => ENVIRONMENT === 'development' ? $e->getMessage() : null ]); } } /** * 매핑 리스트 조회 (벤더사용 승인요청 목록 포함) */ public function getList() { try { $request = $this->request->getJSON(); $vendorSeq = $request->vendorSeq ?? null; $influencerSeq = $request->influencerSeq ?? null; $status = $request->status ?? null; $requestType = $request->requestType ?? null; $keyword = $request->keyword ?? null; $category = $request->category ?? null; $sortBy = $request->sortBy ?? 'latest'; $page = intval($request->page ?? 1); $size = intval($request->size ?? 12); $builder = $this->vendorInfluencerModel->builder(); $builder->select('vim.SEQ, vim.VENDOR_SEQ, vim.INFLUENCER_SEQ, vim.REQUEST_TYPE, vim.STATUS, vim.REQUEST_MESSAGE, vim.RESPONSE_MESSAGE, vim.REQUESTED_BY, vim.APPROVED_BY, vim.REQUEST_DATE, vim.RESPONSE_DATE, vim.EXPIRED_DATE, vim.PARTNERSHIP_START_DATE, vim.PARTNERSHIP_END_DATE, vim.COMMISSION_RATE, vim.SPECIAL_CONDITIONS, vim.IS_ACT, vim.REG_DATE, vim.MOD_DATE, vim.ADD_INFO1, vim.ADD_INFO2, vim.ADD_INFO3, v.COMPANY_NAME as vendorName, v.EMAIL as vendorEmail, v.LOGO as vendorLogo, inf.NICK_NAME as influencerNickname, inf.NAME as influencerName, inf.EMAIL as influencerEmail, inf.PHONE as influencerPhone, inf.PROFILE_IMAGE as influencerAvatar, inf.REGION as influencerRegion, inf.PRIMARY_CATEGORY as influencerCategory, inf.FOLLOWER_COUNT as followerCount, inf.AVG_VIEWS as avgViews, inf.ENGAGEMENT_RATE as engagementRate, inf.DESCRIPTION as influencerDescription, inf.SNS_CHANNELS as influencerSnsChannels, req_user.NICK_NAME as requestedByName, app_user.NICK_NAME as approvedByName, CASE WHEN vim.EXPIRED_DATE < NOW() AND vim.STATUS = "PENDING" THEN "EXPIRED" ELSE vim.STATUS END as displayStatus') ->distinct() ->from('VENDOR_INFLUENCER_MAPPING vim') ->join('VENDOR_LIST v', 'vim.VENDOR_SEQ = v.SEQ', 'left') ->join('USER_LIST inf', 'vim.INFLUENCER_SEQ = inf.SEQ', 'left') ->join('USER_LIST req_user', 'vim.REQUESTED_BY = req_user.SEQ', 'left') ->join('USER_LIST app_user', 'vim.APPROVED_BY = app_user.SEQ', 'left') ->where('vim.IS_ACT', 'Y'); // 필터 적용 if ($vendorSeq) { $builder->where('vim.VENDOR_SEQ', $vendorSeq); } if ($influencerSeq) { $builder->where('vim.INFLUENCER_SEQ', $influencerSeq); } if ($status) { $builder->where('vim.STATUS', $status); } if ($requestType) { $builder->where('vim.REQUEST_TYPE', $requestType); } // 키워드 검색 (인플루언서명) if (!empty($keyword)) { $builder->groupStart() ->like('inf.NICK_NAME', $keyword) ->orLike('inf.NAME', $keyword) ->groupEnd(); } // 카테고리 필터 (인플루언서 카테고리) if (!empty($category)) { $builder->where('inf.PRIMARY_CATEGORY', $category); } // 전체 개수 $totalCount = $builder->countAllResults(false); // 정렬 switch ($sortBy) { case 'oldest': $builder->orderBy('vim.REG_DATE', 'ASC'); break; case 'expiring': $builder->orderBy('vim.EXPIRED_DATE', 'ASC'); break; case 'latest': default: $builder->orderBy('vim.REG_DATE', 'DESC'); break; } // 페이징 $offset = ($page - 1) * $size; $items = $builder->limit($size, $offset)->get()->getResultArray(); $totalPages = ceil($totalCount / $size); // 통계 데이터 계산 (벤더사용) $stats = []; if ($vendorSeq) { $statsBuilder = $this->vendorInfluencerModel->builder(); $stats = [ 'pending' => $statsBuilder->where('VENDOR_SEQ', $vendorSeq)->where('STATUS', 'PENDING')->where('IS_ACT', 'Y')->countAllResults(), 'approved' => $statsBuilder->resetQuery()->where('VENDOR_SEQ', $vendorSeq)->where('STATUS', 'APPROVED')->where('IS_ACT', 'Y')->countAllResults(), 'rejected' => $statsBuilder->resetQuery()->where('VENDOR_SEQ', $vendorSeq)->where('STATUS', 'REJECTED')->where('IS_ACT', 'Y')->countAllResults(), 'total' => $totalCount ]; } return $this->response->setJSON([ 'success' => true, 'data' => [ 'items' => $items, 'pagination' => [ 'currentPage' => $page, 'totalPages' => $totalPages, 'totalCount' => $totalCount, 'pageSize' => $size, 'hasNext' => $page < $totalPages, 'hasPrev' => $page > 1 ], 'stats' => $stats ] ]); } catch (\Exception $e) { return $this->response->setStatusCode(500)->setJSON([ 'success' => false, 'message' => '리스트 조회 중 오류가 발생했습니다.', 'error' => ENVIRONMENT === 'development' ? $e->getMessage() : null ]); } } /** * 요청 취소 */ public function cancelRequest() { try { $request = $this->request->getJSON(); $mappingSeq = $request->mappingSeq ?? null; $cancelledBy = $request->cancelledBy ?? null; $cancelReason = $request->cancelReason ?? '요청자에 의해 취소됨'; // 필수 파라미터 검증 if (!$mappingSeq || !$cancelledBy) { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => '필수 파라미터가 누락되었습니다.' ]); } // 기존 요청 확인 $existingMapping = $this->vendorInfluencerModel ->where('SEQ', $mappingSeq) ->where('STATUS', 'PENDING') ->where('IS_ACT', 'Y') ->first(); if (!$existingMapping) { return $this->response->setStatusCode(404)->setJSON([ 'success' => false, 'message' => '취소할 수 있는 요청을 찾을 수 없습니다.' ]); } // 권한 확인 if ($existingMapping['REQUESTED_BY'] != $cancelledBy) { return $this->response->setStatusCode(403)->setJSON([ 'success' => false, 'message' => '요청을 취소할 권한이 없습니다.' ]); } // 취소 처리 $updateData = [ 'STATUS' => 'CANCELLED', 'RESPONSE_MESSAGE' => $cancelReason, 'RESPONSE_DATE' => date('Y-m-d H:i:s'), 'APPROVED_BY' => $cancelledBy ]; $this->vendorInfluencerModel->update($mappingSeq, $updateData); // 업데이트된 데이터 조회 $updatedMapping = $this->vendorInfluencerModel ->select('vim.*, v.COMPANY_NAME as vendorName, u.NICK_NAME as influencerName, req_user.NICK_NAME as requestedByName') ->from('VENDOR_INFLUENCER_MAPPING vim') ->join('VENDOR_LIST v', 'vim.VENDOR_SEQ = v.SEQ', 'left') ->join('USER_LIST u', 'vim.INFLUENCER_SEQ = u.SEQ', 'left') ->join('USER_LIST req_user', 'vim.REQUESTED_BY = req_user.SEQ', 'left') ->where('vim.SEQ', $mappingSeq) ->get() ->getRowArray(); return $this->response->setJSON([ 'success' => true, 'message' => '요청이 취소되었습니다.', 'data' => $updatedMapping ]); } catch (\Exception $e) { return $this->response->setStatusCode(500)->setJSON([ 'success' => false, 'message' => '요청 취소 중 오류가 발생했습니다.', 'error' => ENVIRONMENT === 'development' ? $e->getMessage() : null ]); } } /** * 요청 승인/거부 처리 */ public function approveRequest() { try { $request = $this->request->getJSON(); $mappingSeq = $request->mappingSeq ?? null; $action = $request->action ?? 'APPROVE'; // 'APPROVE' or 'REJECT' $processedBy = $request->processedBy ?? $request->approvedBy ?? null; // 기존 코드 호환성 $responseMessage = $request->responseMessage ?? ''; $commissionRate = $request->commissionRate ?? null; if (!$mappingSeq || !$processedBy) { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => '필수 파라미터가 누락되었습니다.' ]); } // processedBy가 벤더사 SEQ인지 사용자 SEQ인지 확인 $processingUser = null; // 1. 먼저 USER_LIST에서 확인 (인플루언서) $processingUser = $this->userModel ->where('SEQ', $processedBy) ->where('IS_ACT', 'Y') ->first(); if ($processingUser) { // 사용자 SEQ인 경우 (인플루언서) - 바로 사용 $approvedByUserSeq = $processedBy; } else { // 2. VENDOR_LIST에서 확인 (벤더사) $vendorInfo = $this->vendorModel ->where('SEQ', $processedBy) ->where('IS_ACT', 'Y') ->first(); if ($vendorInfo) { // 벤더사 SEQ인 경우 - 벤더사가 직접 처리하는 것으로 간주 $approvedByUserSeq = $processedBy; log_message('debug', "벤더사 SEQ {$processedBy}가 직접 처리합니다."); } else { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => "처리자 SEQ {$processedBy}는 USER_LIST나 VENDOR_LIST에서 찾을 수 없습니다." ]); } } // 기존 요청 확인 $existingMapping = $this->vendorInfluencerModel ->where('SEQ', $mappingSeq) ->where('STATUS', 'PENDING') ->where('IS_ACT', 'Y') ->first(); if (!$existingMapping) { return $this->response->setStatusCode(404)->setJSON([ 'success' => false, 'message' => '처리할 수 있는 요청을 찾을 수 없습니다.' ]); } // 벤더사 권한 확인 (선택적) // 벤더사만 자신의 요청을 처리할 수 있도록 하려면 주석 해제 // if ($existingMapping['VENDOR_SEQ'] !== $processingUser['SEQ']) { // return $this->response->setStatusCode(403)->setJSON([ // 'success' => false, // 'message' => '해당 요청을 처리할 권한이 없습니다.' // ]); // } $status = $action === 'APPROVE' ? 'APPROVED' : 'REJECTED'; $updateData = [ 'STATUS' => $status, 'APPROVED_BY' => $approvedByUserSeq, 'RESPONSE_MESSAGE' => $responseMessage, 'RESPONSE_DATE' => date('Y-m-d H:i:s') ]; if ($commissionRate !== null && $action === 'APPROVE') { $updateData['COMMISSION_RATE'] = $commissionRate; } // 단계별 업데이트로 외래키 제약조건 문제 우회 try { // 1단계: APPROVED_BY 없이 먼저 업데이트 $firstUpdateData = [ 'STATUS' => $status, 'RESPONSE_MESSAGE' => $responseMessage, 'RESPONSE_DATE' => date('Y-m-d H:i:s') ]; if ($commissionRate !== null && $action === 'APPROVE') { $firstUpdateData['COMMISSION_RATE'] = $commissionRate; } log_message('debug', "1단계 업데이트 시도: " . json_encode($firstUpdateData)); $result1 = $this->vendorInfluencerModel->update($mappingSeq, $firstUpdateData); log_message('debug', "1단계 업데이트 결과: " . ($result1 ? 'SUCCESS' : 'FAILED')); // 2단계: APPROVED_BY만 별도로 업데이트 $secondUpdateData = [ 'APPROVED_BY' => $approvedByUserSeq ]; log_message('debug', "2단계 업데이트 시도: " . json_encode($secondUpdateData)); $result2 = $this->vendorInfluencerModel->update($mappingSeq, $secondUpdateData); log_message('debug', "2단계 업데이트 결과: " . ($result2 ? 'SUCCESS' : 'FAILED')); if (!$result1 || !$result2) { throw new \Exception("단계별 업데이트 실패: 1단계={$result1}, 2단계={$result2}"); } } catch (\Exception $updateException) { log_message('error', "단계별 업데이트 실패: " . $updateException->getMessage()); // 외래키 제약조건 오류인 경우 더 상세한 정보 제공 if (strpos($updateException->getMessage(), 'foreign key constraint') !== false) { // 추가 디버깅: 직접 SQL로 사용자 존재 확인 $db = \Config\Database::connect(); $userCheck = $db->query("SELECT SEQ, NICK_NAME, EMAIL, IS_ACT FROM USER_LIST WHERE SEQ = ?", [$processedBy])->getRowArray(); // 추가 디버깅: 현재 매핑 레코드 상태 확인 $mappingCheck = $db->query("SELECT SEQ, STATUS, APPROVED_BY FROM VENDOR_INFLUENCER_MAPPING WHERE SEQ = ?", [$mappingSeq])->getRowArray(); return $this->response->setStatusCode(500)->setJSON([ 'success' => false, 'message' => '외래키 제약조건 오류가 발생했습니다.', 'error' => $updateException->getMessage(), 'debug_info' => [ 'processedBy' => $processedBy, 'approvedByUserSeq' => $approvedByUserSeq, 'processingUser' => $processingUser, 'existingMapping' => $existingMapping, 'userCheck' => $userCheck, 'mappingCheck' => $mappingCheck, 'updateData' => $updateData ] ]); } throw $updateException; // 다른 예외는 기존 처리 방식 유지 } $actionText = $action === 'APPROVE' ? '승인' : '거부'; return $this->response->setJSON([ 'success' => true, 'message' => "요청이 {$actionText}되었습니다.", 'data' => $updateData ]); } catch (\Exception $e) { return $this->response->setStatusCode(500)->setJSON([ 'success' => false, 'message' => '요청 처리 중 오류가 발생했습니다.', 'error' => ENVIRONMENT === 'development' ? $e->getMessage() : null ]); } } /** * 상세 조회 */ public function getDetail() { try { $request = $this->request->getJSON(); $mappingSeq = $request->mappingSeq ?? null; if (!$mappingSeq) { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => '매핑 SEQ가 필요합니다.' ]); } $detail = $this->vendorInfluencerModel ->select('vim.*, v.*, u.NICK_NAME as influencerName, u.EMAIL as influencerEmail') ->from('VENDOR_INFLUENCER_MAPPING vim') ->join('VENDOR_LIST v', 'vim.VENDOR_SEQ = v.SEQ', 'left') ->join('USER_LIST u', 'vim.INFLUENCER_SEQ = u.SEQ', 'left') ->where('vim.SEQ', $mappingSeq) ->where('vim.IS_ACT', 'Y') ->get() ->getRowArray(); if (!$detail) { return $this->response->setStatusCode(404)->setJSON([ 'success' => false, 'message' => '요청을 찾을 수 없습니다.' ]); } return $this->response->setJSON([ 'success' => true, 'data' => $detail ]); } catch (\Exception $e) { return $this->response->setStatusCode(500)->setJSON([ 'success' => false, 'message' => '상세 조회 중 오류가 발생했습니다.', 'error' => ENVIRONMENT === 'development' ? $e->getMessage() : null ]); } } /** * 승인된 파트너십 해지 */ public function terminate() { try { $request = $this->request->getJSON(); $mappingSeq = $request->mappingSeq ?? null; $terminateReason = $request->terminateReason ?? null; $terminatedBy = $request->terminatedBy ?? null; // 필수 파라미터 검증 if (!$mappingSeq || !$terminateReason || !$terminatedBy) { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => '필수 파라미터가 누락되었습니다.' ]); } // 해지 사유 길이 검증 if (strlen($terminateReason) > 500) { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => '해지 사유는 500자를 초과할 수 없습니다.' ]); } // 기존 매핑 확인 (승인된 상태여야 함) $existingMapping = $this->vendorInfluencerModel ->where('SEQ', $mappingSeq) ->where('STATUS', 'APPROVED') ->where('IS_ACT', 'Y') ->first(); if (!$existingMapping) { return $this->response->setStatusCode(404)->setJSON([ 'success' => false, 'message' => '해지할 수 있는 승인된 파트너십을 찾을 수 없습니다.' ]); } // 해지 처리자 확인 (벤더사 SEQ인지 사용자 SEQ인지 확인) $terminatingUser = null; // 1. 먼저 USER_LIST에서 확인 (인플루언서) $terminatingUser = $this->userModel ->where('SEQ', $terminatedBy) ->where('IS_ACT', 'Y') ->first(); if ($terminatingUser) { // 사용자 SEQ인 경우 (인플루언서) - 바로 사용 $approvedByUserSeq = $terminatedBy; } else { // 2. VENDOR_LIST에서 확인 (벤더사) $vendorInfo = $this->vendorModel ->where('SEQ', $terminatedBy) ->where('IS_ACT', 'Y') ->first(); if ($vendorInfo) { // 벤더사 SEQ인 경우 - 벤더사가 직접 처리하는 것으로 간주 // 벤더사 자체의 SEQ를 APPROVED_BY에 저장 (벤더사 계정이 처리) $approvedByUserSeq = $terminatedBy; // 응답용 정보 설정 $terminatingUser = [ 'SEQ' => $vendorInfo['SEQ'], 'NICK_NAME' => $vendorInfo['COMPANY_NAME'] . ' (벤더사)', 'NAME' => $vendorInfo['COMPANY_NAME'] ]; } else { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => "해지 처리자 SEQ {$terminatedBy}는 USER_LIST나 VENDOR_LIST에서 찾을 수 없습니다." ]); } } // 해지 처리 $terminateData = [ 'STATUS' => 'TERMINATED', 'RESPONSE_MESSAGE' => '파트너십 해지: ' . $terminateReason, 'RESPONSE_DATE' => date('Y-m-d H:i:s'), 'PARTNERSHIP_END_DATE' => date('Y-m-d H:i:s'), 'MOD_DATE' => date('Y-m-d H:i:s') ]; log_message('debug', "해지 처리 데이터: " . json_encode($terminateData)); log_message('debug', "매핑 SEQ: {$mappingSeq}"); log_message('debug', "처리자 SEQ: {$terminatedBy} -> 승인자 SEQ: {$approvedByUserSeq}"); // 단계별 업데이트로 외래키 제약조건 문제 우회 try { // 1단계: APPROVED_BY 없이 먼저 업데이트 $result1 = $this->vendorInfluencerModel->update($mappingSeq, $terminateData); log_message('debug', "1단계 업데이트 결과: " . ($result1 ? 'SUCCESS' : 'FAILED')); // 2단계: APPROVED_BY만 별도로 업데이트 (필요시에만) if ($result1 && $approvedByUserSeq) { $approvedByData = ['APPROVED_BY' => $approvedByUserSeq]; $result2 = $this->vendorInfluencerModel->update($mappingSeq, $approvedByData); log_message('debug', "2단계 업데이트 결과: " . ($result2 ? 'SUCCESS' : 'FAILED')); } else { $result2 = true; // APPROVED_BY 업데이트가 필요없는 경우 } if (!$result1 || !$result2) { throw new \Exception("단계별 업데이트 실패: 1단계={$result1}, 2단계={$result2}"); } } catch (\Exception $updateException) { log_message('error', "해지 처리 업데이트 오류: " . $updateException->getMessage()); // 외래키 제약조건 오류인 경우 더 상세한 정보 제공 if (strpos($updateException->getMessage(), 'foreign key constraint') !== false) { return $this->response->setStatusCode(500)->setJSON([ 'success' => false, 'message' => '외래키 제약조건 오류가 발생했습니다.', 'error' => ENVIRONMENT === 'development' ? $updateException->getMessage() : null, 'debug_info' => ENVIRONMENT === 'development' ? [ 'terminatedBy' => $terminatedBy, 'approvedByUserSeq' => $approvedByUserSeq, 'terminatingUser' => $terminatingUser, 'existingMapping' => $existingMapping, 'terminateData' => $terminateData ] : null ]); } throw $updateException; // 다른 예외는 기존 처리 방식 유지 } // 해지된 매핑 정보 조회 $terminatedMapping = $this->vendorInfluencerModel ->select('vim.SEQ, vim.VENDOR_SEQ, vim.INFLUENCER_SEQ, vim.STATUS, vim.RESPONSE_MESSAGE, vim.RESPONSE_DATE, vim.PARTNERSHIP_END_DATE, v.COMPANY_NAME as vendorName, inf.NICK_NAME as influencerNickname, inf.NAME as influencerName') ->from('VENDOR_INFLUENCER_MAPPING vim') ->join('VENDOR_LIST v', 'vim.VENDOR_SEQ = v.SEQ', 'left') ->join('USER_LIST inf', 'vim.INFLUENCER_SEQ = inf.SEQ', 'left') ->where('vim.SEQ', $mappingSeq) ->get() ->getRowArray(); return $this->response->setJSON([ 'success' => true, 'message' => '파트너십이 성공적으로 해지되었습니다.', 'data' => [ 'terminatedMapping' => $terminatedMapping, 'terminateDate' => date('Y-m-d H:i:s'), 'terminatedBy' => $terminatingUser['NICK_NAME'] ?? $terminatingUser['NAME'] ] ]); } catch (\Exception $e) { log_message('error', "파트너십 해지 처리 오류: " . $e->getMessage()); log_message('error', "스택 트레이스: " . $e->getTraceAsString()); return $this->response->setStatusCode(500)->setJSON([ 'success' => false, 'message' => '파트너십 해지 처리 중 오류가 발생했습니다.', 'error' => ENVIRONMENT === 'development' ? $e->getMessage() : null, 'trace' => ENVIRONMENT === 'development' ? $e->getTraceAsString() : null ]); } } /** * 재승인 요청 (해지된 파트너십에 대한 재계약 요청) */ public function reapplyRequest() { try { $request = $this->request->getJSON(); $vendorSeq = $request->vendorSeq ?? null; $influencerSeq = $request->influencerSeq ?? null; $requestMessage = $request->requestMessage ?? ''; $requestedBy = $request->requestedBy ?? null; // 필수 파라미터 검증 if (!$vendorSeq || !$influencerSeq || !$requestedBy) { return $this->response->setStatusCode(400)->setJSON([ 'success' => false, 'message' => '필수 파라미터가 누락되었습니다.' ]); } // 기존 해지된 파트너십 확인 $terminatedPartnership = $this->vendorInfluencerModel ->where('VENDOR_SEQ', $vendorSeq) ->where('INFLUENCER_SEQ', $influencerSeq) ->where('STATUS', 'TERMINATED') ->where('IS_ACT', 'Y') ->orderBy('REG_DATE', 'DESC') ->first(); if (!$terminatedPartnership) { return $this->response->setStatusCode(404)->setJSON([ 'success' => false, 'message' => '해지된 파트너십 기록을 찾을 수 없습니다.' ]); } // 현재 처리 중인 요청이 있는지 확인 $existingPendingRequest = $this->vendorInfluencerModel ->where('VENDOR_SEQ', $vendorSeq) ->where('INFLUENCER_SEQ', $influencerSeq) ->where('STATUS', 'PENDING') ->where('IS_ACT', 'Y') ->first(); if ($existingPendingRequest) { return $this->response->setStatusCode(409)->setJSON([ 'success' => false, 'message' => '이미 처리 중인 승인 요청이 있습니다.' ]); } // 재승인 요청 생성 $reapplyData = [ 'VENDOR_SEQ' => $vendorSeq, 'INFLUENCER_SEQ' => $influencerSeq, 'REQUEST_TYPE' => 'INFLUENCER_REQUEST', 'STATUS' => 'PENDING', 'REQUEST_MESSAGE' => '[재계약 요청] ' . $requestMessage, 'REQUESTED_BY' => $requestedBy, 'COMMISSION_RATE' => $terminatedPartnership['COMMISSION_RATE'], // 이전 수수료율 유지 'SPECIAL_CONDITIONS' => $terminatedPartnership['SPECIAL_CONDITIONS'], // 이전 특별조건 유지 'EXPIRED_DATE' => date('Y-m-d H:i:s', strtotime('+7 days')), 'ADD_INFO1' => 'REAPPLY', // 재신청 구분자 'ADD_INFO2' => $terminatedPartnership['SEQ'], // 이전 파트너십 SEQ 참조 'ADD_INFO3' => date('Y-m-d H:i:s') // 재신청 일시 ]; $insertId = $this->vendorInfluencerModel->insert($reapplyData); // 생성된 재승인 요청 정보 조회 $createdReapply = $this->vendorInfluencerModel ->select('vim.*, v.COMPANY_NAME as vendorName, u.NICK_NAME as influencerName, req_user.NICK_NAME as requestedByName') ->from('VENDOR_INFLUENCER_MAPPING vim') ->join('VENDOR_LIST v', 'vim.VENDOR_SEQ = v.SEQ', 'left') ->join('USER_LIST u', 'vim.INFLUENCER_SEQ = u.SEQ', 'left') ->join('USER_LIST req_user', 'vim.REQUESTED_BY = req_user.SEQ', 'left') ->where('vim.SEQ', $insertId) ->get() ->getRowArray(); return $this->response->setJSON([ 'success' => true, 'message' => '재승인 요청이 성공적으로 생성되었습니다.', 'data' => [ 'reapplyRequest' => $createdReapply, 'previousPartnership' => $terminatedPartnership ] ]); } catch (\Exception $e) { return $this->response->setStatusCode(500)->setJSON([ 'success' => false, 'message' => '재승인 요청 생성 중 오류가 발생했습니다.', 'error' => ENVIRONMENT === 'development' ? $e->getMessage() : null ]); } } }