From 6b7e8ad386c2be5f06fe29141faea883dc7ac5a8 Mon Sep 17 00:00:00 2001 From: yangsh Date: Wed, 24 Dec 2025 08:42:23 +0900 Subject: [PATCH 01/10] =?UTF-8?q?=EC=98=A4=ED=83=80=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Controllers/Manage/Scomplex.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Controllers/Manage/Scomplex.php b/app/Controllers/Manage/Scomplex.php index 00bc5e4..608a3ec 100644 --- a/app/Controllers/Manage/Scomplex.php +++ b/app/Controllers/Manage/Scomplex.php @@ -1,10 +1,10 @@ Date: Fri, 26 Dec 2025 09:28:39 +0900 Subject: [PATCH 02/10] =?UTF-8?q?=EC=95=84=ED=8C=8C=ED=8A=B8=EB=8B=A8?= =?UTF-8?q?=EC=A7=80=20=EC=83=81=EC=84=B8=20=ED=8C=8C=EC=9D=BC=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Config/Routes.php | 6 +- app/Controllers/Article/Apt.php | 294 ++++++++++++++-- app/Libraries/MyUpload.php | 165 +++++++-- app/Models/article/AptModel.php | 195 ++++++++++- app/Models/common/CodeModel.php | 2 +- app/Views/pages/article/detail.php | 534 +++++++++++++++++++++++++++-- 6 files changed, 1114 insertions(+), 82 deletions(-) diff --git a/app/Config/Routes.php b/app/Config/Routes.php index f969561..44a3634 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -73,11 +73,15 @@ $routes->group('article', ['namespace' => 'App\Controllers\Article'], function ( $routes->post('apt/savePhoReason', 'Apt::savePhoReason'); $routes->post('apt/saveCate', 'Apt::saveCate'); $routes->post('apt/savePhotoView', 'Apt::savePhotoView'); - $routes->post('apt/savePhotoView', 'Apt::savePhotoView'); + $routes->post('apt/removePhoto', 'Apt::removePhoto'); $routes->post('apt/confirmAptInfo', 'Apt::confirmAptInfo'); $routes->post('apt/resendAptInfo', 'Apt::resendAptInfo'); + $routes->post('apt/savePhoExplain', 'Apt::savePhoExplain'); + $routes->post('apt/saveWriteComplete', 'Apt::saveWriteComplete'); $routes->post('apt/uploadFile', 'Apt::uploadFile'); + $routes->post('apt/savePhoCate', 'Apt::savePhoCate'); + $routes->post('apt/reqRemovePho', 'Apt::reqRemovePho'); }); diff --git a/app/Controllers/Article/Apt.php b/app/Controllers/Article/Apt.php index e431459..65ca672 100644 --- a/app/Controllers/Article/Apt.php +++ b/app/Controllers/Article/Apt.php @@ -6,7 +6,6 @@ use App\Controllers\BaseController; use App\Libraries\MyUpload; use App\Models\article\AptModel; use App\Models\common\CodeModel; -use MY_Upload; class Apt extends BaseController { @@ -673,6 +672,34 @@ class Apt extends BaseController } } + // 단지정보 작성완료 + public function saveWriteComplete() + { + try { + + $rcpt_no = $this->request->getPost('rcpt_no'); + if (empty($rcpt_no)) { + return $this->response->setJSON([ + 'code' => '9', + 'msg' => '정보누락' + ]); + } + + $this->aptModel->saveWriteComplete($rcpt_no); + + return $this->response->setJSON([ + 'code' => '0', + 'msg' => 'success' + ]); + + } catch (\Exception $e) { + return $this->response->setJSON([ + 'code' => '9', + 'msg' => $e->getMessage(), + ]); + } + } + // 검수완료 저장 public function confirmAptInfo() { @@ -868,11 +895,7 @@ class Apt extends BaseController $rcpt_no = $this->request->getPost('rcpt_no'); $uploadType = $this->request->getPost('upload_type'); $files = $this->request->getFiles(); - $imgPath = "/upload/apt_file/" . $rcpt_no . "/"; - - $imgPath = NCLOUD_OBJECT_STORAGE_URL . $imgPath; - $moviePath = $imgPath; - $photo360Path = $imgPath; + $uploadPath = "/upload/apt_file/" . $rcpt_no . "/"; if (!isset($files['files'])) { return $this->response->setJSON([ @@ -881,36 +904,267 @@ class Apt extends BaseController ]); } - if ($uploadType === "photo") { + $arrUploadfile = []; + foreach ($files['files'] as $file) { - // foreach ($files['files'] as $file) { - // if ($file->isValid()) { - // $file->move(WRITEPATH . 'uploads'); + $uploadData = $lib->do_upload2($file, $uploadPath); - // INSERT apt_photo + if ($uploadData !== false) { + $arrUploadfile[] = $uploadData; + } - print_r($_FILES); - // } + } + + $gps_lat = null; + $gps_lon = null; + $camDate = null; + if (!empty($arrUploadfile)) { + foreach ($arrUploadfile as $key => $uploadFile) { + $object_storage_url = $uploadFile['object_storage_url']; + $arrExifData = @exif_read_data($object_storage_url); + if (!empty($arrExifData)) { + $notFound = "Unavailable"; + if (@array_key_exists('DateTime', $arrExifData)) { + $camDate = $arrExifData['DateTime']; + } else { + $camDate = $notFound; + } + + $imageMetaData = $camDate; + $camDate = substr(str_replace(':', '-', $camDate), 0, 10); - // $imageDataBlob = file_get_contents($object_storage_url); - // $im = new Imagick(); - // $im->readImageBlob($imageDataBlob); - // $im->thumbnailImage(105, 80, false); - // $thumb_im = $im->getImageBlob(); - // $lib->upload_object_storage_imagick(); + $arrGPS = $arrExifData['GPS'] ?? null; - // } + if (empty($arrGPS)) { // GPS 섹션이 없으면, 개별 키로도 체크 + if (!empty($arrExifData['GPSLongitude']) && !empty($arrExifData['GPSLatitude'])) { + $arrGPS = [ + 'GPSLongitude' => $arrExifData['GPSLongitude'], + 'GPSLatitude' => $arrExifData['GPSLatitude'], + ]; + } + } + if ( + !empty($arrGPS) + && !empty($arrGPS['GPSLongitude']) + && !empty($arrGPS['GPSLatitude']) + && is_array($arrGPS['GPSLongitude']) + && is_array($arrGPS['GPSLatitude']) + ) { //GPS 정보가 있다면 + if (@array_key_exists('GPSLongitude', $arrGPS) && (@array_key_exists('GPSLatitude', $arrGPS))) { + list($temp_d1, $temp_d2) = sscanf($arrGPS["GPSLatitude"][0], "%d/%d"); //문자->숫자로 계산 + $gps_lat_d = $temp_d1 / $temp_d2; + list($temp_d1, $temp_d2) = sscanf($arrGPS["GPSLatitude"][1], "%d/%d"); + $gps_lat_m = $temp_d1 / $temp_d2; + list($temp_d1, $temp_d2) = sscanf($arrGPS["GPSLatitude"][2], "%d/%d"); + $gps_lat_s = $temp_d1 / $temp_d2; + + list($temp_d1, $temp_d2) = sscanf($arrGPS["GPSLongitude"][0], "%d/%d"); //문자->숫자로 계산 + $gps_lon_d = $temp_d1 / $temp_d2; + list($temp_d1, $temp_d2) = sscanf($arrGPS["GPSLongitude"][1], "%d/%d"); + $gps_lon_m = $temp_d1 / $temp_d2; + list($temp_d1, $temp_d2) = sscanf($arrGPS["GPSLongitude"][2], "%d/%d"); + $gps_lon_s = $temp_d1 / $temp_d2; + + $gps_lat = $gps_lat_d + $gps_lat_m / 60 + $gps_lat_s / 3600; //도분초를 도로 변환 + $gps_lon = $gps_lon_d + $gps_lon_m / 60 + $gps_lon_s / 3600; + } + } else { + $xy = $this->aptModel->getDetail($rcpt_no); + + $gps_lat = $xy['rcpt_y']; + $gps_lon = $xy['rcpt_x']; + } + + + } + + $base = $uploadFile['base_name']; // xxxx + $dir = rtrim(dirname($uploadFile['object_key']), '/'); // upload/apt_file/2 + $thumbKey = $dir . '/' . $base . '_thumb.jpg'; + + $imageDataBlob = file_get_contents($object_storage_url); + $im = new \Imagick(); + $im->readImageBlob($imageDataBlob); + $im->thumbnailImage(105, 80, false); + $thumb_im = $im->getImageBlob(); + // 썸네일 s3 전송 + $lib->upload_object_storage_imagick2($thumbKey, $thumb_im); + + + /** + * 파일업로드 내용 저장 + * rcpt_no, pho_lati, pho_long, filenm, filenm_up, file_path, thumb_path, thumb_nm, cloud_upload_yn + * + */ + $uploadParam = [ + 'rcpt_no' => $rcpt_no, // 접수번호 + 'gps_lat' => $gps_lat, // latitude + 'gps_lon' => $gps_lon, // longitude + 'origin_name' => $uploadFile['origin_name'], // 원본파일명 + 'file_name' => $uploadFile['file_name'], // 저장파일명 + 'upload_path' => $uploadPath, // 저장경로 + 'thumb_name' => $base . '_thumb.jpg', + 'cam_date' => $camDate, // 촬영일 + ]; + + $res = $this->aptModel->saveImg($uploadParam); + log_message('debug', 'apt_file :: rcpt_no : ' . $rcpt_no . ', fileName : ' . $uploadFile['file_name']); + + } + } } else if ($uploadType === "video") { + + $arrUploadfile = []; + foreach ($files['files'] as $file) { + + $uploadData = $lib->do_upload2($file, $uploadPath); + + if ($uploadData !== false) { + $arrUploadfile[] = $uploadData; + } + + } + + // print_r($arrUploadfile); + // exit; + + if (!empty($arrUploadfile)) { + foreach ($arrUploadfile as $key => $uploadFile) { + $uploadParam = [ + 'rcpt_no' => $rcpt_no, // 접수번호 + 'origin_name' => $uploadFile['origin_name'], // 원본파일명 + 'file_name' => $uploadFile['file_name'], // 저장파일명 + 'upload_path' => $uploadPath, // 저장경로 + // 'thumb_name' => $base . '_thumb.jpg', + // 'cam_date' => $camDate, // 촬영일 + ]; + + + // 동영상 정보 저장 + $this->aptModel->saveVideo($uploadParam); + + } + } + + } + return $this->response->setJSON([ + 'code' => '0', + 'msg' => 'success' + ]); + + + } catch (\Exception $e) { + return $this->response->setJSON([ + 'code' => '9', + 'msg' => $e->getMessage(), + ]); + } + } + + // 업로드파일삭제 + public function reqRemovePho() + { + try { + + $type = $this->request->getPost('type'); + + + if ($type === "all") { + $rcpt_no = $this->request->getPost('rcpt_no'); + + // 사진 일괄 삭제 + $this->aptModel->removeAllPho($rcpt_no); + + + } else if ($type === "select") { + $phoNo = $this->request->getPost('phoNo'); // ✅ 배열로 들어옴 + + if (!is_array($phoNo)) + $phoNo = [$phoNo]; + + if (empty($phoNo)) { + return $this->response->setJSON([ + 'code' => '9', + 'msg' => '데이터 누락' + ]); + } + + // 선택 사진 삭제 + $this->aptModel->removePho($phoNo); + + + } else { + return $this->response->setJSON([ + 'code' => '9', + 'msg' => '잘못된 접근' + ]); + } + + + return $this->response->setJSON([ + 'code' => '0', + 'msg' => 'success' + ]); + + + } catch (\Exception $e) { + return $this->response->setJSON([ + 'code' => '9', + 'msg' => $e->getMessage(), + ]); + } + } + + + // 업로드 파일 카테고리 지정 + public function savePhoCate() + { + try { + + $rcpt_no = $this->request->getPost('rcpt_no'); + $code1 = $this->request->getPost('code1'); + $code2 = $this->request->getPost('code2'); + $phoNo = $this->request->getPost('phoNo'); + + if (!is_array($phoNo)) + $phoNo = [$phoNo]; + + if (empty($phoNo)) { + return $this->response->setJSON([ + 'code' => '9', + 'msg' => '데이터 누락' + ]); + } + + foreach ($phoNo as $pho) { + + $data = [ + 'rcpt_no' => $rcpt_no, + 'pho_no' => $pho, + 'code1' => $code1, + 'code2' => $code2, + ]; + + // 카테고리 지정 + $this->aptModel->updatePhoCate($data); + + } + + return $this->response->setJSON([ + 'code' => '0', + 'msg' => 'success' + ]); + } catch (\Exception $e) { return $this->response->setJSON([ diff --git a/app/Libraries/MyUpload.php b/app/Libraries/MyUpload.php index 27d33ed..5615b93 100644 --- a/app/Libraries/MyUpload.php +++ b/app/Libraries/MyUpload.php @@ -3,6 +3,10 @@ namespace App\Libraries; use Aws\S3\S3Client; +use Aws\Exception\AwsException; +use Aws\Credentials\CredentialProvider; +use Aws\Credentials\Credentials; +use CodeIgniter\HTTP\Files\UploadedFile; class MyUpload { @@ -49,12 +53,76 @@ class MyUpload return $this; } - // CI3 스타일: do_upload() + /** + * 파일 업로드 요청 + * 추가일 2025.12.24 + * 작성자 - yangsh + */ + public function do_upload2(UploadedFile $file, $filePath = null): array|false + { + if (!$file->isValid()) { + $this->set_error('upload_invalid_file'); + return false; + } + + // 업로드 전인데 hasMoved()가 true면 비정상 상태 + if ($file->hasMoved()) { + $this->set_error('upload_file_already_moved'); + return false; + } + + $newName = $file->getRandomName(); + + // ✅ PHP 임시 업로드 파일 경로 (writable로 move() 필요 없음) + $tmpFile = $file->getTempName(); + if (!is_file($tmpFile)) { + $this->set_error('upload_temp_file_missing'); + log_message('error', 'do_upload2 temp file missing: ' . $tmpFile); + return false; + } + + // ✅ 클라우드에 올라갈 "Key"를 직접 만든다 (로컬 경로 절대 넣지 말기) + // 예시: upload/tmp/랜덤파일명 또는 upload/apt_file/{rcpt_no}/... + $objectKey = $filePath . $newName; + + $up = $this->upload_object_storage($objectKey, $tmpFile, 'file'); + if ($up === false) { + $this->set_error('upload_destination_cloud_error'); + return false; + } + + // (선택) tmp 파일 삭제 + @unlink($tmpFile); + + $this->s3_data = [ + 'object_key' => $objectKey, + 'object_storage_url' => $up['object_storage_url'] ?? null, + 'origin_name' => $file->getClientName(), + 'file_name' => basename($objectKey), // xxxx.jpg + 'base_name' => pathinfo($objectKey, PATHINFO_FILENAME), // xxxx + 'ext' => pathinfo($objectKey, PATHINFO_EXTENSION), // jpg + ]; + + + log_message('debug', 's3_data=' . json_encode($this->s3_data ?? null, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + + + return $this->s3_data; + } + + /** + * S3(NCLOUD) 파일 업로드 + * 추가일 2025.12.24 + * 작성자 - yangsh + */ public function do_upload(string $field = 'userfile'): bool { + $request = service('request'); $file = $request->getFile($field); + var_dump($file); + if (!$file) { $this->set_error('upload_no_file_selected'); return false; @@ -222,10 +290,13 @@ class MyUpload $this->errors[] = $msg; } - // -------------------------------------------------------------------- - // === 너의 기존 S3 메서드들 (CI4용으로 client 생성만 보강) === - public function upload_object_storage(string $key, string $temp_file, string $type = 'file'): bool + /** + * S3(NCLOUD) 파일 업로드 + * 수정일 2025.12.24 + * 작성자 - yangsh + */ + public function upload_object_storage(string $key, string $temp_file, string $type = 'file'): array|false { // CI3 코드의 경로 치환 로직 유지 (FCPATH는 CI4에도 존재) $object_storage_upload_path = str_replace(FCPATH, '/', $key); @@ -236,27 +307,77 @@ class MyUpload $s3Client = $this->makeS3Client(); try { - $body = file_get_contents($temp_file); + // $body = file_get_contents($temp_file); + + // $response = $s3Client->putObject([ + // 'Bucket' => NCLOUD_S3_BUCKET, + // 'Key' => ltrim($object_storage_upload_path, '/'), + // 'Body' => $body, + // 'ACL' => 'public-read', + // ]); $response = $s3Client->putObject([ 'Bucket' => NCLOUD_S3_BUCKET, 'Key' => ltrim($object_storage_upload_path, '/'), - 'Body' => $body, + 'SourceFile' => $temp_file, // ✅ 동영상도 OK 'ACL' => 'public-read', + + // (선택) 타입별 ContentType 지정 (브라우저 재생/다운로드에 중요) + // 'ContentType' => $this->guessMime($temp_file, $type), ]); - $this->s3_data = [ - 'object_storage_upload_path' => $object_storage_upload_path, + // $this->s3_data = [ + // 'object_storage_upload_path' => $object_storage_upload_path, + // 'object_storage_url' => $response['ObjectURL'] ?? null, + // ]; + + return [ 'object_storage_url' => $response['ObjectURL'] ?? null, ]; } catch (\Throwable $e) { // 운영에서는 echo 지양. 로그로 남기는 걸 추천 // log_message('error', $e->getMessage()); + log_message('error', '[S3 UPLOAD FAIL] ' . $e->getMessage()); + log_message('error', '[S3 UPLOAD TRACE] ' . $e->getTraceAsString()); + return false; + } + + } + + /** + * S3(NCLOUD) 파일 업로드 - 썸네일 + * 추가일 2025.12.24 + * 작성자 - yangsh + */ + public function upload_object_storage_imagick2($key, $blobData): bool + { + $object_storage_upload_path = str_replace(FCPATH, '/', $key); + $object_storage_upload_path = str_replace('/image/confirms_upload/', '/upload/', $object_storage_upload_path); + $object_storage_upload_path = str_replace('//', '/', $object_storage_upload_path); + $object_storage_upload_path = str_replace('/home/www/upload/confirms_upload/', '/upload/', $object_storage_upload_path); + + $s3Client = $this->makeS3Client(); + + try { + + $response = $s3Client->putObject([ + 'Bucket' => NCLOUD_S3_BUCKET, + 'Key' => ltrim($object_storage_upload_path, '/'), + 'Body' => $blobData, + 'ACL' => 'public-read', + ]); + + + } catch (\Throwable $e) { + // 운영에서는 echo 지양. 로그로 남기는 걸 추천 + log_message('error', '[S3 UPLOAD FAIL] ' . $e->getMessage()); + log_message('error', '[S3 UPLOAD TRACE] ' . $e->getTraceAsString()); return false; } return true; + } public function upload_object_storage_imagick(string $key, $blobData): bool @@ -306,28 +427,18 @@ class MyUpload protected function makeS3Client(): S3Client { - // AWS SDK v2: S3Client::factory / v3+: new S3Client - if (method_exists(S3Client::class, 'factory')) { - /** @noinspection PhpUndefinedMethodInspection */ - return S3Client::factory([ - 'key' => NCLOUD_S3_KEY, - 'secret' => NCLOUD_S3_SECRET, - 'endpoint' => NCLOUD_S3_ENDPOINT, - 'debug' => true, - 'ssl.certificate_authority' => false, - ]); - } + $region = defined('NCLOUD_S3_REGION') ? NCLOUD_S3_REGION : 'ap-northeast-2'; + + // ✅ credentials를 provider로 강제하면 provider-chain(IMDS) 안 탐 + $creds = new Credentials(NCLOUD_S3_KEY, NCLOUD_S3_SECRET); + $provider = CredentialProvider::fromCredentials($creds); return new S3Client([ - 'endpoint' => NCLOUD_S3_ENDPOINT, - 'credentials' => [ - 'key' => NCLOUD_S3_KEY, - 'secret' => NCLOUD_S3_SECRET, - ], - 'region' => defined('NCLOUD_S3_REGION') ? NCLOUD_S3_REGION : 'kr-standard', 'version' => 'latest', - // (보안주의) 필요하면 verify 설정을 추가 - // 'http' => ['verify' => false], + 'region' => $region, + 'endpoint' => NCLOUD_S3_ENDPOINT, + 'credentials' => $provider, + 'use_path_style_endpoint' => true, ]); } diff --git a/app/Models/article/AptModel.php b/app/Models/article/AptModel.php index 47752ca..20d8bc7 100644 --- a/app/Models/article/AptModel.php +++ b/app/Models/article/AptModel.php @@ -772,6 +772,7 @@ class AptModel extends Model ,b.memo, b.note, b.video_target, b.vdo_up_ynx, b.not_vdo_reson, b.apt_step, b.not_vdo_tm, b.check_yn, b.resend_yn, b.write_complete_yn, b.all_no_pho ,b.write_complete_tm, DATE_FORMAT(b.write_complete_tm, '%Y-%m-%d') as rdate_dt_cmpl ,DATE_FORMAT(b.write_complete_tm, '%H:%i:%s') as rdate_tm_cmpl ,b.charger, b.dept_sq ,(SELECT pdept_sq FROM departments WHERE dept_sq = b.dept_sq) bonbu + ,IFNULL((SELECT CONCAT(file_path, '', filenm_up) FROM apt_photo WHERE rcpt_no = a.rcpt_no AND pho_cate1 = 'V' AND pho_cate2 = 'V01' ORDER BY pho_no DESC LIMIT 1), '') AS vdo_path FROM apt_receipt a JOIN apt_result b ON a.rcpt_no = b.rcpt_no @@ -1030,8 +1031,24 @@ class AptModel extends Model 'pho_cate2' => $params['pho_cate2'], ]; - $this->db->where_in('pho_no', $params['pho_no']); - $result = $this->db->update('apt_photo', $data); + $phoNos = $params['pho_no'] ?? []; + + if (!is_array($phoNos)) { + $phoNos = [$phoNos]; + } + + if (empty($phoNos)) { + return [ + 'success' => false, + 'msg' => '대상 pho_no가 없습니다.', + ]; + } + + $builder = $this->db->table('apt_photo'); + + $builder->whereIn('pho_no', $phoNos); + $result = $builder->update($data); + if ($result === false) { return [ @@ -1182,6 +1199,31 @@ class AptModel extends Model ]; } + // 단지정보 작성완료 + public function saveWriteComplete($rcpt_no) + { + $sql = " UPDATE apt_result" . + " SET write_complete_yn = 'Y'" . + " ,apt_step = CASE WHEN vdo_up_ynx = 'N' THEN 'S02' ELSE 'S04' END" . + " ,write_complete_tm = now()" . + " WHERE rcpt_no = ? "; + + if ($this->db->query($sql, [$rcpt_no])) { + return [ + 'success' => false, + 'msg' => '저장실패', + ]; + } + + $now = $this->getDetail($rcpt_no); + $this->saveHistory($rcpt_no, $now['apt_step'], 'C', 'C1', session('usr_id')); + + // 성공 + return [ + 'success' => true, + ]; + } + // 단지실사 API 정보 public function new_api_photo_send_data($rcpt_no) @@ -1246,4 +1288,153 @@ class AptModel extends Model 'success' => true, ]; } + + + // 업로드 파일정보 저장 + public function saveImg($params) + { + $sql = "INSERT INTO apt_photo + (rcpt_no, pho_lati, pho_long, filenm, filenm_up, pho_view_yn, pho_date, insert_tm, file_path, use_yn, thumb_path, thumb_nm, cloud_upload_yn) + VALUES + ( + {$params['rcpt_no']}, + '{$params['gps_lat']}', + '{$params['gps_lon']}', + '{$params['origin_name']}', + '{$params['file_name']}', + 'Y', + '{$params['cam_date']}', + NOW(), + '{$params['upload_path']}', + 'Y', + '{$params['upload_path']}', + '{$params['thumb_name']}', + 'Y' + ) + "; + + if ($this->db->query($sql) === false) { + return [ + 'success' => false, + 'msg' => '저장실패', + ]; + } + + return [ + 'success' => true, + ]; + } + + // 동영상 정보 저장 + public function saveVideo($params) + { + $sql = "INSERT INTO apt_photo + (rcpt_no, pho_cate1, pho_cate2, filenm, filenm_up, insert_tm, file_path, use_yn, cloud_upload_yn) + VALUES + ( + {$params['rcpt_no']}, + 'V', + 'V01', + '{$params['origin_name']}', + '{$params['file_name']}', + NOW(), + '{$params['upload_path']}', + 'Y', + 'Y' + ) + "; + + // print ($sql); + + if ($this->db->query($sql) === false) { + return [ + 'success' => false, + 'msg' => '저장실패', + ]; + } + + $sql = "UPDATE apt_result" . + " SET vdo_up_ynx = 'Y'" . + " ,not_vdo_reson = ''" . + " ,video_target = 'Y'" . + " ,not_vdo_tm = NULL " . + " ,vdo_up_tm = NOW() " . + " ,apt_step = 'S03'" . + " WHERE rcpt_no = {$params['rcpt_no']}"; + + $this->db->query($sql); + + // print ($sql); + + //히스토리 + $this->saveHistory($params['rcpt_no'], 'S03', 'F', 'F1', session('usr_id')); + + return [ + 'success' => true, + ]; + } + + + // 업로드파일 일괄삭제 + public function removeAllPho($rcpt_no) + { + $sql = "UPDATE apt_photo" . + " SET use_yn = 'N'" . + " WHERE rcpt_no = ? "; + + if ($this->db->query($sql, [$rcpt_no]) === false) { + return [ + 'success' => false, + 'msg' => '저장실패', + ]; + } + + return [ + 'success' => true, + ]; + } + + // 선택파일 삭제 + public function removePho($params) + { + $builder = $this->db->table('apt_photo'); + + $builder->whereIn('pho_no', $params); + + $result = $builder->update([ + 'use_yn' => 'N', + ]); + + if ($result === false) { + return [ + 'success' => false, + 'msg' => 'DB 업데이트 실패', + ]; + } + + return [ + 'success' => true, + ]; + } + + // 카테고리 지정 + public function updatePhoCate($data) + { + $sql = "UPDATE apt_photo SET + pho_cate1 = '{$data['code1']}', + pho_cate2 = '{$data['code2']}' + WHERE pho_no = {$data['pho_no']} + "; + + if ($this->db->query($sql) === false) { + return [ + 'success' => false, + 'msg' => '저장 실패', + ]; + } + + return [ + 'success' => true, + ]; + } } \ No newline at end of file diff --git a/app/Models/common/CodeModel.php b/app/Models/common/CodeModel.php index 9fdcdce..c258b2c 100644 --- a/app/Models/common/CodeModel.php +++ b/app/Models/common/CodeModel.php @@ -32,7 +32,7 @@ class CodeModel extends Model ->getResultArray(); } - public function getCategoryCodeList($category = array(), $useYn = '') + public function getCategoryCodeList($category = [], $useYn = '') { $this->db->select('category, cd, cd_nm, use_yn'); $this->db->from('codes'); diff --git a/app/Views/pages/article/detail.php b/app/Views/pages/article/detail.php index 1486799..3ea0812 100644 --- a/app/Views/pages/article/detail.php +++ b/app/Views/pages/article/detail.php @@ -42,6 +42,31 @@ background-color: #ff0000 !important; color: #fff !important; } + + .dropzone { + min-height: 260px; + background-color: #fafbfc; + } + + .dropzone:hover { + background-color: #f4f6f8; + } + + #myDropzone { + position: relative; + padding-top: 110px; + /* 메시지 영역 높이만큼 */ + } + + #uploadModal .modal-dialog { + max-width: 1140px; + /* modal-xl */ + } + + #uploadModal .modal-content { + height: 450px; + max-height: 450px; + }

