naverClient = new NaverApiClient(); $this->VrfcReqModel = model(VrfcReqModel::class); $this->V2stdailyModel = model(V2stdailyModel::class); $this->statusService = new StatusService(); // 인스턴스 생성 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); // if ($vr_sq) $this->V2stdailyModel->set_v2_st_daily(null, $vrfcParams['cpid'], 'A0101', '1', 'add'); // break; default: throw new \Exception("알 수 없는 requestType: $requestType"); } return ['vr_sq' => $vr_sq, 'articleNumber' => $articleNumber]; } /** * [REG] 신규 등록 */ private function insertVrfcReq($articleNumber, $params) { CLI::write(CLI::color('🟢 매물 정보 시작', 'green')); $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->statusService->recordStatusAndHistory($vr_sq, '10', 'C9', "신규접수 : 10"); return $vr_sq; } /** * [MOD] 수정 처리 */ private function updateVrfcReq($articleNumber, $params) { // 1. 데이터 존재 여부 확인 $existing = $this->VrfcReqModel->where('atcl_no', $articleNumber)->first(); if (!$existing) { // [A] 데이터가 없으면 먼저 신규 등록(10) 실행 write_custom_log("MOD_NOT_FOUND | Atcl: $articleNumber | First, Insert new data.", 'INFO', 'service'); $vr_sq = $this->insertVrfcReq($articleNumber, $params); // 새로 등록된 정보를 다시 가져옴 (updateProcess를 태우기 위해) $existing = $this->VrfcReqModel->find($vr_sq); // 통계 기록 (신규 등록 건으로 카운트) $this->V2stdailyModel->set_v2_st_daily(null, $params['cpid'], $params['vrfc_type'] . '0103', '1', 'add'); } $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) { try { $existing = $this->findExisting($articleNumber); } catch (\Exception $e) { // 취소(CNC)하려는데 데이터가 없으면 이미 지워졌거나 오류일 수 있습니다. // 여기서는 로그만 남기고 종료하거나, 필요 시 Exception을 던집니다. throw new \Exception("CNC_NOT_FOUND | Atcl: $articleNumber WARNING service"); } $params['stat_cd'] = '19'; $params['req_type'] = 'D'; return $this->updateProcess($existing, $params, 'CNC', "취소 처리: {$existing['stat_cd']} => 19"); } /** * [FIN] 완료 처리 */ private function finVrfcReq($articleNumber, $params) { try { $existing = $this->findExisting($articleNumber); } catch (\Exception $e) { // 완료(FIN)하려는데 데이터가 없으면 이미 지워졌거나 오류일 수 있습니다. // 여기서는 로그만 남기고 종료하거나, 필요 시 Exception을 던집니다. throw new \Exception("FIN_NOT_FOUND | Atcl: $articleNumber WARNING service"); } $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->statusService->recordStatusAndHistory($vr_sq, $params['stat_cd'], 'C9', $memo); return $vr_sq; } /** * 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']; } } $ownerTypeRaw = ownerTypeRaw ?? null; $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), ]; $articl_info_param = [ 'address_code' => $articleInfo['address']['legalDivision']['sectorNumber'] ?? '', //읍면동코드 'address1' => $articleInfo['address']['legalDivision']['legalDivisionAddress'] ?? '', //법정도 주소 'address2' => $articleInfo['address']['jibunAddress'] ?? '', 'address3' => $articleInfo['address']['referenceAddress'] ?? '', 'address4' => $articleInfo['address']['etcAddress'] ?? '', 'sply_spc' => $articleInfo['space']['supplySpace'] ?? 0, 'excls_spc' => $articleInfo['space']['exclusiveSpace'] ?? 0, 'tot_spc' => $articleInfo['space']['totalSpace'] ?? 0, 'grnd_spc' => $articleInfo['space']['groundSpace'] ?? 0, 'bldg_spc' => $articleInfo['space']['buildingSpace'] ?? 0, 'deal_amt' => $articleInfo['price']['dealAmount'] ?? 0, // 매매금액 'wrrnt_amt' => $articleInfo['price']['warrantyAmount'] ?? 0, // 보증금 'lease_amt' => $articleInfo['price']['leaseAmount'] ?? 0, // 월세가 -> 임대가 'isale_amt' => $articleInfo['price']['preSaleAmount'] ?? 0, // 분양가 'prem_amt' => $articleInfo['price']['premiumAmount'] ?? 0, // 프리미엄 'sise' => null, // 매매, 전세 시세 'floor' => $articleInfo['floor']['correspondenceFloorCount'] ?? 0, // 층 / 해당 층 '_correspondenceFloorType' => $articleInfo['floor']['correspondenceFloorType'] ?? null, // 층 / 해당 층 타입 // 기존 없음 'floor2' => $articleInfo['floor']['totalFloorCount'] ?? 0, // 층 / 총 층수 // 기존 없음 '_undergroundFloorCount' => $articleInfo['floor']['undergroundFloorCount'] ?? 0, // 층 / 지하 층수 // 기존 없음 'rdate' => $requestDatetime ?? db_now('Y-m-d H:i:s'), 'seller_tel_no' => $articleInfo['seller']['sellerTelephoneNumber'] ?? null, 'seller_nm' => $articleInfo['seller']['sellerName'] ?? null, 'ownerTelNo' => $articleInfo['seller']['ownerTelephoneNumber'] ?? null, // 소유자전화번호 기존 없음 'ownerNm' => $articleInfo['seller']['ownerName'] ?? null, // 소유자명 기존 없음 '_isOwnerCertificationAgree' => $articleInfo['seller']['isOwnerCertificationAgree'] ?? null, // 소유자확인동의여부 기존 없음 'direct_trad_yn' => $articleInfo['seller']['isDirectTrade'] ?? null, // 직거래여부 기존 없음 'realtor_nm' => $articleInfo['realtor']['realtorName'] ?? null, 'realtor_tel_no' => $articleInfo['realtor']['representativeCellphoneNumber'] ?? null, '_representativeTelephoneNumber' => $articleInfo['realtor']['representativeTelephoneNumber'] ?? null, // 중개사대표전화번호 기존 없음 'hscp_no' => $articleInfo['address']['complexNumber'] ?? null, 'hscp_nm' => $articleInfo['address']['complexName'] ?? null, 'ptp_no' => $articleInfo['address']['pyeongTypeNumber'] ?? null, 'ptp_nm' => null, 'charger' => null, 'req_price_yn' => 'N', 'reg_charger' => null, 'dept1_sq' => null, 'dept2_sq' => null, 'reg_dept2_sq' => null, 'reg_dept1_sq' => null, 'dong_ho_chk' => $articleInfo['address']['isDongHoChecked'] ?? null, 'hscplqry_lv' => $articleInfo['address']['inquiryLevel'] ?? null, 'chg_trade_type' => null, 'chg_address2' => null, 'chg_address3' => null, 'chg_seller_tel' => null, 'chg_amt' => null, 'reg_status' => null, 'cupnNo' => null, 'roomSiteAtclRgstCnt' => null, 'roomSiteAtclExpsCnt' => null, 'redvlp_area_nm' => $articleInfo['address']['redevelopmentArea']['redevelopmentAreaName'] ?? null, 'biz_stp_desc' => $articleInfo['address']['redevelopmentArea']['businessStep'] ?? null, 'cert_register' => empty($certRegister) ? null : json_encode($certRegister, JSON_UNESCAPED_UNICODE), 'confirm_doc_img_url' => empty($confirm_doc_img_url) ? null : json_encode($confirm_doc_img_url, JSON_UNESCAPED_UNICODE), 'confirm_doc_owner_check_yn' => $articleInfo['seller']['isOwnerCertificationAgree'] === true ? 'Y' : 'N', 'owner_birth' => $articleInfo['seller']['ownerBirthDate'] ?? null, 'vrfc_type_sub' => null, 'cert_register_save_yn' => '', 'confirm_doc_img_url_save_yn' => '', 'reference_file_url' => empty($referenceFileUrl) ? null : json_encode($referenceFileUrl, JSON_UNESCAPED_UNICODE), 'reference_file_url_save_yn' => '', 'reference_file_url_yn' => empty($referenceFileUrl) ? 'N' : 'Y', 'registerBookUniqueNo' => null, // 검증 참고란 'relationSellerAndOwner' => null, // 의뢰인과 소유주 관계 'ownerTypeCode' => getOwnerTypeCodeNo($ownerTypeRaw) ?? null, // 소유자구분코드 'registerBookUniqueNumber' => null, // 등기부 고유번호 ]; $article_info_etc_param = [ 'corp_own' => $ownerTypeRaw == 'CORP' ? 'Y' : 'N', 'bild_no' => null, // 건물번호 'address2a' => $articleInfo['address']['liAddress'] ?? null, // 지번주소 'address2b' => $articleInfo['address']['jibunAddress'] ?? null, // 도로명주소 'expsStartYmdt' => $articleInfo['exposureStartDateTime'] ?? null, 'vrfcAutoPassYn' => $articleInfo['isAutoVerificationRequested'] === true ? 'Y' : 'N', 'registerBookUniqueNo' => null, 'ownerTypeCode' => getOwnerTypeCodeNo($ownerTypeRaw) ?? null, 'orgRepCphNo' => null, 'orgRepTelNo' => null,// 원중개사 대표자명 'orgRltrNm' => null, // 원중개사 대표자명 'smsSendTime' => null, // 서류확인 내용 'document_cert_method' => null, 'noRgbkVrfcReqYn' => $articleInfo['isUnregisteredVerificationRequested'] === true ? 'Y' : 'N', 'areaByBdbkVrfcReqYn' => $articleInfo['isBuildingRegisterAreaCheckRequested'] === true ? 'Y' : 'N', //건축물대장기준 면적검증요청 여부 Y / N 또는 항목미전송 'orgAtclNo' => null, // 원매물 번호 'atclStatCd' => null, // 매물상태코드 J1W : 공동중개 검증 원)중개사 확인 요청 'repNm' => $articleInfo['realtor']['representativeName'] ?? null, // 원중개사 대표자명 'cpName' => $articleInfo['realtor']['realtorName'], // 중개업소 대표자명 ? 'document_not_received' => null, 'final_failure' => null ]; // $vrfc_params = array_merge($vrfc_params, $articl_info_param, $article_info_etc_param); // 개인: INDIV => 0 // 법인: CORP => 1 // 외국인: FRGNR => 2 // 위임장: DELEG => 3 return $vrfc_params; } }