model = new ReceiptModel(); $this->codeModel = new CodeModel(); } public function lists(): string { error_log('========== Receipt::lists() CALLED =========='); $usr_id = $this->request->getGet('usr_id') ?: ''; $sBonbu = $this->request->getGet('bonbu') ?: ''; $sTeanm = $this->request->getGet('dept_sq') ?: ''; $codes = $this->codeModel->getCodeLists(['NHN_DEAL_TYPE', 'CP_ID', 'ARTICLE_TYPE', 'VRFCREQ_WAY', 'STEP_VERIFICATION']); // 코드조회 $sido = $this->model->getAreaList(); // 지역조회 $bonbu = $this->model->getBonbuList(); $team = $this->model->getTeamList(); $user = $this->model->getUserList(); $this->data['sido'] = $sido; $this->data['bonbu'] = $bonbu; $this->data['team'] = $team; $this->data['user'] = $user; $this->data['codes'] = $codes; if (!empty($usr_id)) { $srchUser = $this->model->getSrchUserInfo($usr_id); $this->data['srchUser'] = $srchUser; } $this->data['sBonbu'] = $sBonbu; $this->data['sTeanm'] = $sTeanm; return view("pages/article/receipt/lists", $this->data); } public function getResultList() { error_log('========== Receipt::getResultList() CALLED =========='); $start = (int) $this->request->getGet('start') ?: 0; $end = (int) $this->request->getGet('length') ?: 10; $data = [ 'rcpt_atclno' => $this->request->getGet('rcpt_atclno'), // 매물ID 'schDateGb' => $this->request->getGet('schDateGb'), // 일자유형 'sdate' => $this->request->getGet('sdate'), // 시작일 'edate' => $this->request->getGet('edate'), // 종료일 'bonbu' => $this->request->getGet('bonbu'), // 본부 'team' => $this->request->getGet('team'), // 팀 'user' => $this->request->getGet('user'), // 담당자 'sido' => $this->request->getGet('sido'), // 시도 'gugun' => $this->request->getGet('gugun'), // 시군구 'dong' => $this->request->getGet('dong'), // 읍면동 'rcpt_stat1' => $this->request->getGet('rcpt_stat1'), // 상태1 'rcpt_stat2' => $this->request->getGet('rcpt_stat2'), // 상태2 'rcpt_stat3' => $this->request->getGet('rcpt_stat3'), // 상태3 'rcpt_product_info1' => $this->request->getGet('rcpt_product_info1'), // 거래구분 'exp_movie_yn' => $this->request->getGet('exp_movie_yn'), // 동영상촬영여부 'conf_img_yn' => $this->request->getGet('conf_img_yn'), // 홍보확인서여부 'parcel_out_yn' => $this->request->getGet('parcel_out_yn'), // 분양권 'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // CPID 'rcpt_product' => $this->request->getGet('rcpt_product'), // 매물종류 'exp_spc_yn' => $this->request->getGet('exp_spc_yn'), // 면적확인 'check_list_img_yn' => $this->request->getGet('check_list_img_yn'), // 체크리스트 'ground_plan_yn' => $this->request->getGet('ground_plan_yn'), // 평면도유무 'ground_plan' => $this->request->getGet('ground_plan'), // 평면도요청 'direct_trad_yn' => $this->request->getGet('direct_trad_yn'), // 직거래 'image_360_yn' => $this->request->getGet('image_360_yn'), // 360촬영여부 'srchType' => $this->request->getGet('srchType'), // 검색유형 'srchTxt' => $this->request->getGet('srchTxt'), // 검색어 ]; error_log('[Receipt::getResultList] START - start=' . $start . ' length=' . $end); $totalCount = $this->model->getTotalCount($data); error_log('[Receipt::getResultList] TotalCount = ' . $totalCount); $datas = $this->model->getResultList($start, $end, $data); error_log('[Receipt::getResultList] END - returned ' . count($datas) . ' rows'); return $this->response->setJSON(body: [ 'recordsTotal' => $totalCount, 'recordsFiltered' => $totalCount, 'data' => $datas, ]); } // 엑셀 다운로드 public function excel() { try { $data = [ 'rcpt_atclno' => $this->request->getGet('rcpt_atclno'), // 매물ID 'schDateGb' => $this->request->getGet('schDateGb'), // 일자유형 'sdate' => $this->request->getGet('sdate'), // 시작일 'edate' => $this->request->getGet('edate'), // 종료일 'bonbu' => $this->request->getGet('bonbu'), // 본부 'team' => $this->request->getGet('team'), // 팀 'user' => $this->request->getGet('user'), // 담당자 'sido' => $this->request->getGet('sido'), // 시도 'gugun' => $this->request->getGet('gugun'), // 시군구 'dong' => $this->request->getGet('dong'), // 읍면동 'rcpt_stat1' => $this->request->getGet('rcpt_stat1'), // 상태1 'rcpt_stat2' => $this->request->getGet('rcpt_stat2'), // 상태2 'rcpt_stat3' => $this->request->getGet('rcpt_stat3'), // 상태3 'rcpt_product_info1' => $this->request->getGet('rcpt_product_info1'), // 거래구분 'exp_movie_yn' => $this->request->getGet('exp_movie_yn'), // 동영상촬영여부 'conf_img_yn' => $this->request->getGet('conf_img_yn'), // 홍보확인서여부 'parcel_out_yn' => $this->request->getGet('parcel_out_yn'), // 분양권 'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // CPID 'rcpt_product' => $this->request->getGet('rcpt_product'), // 매물종류 'exp_spc_yn' => $this->request->getGet('exp_spc_yn'), // 면적확인 'check_list_img_yn' => $this->request->getGet('check_list_img_yn'), // 체크리스트 'ground_plan_yn' => $this->request->getGet('ground_plan_yn'), // 평면도유무 'ground_plan' => $this->request->getGet('ground_plan'), // 평면도요청 'direct_trad_yn' => $this->request->getGet('direct_trad_yn'), // 직거래 'image_360_yn' => $this->request->getGet('image_360_yn'), // 360촬영여부 'srchType' => $this->request->getGet('srchType'), // 검색유형 'srchTxt' => $this->request->getGet('srchTxt'), // 검색어 ]; $datas = $this->model->getExcelList($data); return $this->response->setJSON(body: [ 'data' => $datas, ]); } catch (\Exception $e) { $e->getPrevious()->getTraceAsString(); } } // 상세화면 public function detail($id) { $naver = new NaverApiClient(); $id = (string) $id; if ($id === '') { throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); } $t0 = microtime(true); $codes = $this->codeModel->getCodeLists(['TRADE_TYPE', 'RECEIPT_STATUS2', 'RECEIPT_STATUS3', 'SMS_MSG_TYPE', 'SMS_MSG_TYPE2']); // 코드조회 log_message('info', '[Receipt::detail] getCodeLists {ms}ms', ['ms' => (int) ((microtime(true) - $t0) * 1000)]); $t1 = microtime(true); $bonbu = $this->model->getBonbuList(); log_message('info', '[Receipt::detail] getBonbuList {ms}ms', ['ms' => (int) ((microtime(true) - $t1) * 1000)]); $t2 = microtime(true); $team = $this->model->getTeamList(); log_message('info', '[Receipt::detail] getTeamList {ms}ms', ['ms' => (int) ((microtime(true) - $t2) * 1000)]); $damdang = $this->model->getUserList(); // sms 코드 $sms = []; foreach ($codes as $c) { if ($c['category'] === "SMS_MSG_TYPE2") array_push($sms, $c); } $t3 = microtime(true); $data = $this->model->getDetail($id); log_message('info', '[Receipt::detail] getDetail {ms}ms', ['ms' => (int) ((microtime(true) - $t3) * 1000)]); $t4 = microtime(true); // rcpt_sq를 사용하여 이력 조회 $history = $this->model->getHistory($data['rcpt_sq'] ?? $id); log_message('info', '[Receipt::detail] getHistory {ms}ms', ['ms' => (int) ((microtime(true) - $t4) * 1000)]); $t5 = microtime(true); if ($data['rcpt_jibun_addr']) { $dupleGroundPlan = $this->model->getDupleGP_na($id, $data['rcpt_sido'], $data['rcpt_gugun'], $data['rcpt_dong'], $data['rcpt_hscp_nm'], $data['rcpt_dtl_addr'], $data['rcpt_li_addr'], $data['rcpt_jibun_addr'], $data['rcpt_etc_addr']); } else { $dupleGroundPlan = $this->model->getDupleGP($id, $data['rcpt_sido'], $data['rcpt_gugun'], $data['rcpt_dong'], $data['rcpt_hscp_nm'], $data['rcpt_dtl_addr'], $data['rcpt_ho']); } log_message('info', '[Receipt::detail] getDupleGP {ms}ms', ['ms' => (int) ((microtime(true) - $t5) * 1000)]); $t6 = microtime(true); $aptGround = $this->model->getAptGround($data['rcpt_dong'] ?? ''); log_message('info', '[Receipt::detail] getAptGround {ms}ms', ['ms' => (int) ((microtime(true) - $t6) * 1000)]); // 이미지 파일리스트 $t7 = microtime(true); $images = $this->model->getImageList2($data['rsrv_sq']); log_message('info', '[Receipt::detail] getImageList2 {ms}ms', ['ms' => (int) ((microtime(true) - $t7) * 1000)]); $t8 = microtime(true); $imgs_count = $this->model->getImageCountByType($data['rsrv_sq']); log_message('info', '[Receipt::detail] getImageCountByType {ms}ms', ['ms' => (int) ((microtime(true) - $t8) * 1000)]); $imgs_count = convertArrayToHashTable($imgs_count, 'img_type', 'img_cnt'); //녹취파일 $t9 = microtime(true); $record = $this->model->getRecordInfo($data['rsrv_sq']); log_message('info', '[Receipt::detail] getRecordInfo {ms}ms', ['ms' => (int) ((microtime(true) - $t9) * 1000)]); // 시간대별통계 $t10 = microtime(true); $tmCount = $this->model->getUsrRsrvDateTmCount($id); log_message('info', '[Receipt::detail] getUsrRsrvDateTmCount {ms}ms', ['ms' => (int) ((microtime(true) - $t10) * 1000)]); // 당일 방문예정 매물. $assignList = $this->model->getAssignReceiptListByUser($data['rsrv_date'], $data['usr_sq'], array($id)); // 체크리스트 조회 $t11 = microtime(true); if ($data['exp_photo_yn'] === "N") { $result_check = $this->model->getChecklist($data['rsrv_sq']); } else { $result_check = []; } $pdept = ''; if (!empty($data['dept_sq'])) { $pdept = $this->model->getDeptDetail($data['dept_sq']); } else { $pdept = $this->model->getDeptDetail($data['region_dept_sq']); } $complexList = []; $ptpList = []; // print_r($data); // exit; if ($data['comp_sq'] == '2') { // 아파트단지목록 $complexList = $naver->complexList($data['rcpt_dong']); // 평형목록 $ptpList = $naver->ptpList($data['rcpt_hscp_no']); } // print_r($ptpList); // exit; log_message('info', '[Receipt::detail] getChecklist {ms}ms', ['ms' => (int) ((microtime(true) - $t11) * 1000)]); log_message('info', '[Receipt::detail] total {ms}ms', ['ms' => (int) ((microtime(true) - $t0) * 1000)]); $this->data['codes'] = $codes; $this->data['bonbu'] = $bonbu; $this->data['team'] = $team; $this->data['damdang'] = $damdang; $this->data['pdept'] = $pdept; $this->data['sms'] = $sms; $this->data['data'] = $data; $this->data['assignList'] = $assignList; $this->data['history'] = $history; $this->data['dupleGroundPlan'] = $dupleGroundPlan; $this->data['apt_ground'] = $aptGround; $this->data['images'] = $images; $this->data['imgs_count'] = $imgs_count; $this->data['record'] = $record; $this->data['tmCount'] = $tmCount; $this->data['result_check'] = $result_check; $this->data['complexList'] = $complexList; $this->data['ptpList'] = $ptpList; return view("pages/article/receipt/detail", $this->data); } // 연락처 저장 public function saveTel() { try { $tel = $this->request->getPost('agent_tel'); $this->model->saveTel($tel); return $this->response->setJSON([ 'code' => '0', 'msg' => 'success' ]); } catch (\Exception $e) { return $this->response->setJSON([ 'code' => '9', 'msg' => $e->getMessage(), ]); } } // 거주여부 저장 public function resDbYn() { $naver = new NaverApiClient(); try { $rcpt_key = $this->request->getPost('rcpt_key'); $rcpt_sq = $this->request->getPost('rcpt_sq'); $rsrv_sq = $this->request->getPost('rsrv_sq'); $res_yn = $this->request->getPost('resYn'); $dbUsageAgrYn = $this->request->getPost('dbUsageAgrYn'); $this->model->saveResDB($rcpt_sq, $rsrv_sq, $res_yn, $dbUsageAgrYn); $receipt = $this->model->getDetail($rcpt_key); if ($res_yn == 'Y') { $isResidentsExist = true; } else { $isResidentsExist = false; } $updateData = [ 'residentsExistence' => $isResidentsExist ]; $charger = session('usr_id'); log_message('info', '[resDbYn] 네이버 API 호출 시작 - rcpt_key: ' . $rcpt_key . ', charger: ' . $charger . ', updateData: ' . json_encode($updateData, JSON_UNESCAPED_UNICODE)); $api_result = $naver->updateArticleInfo($rcpt_key, $updateData, $charger); log_message('info', '[resDbYn] 네이버 API 응답 - result: ' . json_encode($api_result, JSON_UNESCAPED_UNICODE) . ' (type: ' . gettype($api_result) . ')'); // API 호출 실패 체크 if (isset($api_result['error']) && $api_result['error'] === true) { // 에러 정보를 상세하게 표시 if ($api_result['error_type'] === 'CURL_ERROR') { $errorMsg = "네이버 API 통신 실패 (CURL 오류)\n"; $errorMsg .= "오류코드: {$api_result['curl_errno']}\n"; $errorMsg .= "오류내용: {$api_result['curl_error']}\n"; $errorMsg .= "URL: {$api_result['url']}"; } else { $errorMsg = "네이버 API 응답 오류 (HTTP {$api_result['http_code']})\n"; $errorMsg .= "응답내용: {$api_result['response']}\n"; $errorMsg .= "URL: {$api_result['url']}"; } throw new \Exception($errorMsg); } // 정상 응답이지만 성공이 아닌 경우 if (!isset($api_result['code']) || $api_result['code'] !== 'success') { $errorMsg = "네이버 API 응답 오류\n"; $errorMsg .= "응답: " . json_encode($api_result, JSON_UNESCAPED_UNICODE); throw new \Exception($errorMsg); } return $this->response->setJSON([ 'code' => '0', 'msg' => 'success' ]); } catch (\Exception $e) { return $this->response->setJSON([ 'code' => '9', 'msg' => $e->getMessage(), ]); } } // 평면도요청 저장 public function resGround() { try { $rcpt_sq = $this->request->getPost('rcpt_sq'); $ground_plan = $this->request->getPost('ground_plan'); $this->model->saveGround($rcpt_sq, $ground_plan); return $this->response->setJSON([ 'code' => '0', 'msg' => 'success' ]); } catch (\Exception $e) { return $this->response->setJSON([ 'code' => '9', 'msg' => $e->getMessage(), ]); } } // 예약확정 저장 public function assignRegist() { $naver = new NaverApiClient(); $deptModel = new DeptModel(); try { //전달받은 값 $rcpt_sq = $this->request->getPost('rcpt_sq'); $rcpt_key = $this->request->getPost('rcpt_key'); $rsrv_sq = $this->request->getPost('rsrv_sq'); $rsrv_date = $this->request->getPost('rsrv_date'); $rsrv_tm_ap = $this->request->getPost('rsrv_tm_ap'); $rsrv_tm_hour = $this->request->getPost('rsrv_tm_hour'); $bonbu = $this->request->getPost('bonbu'); $dept_sq = $this->request->getPost('dept_sq'); $usr_sq = $this->request->getPost('usr_sq'); $bonbuInfo = $deptModel->getDeptDetail($bonbu); $deptInfo = $deptModel->getDeptDetail($dept_sq); $userInfo = $this->model->getUserDetail($usr_sq); $receipt = $this->model->getDetail($rcpt_key); /*** 네이버 연동[s] ***/ $na_result = $naver->reserveSuccess($rcpt_key, 'Y', $bonbuInfo['dept_nm'], $deptInfo['dept_nm'], $userInfo['usr_nm'], $userInfo['usr_tel1'], $rsrv_date, $rsrv_tm_ap); /*** 네이버 연동[e] ***/ if (isset($na_result['code']) && $na_result['code'] === 'success') { //네이버연동 상태변경 완료 $result = $this->model->assignRegist($rcpt_sq, $rsrv_date, $rsrv_tm_ap, $rsrv_tm_hour, $dept_sq, $usr_sq, $receipt); return $this->response->setJSON([ 'code' => '0', 'msg' => 'success' ]); } else { throw new \Exception($na_result['message'] ?? '네이버 API 연동 실패'); } } catch (\Exception $e) { return $this->response->setJSON([ 'code' => '9', 'msg' => $e->getMessage(), ]); } } // 동영상촬영여부 저장 public function requestMovie() { try { $rcpt_sq = $this->request->getPost('rcpt_sq'); $rsrv_sq = $this->request->getPost('rsrv_sq'); $exp_movie_yn = $this->request->getPost('exp_movie_yn'); $this->model->saveRequestMovie($rcpt_sq, $rsrv_sq, $exp_movie_yn); return $this->response->setJSON([ 'code' => '0', 'msg' => 'success' ]); } catch (\Exception $e) { return $this->response->setJSON([ 'code' => '9', 'msg' => $e->getMessage(), ]); } } // 중개사메모 저장 public function requestMessage() { try { $rcpt_sq = $this->request->getPost('rcpt_sq'); $rsrv_sq = $this->request->getPost('rsrv_sq'); $request_msg = $this->request->getPost('request_msg'); $this->model->saveRequestMessage($rcpt_sq, $rsrv_sq, $request_msg); return $this->response->setJSON([ 'code' => '0', 'msg' => 'success' ]); } catch (\Exception $e) { return $this->response->setJSON([ 'code' => '9', 'msg' => $e->getMessage(), ]); } } // 예약취소 public function rsrvcancel() { $naver = new NaverApiClient(); try { //전달받은 값 $rcpt_sq = $this->request->getPost('rcpt_sq'); $rsrv_sq = $this->request->getPost('rsrv_sq'); $rcpt_key = $this->request->getPost('rcpt_key'); $result_cd2 = $this->request->getPost('result_cd2'); $result_cd3 = $this->request->getPost('result_cd3'); $result_msg = $this->request->getPost('result_msg'); $rcpt_stat1 = $this->request->getPost('rcpt_stat1'); $receipt = $this->model->getDetail($rcpt_key); /*** 네이버 연동[s] ***/ if ($result_cd2 == '9010' || $result_cd2 == '9020') { //예약취소 $na_result = $naver->reserveFail($rcpt_key, "E11", $result_msg); } else if ($result_cd2 == '9030') { if ($rcpt_stat1 == '70') { throw new \Exception('방문전 취소 할 수 없습니다.'); } else { $na_result = $naver->shootFail($rcpt_key, "E21", $result_msg); } } else if ($result_cd2 == '9040') { $na_result = $naver->shootFail($rcpt_key, "E22", $result_msg); } else if ($result_cd2 == '9045') { $na_result = $naver->shootFail($rcpt_key, "E23", $result_msg); } else if ($result_cd2 == '9050') { $na_result = $naver->inspectFail($rcpt_key, 'E31', $result_msg); } /*** 네이버 연동[e] ***/ if (isset($na_result['code']) && $na_result['code'] === 'success') { //네이버연동 상태변경 완료 $result = $this->model->rsrvcancel($rcpt_sq, $rsrv_sq, $result_cd2, $result_cd3, $result_msg, $receipt); } else { throw new \Exception($na_result['message'] ?? '네이버 API 연동 실패'); } return $this->response->setJSON([ 'code' => '0', 'msg' => 'success' ]); } catch (\Exception $e) { return $this->response->setJSON([ 'code' => '9', 'msg' => $e->getMessage(), ]); } } // 상태변경 public function chgStatus() { try { $rcpt_sq = $this->request->getPost('rcpt_sq'); $rcpt_key = $this->request->getPost('rcpt_key'); $rcpt_stat = $this->request->getGet('rcpt_stat'); $rsrv_sq = $this->request->getPost('rsrv_sq'); $req_rec_yn = $this->request->getGet('reqRecYn'); $rsrv_date = $this->request->getPost('rsrv_date'); $rsrv_tm_ap = $this->request->getPost('rsrv_tm_ap'); $rsrv_tm_hour = $this->request->getPost('rsrv_tm_hour'); $bonbu = $this->request->getPost('bonbu'); $dept_sq = $this->request->getPost('dept_sq'); $usr_sq = $this->request->getPost('usr_sq'); $rletTypeCd = $this->request->getGet('rletTypeCd'); // 파라미터 디버그 로깅 $p = [ 'rcpt_sq' => $rcpt_sq, 'rcpt_key' => $rcpt_key, 'rcpt_stat' => $rcpt_stat, 'rsrv_sq' => $rsrv_sq, 'req_rec_yn' => $req_rec_yn, 'rsrv_date' => $rsrv_date, 'rsrv_tm_ap' => $rsrv_tm_ap, 'rsrv_tm_hour' => $rsrv_tm_hour, 'bonbu' => $bonbu, 'dept_sq' => $dept_sq, 'usr_sq' => $usr_sq, 'rletTypeCd' => $rletTypeCd, ]; print_r($p); exit; } catch (\Exception $e) { return $this->response->setJSON([ 'code' => '9', 'msg' => $e->getMessage(), ]); } } // 문자발송 public function sendSms() { try { $rcpt_key = $this->request->getPost('rcpt_key'); $rcpt_sq = $this->request->getPost('rcpt_sq'); $rsrv_sq = $this->request->getPost('rsrv_sq'); $cd = $this->request->getPost('cd'); $send_phone = "1600-5749"; $phone = $this->request->getPost('phone'); $content = $this->request->getPost('content'); $send_nm = session('usr_nm'); $data = $this->model->getDetail($rcpt_key); $dest_name = ""; if ($cd == "S7" || $cd == "S14") { $dest_name = "(거주인)" . $data['rec_nm']; } else { $dest_name = "(중개인)" . $data['agent_nm']; } $this->model->sendSms($phone, "", $send_phone, $send_nm, $dest_name, $rsrv_sq, $rcpt_sq, $cd, $data); return $this->response->setJSON([ 'code' => '0', 'msg' => 'success' ]); } catch (\Exception $e) { return $this->response->setJSON([ 'code' => '9', 'msg' => $e->getMessage(), ]); } } // 거주인정보저장 public function saveRecInfo() { $lib = new MyUpload(); try { $rcpt_sq = $this->request->getPost('rcpt_sq'); $rcpt_key = $this->request->getPost('rcpt_key'); $rsrv_sq = $this->request->getPost('rsrv_sq'); $rec_tel1 = $this->request->getPost('rec_tel1'); $rec_tel2 = $this->request->getPost('rec_tel2'); $rec_tel3 = $this->request->getPost('rec_tel3'); $rec_tel = $rec_tel1 . '-' . $rec_tel2 . '-' . $rec_tel3; $rec_nm = $this->request->getPost('rec_nm'); $rec_remark = $this->request->getPost('rec_remark'); $data = [ 'rcpt_sq' => $rcpt_sq, 'rsrv_sq' => $rsrv_sq, 'rcpt_key' => $rcpt_key, 'rec_tel' => $rec_tel, 'rec_nm' => $rec_nm, 'rec_remark' => $rec_remark, ]; // 파일 업로드 처리 $file = $this->request->getFile('rec_file'); if ($file && $file->isValid() && !$file->hasMoved()) { $uploadPath = "/upload/result/" . $rsrv_sq . "/"; $uploadData = $lib->do_upload2($file, $uploadPath); if ($uploadData !== false && is_array($uploadData)) { // do_upload2 반환값 구조: object_key, object_storage_url, origin_name, file_name, base_name, ext $data['file'] = [ 'upload_path' => $uploadPath, 'file_name' => isset($uploadData['file_name']) ? $uploadData['file_name'] : '', 'origin_name' => isset($uploadData['origin_name']) ? $uploadData['origin_name'] : '', 'ext' => isset($uploadData['ext']) ? $uploadData['ext'] : '', 'size' => $file->getSize(), ]; } else { throw new \Exception('파일 업로드에 실패했습니다.'); } } $result = $this->model->saveRecInfo($data); return $this->response->setJSON([ 'code' => '0', 'msg' => 'success', 'data' => [ 'rec_tel' => $result['rec_tel'] ?? '', 'rec_nm' => $result['rec_nm'] ?? '', 'remark' => $result['remark'] ?? '', 'record' => $result['record'] ?? null ] ]); } catch (\Exception $e) { log_message('error', '[Receipt::saveRecInfo] Error: ' . $e->getMessage()); return $this->response->setJSON([ 'code' => '9', 'msg' => $e->getMessage(), ]); } } // 거주인 정보 조회 public function getRecInfo() { try { $rcpt_sq = $this->request->getGet('rcpt_sq'); if (empty($rcpt_sq)) { throw new \Exception('rcpt_sq가 필요합니다.'); } // 거주인 정보 조회 $recInfo = $this->model->getRecInfoByRcptSq($rcpt_sq); if (empty($recInfo)) { throw new \Exception('거주인 정보를 찾을 수 없습니다.'); } return $this->response->setJSON([ 'code' => '0', 'msg' => 'success', 'data' => $recInfo ]); } catch (\Exception $e) { log_message('error', '[Receipt::getRecInfo] Error: ' . $e->getMessage()); return $this->response->setJSON([ 'code' => '9', 'msg' => $e->getMessage(), ]); } } // 정보변경 이력 조회 public function getHistory() { try { $rcpt_sq = $this->request->getGet('rcpt_sq'); if (empty($rcpt_sq)) { throw new \Exception('rcpt_sq가 필요합니다.'); } // 정보변경 이력 조회 $history = $this->model->getHistory($rcpt_sq); return $this->response->setJSON([ 'code' => '0', 'msg' => 'success', 'data' => $history ?? [] ]); } catch (\Exception $e) { log_message('error', '[Receipt::getHistory] Error: ' . $e->getMessage()); return $this->response->setJSON([ 'code' => '9', 'msg' => $e->getMessage(), ]); } } // 파일업로드 public function uploadFile() { $lib = new MyUpload(); $common = new Common(); try { $rcpt_key = $this->request->getPost('rcpt_key'); $rsrv_sq = $this->request->getPost('rsrv_sq'); $rcpt_sq = $this->request->getPost('rcpt_sq'); $img_type = $this->request->getPost('img_type'); $img_sub_type = $this->request->getPost('img_sub_type'); $file_order = $this->request->getPost('file_order'); log_message('info', '[Receipt::uploadFile] Request received - rcpt_key:{rcpt_key}, rsrv_sq:{rsrv_sq}, rcpt_sq:{rcpt_sq}, img_type:{img_type}, img_sub_type:{img_sub_type}, file_order:{file_order}', [ 'rcpt_key' => $rcpt_key, 'rsrv_sq' => $rsrv_sq, 'rcpt_sq' => $rcpt_sq, 'img_type' => $img_type, 'img_sub_type' => $img_sub_type, 'file_order' => $file_order, ]); // 필수 파라미터 검증 if (empty($rsrv_sq)) { log_message('error', '[Receipt::uploadFile] rsrv_sq is empty'); return $this->response->setJSON([ 'code' => '9', 'msg' => 'rsrv_sq(예약일련번호)가 필요합니다.' ]); } $files = $this->request->getFiles(); $uploadPath = "/upload/result/" . $rsrv_sq . "/"; if (!isset($files['files'])) { return $this->response->setJSON([ 'success' => false, 'msg' => '파일 없음' ]); } $uploadFiles = $files['files']; if ($uploadFiles instanceof \CodeIgniter\HTTP\Files\UploadedFile) { $uploadFiles = [$uploadFiles]; } // 매물정보 가져오기 $receipt = $this->model->getDetail($rcpt_key); // 워터마크 이미지 조회 $watermark_info = []; if (!empty($receipt) && !empty($receipt['rcpt_cpid'])) { $watermark_info = $this->model->getWatermarkList($receipt['rcpt_cpid']); } $arrUploadfile = []; foreach ($uploadFiles as $file) { log_message('info', '[Receipt::uploadFile] upload start name={name} size={size} type={type} error={error}:{errorString} path={path}', [ 'name' => $file->getName(), 'size' => $file->getSize(), 'type' => $file->getMimeType(), 'error' => $file->getError(), 'errorString' => $file->getErrorString(), 'path' => $uploadPath, ]); $uploadData = $lib->do_upload2($file, $uploadPath); if ($uploadData !== false) { $arrUploadfile[] = $uploadData; log_message('info', '[Receipt::uploadFile] upload success name={name} stored={stored} cloud_url={url}', [ 'name' => $file->getName(), 'stored' => $uploadData['file_name'] ?? '(unknown)', 'url' => $uploadData['object_storage_url'] ?? '(none)', ]); } else { log_message('error', '[Receipt::uploadFile] Cloud upload failed name={name}', [ 'name' => $file->getName(), ]); // 클라우드 업로드 실패 시 즉시 에러 반환 return $this->response->setJSON([ 'code' => '9', 'msg' => '클라우드 스토리지 업로드 실패: ' . $file->getName() . '\n네트워크 연결 또는 권한을 확인해주세요.' ]); } } $gps_lat = null; $gps_lon = null; $camDate = null; if (!empty($arrUploadfile)) { foreach ($arrUploadfile as $key => $uploadFile) { $object_storage_url = $uploadFile['object_storage_url']; $imgWidth = ''; $imgHeight = ''; if ($img_type !== 'V1') { $base = $uploadFile['base_name']; // xxxx $dir = rtrim(dirname($uploadFile['object_key']), '/'); // upload/result/* $thumbKey = $dir . '/' . $base . '_thumb.jpg'; try { // 원본 이미지 다운로드 (클라우드에서) $imageDataBlob = file_get_contents($object_storage_url); if (!$imageDataBlob) { throw new \Exception('Failed to download original image from cloud storage'); } if (class_exists('\Imagick')) { // Imagick 사용 $im = new \Imagick(); $im->readImageBlob($imageDataBlob); $imgWidth = $im->getImageWidth(); $imgHeight = $im->getImageHeight(); $im->thumbnailImage(105, 80, false); $thumb_im = $im->getImageBlob(); $im->destroy(); // 썸네일 클라우드 업로드 $thumbUploaded = $lib->upload_object_storage_imagick2($thumbKey, $thumb_im); if (!$thumbUploaded) { log_message('warning', '[Receipt::uploadFile] Thumbnail cloud upload failed: {key}', ['key' => $thumbKey]); } } elseif (extension_loaded('gd')) { // GD 라이브러리 사용 $source = imagecreatefromstring($imageDataBlob); if ($source !== false) { $imgWidth = imagesx($source); $imgHeight = imagesy($source); // 썸네일 크기 계산 (105x80 비율 유지) $thumb_width = 105; $thumb_height = 80; // 썸네일 이미지 생성 $thumb = imagecreatetruecolor($thumb_width, $thumb_height); imagecopyresampled($thumb, $source, 0, 0, 0, 0, $thumb_width, $thumb_height, $imgWidth, $imgHeight); // JPEG로 변환 ob_start(); imagejpeg($thumb, null, 85); $thumb_im = ob_get_clean(); // 메모리 해제 imagedestroy($source); imagedestroy($thumb); // 썸네일 클라우드 업로드 $thumbUploaded = $lib->upload_object_storage_imagick2($thumbKey, $thumb_im); if (!$thumbUploaded) { log_message('warning', '[Receipt::uploadFile] Thumbnail cloud upload failed: {key}', ['key' => $thumbKey]); } log_message('info', '[Receipt::uploadFile] Thumbnail created with GD: {key}', ['key' => $thumbKey]); } else { log_message('warning', '[Receipt::uploadFile] GD failed to create image from string'); } } else { log_message('warning', '[Receipt::uploadFile] No image library available (Imagick or GD), skipping thumbnail generation'); } } catch (\Exception $e) { log_message('error', '[Receipt::uploadFile] Thumbnail generation failed: {message}', [ 'message' => $e->getMessage(), ]); } } // 워터마크 삽입 $watermark_imgs = ['I3', 'I4']; if (in_array($img_type, $watermark_imgs) && !empty($watermark_info)) { $common->watermarking($object_storage_url, $watermark_info, '', $receipt['rcpt_cpid'], $uploadFile['object_key']); } // 촬영정보 가져오기 $arrExifData = @exif_read_data($object_storage_url); if (!is_array($arrExifData)) { $arrExifData = []; } if (!isset($arrExifData['COMPUTED']) || !is_array($arrExifData['COMPUTED'])) { $arrExifData['COMPUTED'] = []; } $notFound = "Unavailable"; // Make - 카메라 제조사 if (@array_key_exists('Make', $arrExifData)) { $camMake = $arrExifData['Make']; } else { $camMake = $notFound; } // Model - 카메라 모델 if (@array_key_exists('Model', $arrExifData)) { $camModel = $arrExifData['Model']; } else { $camModel = $notFound; } // Date - 촬영일시 if (@array_key_exists('DateTime', $arrExifData)) { $camDate = $arrExifData['DateTime']; } else { $camDate = $notFound; } // COMPUTED.Height - 세로크기 if (@array_key_exists('Height', $arrExifData['COMPUTED'])) { $camHeight = $arrExifData['COMPUTED']['Height']; } else { $camHeight = $notFound; } // COMPUTED.Width - 가로크기 if (@array_key_exists('Width', $arrExifData['COMPUTED'])) { $camWidth = $arrExifData['COMPUTED']['Width']; } else { $camWidth = $notFound; } $imageMetaData = "카메라 제조사: " . $camMake; $imageMetaData .= "\n카메라 모델: " . $camModel; $imageMetaData .= "\n촬영일시: " . $camDate; $imageMetaData .= "\n가로: " . $camWidth; $imageMetaData .= "\n세로: " . $camHeight; $metaData = $imageMetaData; /** * 파일업로드 내용 저장 */ $uploadParam = [ 'rcpt_key' => $rcpt_key, // 접수번호 'rsrv_sq' => $rsrv_sq, // 예약일련번호 'rcpt_sq' => $rcpt_sq, // 접수일련번호 // 'gps_lat' => $gps_lat, // latitude // 'gps_lon' => $gps_lon, // longitude 'origin_name' => $uploadFile['origin_name'], // 원본파일명 'file_name' => $uploadFile['file_name'], // 저장파일명 'upload_path' => $uploadPath, // 저장경로 'size' => $file->getSize(), 'width' => $imgWidth, 'height' => $imgHeight, // 'thumb_name' => $base . '_thumb.jpg', 'cam_date' => $camDate, // 촬영일 'meta_data' => $metaData, // 이미지메타데이터 'receipt' => $receipt, 'img_type' => $img_type, 'cloud_upload_yn' => 'Y', // 클라우드 업로드 성공 (여기까지 왔으면 성공) ]; $img_sq = $this->model->saveImg($uploadParam); } } // 성공 응답 - JavaScript에서 사용할 이미지 정보 포함 $lastUploadedFile = end($arrUploadfile); return $this->response->setJSON([ 'code' => '0', 'msg' => 'success', 'img_sq' => $img_sq ?? null, 'img_path' => $uploadPath, 'img_filenm' => $lastUploadedFile['file_name'] ?? '', 'cloud_upload_yn' => 'Y', 'img_location' => '' ]); } catch (\Exception $e) { log_message('error', '[Receipt::uploadFile] Exception occurred: {message} at {file}:{line}', [ 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString(), ]); return $this->response->setJSON([ 'code' => '9', 'msg' => $e->getMessage(), ]); } } // 매물사진 일괄 다운로드 (zip) public function downloadAllImages() { $rsrv_sq = $this->request->getGet('rsrv_sq'); $img_type = $this->request->getGet('img_type') ?? 'I4'; if (empty($rsrv_sq)) { return $this->response->setStatusCode(400)->setBody('rsrv_sq required'); } $images = $this->model->getImageListByType($rsrv_sq, $img_type); if (empty($images)) { return $this->response->setStatusCode(404)->setBody('No files'); } $zip = new \ZipArchive(); $zipName = 'images_' . $rsrv_sq . '_' . $img_type . '_' . date('Ymd_His') . '.zip'; $zipPath = WRITEPATH . 'cache/' . $zipName; if ($zip->open($zipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) { return $this->response->setStatusCode(500)->setBody('Failed to create zip'); } foreach ($images as $index => $img) { $filePath = $img['img_path'] . $img['img_filenm']; $zipFileName = sprintf('%02d_%s', $index + 1, basename($img['img_filenm'])); if (($img['cloud_upload_yn'] ?? 'N') === 'Y') { $sourceUrl = NCLOUD_OBJECT_STORAGE_URL . $filePath; $content = @file_get_contents($sourceUrl); if ($content !== false) { $zip->addFromString($zipFileName, $content); } continue; } $localPath = FCPATH . ltrim($filePath, '/'); if (is_file($localPath)) { $zip->addFile($localPath, $zipFileName); continue; } $content = @file_get_contents($filePath); if ($content !== false) { $zip->addFromString($zipFileName, $content); } } $zip->close(); if (!is_file($zipPath)) { return $this->response->setStatusCode(500)->setBody('Zip not found'); } register_shutdown_function(static function () use ($zipPath) { if (is_file($zipPath)) { @unlink($zipPath); } }); return $this->response->download($zipPath, null)->setFileName($zipName); } // 촬영위치 저장 public function saveImgLocation() { try { $img_sq = $this->request->getPost('img_sq'); $rsrv_sq = $this->request->getPost('rsrv_sq'); $location = $this->request->getPost('location'); $this->model->saveImgLocation($img_sq, $rsrv_sq, $location); return $this->response->setJSON([ 'code' => '0', 'msg' => 'success' ]); } catch (\Exception $e) { return $this->response->setJSON([ 'code' => '9', 'msg' => $e->getMessage(), ]); } } // 업로드파일 목록 조회 (AJAX용) public function getImages() { try { $rsrv_sq = $this->request->getGet('rsrv_sq') ?? $this->request->getPost('rsrv_sq'); $img_type = $this->request->getGet('img_type') ?? $this->request->getPost('img_type'); if (empty($rsrv_sq)) { return $this->response->setJSON([ 'success' => false, 'msg' => 'rsrv_sq가 필요합니다.' ]); } // 전체 이미지 리스트 조회 $images = $this->model->getImageList2($rsrv_sq); // img_type이 지정된 경우 필터링 if (!empty($img_type)) { $images = array_filter($images, function($img) use ($img_type) { return $img['img_type'] === $img_type; }); $images = array_values($images); // 인덱스 재정렬 } // 이미지 URL 생성 foreach ($images as &$img) { $temp = explode(".", $img['img_filenm']); $thumbName = $temp[0] . "_thumb.jpg"; $localOriginalPath = $img['img_path'] . $img['img_filenm']; $localThumbPath = $img['img_path'] . $thumbName; // 모든 파일은 클라우드에 저장됨 if (($img['cloud_upload_yn'] ?? 'Y') === 'Y') { $img['original_url'] = NCLOUD_OBJECT_STORAGE_URL . $localOriginalPath; $img['thumbnail_url'] = NCLOUD_OBJECT_STORAGE_URL . $localThumbPath; } else { // 혹시 모를 레거시 로컬 파일 (로컬 파일 시스템 체크) $localThumbFullPath = FCPATH . $localThumbPath; if (file_exists($localThumbFullPath)) { $img['thumbnail_url'] = $localThumbPath; } else { $img['thumbnail_url'] = $localOriginalPath; } $img['original_url'] = $localOriginalPath; } } return $this->response->setJSON([ 'success' => true, 'images' => $images ]); } catch (\Exception $e) { log_message('error', '[Receipt::getImages] Error: ' . $e->getMessage()); return $this->response->setJSON([ 'success' => false, 'msg' => $e->getMessage() ]); } } // 업로드파일 삭제 public function removeUploadFile() { try { $rcpt_sq = $this->request->getPost('rcpt_sq'); $img_sq = $this->request->getPost('img_sq'); // 파일정보 조회 $file = $this->model->getUploadFileInfo($img_sq); if (!empty($file)) { $lib = new MyUpload(); $path = $file['img_path'] . "" . $file['img_filenm']; $thumb = explode(".", $file['img_filenm']); $thumbPath = $file['img_path'] . "" . $thumb[0] . "_thumb." . $thumb[1]; // if ($file['cloud_upload_yn'] == "Y") { // $path = NCLOUD_OBJECT_STORAGE_URL . $path; // } $lib->deleteFile($path); $lib->deleteFile($thumbPath); } $result = $this->model->removeUploadFile($rcpt_sq, $img_sq); if (!$result['success']) { return $this->response->setJSON([ 'code' => '1', 'msg' => $result['msg'] ?? '삭제 실패' ]); } return $this->response->setJSON([ 'code' => '0', 'msg' => 'success' ]); } catch (\Exception $e) { log_message('error', 'removeUploadFile error: ' . $e->getMessage()); return $this->response->setJSON([ 'code' => '1', 'msg' => '삭제 중 에러가 발생했습니다: ' . $e->getMessage() ]); } } // 이미지 순서 업데이트 public function updateImageOrder() { try { $rcpt_sq = $this->request->getPost('rcpt_sq'); $img_type = $this->request->getPost('img_type'); $orders = $this->request->getPost('orders'); log_message('info', '[updateImageOrder] 요청 데이터 - rcpt_sq: ' . $rcpt_sq . ', img_type: ' . $img_type . ', orders: ' . $orders); if (!$rcpt_sq || !$img_type || !$orders) { log_message('error', '[updateImageOrder] 필수 파라미터 누락'); return $this->response->setJSON([ 'code' => '1', 'msg' => '필수 파라미터가 누락되었습니다.' ]); } // JSON 파싱 $orderData = json_decode($orders, true); if (!is_array($orderData)) { return $this->response->setJSON([ 'code' => '1', 'msg' => '순서 데이터 형식이 올바르지 않습니다.' ]); } // 순서 업데이트 $result = $this->model->updateImageOrder($rcpt_sq, $img_type, $orderData); if (!$result['success']) { return $this->response->setJSON([ 'code' => '1', 'msg' => $result['msg'] ?? '순서 업데이트 실패' ]); } return $this->response->setJSON([ 'code' => '0', 'success' => true, 'msg' => '순서가 저장되었습니다.' ]); } catch (\Exception $e) { log_message('error', 'updateImageOrder error: ' . $e->getMessage()); return $this->response->setJSON([ 'code' => '1', 'msg' => '순서 업데이트 중 에러가 발생했습니다: ' . $e->getMessage() ]); } } }