아파트단지 DB구축 상세

@@ -85,12 +110,11 @@
-
-
@@ -155,7 +179,7 @@ - @@ -227,7 +251,7 @@
단지 특이사항
+ style="height: 220px;resize: none;">
@@ -279,10 +303,21 @@ 동영상 - 비디오
- +
+ + + + + 비디오
+ + +
동영상 촬영불가 @@ -352,17 +387,181 @@ 사진 일괄삭제 - + 단지정보 작성완료 - +
+ $val) { + if (empty($val['pho_cate2'])) { + $arrPho[] = $val; + unset($image[$key]); + } + } + + if (!empty($arrPho)): + ?> + +
+
+
+
카테고리 미지정 이미지
+ +
+
+ + +
+ +
+
+ +
+ +
+
+ + <?= esc($fileNm) ?> + + +
+ + +
+
+
+ + + +
+
+ +
+ + +
단지 사진 및 설명 정보
@@ -493,7 +692,7 @@ - + @@ -635,7 +834,7 @@ + style="width:350px;height:90px;resize: none;"> @@ -671,7 +870,7 @@
- -
+ +
+
+
+ 파일을 드래그하거나 클릭해서 추가하세요
+ + 사진 여러 장 가능 / 동영상은 1개만 + +
@@ -773,6 +979,25 @@
+ + endSection() ?> + +

