naverClient = new NaverApiClient(); $this->VrfcReqModel = model(VrfcReqModel::class); $this->V2stdailyModel = model(V2stdailyModel::class); $this->V2chgstatModel = model(V2chgstatModel::class); $this->V2chghistoryModel = model(V2chghistoryModel::class); helper('log'); } /** * 메인 프로세스: 요청 타입에 따른 분기 처리 */ public function processArticle(array $payload) { $articleNumber = $payload['articleNumber']; $requestType = $payload['requestType'] ?? ''; // 1. 네이버 API 호출 $response = $this->naverClient->getArticleInfo($articleNumber); if (!$response || $response['code'] !== 'success') { throw new \Exception("네이버 API 응답 에러: $articleNumber"); } $vrfcParams = $this->mapToDatabaseParams($response['data'], $payload); write_custom_log("PROCESS_START | Type: $requestType | Atcl: $articleNumber", 'INFO', 'service'); switch ($requestType) { case 'REG': // 신규 등록 $vr_sq = $this->insertVrfcReq($articleNumber, $vrfcParams); if ($vr_sq) $this->V2stdailyModel->set_v2_st_daily(null, $vrfcParams['cpid'], $vrfcParams['vrfc_type'] . '0103', '1', 'add'); break; case 'MOD': // 수정 $vr_sq = $this->updateVrfcReq($articleNumber, $vrfcParams); if ($vr_sq) $this->V2stdailyModel->set_v2_st_daily(null, $vrfcParams['cpid'], $vrfcParams['vrfc_type'] . '0102', '1', 'add'); break; case 'CNC': // 취소 $vr_sq = $this->deleteVrfcReq($articleNumber, $vrfcParams); if ($vr_sq) $this->V2stdailyModel->set_v2_st_daily(null, $vrfcParams['cpid'], 'A0101', '1', 'add'); break; case 'FIN': // 완료 $vr_sq = $this->finVrfcReq($articleNumber, $vrfcParams); break; default: throw new \Exception("알 수 없는 requestType: $requestType"); } return ['vr_sq' => $vr_sq, 'articleNumber' => $articleNumber]; } /** * [REG] 신규 등록 */ private function insertVrfcReq($articleNumber, $params) { $existing = $this->VrfcReqModel->where('atcl_no', $articleNumber)->first(); if ($existing) throw new \Exception("중복 등록 시도: $articleNumber"); $params['stat_cd'] = '10'; $params['insert_user'] = '0'; $params['req_type'] = 'C'; if (!$this->VrfcReqModel->insert($params)) { $sql = (string)$this->VrfcReqModel->getLastQuery(); write_custom_log("INSERT_FAILED | Atcl: $articleNumber | SQL: $sql", 'ERROR', 'failed'); throw new \Exception("신규 등록 실패"); } $vr_sq = $this->VrfcReqModel->getInsertID(); $this->recordStatusAndHistory($vr_sq, '10', 'C9', "신규접수 : 10"); return $vr_sq; } /** * [MOD] 수정 처리 */ private function updateVrfcReq($articleNumber, $params) { $existing = $this->findExisting($articleNumber); if (!$existing) return $this->insertVrfcReq($articleNumber, $params); $params['stat_cd'] = '30'; $params['req_type'] = 'U'; $params['insert_tm'] = db_now(); return $this->updateProcess($existing, $params, 'MOD', "재접수 상태변경: {$existing['stat_cd']} => 30"); } /** * [CNC] 취소 처리 */ private function deleteVrfcReq($articleNumber, $params) { $existing = $this->findExisting($articleNumber); $params['stat_cd'] = '19'; $params['req_type'] = 'D'; return $this->updateProcess($existing, $params, 'CNC', "취소 처리: {$existing['stat_cd']} => 19"); } /** * [FIN] 완료 처리 */ private function finVrfcReq($articleNumber, $params) { $existing = $this->findExisting($articleNumber); $params['stat_cd'] = '60'; $params['req_type'] = 'F'; return $this->updateProcess($existing, $params, 'FIN', "완료 처리: {$existing['stat_cd']} => 60"); } // --- 내부 공통 유틸리티 함수 --- private function findExisting($articleNumber) { $existing = $this->VrfcReqModel->where('atcl_no', $articleNumber)->first(); if (!$existing) throw new \Exception("해당 매물 없음: $articleNumber"); return $existing; } /** * 공통 업데이트 및 이력 기록 로직 (Lock 최소화) */ private function updateProcess($existing, $params, $type, $memo) { $vr_sq = $existing['vr_sq']; if (!$this->VrfcReqModel->update($vr_sq, $params)) { $sql = (string)$this->VrfcReqModel->getLastQuery(); write_custom_log("UPDATE_FAILED | Type: $type | vr_sq: $vr_sq | SQL: $sql", 'ERROR', 'failed'); throw new \Exception("[$type] 업데이트 실패"); } $this->recordStatusAndHistory($vr_sq, $params['stat_cd'], 'C9', $memo); return $vr_sq; } /** * 상태 및 이력 테이블 기록 (독립적 에러 처리) */ private function recordStatusAndHistory($vr_sq, $stat_cd, $chg_type, $memo) { // 1. 상태(stat) 저장 try { $this->V2chgstatModel->saveChgstat([ 'vr_sq' => $vr_sq, 'stat_cd' => $stat_cd, 'insert_user' => '0', 'insert_tm' => db_now() ], 'I'); } catch (\Exception $e) { write_custom_log("STAT_SAVE_ERR | vr_sq: $vr_sq | Msg: " . $e->getMessage(), 'ERROR', 'failed'); } // 2. 이력(history) 저장 try { $this->V2chghistoryModel->v2_savehistory([ 'vr_sq' => $vr_sq, 'stat_cd' => $stat_cd, 'chg_type' => $chg_type, 'memo' => $memo, 'insert_id' => 'SYSTEM', 'insert_tm' => db_now() ]); } catch (\Exception $e) { write_custom_log("HIST_SAVE_ERR | vr_sq: $vr_sq | Msg: " . $e->getMessage(), 'ERROR', 'failed'); } } /** * API 데이터를 DB 컬럼에 맞게 변환 */ private function mapToDatabaseParams(array $articleInfo, array $payload): array { $files = $articleInfo['files'] ?? []; $certRegister = []; $confirm_doc_img_url = []; $referenceFileUrl = []; $requestDatetime = date('YmdHis', strtotime($payload['requestDatetime'] ?? 'now')); foreach ($files as $file) { $fileTypeCode = $file['fileTypeCode']; if ($fileTypeCode == 'RCDOC') { $certRegister[] = $file['fileUrl']; } elseif ($fileTypeCode == 'ADDOC') { $confirm_doc_img_url[] = $file['fileUrl']; } elseif ($fileTypeCode == 'REFER') { $referenceFileUrl[] = $file['fileUrl']; } } $vrfc_params = [ 'reqSeq' => '', 'atcl_no' => $articleInfo['articleNumber'], 'step' => '', 'cpid' => $articleInfo['cpId'], 'cp_atcl_id' => $articleInfo['cpArticleNumber'], 'trade_type' => $articleInfo['tradeTypeCode'], 'realtor_nm' => $articleInfo['realtor']['realtorName'], 'realtor_tel_no' => $articleInfo['realtor']['representativeCellphoneNumber'], 'seller_tel_no' => $articleInfo['seller']['sellerTelephoneNumber'], 'vrfc_type' => $articleInfo['verificationTypeCode'], 'rgbk_confirm' => $articleInfo['isUnregisteredVerificationRequested'] ? 'Y' : 'N', 'req_type' => '', 'rdate' => $requestDatetime ?? db_now('Y-m-d H:i:s'), 'cpTelNo' => $articleInfo['seller']['sellerTelephoneNumber'], 'stat_cd' => '', 'try_cnt' => '0', 'insert_user' => '', 'insert_tm' => db_now(), 'memo' => '', 'contact_fail_cnt' => '0', 'sync_yn' => 'Y', 'reg_try_cnt' => '0', 'tel_fail_cause' => null, 'rgbk_confirm_owner_nm' => $articleInfo['seller']['ownerName'] ?? null, 'direct_trad_yn' => $articleInfo['seller']['isDirectTrade'] === true ? 'Y' : 'N', 'confirm_doc_img_url' => empty($confirm_doc_img_url) ? null : json_encode($confirm_doc_img_url, JSON_UNESCAPED_UNICODE), 'confirm_doc_owner_check_yn' => '', 'owner_verifiable' => null, 'vrfc_cmpl_type' => null, 'rgbk_doc_img_url' => null, 'certRegister' => empty($certRegister) ? null : json_encode($certRegister, JSON_UNESCAPED_UNICODE), 'referenceFileUrl' => empty($referenceFileUrl) ? null : json_encode($referenceFileUrl, JSON_UNESCAPED_UNICODE), ]; return $vrfc_params; } }