db = \Config\Database::connect(); $this->naverClient = new NaverApiClient(); $this->VrfcReqModel = model(VrfcReqModel::class); $this->articleModel = model(V2ArticleInfoModel::class); $this->V2stdailyModel = model(V2stdailyModel::class); $this->rawStagingModel = model(NaverRawStagingModel::class); $this->receiptModel = model(ReceiptModel::class); $this->resultModel = model(ResultModel::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"); } $rawData = $response['data']; $vType = $rawData['verificationTypeCode'] ?? ''; // 2. [Staging] 원본 DB 저장 (JSON 타입 컬럼 활용) $this->rawStagingModel->insert([ 'atcl_no' => $articleNumber, 'verification_type' => $vType, 'request_type' => $requestType, 'raw_json' => $rawData // 모델에서 json_encode 처리됨 ]); CLI::write(CLI::color('🟢 임시테이블 :: ' . $this->rawStagingModel->getLastQuery() , 'green')); // 3. 타입별 분기 처리 if ($vType === 'S') { // [Type S] 현장확인 응답 처리 (A01 등) return $this->processTypeS($articleNumber, $rawData, $payload); } else { // [Type D/기타] 서류확인/비공동 처리 (D04, F01 등) return $this->processTypeV2($articleNumber, $rawData, $payload); } // $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]; } /** * [Type S] 현장확인 응답 처리 (A01 등) */ private function processTypeS($articleNumber, $rawData, $payload) { $now = db_now(); // 시작 전 트랜잭션 $this->db->transStart(); switch ( trim($rawData['tradeType']) ) { case '매매': $trade_type = 'A1'; break; case '전세': $trade_type = 'B1'; break; case '월세': $trade_type = 'B2'; break; case '단기임대': $trade_type = 'B3'; break; } /* 좌표와 전용면적을 기준으로 */ if( in_array($rawData['realEstateTypeCode'], array('C01', 'C02'))){ $ground_plan = 'N'; } else { $ground_plan = 'Y'; } try { // 1. receipt 데이터 준비 $receiptData = [ 'comp_sq' => '2', 'rcpt_rating' => '3', 'rcpt_key' => $articleNumber, 'rcpt_atclno' => $articleNumber, 'rcpt_product' => $rawData['realEstateTypeCode'] ?? null, 'rcpt_product_nm' => $rawData['realEstateType'] ?? null, 'rcpt_product_info1'=> $rawData['tradeType'] ?? null, 'rcpt_product_info2'=> $rawData['price']['dealAmount'] ?? '0', 'rcpt_product_info4'=> $rawData['price']['preSaleAmount'] ?? '0', 'rcpt_product_info5'=> $rawData['price']['premiumAmount'] ?? '0', 'rcpt_living_yn' => ($rawData['site']['isRegistration'] ?? false) ? 'Y' : 'N', 'rcpt_agent' => $rawData['realtor']['realtorName'] ?? null, 'rcpt_sido' => mb_substr($rawData['address']['legalDivision']['cityNumber'] ?? '', 0, 5), 'rcpt_gugun' => mb_substr($rawData['address']['legalDivision']['divisionNumber'] ?? '', 0, 10), 'rcpt_dong' => $rawData['address']['legalDivision']['sectorNumber'] ?? null, 'rcpt_hscp_nm' => $rawData['address']['complexName'] ?? null, 'rcpt_hscp_no' => $rawData['address']['complexNumber'] ?? null, 'rcpt_ptp_nm' => null, 'rcpt_ptp_no' => $rawData['address']['pyeongTypeNumber'] ?? null, 'rcpt_dtl_addr' => trim(($rawData['address']['legalDivision']['legalDivisionAddress'] ?? '') . $rawData['address']['buildingName'] . '동 ' . ($rawData['address']['hoName'] ?? '') . '호'), 'rcpt_etc_addr' => $rawData['address']['hoName'] ?? null, 'rcpt_floor' => $rawData['floor']['correspondenceFloorCount'] ?? null, 'rcpt_floor2' => $rawData['floor']['totalFloorCount'] ?? null, 'rcpt_exps_type' => '', 'rcpt_exp_photo_yn' => 'Y', 'rcpt_deal_type' => $rawData['tradeTypeCode'] ?? null, 'rcpt_product_nm' => $rawData['tradeType'] ?? null, 'trade_type' => $trade_type ?? null, 'ground_plan' => $ground_plan, 'excls_spce' => $rawData['space']['exclusiveSpace'] ?? null, 'sply_spc' => $rawData['space']['supplySpace'] ?? null, 'tot_spc' => $rawData['space']['totalSpace'] ?? null, 'grnd_spc' => $rawData['space']['groundSpace'] ?? null, 'bldg_spc' => $rawData['space']['buildingSpace'] ?? null, 'share_spc' => $rawData['space']['supplySpace']-$rawData['space']['exclusiveSpace'] ?? null, 'room_cnt' => $rawData['facilities']['roomCount'] ?? null, 'cupnNo' => $rawData['couponNumber'] ?? null, 'roomSiteAtclRgstCnt' => $rawData['site']['monthlyRegisterCount'] ?? null, 'roomSiteAtclExpsCnt' => $rawData['site']['monthlyExposureCount'] ?? null, 'direct_trad_yn' => ($rawData['seller']['isDirectTrade'] ?? false) ? 'Y' : 'N', 'sellr_nm' => $rawData['seller']['sellerName'] ?? null, 'sellr_tel_no' => $rawData['seller']['sellerTelephoneNumber'] ?? null, 'rcpt_ref_addr' => $rawData['address']['etcAddress'] ?? null, 'rcpt_tm' => $now, 'rcpt_stat' => '100000', 'rcpt_x' => $rawData['address']['longitude'] ?? null, 'rcpt_y' => $rawData['address']['latitude'] ?? null, 'agent_id' => '', 'agent_nm' => $rawData['realtor']['realtorName'] ?? null, 'agent_head_tel' => $rawData['realtor']['representativeCellphoneNumber'] ?? null, 'rsrv_date' => $rawData['site']['visitReserveDate'] ?? null, 'rsrv_tm_ap' => '00', // 컬럼명이 rsrv_tm_ap 인지 확인 필요 (제공해주신 스키마 기준) 'insert_tm' => $now, 'rcpt_cpid' => $rawData['cpId'] ?? 'naver', 'room_cnt' => $rawData['facilities']['roomCount'] ?? null, 'isSiteVRVerification' => ($rawData['site']['isVrVerification'] ?? false) ? 'Y' : 'N', 'isPromotionApply' => ($rawData['site']['isVrRepresentativeApply'] ?? false) ? 'Y' : 'N', ]; if (!$this->receiptModel->insert($receiptData)) { throw new \Exception("Receipt Insert 실패: " . json_encode($this->receiptModel->errors())); } $rcpt_sq = $this->receiptModel->getInsertID(); if ( $receiptData['isVrVerification'] == "Y") { $dept_sq = '29'; $usr_sq = '1993'; } // 2. result 데이터 준비 $resultData = [ 'rcpt_sq' => $rcpt_sq, 'use_yn' => 'Y', 'cust_nm' => '', 'rsrv_date' => $rawData['site']['visitReserveDate'] ?? null, 'rsrv_tm_ap' => '00', // 컬럼명이 rsrv_tm_ap 인지 확인 필요 (제공해주신 스키마 기준) 'result_cd1' => '10', 'result_cd2' => '1000', 'result_cd3' => '100000', 'insert_tm' => $now, 'insert_usr' => 0, 'update_tm' => $now, 'update_usr' => 0, 'dept_sq' => $dept_sq, // 필요 시 매핑 로직 추가 'usr_sq' => $usr_sq, // 필요 시 매핑 로직 추가 'resYn' => ($rawData['site']['isRegistration'] ?? false) ? 'Y' : 'N', ]; if (!$this->resultModel->insert($resultData)) { throw new \Exception("Result Insert 실패"); } $this->db->transComplete(); // 성공 로그 생성 쿼리 포함 write_custom_log("Type S 처리 성공 | Atcl: $articleNumber | Rcpt_sq: $rcpt_sq", 'INFO', 'service'); write_custom_log("Receipt Insert SQL: " . (string)$this->receiptModel->getLastQuery(), 'INFO', 'service'); write_custom_log("Result Insert SQL: " . (string)$this->resultModel->getLastQuery(), 'INFO', 'service'); // 예약 정보 동기화 전송 $return = $this->naverClient->submitSyncResult($rawData['reserveNo']); write_custom_log("Naver Sync Result Response: " . json_encode($return), 'INFO', 'service'); // transComplete 이후에 transStatus를 확인하는 것이 CI4의 표준입니다. if ($this->db->transStatus() === false) { // transComplete가 실패하면 자동으로 롤백되지만, 명시적 예외 처리가 안전합니다. // 로그 남기기 write_custom_log("Type S DB 트랜잭션 최종 실패", 'ERROR', 'service'); throw new \Exception("Type S DB 트랜잭션 최종 실패"); } return $rcpt_sq; } catch (\Exception $e) { // 이미 transComplete 내부에서 실패 시 롤백되지만, 예외 발생 시 수동 롤백 보장 // if ($this->db->transEnabled()) { $this->db->transRollback(); // } throw $e; } } /** * [Type V2] 일반/서류/비공동주택 처리 로직 */ private function processTypeV2($articleNumber, $rawData, $payload) { $vrfcParam = $this->v2Parameter($articleNumber, $rawData, $payload); $articleInfoParam = $this->articleInfoParameter($articleNumber, $rawData, $payload); try { switch ($payload['requestType']){ case "REG": $vr_sq = $this->insertVrfcReq($vrfcParam); $articleInfoParam['vr_sq'] = $vr_sq; write_custom_log("articleInfoParam :: " . json_encode($articleInfoParam, JSON_UNESCAPED_UNICODE) , "INFO", "SERVICE"); // 인서트 실행 if ($this->articleModel->insert($articleInfoParam)) { // 성공 로그 write_custom_log("articleInfo Insert Success :: vr_sq: $vr_sq", "INFO", "SERVICE"); } else { // 1. 모델에서 발생한 에러 정보 가져오기 $dbError = $this->db->error(); $lastQuery = (string)$this->articleModel->getLastQuery(); // 2. 로그 기록 (에러 메시지 + 실패한 쿼리) write_custom_log( "DB_ERROR | Code: {$dbError['code']} | Message: {$dbError['message']} | Query: {$lastQuery}", "ERROR", "SERVICE" ); // 필요하다면 예외를 던져서 상위(Worker)의 try-catch에서 처리하게 함 throw new \Exception("ArticleInfo Insert Failed: " . $dbError['message']); } break; case "MOD": break; case "CNC": break; } } catch (\Exception $e) { write_custom_log("CRITICAL_ERROR :: " . $e->getMessage(), "ERROR", "SERVICE"); throw $e; } } private function v2Parameter($articleNumber, $rawData , $payload){ $now = db_now(); $step = isset($rawData['step']) ? $rawData['step'] : '00'; $rdate = $payload['requestDate'] ?? db_now('YmdHis'); $insert_user = 0; $stat_cd = '10'; $sync_yn = 'N'; $insert_tm = $now; $reg_type = function($payload) { switch ($payload['requestType'] ?? '') { case 'REG': return 'C'; case 'MOD': return 'U'; case 'CNC': return 'D'; default: return '0'; } };($payload); $files = $rawData['files'] ?? []; $certRegister = []; $confirm_doc_img_url = []; $referenceFileUrl = []; 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']; } } // 1. v2_vrfc_req (검증요청) 데이터 준비 $vrfcReqData = [ 'reqSeq' => '', // 네이버 요청 고유 ID 'atcl_no' => $articleNumber, 'step' => $step, // 기본 단계 설정 'cpid' => $rawData['cpId'] ?? 'naver', 'cp_atcl_id' => $rawData['cpArticleNumber'] ?? '', 'trade_type' => $rawData['tradeTypeCode'] ?? '', 'realtor_nm' => $rawData['realtor']['realtorName'] ?? null, 'realtor_tel_no' => $rawData['realtor']['representativeCellphoneNumber'] ?? null, 'seller_tel_no' => $rawData['seller']['cellphoneNumber'] ?? null, 'vrfc_type' => $rawData['verificationTypeCode'] ?? 'D', // D, T, P 등 'rgbk_confirm' => null, 'req_type' => $reg_type($payload), // 등록:C, 수정:U, 취소:D 'rdate' => $rdate, 'cpTelNo' => null, 'stat_cd' => $stat_cd, // 초기 대기 상태 'insert_user' => $insert_user, 'insert_tm' => $insert_tm, 'sync_yn' => $sync_yn, 'rgbk_confirm_owner_nm' => null, 'direct_trad_yn' => ($rawData['seller']['isDirectTrade'] ?? false) ? 'Y' : 'N', 'confirm_doc_img_url' => $confirm_doc_img_url, 'confirm_doc_owner_check_yn' => null, 'certRegister' => json_encode($certRegister), 'referenceFileUrl' => json_encode($referenceFileUrl), ]; return $vrfcReqData; } private function articleInfoParameter($articleNumber, $rawData , $payload){ // JSON 객체 안전하게 로드 $address = $rawData['address'] ?? []; $space = $rawData['space'] ?? []; $price = $rawData['price'] ?? []; $floor = $rawData['floor'] ?? []; $seller = $rawData['seller'] ?? []; $realtor = $rawData['realtor'] ?? []; $files = $rawData['realtor']['file'] ?? []; $certRegister = []; $confirm_doc_img_url = []; $referenceFileUrl = []; 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']; } } $ownerTypeCode = null; switch ($rawData['ownerTypeCode']) { case "INDIV": $ownerTypeCode = 0; break; case "CORP": $ownerTypeCode = 1; break; case "FRGNR": $ownerTypeCode = 2; break; case "DELEG": $ownerTypeCode = 3; break; } $ownerCheckYn = ($seller['isOwnerCertificationAgree'] ?? false) ? 'Y' : 'N'; return [ // 'vr_sq' => $vr_sq, 'atcl_no' => $articleNumber, 'cpid' => $rawData['cpId'] ?? null, 'cp_atcl_id' => $rawData['cpArticleNumber'] ?? null, 'rlet_type_cd' => $rawData['realEstateTypeCode'] ?? null, 'trade_type' => $rawData['tradeTypeCode'] ?? null, 'address_code' => $address['legalDivision']['sectorNumber'] ?? null, 'address1' => $address['complexName'] ?? null, 'address2' => trim(($address['buildingName'] ?? '') . ' ' . ($address['hoName'] ?? '')), 'address3' => $address['legalDivision']['legalDivisionAddress'] ?? null, // 면적 및 가격 (데이터 없으면 null) 'sply_spc' => $space['supplySpace'] ?? null, 'excls_spc' => $space['exclusiveSpace'] ?? null, 'tot_spc' => $space['totalSpace'] ?? null, 'grnd_spc' => $space['groundSpace'] ?? null, 'bldg_spc' => $space['buildingSpace'] ?? null, 'deal_amt' => $price['dealAmount'] ?? 0, 'wrrnt_amt' => $price['warrantyAmount'] ?? 0, 'lease_amt' => $price['leaseAmount'] ?? 0, 'isale_amt' => $price['preSaleAmount'] ?? 0, 'prem_amt' => $price['premiumAmount'] ?? 0, 'sise' => null, // 층 및 일정 'floor' => $floor['correspondenceFloorCount'] ?? null, 'floor2' => $floor['totalFloorCount'] ?? null, 'rdate' => date("Y-m-d H:i:s" , strtotime( $payload['requestDatetime'] )), // 셀러 'seller_tel_no' => $seller['sellerTelephoneNumber'] ?? null, // JSON seller 객체에 연락처 필드 부재 'seller_nm' => $seller['sellerName'] ?? null, // 중개업소 'realtor_nm' => $realtor['realtorName'] ?? null, 'realtor_tel_no' => $realtor['representativeTelephoneNumber'] ?? null, // 단지 정보 'hscp_no' => $address['complexNumber'] ?? null, 'hscp_nm' => $address['complexName'] ?? null, 'ptp_no' => $address['pyeongTypeNumber'] ?? null, 'ptp_nm' => $address['pyeongTypeNumber'] ?? null, // 담당자 'charger' => null, // 담당자 'reg_price_yn' => 'N', // 가격수정요청여부 'reg_charger' => null, // 등기부등본 담당자 'dept1_sq' => null, // 부서(본부) 'dept2_sq' => null, // 부서(팀) 'reg_dept2_sq' => null, // 부서(팀) 'reg_dept1_sq' => null, // 부서(본부) // 상태 및 기타 'dong_ho_chk' => ($address['isDongHoCheck'] ?? false) ? 'Y' : 'N', 'hscplqry_lv' => $address['inquiryLevel'] ?? null, // 소유자 'ownerNm' => $seller['ownerName'] ?? null, 'ownerTelNo' => $seller['ownerName'] ?? 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, 'rootSiteAtclExpsCnt' => null, 'redvlp_area_nm' => $adress['redevelopAreaName'] ?? null, 'biz_stp_desc' => $address['bizStepDescription'] ?? null, // file 'cert_register' => json_encode($certRegister, JSON_UNESCAPED_UNICODE), 'direct_trad_yn' => ($seller['isDirectTrade'] ?? false) ? 'Y' : 'N', 'confirm_doc_img_url' => json_encode( $confirm_doc_img_url , JSON_UNESCAPED_UNICODE), 'confirm_doc_owner_check_yn' => $ownerCheckYn, 'owner_birth' => null, 'vrfc_type_sub' => ($rawData['verificationTypeCode'] === 'D') ? ($ownerCheckYn === 'Y' ? 'D2' : 'D1') : ($rawData['verificationTypeCode'] === 'N' ? 'N2' : $rawData['verificationTypeCode'] . '1'), 'cert_register_save_yn' => 'N', 'confirm_doc_img_url_save_yn' => 'N', 'address4' => $address['etcAddress'], 'reference_file_url' => !empty($referenceFileUrl) ? implode('|', $referenceFileUrl) : '', 'reference_file_url_save_yn' => !empty($referenceFileUrl) ? 'Y' : 'N', 'registerBookUniqueNo' => $rawData['verificationReference'] ?? null, 'relationSellerAndOwner' => null, 'ownerTypeCode' => $ownerTypeCode, 'registerBookUniqueNumber' => null ]; } /** * [REG] 신규 등록 */ private function insertVrfcReq($vrfcParam) { CLI::write(CLI::color('🟢 매물 정보 시작', 'green')); $existing = $this->VrfcReqModel->where('atcl_no', $vrfcParam['atcl_no'])->first(); if ($existing) throw new \Exception("중복 등록 시도: " . $vrfcParam['atcl_no']); if (!$this->VrfcReqModel->insert($vrfcParam)) { $dbError = $this->VrfcReqModel->db()->error(); // DB 에러 코드와 메시지 가져오기 $sql = (string)$this->VrfcReqModel->getLastQuery(); write_custom_log("INSERT_FAILED | Atcl: " . $vrfcParam['atcl_no'] . " | Error: {$dbError['message']} | SQL: ".$sql, 'ERROR', 'failed'); throw new \Exception("신규 등록 실패: " . $dbError['message']); } $vr_sq = $this->VrfcReqModel->getInsertID(); $this->statusService->recordStatusAndHistory($vr_sq, '10', 'C9', "신규접수 : 10"); return $vr_sq; } private function insertArticleInfo($article){ } /** * [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 = $articleInfo['seller']['ownerTypeCode'] ?? 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; } }