조직 관리

+ +
+
+
+
+
+ +
+ + +
+ + +
+ +
+ +
+
+
+
+ + +
+
+
조직 관리
+
+ + + + + + + + + + + + + +
순번단지코드단지구역명단지코드변경
+
+ +
+
+
+ + + + + +endSection() ?> \ No newline at end of file From 06e266425ce5574d226c8d9dbb23cac955e09ee2 Mon Sep 17 00:00:00 2001 From: yangsh Date: Fri, 26 Dec 2025 17:21:21 +0900 Subject: [PATCH 04/10] =?UTF-8?q?=EC=95=84=ED=8C=8C=ED=8A=B8=ED=8F=89?= =?UTF-8?q?=EB=A9=B4=EB=8F=84=20=EB=AA=A9=EB=A1=9D=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Config/Routes.php | 16 +- app/Controllers/Article/Apt.php | 17 +- app/Controllers/Article/DelChgApt.php | 5 + app/Controllers/Article/Ground.php | 358 +++++++ app/Models/article/AptModel.php | 65 ++ app/Models/article/GroundModel.php | 651 +++++++++++++ app/Views/pages/article/lists.php | 45 +- app/Views/pages/article/lists2.php | 1289 +++++++++++++++++++++++++ 8 files changed, 2407 insertions(+), 39 deletions(-) create mode 100644 app/Controllers/Article/Ground.php create mode 100644 app/Models/article/GroundModel.php create mode 100644 app/Views/pages/article/lists2.php diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 707c0ef..ece01fe 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -54,7 +54,6 @@ $routes->group('article', ['namespace' => 'App\Controllers\Article'], function ( // 관할포인트 인쇄 $routes->get('apt/print', 'Apt::print'); - /** API - 아파트단지 */ $routes->get('apt/getAptLists', 'Apt::getAptLists'); $routes->post('apt/saveAptMemo', 'Apt::saveAptMemo'); @@ -92,6 +91,21 @@ $routes->group('article', ['namespace' => 'App\Controllers\Article'], function ( */ $routes->get('apt/delChgApt/getAptLists', 'DelChgApt::getAptLists'); $routes->post('apt/delChgApt/chgAptHscp', 'Apt::chgAptHscp'); + + + // 아파트 평면도 + $routes->get('apt/lists2', 'Ground::lists'); + + /** + * 아파트 평면도 - API + */ + $routes->get('apt/ground/getAptLists', 'Ground::getAptLists'); + $routes->get('apt/ground/excel', 'Ground::excel'); + $routes->post('apt/ground/chgAptDamdang', 'Ground::chgAptDamdang'); + $routes->post('apt/ground/uploadFile', 'Ground::uploadFile'); + $routes->get('apt/ground/print', 'Ground::print'); + + }); /** diff --git a/app/Controllers/Article/Apt.php b/app/Controllers/Article/Apt.php index 65ca672..1d6b958 100644 --- a/app/Controllers/Article/Apt.php +++ b/app/Controllers/Article/Apt.php @@ -166,11 +166,17 @@ class Apt extends BaseController $damdang = $this->request->getPost(index: 'damdang'); if (empty($team)) { - throw new Exception("팀정보 누락"); + return $this->response->setJSON([ + 'code' => '9', + 'msg' => '팀정보 누락', + ]); } if (empty($damdang)) { - throw new Exception("담당자정보 누락"); + return $this->response->setJSON([ + 'code' => '9', + 'msg' => '담당자정보 누락', + ]); } @@ -192,7 +198,10 @@ class Apt extends BaseController } } else { - throw new Exception("저장할 데이터 누락"); + return $this->response->setJSON([ + 'code' => '9', + 'msg' => '저장데이터 누락', + ]); } return $this->response->setJSON([ @@ -262,8 +271,6 @@ class Apt extends BaseController // 엑셀 다운로드 public function excel() { - - try { $data = [ diff --git a/app/Controllers/Article/DelChgApt.php b/app/Controllers/Article/DelChgApt.php index 3545ba1..8b30e16 100644 --- a/app/Controllers/Article/DelChgApt.php +++ b/app/Controllers/Article/DelChgApt.php @@ -4,6 +4,11 @@ namespace App\Controllers\Article; use App\Controllers\BaseController; use App\Models\article\DelChgAptModel; +/** + * 단지코드관리 + * 2025.12.26 + * 작성자 : yangsh + */ class DelChgApt extends BaseController { private $model; diff --git a/app/Controllers/Article/Ground.php b/app/Controllers/Article/Ground.php new file mode 100644 index 0000000..db20cd4 --- /dev/null +++ b/app/Controllers/Article/Ground.php @@ -0,0 +1,358 @@ +model = new GroundModel(); + $this->codeModel = new CodeModel(); + } + + public function lists(): string + { + + $codes = $this->codeModel->getCodeLists(['VIDEO_TARGET', 'APT_GROUND_STEP', 'PHO_YN', 'VDO_YN']); // 코드조회 + $sido = $this->model->getAreaList(); // 지역조회 + $bonbu = $this->model->getBonbuList(); // 본부 + $team = $this->model->getTeamList(); // 팀 + $user = $this->model->getUserList(); // 유저 + + return view("pages/article/lists2", [ + 'codes' => $codes, + 'sido' => $sido, + 'bonbu' => $bonbu, + 'team' => $team, + 'user' => $user, + ]); + } + + // 아파트단지목록 조회 + public function getAptLists() + { + + $start = (int) $this->request->getGet('start') ?: 0; + $end = (int) $this->request->getGet('length') ?: 10; + + $data = [ + 'hscp_no' => $this->request->getGet('hscp_no'), // 단지코드 + 'part_no' => $this->request->getGet('part_no'), // 구분코드 + 'srcSido' => $this->request->getGet('srcSido'), // 시|도 + 'srcGugun' => $this->request->getGet('srcGugun'), // 시|군|구 + 'srcDong' => $this->request->getGet('srcDong'), // 읍|면|동 + + 'rcpt_hscp_nm' => $this->request->getGet('rcpt_hscp_nm'), // 단지명 + 'sdate' => $this->request->getGet('sdate'), // 시작일 + 'edate' => $this->request->getGet('edate'), // 종료일 + + 'bonbu' => $this->request->getGet('bonbu'), // 본부 + 'team' => $this->request->getGet('team'), // 팀 + 'damdang' => $this->request->getGet('damdang'), // 담당 + + 'stat' => $this->request->getGet('stat'), // 진행상태 + ]; + + $totalCount = $this->model->getTotalCount($data); + $datas = $this->model->getAptLists($start, $end, $data); + + $deptStatistics = $this->model->getDeptStatistics($data); // 조직별통계 + $areaStatistics = $this->model->getStatistics($data); // 지역별통계 + + return $this->response->setJSON(body: [ + 'draw' => (int) ($this->request->getGetPost('draw') ?? 0), // 서버사이드면 권장 + 'recordsTotal' => $totalCount, + 'recordsFiltered' => $totalCount, + 'data' => $datas, + 'widgets' => [ + 'deptList' => $deptStatistics, + 'areaStats' => $areaStatistics, + ], + ]); + + } + + // 엑셀 다운로드 + public function excel() + { + try { + + $data = [ + 'hscp_no' => $this->request->getGet('hscp_no'), // 단지코드 + 'part_no' => $this->request->getGet('part_no'), // 구분코드 + 'srcSido' => $this->request->getGet('srcSido'), // 시|도 + 'srcGugun' => $this->request->getGet('srcGugun'), // 시|군|구 + 'srcDong' => $this->request->getGet('srcDong'), // 읍|면|동 + + 'rcpt_hscp_nm' => $this->request->getGet('rcpt_hscp_nm'), // 단지명 + 'sdate' => $this->request->getGet('sdate'), // 시작일 + 'edate' => $this->request->getGet('edate'), // 종료일 + + 'bonbu' => $this->request->getGet('bonbu'), // 본부 + 'team' => $this->request->getGet('team'), // 팀 + 'damdang' => $this->request->getGet('damdang'), // 담당 + + 'stat' => $this->request->getGet('stat'), // 진행상태 + ]; + + $datas = $this->model->getExcelList($data); + + return $this->response->setJSON(body: [ + 'data' => $datas, + ]); + + } catch (\Exception $e) { + $e->getPrevious()->getTraceAsString(); + } + } + + + // 관할포인트 인쇄 - 화면 + public function print(): string + { + $deptSq = $this->request->getGet('depChk'); + $dept_cnt = count($deptSq); + + + $listDept = $this->model->getDeptMapList($deptSq); + + if (!empty($listDept)) { + $lati = 0; + $long = 0; + foreach ($listDept as $dept) { + $lati += $dept['rcpt_y']; + $long += $dept['rcpt_x']; + } + + $lati = $lati / $dept_cnt; + $long = $long / $dept_cnt; + } + + return view("pages/article/printMap", [ + // 'lati' => $lati, + // 'long' => $long, + 'listDept' => $listDept, + ]); + } + + // 담당자정보변경 + public function chgAptDamdang() + { + try { + + $team = $this->request->getPost('team'); + $damdang = $this->request->getPost(index: 'damdang'); + + if (empty($team)) { + return $this->response->setJSON([ + 'code' => '9', + 'msg' => '팀정보 누락', + ]); + } + + if (empty($damdang)) { + return $this->response->setJSON([ + 'code' => '9', + 'msg' => '담당자정보 누락', + ]); + } + + + $rows = $this->request->getPost('rows'); + + $rows = json_decode($rows, true); + + if (count($rows) > 0) { + foreach ($rows as $row) { + $params = [ + $team, + $damdang, + $row['rcpt_no'], + ]; + + + // UPDATE apt_result + $this->model->updateAptDamdang($params); + + } + } else { + return $this->response->setJSON([ + 'code' => '9', + 'msg' => '저장데이터 누락', + ]); + } + + return $this->response->setJSON([ + 'code' => '0', + 'msg' => 'success' + ]); + + + } catch (\Exception $e) { + return $this->response->setJSON([ + 'code' => '9', + 'msg' => $e->getMessage(), + ]); + } + } + + + // 파일업로드(평면도) + public function uploadFile() + { + $lib = new MyUpload(); + + try { + + $rcpt_no = $this->request->getPost('rcpt_no'); + $files = $this->request->getFiles(); + $uploadPath = "/upload/apt_file/" . $rcpt_no . "/"; + + + if (!isset($rcpt_no)) { + return $this->response->setJSON([ + 'success' => false, + 'msg' => '접수번호 누락' + ]); + } + + if (!isset($files['file'])) { + return $this->response->setJSON([ + 'success' => false, + 'msg' => '파일 없음' + ]); + } + + + $arrUploadfile = []; + $file = $files['file']; + if ($file->isValid() && !$file->hasMoved()) { + $uploadData = $lib->do_upload2($file, $uploadPath); + + if ($uploadData !== false) { + $arrUploadfile[] = $uploadData; + } + } + + $gps_lat = null; + $gps_lon = null; + $camDate = null; + // print_r($arrUploadfile); + // exit; + if (!empty($arrUploadfile)) { + foreach ($arrUploadfile as $key => $uploadFile) { + $object_storage_url = $uploadFile['object_storage_url']; + $arrExifData = @exif_read_data($object_storage_url); + if (!empty($arrExifData)) { + $notFound = "Unavailable"; + if (@array_key_exists('DateTime', $arrExifData)) { + $camDate = $arrExifData['DateTime']; + } else { + $camDate = $notFound; + } + + $imageMetaData = $camDate; + $camDate = substr(str_replace(':', '-', $camDate), 0, 10); + + $arrGPS = $arrExifData['GPS'] ?? null; + + if (empty($arrGPS)) { // GPS 섹션이 없으면, 개별 키로도 체크 + if (!empty($arrExifData['GPSLongitude']) && !empty($arrExifData['GPSLatitude'])) { + $arrGPS = [ + 'GPSLongitude' => $arrExifData['GPSLongitude'], + 'GPSLatitude' => $arrExifData['GPSLatitude'], + ]; + } + } + + if ( + !empty($arrGPS) + && !empty($arrGPS['GPSLongitude']) + && !empty($arrGPS['GPSLatitude']) + && is_array($arrGPS['GPSLongitude']) + && is_array($arrGPS['GPSLatitude']) + ) { //GPS 정보가 있다면 + if (@array_key_exists('GPSLongitude', $arrGPS) && (@array_key_exists('GPSLatitude', $arrGPS))) { + list($temp_d1, $temp_d2) = sscanf($arrGPS["GPSLatitude"][0], "%d/%d"); //문자->숫자로 계산 + $gps_lat_d = $temp_d1 / $temp_d2; + list($temp_d1, $temp_d2) = sscanf($arrGPS["GPSLatitude"][1], "%d/%d"); + $gps_lat_m = $temp_d1 / $temp_d2; + list($temp_d1, $temp_d2) = sscanf($arrGPS["GPSLatitude"][2], "%d/%d"); + $gps_lat_s = $temp_d1 / $temp_d2; + + list($temp_d1, $temp_d2) = sscanf($arrGPS["GPSLongitude"][0], "%d/%d"); //문자->숫자로 계산 + $gps_lon_d = $temp_d1 / $temp_d2; + list($temp_d1, $temp_d2) = sscanf($arrGPS["GPSLongitude"][1], "%d/%d"); + $gps_lon_m = $temp_d1 / $temp_d2; + list($temp_d1, $temp_d2) = sscanf($arrGPS["GPSLongitude"][2], "%d/%d"); + $gps_lon_s = $temp_d1 / $temp_d2; + + $gps_lat = $gps_lat_d + $gps_lat_m / 60 + $gps_lat_s / 3600; //도분초를 도로 변환 + $gps_lon = $gps_lon_d + $gps_lon_m / 60 + $gps_lon_s / 3600; + } + } + + } + + $base = $uploadFile['base_name']; // xxxx + $dir = rtrim(dirname($uploadFile['object_key']), '/'); // upload/apt_file/2 + $thumbKey = $dir . '/' . $base . '_thumb.jpg'; + + $imageDataBlob = file_get_contents($object_storage_url); + $im = new \Imagick(); + $im->readImageBlob($imageDataBlob); + $im->thumbnailImage(105, 80, false); + $thumb_im = $im->getImageBlob(); + // 썸네일 s3 전송 + $lib->upload_object_storage_imagick2($thumbKey, $thumb_im); + + + /** + * 파일업로드 내용 저장 + * rcpt_no, pho_lati, pho_long, filenm, filenm_up, file_path, thumb_path, thumb_nm, cloud_upload_yn + * + */ + $uploadParam = [ + 'rcpt_no' => $rcpt_no, // 접수번호 + 'gps_lat' => $gps_lat, // latitude + 'gps_lon' => $gps_lon, // longitude + 'origin_name' => $uploadFile['origin_name'], // 원본파일명 + 'file_name' => $uploadFile['file_name'], // 저장파일명 + 'file_ext' => '.' . $uploadFile['ext'], // 파일확장자 + 'upload_path' => $uploadPath, // 저장경로 + 'thumb_name' => $base . '_thumb.jpg', + 'cam_date' => $camDate, // 촬영일 + ]; + + // INSERT INTO apt_ground_photo + $res = $this->model->saveImg($uploadParam); + log_message('debug', 'apt_ground_file :: rcpt_no : ' . $rcpt_no . ', fileName : ' . $uploadFile['file_name']); + + } + } + + return $this->response->setJSON([ + 'code' => '0', + 'msg' => 'success' + ]); + + } catch (\Exception $e) { + return $this->response->setJSON([ + 'code' => '9', + 'msg' => $e->getMessage(), + ]); + } + } +} \ No newline at end of file diff --git a/app/Models/article/AptModel.php b/app/Models/article/AptModel.php index 20d8bc7..4704df6 100644 --- a/app/Models/article/AptModel.php +++ b/app/Models/article/AptModel.php @@ -203,6 +203,19 @@ class AptModel extends Model $sql .= "AND a.dong_cnt <= {$data['dong_cnt2']} "; } + // 담당자 + if (!empty($data['damdang'])) { + $sql .= "AND a.charger = '{$data['damdang']}' "; + } else { + if (!empty($data['team'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['team']}') "; + } else { + if (!empty($data['bonbu'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['bonbu']}') "; + } + } + } + // 진행상태 if (!empty($data['stat']) && is_array($data['stat'])) { $statList = "'" . implode("','", $data['stat']) . "'"; @@ -296,6 +309,19 @@ class AptModel extends Model $sql .= "AND a.dong_cnt <= {$data['dong_cnt2']} "; } + // 담당자 + if (!empty($data['damdang'])) { + $sql .= "AND a.charger = '{$data['damdang']}' "; + } else { + if (!empty($data['team'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['team']}') "; + } else { + if (!empty($data['bonbu'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['bonbu']}') "; + } + } + } + // 진행상태 if (!empty($data['stat']) && is_array($data['stat'])) { $statList = "'" . implode("','", $data['stat']) . "'"; @@ -391,6 +417,19 @@ class AptModel extends Model $sql .= "AND a.dong_cnt <= {$data['dong_cnt2']} "; } + // 담당자 + if (!empty($data['damdang'])) { + $sql .= "AND a.charger = '{$data['damdang']}' "; + } else { + if (!empty($data['team'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['team']}') "; + } else { + if (!empty($data['bonbu'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['bonbu']}') "; + } + } + } + // 진행상태 if (!empty($data['stat']) && is_array($data['stat'])) { $statList = "'" . implode("','", $data['stat']) . "'"; @@ -491,6 +530,19 @@ class AptModel extends Model $sql .= "AND a.dong_cnt <= {$data['dong_cnt2']} "; } + // 담당자 + if (!empty($data['damdang'])) { + $sql .= "AND a.charger = '{$data['damdang']}' "; + } else { + if (!empty($data['team'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['team']}') "; + } else { + if (!empty($data['bonbu'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['bonbu']}') "; + } + } + } + // 진행상태 if (!empty($data['stat']) && is_array($data['stat'])) { $statList = "'" . implode("','", $data['stat']) . "'"; @@ -729,6 +781,19 @@ class AptModel extends Model $sql .= "AND a.dong_cnt <= {$data['dong_cnt2']} "; } + // 담당자 + if (!empty($data['damdang'])) { + $sql .= "AND a.charger = '{$data['damdang']}' "; + } else { + if (!empty($data['team'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['team']}') "; + } else { + if (!empty($data['bonbu'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['bonbu']}') "; + } + } + } + // 진행상태 if (!empty($data['stat']) && is_array($data['stat'])) { $statList = "'" . implode("','", $data['stat']) . "'"; diff --git a/app/Models/article/GroundModel.php b/app/Models/article/GroundModel.php new file mode 100644 index 0000000..b7f8955 --- /dev/null +++ b/app/Models/article/GroundModel.php @@ -0,0 +1,651 @@ +db->query($sql, [$gugun]); + + } else if (!empty($sido)) { + $chk_sido = substr($sido, '0', '2'); + + if ($chk_sido === '36') { + $sido = substr($sido, '0', '4'); + $sql = "SELECT a.region_cd, TRIM(REPLACE(a.region_nm, b.region_nm, '')) region_nm " . + "FROM region_codes a " . + "LEFT JOIN region_codes b ON b.region_cd = CONCAT(SUBSTR(a.region_cd,1,4),'000000') " . + "WHERE a.region_cd LIKE concat(?, '%') " . + "AND a.region_cd NOT LIKE '%000000' " . + "AND a.region_cd LIKE '%00' " . + "AND a.use_yn = 'Y' " . + "AND EXISTS (SELECT 'x' FROM region_codes c WHERE c.region_cd LIKE CONCAT(SUBSTR(a.region_cd,1,5),'%') AND c.region_cd > CONCAT(SUBSTR(a.region_cd,1,5),'00000')) " . + "ORDER BY a.region_nm ASC"; + } else { + $sido = substr($sido, '0', '2'); + $sql = "SELECT a.region_cd, TRIM(REPLACE(a.region_nm, b.region_nm, '')) region_nm" . + " FROM region_codes a" . + " LEFT JOIN region_codes b ON b.region_cd = CONCAT(SUBSTR(a.region_cd,1,2),'00000000')" . + " WHERE a.region_cd LIKE concat(?, '%')" . + " AND a.region_cd NOT LIKE '%00000000'" . + " AND a.region_cd LIKE '%00000'" . + " AND a.use_yn = 'Y'" . + " AND EXISTS (SELECT 'x' FROM region_codes c WHERE c.region_cd LIKE CONCAT(SUBSTR(a.region_cd,1,5),'%') AND c.region_cd > CONCAT(SUBSTR(a.region_cd,1,5),'00000'))" . + " ORDER BY a.region_nm ASC"; + } + + $query = $this->db->query($sql, [$sido]); + } else { + $sql = "SELECT a.region_cd, a.region_nm " . + "FROM region_codes a " . + "WHERE (a.region_cd LIKE '%00000000' " . + "AND a.use_yn = 'Y') " . + "OR region_cd = 3611000000;"; + + $query = $this->db->query($sql); + } + + + return $query->getResultArray(); + } + + + // 소속본부조회 + public function getBonbuList() + { + $sql = "SELECT dept_sq, pdept_sq, dept_nm, dept_desc, dept_head, use_yn, depth, insert_tm, insert_usr, update_tm, update_usr, lft, rgt" . + " FROM departments" . + " WHERE depth = 1" . + " AND use_yn = 'Y'" . + " ORDER BY lft"; + + $query = $this->db->query($sql); + + + return $query->getResultArray(); + } + + // 소속팀 조회 + public function getTeamList() + { + $sql = "SELECT dept_sq, pdept_sq, dept_nm" . + " FROM departments" . + " WHERE depth = 2" . + " AND use_yn = 'Y'" . + " ORDER BY dept_nm"; + + $query = $this->db->query($sql); + + + return $query->getResultArray(); + } + + // 유저 조회 + public function getUserList() + { + $sql = "SELECT + a.usr_sq, a.usr_id, a.usr_nm, a.dept_sq + FROM users a + WHERE + a.usr_level IN ('3','4','40','5','50','6','60','61','62','7','8','70') + AND a.use_yn = 'Y' + AND EXISTS ( + SELECT 'x' FROM departments a1 INNER JOIN departments a2 ON a2.lft BETWEEN a1.lft AND a1.rgt AND a2.use_yn = 'Y' + WHERE 1=1 AND a2.dept_sq = a.dept_sq AND a1.use_yn = 'Y' + ) + ORDER BY a.usr_level DESC, a.usr_nm ASC "; + + $query = $this->db->query($sql); + + return $query->getResultArray(); + } + + public function getTotalCount($data) + { + $sql = "SELECT COUNT(*) AS cnt + FROM apt_ground AS a + LEFT JOIN apt_ground_photo AS gp ON gp.pho_no = (SELECT p.pho_no + FROM apt_ground_photo p + WHERE p.rcpt_no = a.rcpt_no + ORDER BY p.pho_no DESC + LIMIT 1) + LEFT JOIN users b ON a.charger = b.usr_id + LEFT JOIN region_codes e ON a.region_cd = e.region_cd + LEFT JOIN departments i ON a.dept_sq = i.dept_sq + LEFT JOIN codes f ON a.write_complete_yn = f.cd AND f.category = 'PHO_YN' + LEFT JOIN codes h ON a.vdo_up_ynx = h.cd AND h.category = 'VDO_YN' + LEFT JOIN codes g ON a.apt_step = g.cd AND g.category = 'APT_STEP' + LEFT JOIN apt_history j ON a.rcpt_no = j.rcpt_no AND j.changed_detail = 'C2' AND NOT EXISTS (SELECT 'x' FROM apt_history WHERE changed_detail LIKE 'A%' AND rcpt_no = j.rcpt_no) "; + + $sql .= "WHERE 1=1 "; + + if (!empty($data['hscp_no'])) { + $sql .= "AND a.hscp_no LIKE CONCAT('%', '{$data['hscp_no']}', '%') "; + } else { + + if (!empty($data['hscp_no'])) { + $sql .= "AND a.part_no LIKE CONCAT('%', '{$data['part_no']}', '%') "; + } + + if (!empty($data['rcpt_hscp_nm'])) { + $sql .= "AND a.rcpt_hscp_nm LIKE CONCAT('%', '{$data['rcpt_hscp_nm']}', '%') "; + } + + // 법정동코드로 지역구분 + if (!empty($data['srcDong'])) { + $sql .= "AND a.region_cd = '{$data['srcDong']}' "; + } else { + if (!empty($data['srcGugun'])) { + $str_gugun = substr($data['srcGugun'], '0', '2'); + if ($str_gugun == '36') { //세종시는 군구가 없고 바로 동이라서 예외 + $sql .= "AND a.region_cd = '{$data['srcGugun']}' "; + } else { + $gugunPrefix = substr($data['srcGugun'], '0', '5'); + $sql .= "AND a.region_cd LIKE '{$gugunPrefix}%' "; + } + } else { + if (!empty($data['srcSido'])) { + $sidoPrefix = substr($data['srcSido'], '0', '2'); + $sql .= "AND a.region_cd LIKE '{$sidoPrefix}%' "; + } + } + } + + //촬영일자 == 단지정보작성완료 일자 + if (!empty($data['sdate'])) { + $sql .= "AND b.write_complete_tm >= '{$data['sdate']} 00:00:00' "; + } + + if (!empty($data['edate'])) { + $sql .= "AND b.write_complete_tm <= '{$data['edate']} 00:00:00' "; + } + + // 담당자 + if (!empty($data['damdang'])) { + $sql .= "AND a.charger = '{$data['damdang']}' "; + } else { + if (!empty($data['team'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['team']}') "; + } else { + if (!empty($data['bonbu'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['bonbu']}') "; + } + } + } + + // 진행상태 + if (!empty($data['stat']) && is_array($data['stat'])) { + $statList = "'" . implode("','", $data['stat']) . "'"; + $sql .= " AND a.apt_step IN ({$statList}) "; + } + + } + + + $query = $this->db->query($sql); + + + return $query->getRow()->cnt; + } + + public function getAptLists($start, $end, $data) + { + $sql = "SELECT + a.rcpt_no, a.hscp_no, a.part_no, a.addr, a.addr2, a.rcpt_hscp_nm, a.pyeong_cnt, a.households_cnt, a.dong_cnt, a.rcpt_hscp_nm, + a.apt_cate_nm, a.rcpt_x, a.rcpt_y, + a.charger, a.memo, a.write_complete_yn, a.gpho_up_yn, a.vdo_up_ynx, a.apt_step, a.video_target, a.all_no_pho, a.send_end_tm, + a.dept_sq, a.vdo_up_tm, a.not_vdo_tm, a.check_tm, a.write_complete_tm, + b.usr_nm, i.dept_nm, j.changed_tm, gp.pho_no, gp.filenm, gp.filenm_up, gp.file_path, gp.file_ext, gp.cloud_upload_yn, gp.insert_tm + FROM + apt_ground AS a + LEFT JOIN apt_ground_photo AS gp ON gp.pho_no = (SELECT p.pho_no + FROM apt_ground_photo p + WHERE p.rcpt_no = a.rcpt_no + ORDER BY p.pho_no DESC + LIMIT 1) + LEFT JOIN users b ON a.charger = b.usr_id + LEFT JOIN region_codes e ON a.region_cd = e.region_cd + LEFT JOIN departments i ON a.dept_sq = i.dept_sq + LEFT JOIN codes f ON a.write_complete_yn = f.cd AND f.category = 'PHO_YN' + LEFT JOIN codes h ON a.vdo_up_ynx = h.cd AND h.category = 'VDO_YN' + LEFT JOIN codes g ON a.apt_step = g.cd AND g.category = 'APT_STEP' + LEFT JOIN apt_history j ON a.rcpt_no = j.rcpt_no AND j.changed_detail = 'C2' AND NOT EXISTS (SELECT 'x' FROM apt_history WHERE changed_detail LIKE 'A%' AND rcpt_no = j.rcpt_no) "; + + $sql .= "WHERE 1=1 "; + + if (!empty($data['hscp_no'])) { + $sql .= "AND a.hscp_no LIKE CONCAT('%', '{$data['hscp_no']}', '%') "; + } else { + + if (!empty($data['hscp_no'])) { + $sql .= "AND a.part_no LIKE CONCAT('%', '{$data['part_no']}', '%') "; + } + + if (!empty($data['rcpt_hscp_nm'])) { + $sql .= "AND a.rcpt_hscp_nm LIKE CONCAT('%', '{$data['rcpt_hscp_nm']}', '%') "; + } + + // 법정동코드로 지역구분 + if (!empty($data['srcDong'])) { + $sql .= "AND a.region_cd = '{$data['srcDong']}' "; + } else { + if (!empty($data['srcGugun'])) { + $str_gugun = substr($data['srcGugun'], '0', '2'); + if ($str_gugun == '36') { //세종시는 군구가 없고 바로 동이라서 예외 + $sql .= "AND a.region_cd = '{$data['srcGugun']}' "; + } else { + $gugunPrefix = substr($data['srcGugun'], '0', '5'); + $sql .= "AND a.region_cd LIKE '{$gugunPrefix}%' "; + } + } else { + if (!empty($data['srcSido'])) { + $sidoPrefix = substr($data['srcSido'], '0', '2'); + $sql .= "AND a.region_cd LIKE '{$sidoPrefix}%' "; + } + } + } + + //촬영일자 == 단지정보작성완료 일자 + if (!empty($data['sdate'])) { + $sql .= "AND b.write_complete_tm >= '{$data['sdate']} 00:00:00' "; + } + + if (!empty($data['edate'])) { + $sql .= "AND b.write_complete_tm <= '{$data['edate']} 00:00:00' "; + } + + if (!empty($data['damdang'])) { + $sql .= "AND a.charger = '{$data['damdang']}' "; + } else { + if (!empty($data['team'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['team']}') "; + } else { + if (!empty($data['bonbu'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['bonbu']}') "; + } + } + } + + // 진행상태 + if (!empty($data['stat']) && is_array($data['stat'])) { + $statList = "'" . implode("','", $data['stat']) . "'"; + $sql .= " AND a.apt_step IN ({$statList}) "; + } + + } + + + $sql .= "LIMIT {$start}, {$end}"; + + $query = $this->db->query($sql); + + return $query->getResultArray(); + } + + + // 조직별 통계 + public function getDeptStatistics($data) + { + $sql = "SELECT + a.dept_sq, IFNULL(f.dept_nm, '') AS bonbu_nm, IFNULL(i.dept_nm, '미지정') AS team_nm, COUNT(a.dept_sq) as cnt + FROM + apt_ground a + LEFT JOIN apt_ground_photo AS gp ON gp.pho_no = (SELECT p.pho_no + FROM apt_ground_photo p + WHERE p.rcpt_no = a.rcpt_no + ORDER BY p.pho_no DESC + LIMIT 1) + LEFT JOIN users c ON a.charger = c.usr_id + LEFT JOIN region_codes e ON a.region_cd = e.region_cd + LEFT JOIN departments i ON a.dept_sq = i.dept_sq + LEFT JOIN departments f ON i.dept_sq = f.dept_sq "; + + $sql .= "WHERE 1=1 "; + + if (!empty($data['hscp_no'])) { + $sql .= "AND a.hscp_no LIKE CONCAT('%', '{$data['hscp_no']}', '%') "; + } else { + + if (!empty($data['hscp_no'])) { + $sql .= "AND a.part_no LIKE CONCAT('%', '{$data['part_no']}', '%') "; + } + + if (!empty($data['rcpt_hscp_nm'])) { + $sql .= "AND a.rcpt_hscp_nm LIKE CONCAT('%', '{$data['rcpt_hscp_nm']}', '%') "; + } + + // 법정동코드로 지역구분 + if (!empty($data['srcDong'])) { + $sql .= "AND a.region_cd = '{$data['srcDong']}' "; + } else { + if (!empty($data['srcGugun'])) { + $str_gugun = substr($data['srcGugun'], '0', '2'); + if ($str_gugun == '36') { //세종시는 군구가 없고 바로 동이라서 예외 + $sql .= "AND a.region_cd = '{$data['srcGugun']}' "; + } else { + $gugunPrefix = substr($data['srcGugun'], '0', '5'); + $sql .= "AND a.region_cd LIKE '{$gugunPrefix}%' "; + } + } else { + if (!empty($data['srcSido'])) { + $sidoPrefix = substr($data['srcSido'], '0', '2'); + $sql .= "AND a.region_cd LIKE '{$sidoPrefix}%' "; + } + } + } + + //촬영일자 == 단지정보작성완료 일자 + if (!empty($data['sdate'])) { + $sql .= "AND b.write_complete_tm >= '{$data['sdate']} 00:00:00' "; + } + + if (!empty($data['edate'])) { + $sql .= "AND b.write_complete_tm <= '{$data['edate']} 00:00:00' "; + } + + if (!empty($data['damdang'])) { + $sql .= "AND a.charger = '{$data['damdang']}' "; + } else { + if (!empty($data['team'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['team']}') "; + } else { + if (!empty($data['bonbu'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['bonbu']}') "; + } + } + } + + // 진행상태 + if (!empty($data['stat']) && is_array($data['stat'])) { + $statList = "'" . implode("','", $data['stat']) . "'"; + $sql .= " AND a.apt_step IN ({$statList}) "; + } + + } + + $sql .= "GROUP BY a.dept_sq ORDER BY bonbu_nm ASC, team_nm ASC "; + + $query = $this->db->query($sql); + + return $query->getResultArray(); + } + + // 지역별 통계 + public function getStatistics($data) + { + $sql = "SELECT + a.addr AS addr, COUNT(a.addr) AS cnt + FROM + apt_ground a + LEFT JOIN apt_ground_photo gp ON a.rcpt_no = gp.rcpt_no + LEFT JOIN users c ON a.charger = c.usr_id + LEFT JOIN region_codes e ON a.region_cd = e.region_cd + LEFT JOIN departments i ON a.dept_sq = i.dept_sq "; + + $sql .= "WHERE 1=1 "; + + if (!empty($data['hscp_no'])) { + $sql .= "AND a.hscp_no LIKE CONCAT('%', '{$data['hscp_no']}', '%') "; + } else { + + if (!empty($data['hscp_no'])) { + $sql .= "AND a.part_no LIKE CONCAT('%', '{$data['part_no']}', '%') "; + } + + if (!empty($data['rcpt_hscp_nm'])) { + $sql .= "AND a.rcpt_hscp_nm LIKE CONCAT('%', '{$data['rcpt_hscp_nm']}', '%') "; + } + + // 법정동코드로 지역구분 + if (!empty($data['srcDong'])) { + $sql .= "AND a.region_cd = '{$data['srcDong']}' "; + } else { + if (!empty($data['srcGugun'])) { + $str_gugun = substr($data['srcGugun'], '0', '2'); + if ($str_gugun == '36') { //세종시는 군구가 없고 바로 동이라서 예외 + $sql .= "AND a.region_cd = '{$data['srcGugun']}' "; + } else { + $gugunPrefix = substr($data['srcGugun'], '0', '5'); + $sql .= "AND a.region_cd LIKE '{$gugunPrefix}%' "; + } + } else { + if (!empty($data['srcSido'])) { + $sidoPrefix = substr($data['srcSido'], '0', '2'); + $sql .= "AND a.region_cd LIKE '{$sidoPrefix}%' "; + } + } + } + + //촬영일자 == 단지정보작성완료 일자 + if (!empty($data['sdate'])) { + $sql .= "AND b.write_complete_tm >= '{$data['sdate']} 00:00:00' "; + } + + if (!empty($data['edate'])) { + $sql .= "AND b.write_complete_tm <= '{$data['edate']} 00:00:00' "; + } + + if (!empty($data['damdang'])) { + $sql .= "AND a.charger = '{$data['damdang']}' "; + } else { + if (!empty($data['team'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['team']}') "; + } else { + if (!empty($data['bonbu'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['bonbu']}') "; + } + } + } + + // 진행상태 + if (!empty($data['stat']) && is_array($data['stat'])) { + $statList = "'" . implode("','", $data['stat']) . "'"; + $sql .= " AND a.apt_step IN ({$statList}) "; + } + + } + + $sql .= "GROUP BY a.addr ORDER BY a.addr ASC "; + + $query = $this->db->query($sql); + + return $query->getResultArray(); + } + + // 엑셀 다운로드 + public function getExcelList($data) + { + $sql = "SELECT + a.part_no AS '구분코드', + a.hscp_no AS '단지코드', + SUBSTRING_INDEX(a.addr, ' ', 1) AS '시도', + SUBSTRING_INDEX(a.addr, ' ', 2) AS '시군구', + SUBSTRING_INDEX(a.addr, ' ', 3) AS '읍면동', + a.addr2 AS '지번', + a.rcpt_hscp_nm AS '단지명', + a.apt_cate_nm AS '단지유형', + a.pyeong_cnt AS '평형', + i.dept_nm AS '방문팀', + b.usr_nm AS '담당자', + gp.insert_tm AS '촬영일자', + a.rcpt_x AS '단지X좌표', + a.rcpt_y AS '단지Y좌표' + FROM + apt_ground AS a + LEFT JOIN apt_ground_photo AS gp ON gp.pho_no = (SELECT p.pho_no + FROM apt_ground_photo p + WHERE p.rcpt_no = a.rcpt_no + ORDER BY p.pho_no DESC + LIMIT 1) + LEFT JOIN users b ON a.charger = b.usr_id + LEFT JOIN region_codes e ON a.region_cd = e.region_cd + LEFT JOIN departments i ON a.dept_sq = i.dept_sq + LEFT JOIN codes f ON a.write_complete_yn = f.cd AND f.category = 'PHO_YN' + LEFT JOIN codes h ON a.vdo_up_ynx = h.cd AND h.category = 'VDO_YN' + LEFT JOIN codes g ON a.apt_step = g.cd AND g.category = 'APT_STEP' + LEFT JOIN apt_history j ON a.rcpt_no = j.rcpt_no AND j.changed_detail = 'C2' AND NOT EXISTS (SELECT 'x' FROM apt_history WHERE changed_detail LIKE 'A%' AND rcpt_no = j.rcpt_no) "; + + $sql .= "WHERE 1=1 "; + + if (!empty($data['hscp_no'])) { + $sql .= "AND a.hscp_no LIKE CONCAT('%', '{$data['hscp_no']}', '%') "; + } else { + + if (!empty($data['hscp_no'])) { + $sql .= "AND a.part_no LIKE CONCAT('%', '{$data['part_no']}', '%') "; + } + + if (!empty($data['rcpt_hscp_nm'])) { + $sql .= "AND a.rcpt_hscp_nm LIKE CONCAT('%', '{$data['rcpt_hscp_nm']}', '%') "; + } + + // 법정동코드로 지역구분 + if (!empty($data['srcDong'])) { + $sql .= "AND a.region_cd = '{$data['srcDong']}' "; + } else { + if (!empty($data['srcGugun'])) { + $str_gugun = substr($data['srcGugun'], '0', '2'); + if ($str_gugun == '36') { //세종시는 군구가 없고 바로 동이라서 예외 + $sql .= "AND a.region_cd = '{$data['srcGugun']}' "; + } else { + $gugunPrefix = substr($data['srcGugun'], '0', '5'); + $sql .= "AND a.region_cd LIKE '{$gugunPrefix}%' "; + } + } else { + if (!empty($data['srcSido'])) { + $sidoPrefix = substr($data['srcSido'], '0', '2'); + $sql .= "AND a.region_cd LIKE '{$sidoPrefix}%' "; + } + } + } + + //촬영일자 == 단지정보작성완료 일자 + if (!empty($data['sdate'])) { + $sql .= "AND b.write_complete_tm >= '{$data['sdate']} 00:00:00' "; + } + + if (!empty($data['edate'])) { + $sql .= "AND b.write_complete_tm <= '{$data['edate']} 00:00:00' "; + } + + if (!empty($data['damdang'])) { + $sql .= "AND a.charger = '{$data['damdang']}' "; + } else { + if (!empty($data['team'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['team']}') "; + } else { + if (!empty($data['bonbu'])) { + $sql .= "AND a.dept_sq IN (SELECT h.dept_sq FROM departments i INNER JOIN departments h ON h.lft >= i.lft AND h.lft <= i.rgt WHERE i.dept_sq = '{$data['bonbu']}') "; + } + } + } + + // 진행상태 + if (!empty($data['stat']) && is_array($data['stat'])) { + $statList = "'" . implode("','", $data['stat']) . "'"; + $sql .= " AND a.apt_step IN ({$statList}) "; + } + + } + + $query = $this->db->query($sql); + + return $query->getResultArray(); + } + + // 지도 마커 조회 + public function getDeptMapList($deptSq) + { + $sql = "SELECT + a.hscp_no, a.rcpt_hscp_nm, a.rcpt_no, a.dept_sq, a.rcpt_x, a.rcpt_y + , a.charger, i.dept_nm, i.pdept_sq, i.dept_desc, c.usr_nm, c.usr_id + ,(SELECT pdept_sq FROM departments WHERE dept_sq = i.dept_sq) bonbu + FROM + apt_ground a + LEFT JOIN apt_ground_photo AS gp ON gp.pho_no = (SELECT p.pho_no + FROM apt_ground_photo p + WHERE p.rcpt_no = a.rcpt_no + ORDER BY p.pho_no DESC + LIMIT 1) + LEFT JOIN users c ON a.charger = c.usr_id + LEFT JOIN region_codes e ON a.region_cd = e.region_cd + LEFT JOIN departments i ON a.dept_sq = i.dept_sq + LEFT JOIN codes f ON a.write_complete_yn = f.cd AND f.category = 'PHO_YN' + LEFT JOIN codes h ON a.vdo_up_ynx = h.cd AND h.category = 'VDO_YN' + LEFT JOIN codes g ON a.apt_step = g.cd AND g.category = 'APT_STEP' "; + + $sql .= "WHERE 1=1 "; + + if (!empty($deptSq)) { + $datas = "'" . implode("','", $deptSq) . "'"; + $sql .= " AND a.dept_sq IN ({$datas}) "; + } + + $query = $this->db->query($sql); + + return $query->getResultArray(); + } + + // 아파트 담당자 정보변경 + public function updateAptDamdang($params) + { + $sql = "UPDATE apt_ground SET "; + $sql .= "dept_sq = ?, charger = ? "; + $sql .= "WHERE rcpt_no = ? "; + + $this->db->query($sql, $params); + + + if ($this->db->transStatus() === false) { + return [ + 'success' => false, + 'msg' => '저장실패', + ]; + } + + // 성공 + return [ + 'success' => true, + ]; + } + + + // 평면도 정보 저장 + public function saveImg($data) + { + $sql = "INSERT INTO apt_ground_photo + (rcpt_no, filenm, filenm_up, file_ext, insert_tm, file_path, thumb_path, thumb_nm, use_yn, cloud_upload_yn) + VALUES + ({$data['rcpt_no']}, '{$data['origin_name']}', '{$data['file_name']}', '{$data['file_ext']}', NOW(), '{$data['upload_path']}', '{$data['upload_path']}' + , '{$data['thumb_name']}', 'Y', 'Y') + "; + + + if ($this->db->query($sql) === false) { + return [ + 'success' => false, + 'msg' => '저장실패', + ]; + } + + return [ + 'success' => true, + ]; + } +} \ No newline at end of file diff --git a/app/Views/pages/article/lists.php b/app/Views/pages/article/lists.php index 9b15d45..ec87d6c 100644 --- a/app/Views/pages/article/lists.php +++ b/app/Views/pages/article/lists.php @@ -191,37 +191,16 @@ onclick="progressStatAll(this, this.checked);"> - -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
+ + +
+ + +
+ + @@ -536,7 +515,7 @@ // 이 팀이 현재 본부에 속한 팀인지 체크 if (String(userArr[i].dept_sq) === String(dept_sq)) { str += ` - + `; } } @@ -653,7 +632,7 @@ } }, "columnDefs": [ - { targets: 0, orderable: false, className: 'text-center' }, + { className: 'text-center', targets: '_all' }, { 'targets': '_all', "defaultContent": "" }, // { 'className': 'text-center', 'targets': [0, 2, 3, 4] }, ], diff --git a/app/Views/pages/article/lists2.php b/app/Views/pages/article/lists2.php new file mode 100644 index 0000000..e198b2f --- /dev/null +++ b/app/Views/pages/article/lists2.php @@ -0,0 +1,1289 @@ +extend('layouts/main') ?> + +section('content') ?> + + +

아파트 평면도 내역

+ +
+
+
+ +
+
+
+ + + + + +
+
+ +
+ + 단지코드/구분코드를 입력하면 다른 조건은 무시됩니다. + +
+ +
+ + +
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+
+ +
+ +
+ + ~ + +
+
+ + +
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ +
+ +
+
+ + +
+ + +
+ + +
+ + +
+
+ +
+ + +
+ +
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + +
+ + 관할본부방문담당배정건수
+
+
+ + +
+
+ +
+
+
+
+ + + + + + + + +
방문담당배정건수
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+ + + + + + + + +
+ +
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + +
+ + 진행상태구분코드단지코드주소단지명평형방문팀담당업로드미리보기다운로드
+
+
+
+ + +section('modals') ?> + + + + + +endSection() ?> + + + + + + + + +endSection() ?> \ No newline at end of file From ab39be960221c24c30e3da4e330b4c473b2c2a73 Mon Sep 17 00:00:00 2001 From: yangsh Date: Fri, 26 Dec 2025 18:02:57 +0900 Subject: [PATCH 05/10] =?UTF-8?q?=ED=8F=89=EB=A9=B4=EB=8F=84=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=ED=99=94=EB=A9=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Config/Routes.php | 1 + app/Controllers/Article/Ground.php | 19 +++++ app/Models/article/GroundModel.php | 30 +++++++ app/Views/pages/article/detail2.php | 128 ++++++++++++++++++++++++++++ app/Views/pages/article/lists2.php | 5 +- 5 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 app/Views/pages/article/detail2.php diff --git a/app/Config/Routes.php b/app/Config/Routes.php index ece01fe..e47dbc7 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -95,6 +95,7 @@ $routes->group('article', ['namespace' => 'App\Controllers\Article'], function ( // 아파트 평면도 $routes->get('apt/lists2', 'Ground::lists'); + $routes->get('apt/ground/detail/(:num)/(:num)', 'Ground::detail/$1/$2'); /** * 아파트 평면도 - API diff --git a/app/Controllers/Article/Ground.php b/app/Controllers/Article/Ground.php index db20cd4..79a820e 100644 --- a/app/Controllers/Article/Ground.php +++ b/app/Controllers/Article/Ground.php @@ -209,6 +209,25 @@ class Ground extends BaseController } + // 상세화면 + public function detail($rcpt_no, $hscp_no): string + { + + if ($rcpt_no == null || $hscp_no == null) { + throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); + } + + // 상세정보 + $apt = $this->model->getDetail($rcpt_no, $hscp_no); + + + return view("pages/article/detail2", [ + 'apt' => $apt, + ]); + } + + + // 파일업로드(평면도) public function uploadFile() { diff --git a/app/Models/article/GroundModel.php b/app/Models/article/GroundModel.php index b7f8955..cd51011 100644 --- a/app/Models/article/GroundModel.php +++ b/app/Models/article/GroundModel.php @@ -625,6 +625,35 @@ class GroundModel extends Model ]; } + // 상세정보 + public function getDetail($rcpt_no, $hscp_no) + { + $sql = "SELECT + a.rcpt_no, a.hscp_no, a.part_no, a.addr, a.addr2, a.rcpt_hscp_nm, a.move_ym, a.households_cnt, a.dong_cnt, a.pyeong_cnt, a.apt_cate_nm, a.region_cd, a.rcpt_x, a.rcpt_y + ,a.vdo_up_tm, DATE_FORMAT(a.vdo_up_tm, '%Y-%m-%d') as rdate_dt_vdo ,DATE_FORMAT(a.vdo_up_tm, '%H:%i:%s') as rdate_tm_vdo + ,a.check_tm, DATE_FORMAT(a.check_tm, '%Y-%m-%d') as rdate_dt_chk ,DATE_FORMAT(a.check_tm, '%H:%i:%s') as rdate_tm_chk + ,a.memo, a.note, a.video_target, a.vdo_up_ynx, a.not_vdo_reson, a.apt_step, a.check_yn, a.resend_yn, a.write_complete_yn, a.all_no_pho + ,a.write_complete_tm, DATE_FORMAT(a.write_complete_tm, '%Y-%m-%d') as rdate_dt_cmpl ,DATE_FORMAT(a.write_complete_tm, '%H:%i:%s') as rdate_tm_cmpl + ,a.charger, a.dept_sq ,(SELECT pdept_sq FROM departments WHERE dept_sq = a.dept_sq) bonbu + ,a.send_end_tm, a.supply_no_tm + ,d.pho_cate2, d.pho_explain, d.pho_up_nu + ,gp.filenm_up, gp.file_path, gp.insert_tm + FROM + apt_ground a + LEFT JOIN apt_category d ON a.rcpt_no = d.rcpt_no + LEFT JOIN apt_ground_photo gp ON gp.pho_no = (SELECT p.pho_no + FROM apt_ground_photo p + WHERE p.rcpt_no = a.rcpt_no + ORDER BY p.pho_no DESC + LIMIT 1) + + WHERE a.rcpt_no = {$rcpt_no} AND a.hscp_no = {$hscp_no} "; + + $query = $this->db->query($sql, [$rcpt_no]); + + return $query->getRowArray(); + } + // 평면도 정보 저장 public function saveImg($data) @@ -648,4 +677,5 @@ class GroundModel extends Model 'success' => true, ]; } + } \ No newline at end of file diff --git a/app/Views/pages/article/detail2.php b/app/Views/pages/article/detail2.php new file mode 100644 index 0000000..e34c064 --- /dev/null +++ b/app/Views/pages/article/detail2.php @@ -0,0 +1,128 @@ +extend('layouts/main') ?> + +section('content') ?> + + +
+
+ +
+
+
단지 정보
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
단지코드주소
단지명상세주소
입주년월총세대수
총동수메모 +
+
+ +
+
+ +
+
+
+
+
+
+ +
+ +endSection() ?> \ No newline at end of file diff --git a/app/Views/pages/article/lists2.php b/app/Views/pages/article/lists2.php index e198b2f..cb5d36c 100644 --- a/app/Views/pages/article/lists2.php +++ b/app/Views/pages/article/lists2.php @@ -673,8 +673,9 @@ const rowData = table.row(this).data(); if (!rowData) return; - // const id = rowData.rcpt_no; - // location.href = "/" + id; + const rcpt_no = rowData.rcpt_no; + const hscp_no = rowData.hscp_no; + location.href = "/" + rcpt_no + "/" + hscp_no; }); $('#btnSearch').on('click', function () { From e0672d325ca78540a6244865d42611491f90c19b Mon Sep 17 00:00:00 2001 From: yangsh Date: Mon, 29 Dec 2025 13:50:46 +0900 Subject: [PATCH 06/10] =?UTF-8?q?=EC=95=84=ED=8C=8C=ED=8A=B8=20=ED=8F=89?= =?UTF-8?q?=EB=A9=B4=EB=8F=84=20=EC=83=81=EC=84=B8=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Config/Routes.php | 5 + app/Controllers/Article/Ground.php | 141 ++++ app/Libraries/MyUpload.php | 20 + app/Models/article/GroundModel.php | 262 +++++++- app/Views/layouts/sidebar.php | 15 + app/Views/pages/article/detail2.php | 977 +++++++++++++++++++++++++++- app/Views/pages/article/lists2.php | 48 +- 7 files changed, 1432 insertions(+), 36 deletions(-) diff --git a/app/Config/Routes.php b/app/Config/Routes.php index e47dbc7..2c672c8 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -106,6 +106,11 @@ $routes->group('article', ['namespace' => 'App\Controllers\Article'], function ( $routes->post('apt/ground/uploadFile', 'Ground::uploadFile'); $routes->get('apt/ground/print', 'Ground::print'); + $routes->post('apt/ground/saveMemo', 'Ground::saveMemo'); + $routes->post('apt/ground/saveKeeper', 'Ground::saveKeeper'); + $routes->post('apt/ground/statusChange', 'Ground::statusChange'); + $routes->post('apt/ground/saveNote', 'Ground::saveNote'); + }); diff --git a/app/Controllers/Article/Ground.php b/app/Controllers/Article/Ground.php index 79a820e..beafaae 100644 --- a/app/Controllers/Article/Ground.php +++ b/app/Controllers/Article/Ground.php @@ -217,15 +217,156 @@ class Ground extends BaseController throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); } + $bonbu = $this->model->getBonbuList(); + $team = $this->model->getTeamList(); + $user = $this->model->getUserList(); + // 상세정보 $apt = $this->model->getDetail($rcpt_no, $hscp_no); + // 동일단지 + $rdata = $this->model->getDetailLists($rcpt_no, $hscp_no); + + // 변경이력 + $history = $this->model->getHistory($rcpt_no); return view("pages/article/detail2", [ + 'bonbu' => $bonbu, + 'team' => $team, + 'user' => $user, 'apt' => $apt, + 'rdata' => $rdata, + 'history' => $history, ]); } + // 메모저장 + public function saveMemo() + { + try { + + $data = [ + 'rcpt_no' => $this->request->getPost('rcpt_no'), + 'memo' => $this->request->getPost('memo'), + ]; + + // UPDATE apt_ground + $this->model->saveMemo($data); + + return $this->response->setJSON([ + 'code' => '0', + 'msg' => 'success' + ]); + + } catch (\Exception $e) { + return $this->response->setJSON([ + 'code' => '9', + 'msg' => $e->getMessage(), + ]); + } + } + + // 담당자 변경 + public function saveKeeper() + { + try { + + + $data = [ + 'rcpt_no' => $this->request->getPost('rcpt_no'), + 'bonbu' => $this->request->getPost('bonbu'), + 'team' => $this->request->getPost('team'), + 'user' => $this->request->getPost('user'), + ]; + + + // UPDATE apt_ground + $this->model->saveKeeper($data); + + + return $this->response->setJSON([ + 'code' => '0', + 'msg' => 'success' + ]); + + } catch (\Exception $e) { + return $this->response->setJSON([ + 'code' => '9', + 'msg' => $e->getMessage(), + ]); + } + } + + // 단지상태변경 + public function statusChange() + { + $lib = new MyUpload(); + + try { + + $type = $this->request->getPost('type'); + $rcpt_no = $this->request->getPost('rcpt_no'); + + + if ($type === "phoX") { + $apt = $this->model->getDetail($rcpt_no, ""); + + if (!empty($apt['pho_no'])) { + $path = $apt['file_path'] . "" . $apt['filenm_up']; + + $lib->deleteFile($path); + } + + + } + + // UPDATE apt_ground + $this->model->statusChange($rcpt_no, $type); + + + return $this->response->setJSON([ + 'code' => '0', + 'msg' => 'success' + ]); + + } catch (\Exception $e) { + return $this->response->setJSON([ + 'code' => '9', + 'msg' => $e->getMessage(), + ]); + } + } + + // 단지 특이사항 저장 + public function saveNote() + { + try { + + $rcpt_no = $this->request->getPost('rcpt_no'); + + + $data = [ + 'rcpt_no' => $rcpt_no, + 'note' => $this->request->getPost('note'), + ]; + + + // UPDATE apt_ground + $this->model->saveNote($data); + + + return $this->response->setJSON([ + 'code' => '0', + 'msg' => 'success' + ]); + + } catch (\Exception $e) { + return $this->response->setJSON([ + 'code' => '9', + 'msg' => $e->getMessage(), + ]); + } + } // 파일업로드(평면도) diff --git a/app/Libraries/MyUpload.php b/app/Libraries/MyUpload.php index 5615b93..00e566d 100644 --- a/app/Libraries/MyUpload.php +++ b/app/Libraries/MyUpload.php @@ -110,6 +110,26 @@ class MyUpload return $this->s3_data; } + /** + * + */ + public function deleteFile($key) + { + $s3Client = $this->makeS3Client(); + + try { + + $s3Client->deleteObject([ + 'Bucket' => NCLOUD_S3_BUCKET, + 'Key' => ltrim($key, '/'), + ]); + + return true; + } catch (\Throwable $e) { + return false; + } + } + /** * S3(NCLOUD) 파일 업로드 * 추가일 2025.12.24 diff --git a/app/Models/article/GroundModel.php b/app/Models/article/GroundModel.php index cd51011..cd57b5a 100644 --- a/app/Models/article/GroundModel.php +++ b/app/Models/article/GroundModel.php @@ -637,7 +637,7 @@ class GroundModel extends Model ,a.charger, a.dept_sq ,(SELECT pdept_sq FROM departments WHERE dept_sq = a.dept_sq) bonbu ,a.send_end_tm, a.supply_no_tm ,d.pho_cate2, d.pho_explain, d.pho_up_nu - ,gp.filenm_up, gp.file_path, gp.insert_tm + ,gp.pho_no ,gp.filenm_up, gp.file_path, gp.thumb_nm, gp.cloud_upload_yn, gp.insert_tm FROM apt_ground a LEFT JOIN apt_category d ON a.rcpt_no = d.rcpt_no @@ -645,19 +645,246 @@ class GroundModel extends Model FROM apt_ground_photo p WHERE p.rcpt_no = a.rcpt_no ORDER BY p.pho_no DESC - LIMIT 1) + LIMIT 1) AND gp.use_yn = 'Y' - WHERE a.rcpt_no = {$rcpt_no} AND a.hscp_no = {$hscp_no} "; + WHERE a.rcpt_no = {$rcpt_no} "; + + if (!empty($hscp_no)) { + $sql .= "AND a.hscp_no = {$hscp_no} "; + } + $query = $this->db->query($sql, [$rcpt_no]); return $query->getRowArray(); } + // 동일단지 + public function getDetailLists($rcpt_no, $hscp_no) + { + $sql = "SELECT + a.rcpt_no, a.hscp_no, a.part_no, a.addr, a.addr2, a.rcpt_hscp_nm, a.move_ym, a.households_cnt, a.dong_cnt, a.pyeong_cnt, a.apt_cate_nm, a.region_cd, a.rcpt_x, a.rcpt_y + ,a.vdo_up_tm, DATE_FORMAT(a.vdo_up_tm, '%Y-%m-%d') as rdate_dt_vdo ,DATE_FORMAT(a.vdo_up_tm, '%H:%i:%s') as rdate_tm_vdo + ,a.check_tm, DATE_FORMAT(a.check_tm, '%Y-%m-%d') as rdate_dt_chk ,DATE_FORMAT(a.check_tm, '%H:%i:%s') as rdate_tm_chk + ,a.memo, a.note, a.video_target, a.vdo_up_ynx, a.not_vdo_reson, a.apt_step, a.check_yn, a.resend_yn, a.write_complete_yn, a.all_no_pho + ,a.write_complete_tm, DATE_FORMAT(a.write_complete_tm, '%Y-%m-%d') as rdate_dt_cmpl ,DATE_FORMAT(a.write_complete_tm, '%H:%i:%s') as rdate_tm_cmpl + ,a.charger, a.dept_sq ,(SELECT pdept_sq FROM departments WHERE dept_sq = a.dept_sq) bonbu, b.usr_nm, i.dept_nm + ,a.send_end_tm, a.supply_no_tm + ,d.pho_cate2, d.pho_explain, d.pho_up_nu + ,gp.pho_no, gp.filenm ,gp.filenm_up, gp.file_path, gp.thumb_nm, gp.cloud_upload_yn, gp.insert_tm + FROM + apt_ground a + LEFT JOIN apt_category d ON a.rcpt_no = d.rcpt_no + LEFT JOIN apt_ground_photo gp ON gp.pho_no = (SELECT p.pho_no + FROM apt_ground_photo p + WHERE p.rcpt_no = a.rcpt_no + ORDER BY p.pho_no DESC + LIMIT 1) AND gp.use_yn = 'Y' + LEFT JOIN users b ON a.charger = b.usr_id + LEFT JOIN departments i ON a.dept_sq = i.dept_sq + WHERE a.rcpt_no = {$rcpt_no} AND a.hscp_no != {$hscp_no} "; + + + $query = $this->db->query($sql); + + return $query->getResultArray(); + } + + // 정보변경이력 + public function getHistory($rcpt_no) + { + $sql = " SELECT seq," . + " rcpt_no, " . + " apt_step, get_code_name('APT_GROUND_STEP',apt_step) AS apt_step_nm, " . + " changed_type, get_code_name('APT_GROUND_CHANGED_TYPE',changed_type) AS changed_type_nm, " . + " changed_detail, get_code_name('APT_GROUND_CHANGED_DETAIL',changed_detail) AS changed_detail_nm, " . + " charged_id, " . + " changed_tm, DATE_FORMAT(changed_tm, '%Y-%m-%d') as rdate_dt, DATE_FORMAT(changed_tm, '%H:%i:%s') as rdate_tm" . + " FROM apt_ground_history" . + " WHERE rcpt_no = ?" . + " ORDER BY changed_tm DESC"; + + + + $query = $this->db->query($sql, [$rcpt_no]); + + return $query->getResultArray(); + } + + // 메모저장 + public function saveMemo($data) + { + $sql = "UPDATE apt_ground SET + memo = '{$data['memo']}' + WHERE rcpt_no = {$data['rcpt_no']} + "; + + if ($this->db->query($sql) === false) { + return [ + 'success' => false, + 'msg' => '저장실패', + ]; + } + + + $row = $this->getDetail($data['rcpt_no'], ""); + $this->saveHistory($data['rcpt_no'], $row['apt_step'], 'C', 'A1', session('usr_id')); + + return [ + 'success' => true, + ]; + + } + + // 담당자 변경 + public function saveKeeper($data) + { + $sql = "UPDATE apt_ground SET + dept_sq = {$data['team']}, charger = '{$data['user']}' + WHERE rcpt_no = {$data['rcpt_no']} + "; + + if ($this->db->query($sql) === false) { + return [ + 'success' => false, + 'msg' => '저장실패', + ]; + } + + $row = $this->getDetail($data['rcpt_no'], ""); + $this->saveHistory($data['rcpt_no'], $row['apt_step'], 'C', 'A1', session('usr_id')); + + return [ + 'success' => true, + ]; + } + + // 단지상태변경 + public function statusChange($rcpt_no, $type) + { + $this->db->transStart(); + $data = [ + $rcpt_no + ]; + + + if ($type === "phoX") { + $detail = 'C1'; + + + $sql = "UPDATE apt_ground" . + " SET gpho_up_yn = 'N'" . + " ,apt_step = 'S01'" . + " WHERE rcpt_no = ?"; + + + if ($this->db->query($sql, $data) === false) { + return [ + 'success' => false, + 'msg' => '저장실패', + ]; + } + + $sql = "delete from apt_ground_photo" . + " WHERE rcpt_no = ?"; + + $this->db->query($sql, $data); + + + } else if ($type === "phoY") { + $detail = 'C3'; + + $sql = "UPDATE apt_ground" . + " SET gpho_up_yn = 'Y'" . + " ,apt_step = 'S03'" . + " WHERE rcpt_no = ?"; + + if ($this->db->query($sql, $data) === false) { + return [ + 'success' => false, + 'msg' => '저장실패', + ]; + } + + $sql = "UPDATE apt_ground_photo" . + " SET insert_tm = NOW()" . + " WHERE rcpt_no = ?"; + + $this->db->query($sql, $data); + + } else if ($type === "sendE") { + $detail = 'C4'; + + $sql = "UPDATE apt_ground" . + " SET send_end_tm = NOW()" . + " ,supply_no_tm = NULL" . + " ,apt_step = 'S04'" . + " WHERE rcpt_no = ?"; + + if ($this->db->query($sql, $data) === false) { + return [ + 'success' => false, + 'msg' => '저장실패', + ]; + } + + } else if ($type === "suppN") { + $detail = 'C2'; + + $sql = "UPDATE apt_ground" . + " SET all_no_pho = 'Y'" . + " ,apt_step = 'S02'" . + " ,supply_no_tm = NOW()" . + " ,send_end_tm = NULL" . + " WHERE rcpt_no = ?"; + + if ($this->db->query($sql, $data) === false) { + return [ + 'success' => false, + 'msg' => '저장실패', + ]; + } + } + + $row = $this->getDetail($rcpt_no, ""); + $this->saveHistory($rcpt_no, $row['apt_step'], 'E', $detail, session('usr_id')); + + $this->db->transComplete(); + + return [ + 'success' => true, + ]; + + } + + // 단지 특이사항 저장 + public function saveNote($data) + { + $sql = "UPDATE apt_ground SET + note = '{$data['note']}' + WHERE rcpt_no = {$data['rcpt_no']} + "; + + if ($this->db->query($sql) === false) { + return [ + 'success' => false, + 'msg' => '저장실패', + ]; + } + + $row = $this->getDetail($data['rcpt_no'], ""); + $this->saveHistory($data['rcpt_no'], $row['apt_step'], 'C', 'D1', session('usr_id')); + + return [ + 'success' => true, + ]; + } // 평면도 정보 저장 public function saveImg($data) { + $this->db->transStart(); + $sql = "INSERT INTO apt_ground_photo (rcpt_no, filenm, filenm_up, file_ext, insert_tm, file_path, thumb_path, thumb_nm, use_yn, cloud_upload_yn) VALUES @@ -673,9 +900,38 @@ class GroundModel extends Model ]; } + + $sql = "UPDATE apt_ground SET + gpho_up_yn = 'Y', apt_step = 'S03' + WHERE rcpt_no = {$data['rcpt_no']} + "; + + $this->db->query($sql); + + $this->db->transComplete(); + return [ 'success' => true, ]; } + + // 이력 저장 + public function saveHistory($rcpt_no, $apt_step, $changed_type, $changed_detail, $charged_id) + { + $sql = "INSERT INTO apt_ground_history" . + " (rcpt_no, apt_step, changed_type, changed_detail, charged_id, changed_tm)" . + " VALUES (?, ?, ?, ?, ?, NOW())"; + $data = [ + $rcpt_no, + $apt_step, + $changed_type, + $changed_detail, + $charged_id + ]; + + + $res = $this->db->query($sql, $data); + } + } \ No newline at end of file diff --git a/app/Views/layouts/sidebar.php b/app/Views/layouts/sidebar.php index 9534dca..a9b6632 100644 --- a/app/Views/layouts/sidebar.php +++ b/app/Views/layouts/sidebar.php @@ -93,6 +93,21 @@ section('content') ?>

아파트단지 DB구축 현황

@@ -325,6 +334,10 @@
+ + @@ -375,6 +388,72 @@
+ + endSection() ?> @@ -389,7 +468,7 @@ const teamArr = ; const userArr = ; - var table; + var table, excelTbl; $(function () { @@ -677,6 +756,136 @@ table.ajax.reload() }); + // 엑셀업로드 click + $("#excel-upload").on("click", function () { + $("#excel").val(""); + $("#excelList").DataTable().clear().destroy(); + + $("#excelModal").modal("show"); + + }); + + // 엑셀 업로드 + $("#excel").on("change", async function () { + const file = this.files[0]; + if (!file) return; + + const bodyRows = await readExcelBodyOnly(file); + renderExcelDataTable(bodyRows); + + }); + + // 엑셀 업로드 저장 + $("#btnUpload").on("click", function () { + + const dt = $("#excelList").DataTable(); + + // 전체 row 데이터 (모든 페이지) + const excelData = dt.rows().data().toArray(); + + if (excelData.length === 0) { + Swal.fire({ + title: "업로드할 데이터가 없습니다.", + icon: "warning" + }); + return; + } + + const headers = [ + "줄번호", "단지코드", "통합단지코드", "법정동코드", "주소" + , "상세주소", "단지유형", "단지명" + , "입주년월", "총세대수", "총동수", "평형수", "동호수" + , "사용여부", "경도", "위도", "사진촬영제외" + ]; + + const errors = []; + let hasError = false; + for (let rowIdx = 0; rowIdx < excelData.length; rowIdx++) { + const row = excelData[rowIdx]; + + for (let colIdx = 0; colIdx < headers.length; colIdx++) { + const colName = headers[colIdx]; + const val = row[colIdx]; + + + if (val === undefined || val === null || String(val).trim() === "") { + Swal.fire({ + title: `${rowIdx + 1}행 ${colName} 데이터 누락`, + icon: "warning" + }); + + hasError = true; + break; + } + } + + if (hasError) break; + } + + if (hasError) return; + + swal.fire({ + text: "저장 하시겠습니까?", + type: "warning", + showCancelButton: true, + confirmButtonText: "예", + cancelButtonText: "아니오", + closeOnConfirm: false, + closeOnCancel: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + }).then((result) => { + if (result.isConfirmed) { + $.ajax({ + url: '/article/apt/uploadExcel', + contentType: 'application/json;charset=UTF-8', + method: 'POST', + data: JSON.stringify({ datas: excelData }), + beforeSend: function () { + blockUI.blockPage({ + message: tpl + }) + }, + complete: function () { + blockUI.unblockPage() + }, + error: function (xhr, error, thrown) { + blockUI.unblockPage() + var msg = ""; + if (xhr.responseText != null) { + msg = xhr.responseText + } else { + msg = "잠시후 다시 시도해 주세요." + } + + Swal.fire({ + title: msg, + icon: "error" + }) + }, + success: function (result) { + + if (result.code == '0') { + $("#btnSearch").trigger('click') + $("#excelModal").modal("hide") + Swal.fire({ + title: '정상 처리되었습니다.', + icon: "success" + + }) + } else { + Swal.fire({ + title: result.msg, + icon: "error" + }) + } + } + }); + } + }); + + + }); // 엑셀 다운로드 click $("#excel-download").on("click", function () { @@ -817,7 +1026,7 @@ Swal.fire({ title: msg, icon: "error" - }) + }); }, success: function (result) { @@ -940,6 +1149,48 @@ }); + async function readExcelBodyOnly(file) { + const data = await file.arrayBuffer(); + const wb = XLSX.read(data, { type: "array" }); + const ws = wb.Sheets[wb.SheetNames[0]]; + + const rows2d = XLSX.utils.sheet_to_json(ws, { + header: 1, + defval: "", + raw: false + }); + + // 첫 줄(엑셀 헤더) 제거 + 빈 행 제거 + return rows2d + .slice(1) + .filter(r => r.some(v => String(v).trim() !== "")); + } + + function renderExcelDataTable(data2d) { + // 이미 DataTable이 있으면 파괴 후 재생성(컬럼 구조가 바뀔 수 있으니) + if ($.fn.DataTable.isDataTable("#excelList")) { + $("#excelList").DataTable().clear().destroy(); + } + + excelTbl = $("#excelList").DataTable({ + language: lang_kor, + columnDefs: [ + { className: 'text-center dt-nowrap', targets: '_all' }, + ], + data: data2d, + searching: false, + ordering: false, + destroy: true, + deferRender: true, + pageLength: 10, + lengthMenu: [10, 25, 50, 100], + scrollX: true, + autoWidth: true, + }); + } + + + function hscp_no_enter(event) { if (event.keyCode == 13) { table.ajax.reload() @@ -1055,10 +1306,6 @@ // 단지정보저장 function fn_save_info(row, idx) { - console.log('+++') - console.log(row) - console.log('idx ?? ' + idx) - console.log('+++') const target = $("#video_target_" + idx).val(); console.log(target) diff --git a/public/plugin/sample/sample.xlsx b/public/plugin/sample/sample.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..5152b00904b80e60bdcfdc83ff37519239274f06 GIT binary patch literal 8632 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 literal 0 HcmV?d00001 From 0f5d52271f69fd398d954cc1303f1d4cf1026463 Mon Sep 17 00:00:00 2001 From: yangsh Date: Tue, 30 Dec 2025 09:46:38 +0900 Subject: [PATCH 09/10] =?UTF-8?q?=EC=97=91=EC=85=80=EC=96=91=EC=8B=9D?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Views/pages/article/lists.php | 3 ++- public/plugin/sample/sample_apt.xlsx | Bin 0 -> 10378 bytes 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 public/plugin/sample/sample_apt.xlsx diff --git a/app/Views/pages/article/lists.php b/app/Views/pages/article/lists.php index 6cacb11..7d97a0b 100644 --- a/app/Views/pages/article/lists.php +++ b/app/Views/pages/article/lists.php @@ -404,7 +404,8 @@ - + 샘플양식 다운로드 diff --git a/public/plugin/sample/sample_apt.xlsx b/public/plugin/sample/sample_apt.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..47c97fca1e41c55b4516a643e37c9b870e5ca3e9 GIT binary patch literal 10378 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 T5Do$WsE`j0gvN^0zn%RLC3E%8 literal 0 HcmV?d00001 From 48f4c0e15897cb1603a3b3cf40347989b0c2a143 Mon Sep 17 00:00:00 2001 From: yangsh Date: Tue, 30 Dec 2025 11:06:23 +0900 Subject: [PATCH 10/10] =?UTF-8?q?=EC=95=84=ED=8C=8C=ED=8A=B8=ED=8F=89?= =?UTF-8?q?=EB=A9=B4=EB=8F=84=20=EC=97=91=EC=85=80=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Config/Routes.php | 1 + app/Controllers/Article/Ground.php | 51 ++++ app/Models/article/GroundModel.php | 27 +++ app/Views/pages/article/lists2.php | 247 ++++++++++++++++++++ public/plugin/sample/sample_apt_ground.xlsx | Bin 0 -> 10341 bytes 5 files changed, 326 insertions(+) create mode 100644 public/plugin/sample/sample_apt_ground.xlsx diff --git a/app/Config/Routes.php b/app/Config/Routes.php index bc16274..5686df9 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -103,6 +103,7 @@ $routes->group('article', ['namespace' => 'App\Controllers\Article'], function ( */ $routes->get('apt/ground/getAptLists', 'Ground::getAptLists'); $routes->get('apt/ground/excel', 'Ground::excel'); + $routes->post('apt/ground/uploadExcel', 'Ground::uploadExcel'); $routes->post('apt/ground/chgAptDamdang', 'Ground::chgAptDamdang'); $routes->post('apt/ground/uploadFile', 'Ground::uploadFile'); $routes->get('apt/ground/print', 'Ground::print'); diff --git a/app/Controllers/Article/Ground.php b/app/Controllers/Article/Ground.php index beafaae..f374494 100644 --- a/app/Controllers/Article/Ground.php +++ b/app/Controllers/Article/Ground.php @@ -84,6 +84,57 @@ class Ground extends BaseController } + // 엑셀 업로드 + public function uploadExcel() + { + try { + + $payload = $this->request->getJSON(true); + $datas = $payload['datas'] ?? null; + + if (count($datas) === 0) { + return $this->response->setJSON([ + 'code' => '9', + 'msg' => "데이터 없음", + ]); + } + + foreach ($datas as $data) { + $rdate = date("Y-m-d H:i:s"); + + $params = [ + 'hscp_no' => $data[1], + 'region_cd' => $data[2], + 'part_no' => $data[0], + 'apt_step' => 'S01', + 'addr' => $data[3] . ' ' . $data[4] . ' ' . $data[5], + 'addr2' => $data[6], + 'rcpt_hscp_nm' => $data[7], + 'apt_cate_nm' => $data[8], + 'pyeong_cnt' => $data[9], + 'rcpt_x' => $data[10], + 'rcpt_y' => $data[11], + 'ginsert_tm' => $rdate, + ]; + + // INSERT apt_ground + $this->model->saveExcelUploadData($params); + + } + + return $this->response->setJSON([ + 'code' => '0', + 'msg' => 'success' + ]); + + } catch (\Exception $e) { + return $this->response->setJSON([ + 'code' => '9', + 'msg' => $e->getMessage(), + ]); + } + } + // 엑셀 다운로드 public function excel() { diff --git a/app/Models/article/GroundModel.php b/app/Models/article/GroundModel.php index cd57b5a..790533f 100644 --- a/app/Models/article/GroundModel.php +++ b/app/Models/article/GroundModel.php @@ -468,6 +468,33 @@ class GroundModel extends Model return $query->getResultArray(); } + // 엑셀 업로드 저장 + public function saveExcelUploadData($params) + { + $this->db->transStart(); + + $builder = $this->db->table('apt_ground'); + $res = $builder->insert($params); + + if ($res === false) { + return [ + 'success' => false, + 'msg' => "구분코드 : {$params['part_no']} 저장실패", + ]; + } + + $rcpt_no = $this->db->insertID(); + + $this->saveHistory($rcpt_no, $params['apt_step'], 'U', 'U1', session('usr_id')); + + $this->db->transComplete(); + + // 성공 + return [ + 'success' => true, + ]; + } + // 엑셀 다운로드 public function getExcelList($data) { diff --git a/app/Views/pages/article/lists2.php b/app/Views/pages/article/lists2.php index f2de5df..3b6655b 100644 --- a/app/Views/pages/article/lists2.php +++ b/app/Views/pages/article/lists2.php @@ -44,6 +44,15 @@ background-color: #ff0000 !important; color: #fff !important; } + + #excelList.dataTable { + width: max-content !important; + } + + table.dataTable td, + table.dataTable th { + white-space: nowrap; + }

아파트 평면도 내역

@@ -279,6 +288,10 @@
+ + @@ -386,6 +399,69 @@
+ + endSection() ?> @@ -685,6 +761,137 @@ table.ajax.reload() }); + // 엑셀업로드 click + $("#excel-upload").on("click", function () { + $("#excel").val(""); + $("#excelList").DataTable().clear().destroy(); + + $("#excelModal").modal("show"); + + }); + + // 엑셀 업로드 + $("#excel").on("change", async function () { + const file = this.files[0]; + if (!file) return; + + const bodyRows = await readExcelBodyOnly(file); + renderExcelDataTable(bodyRows); + + }); + + + // 엑셀 업로드 저장 + $("#btnExcelUpload").on("click", function () { + + const dt = $("#excelList").DataTable(); + + // 전체 row 데이터 (모든 페이지) + const excelData = dt.rows().data().toArray(); + + if (excelData.length === 0) { + Swal.fire({ + title: "업로드할 데이터가 없습니다.", + icon: "warning" + }); + return; + } + + const headers = [ + "구분코드", "단지코드", "법정동코드" + , "시/도", "시/군/구", "읍/면/동", "상세주소" + , "단지명", "구분명", "평수", "총동수", "경도", "위도" + ]; + + const errors = []; + let hasError = false; + for (let rowIdx = 0; rowIdx < excelData.length; rowIdx++) { + const row = excelData[rowIdx]; + + for (let colIdx = 0; colIdx < headers.length; colIdx++) { + const colName = headers[colIdx]; + const val = row[colIdx]; + + + if (val === undefined || val === null || String(val).trim() === "") { + Swal.fire({ + title: `${rowIdx + 1}행 ${colName} 데이터 누락`, + icon: "warning" + }); + + hasError = true; + break; + } + } + + if (hasError) break; + } + + if (hasError) return; + + swal.fire({ + text: "저장 하시겠습니까?", + type: "warning", + showCancelButton: true, + confirmButtonText: "예", + cancelButtonText: "아니오", + closeOnConfirm: false, + closeOnCancel: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + }).then((result) => { + if (result.isConfirmed) { + $.ajax({ + url: '/article/apt/ground/uploadExcel', + contentType: 'application/json;charset=UTF-8', + method: 'POST', + data: JSON.stringify({ datas: excelData }), + beforeSend: function () { + blockUI.blockPage({ + message: tpl + }) + }, + complete: function () { + blockUI.unblockPage() + }, + error: function (xhr, error, thrown) { + blockUI.unblockPage() + var msg = ""; + if (xhr.responseText != null) { + msg = xhr.responseText + } else { + msg = "잠시후 다시 시도해 주세요." + } + + Swal.fire({ + title: msg, + icon: "error" + }) + }, + success: function (result) { + + if (result.code == '0') { + $("#btnSearch").trigger('click') + $("#excelModal").modal("hide") + Swal.fire({ + title: '정상 처리되었습니다.', + icon: "success" + + }) + } else { + Swal.fire({ + title: result.msg, + icon: "error" + }) + } + } + }); + } + }); + + + }); + // 엑셀 다운로드 click $("#excel-download").on("click", function () { @@ -984,6 +1191,46 @@ }); + async function readExcelBodyOnly(file) { + const data = await file.arrayBuffer(); + const wb = XLSX.read(data, { type: "array" }); + const ws = wb.Sheets[wb.SheetNames[0]]; + + const rows2d = XLSX.utils.sheet_to_json(ws, { + header: 1, + defval: "", + raw: false + }); + + // 첫 줄(엑셀 헤더) 제거 + 빈 행 제거 + return rows2d + .slice(1) + .filter(r => r.some(v => String(v).trim() !== "")); + } + + function renderExcelDataTable(data2d) { + // 이미 DataTable이 있으면 파괴 후 재생성(컬럼 구조가 바뀔 수 있으니) + if ($.fn.DataTable.isDataTable("#excelList")) { + $("#excelList").DataTable().clear().destroy(); + } + + excelTbl = $("#excelList").DataTable({ + language: lang_kor, + columnDefs: [ + { className: 'text-center dt-nowrap', targets: '_all' }, + ], + data: data2d, + searching: false, + ordering: false, + destroy: true, + deferRender: true, + pageLength: 10, + lengthMenu: [10, 25, 50, 100], + scrollX: true, + autoWidth: true, + }); + } + function hscp_no_enter(event) { if (event.keyCode == 13) { table.ajax.reload() diff --git a/public/plugin/sample/sample_apt_ground.xlsx b/public/plugin/sample/sample_apt_ground.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..e44fed74103a607b6ceedd85c4853fa0aa9a6b81 GIT binary patch literal 10341 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 literal 0 HcmV?d00001