diff --git a/app/Commands/NaverWorker.php b/app/Commands/NaverWorker.php
index bcb265e..bf3a72e 100644
--- a/app/Commands/NaverWorker.php
+++ b/app/Commands/NaverWorker.php
@@ -42,11 +42,13 @@ class NaverWorker extends BaseCommand
// 1. DB 연결 상태 체크 (더 견고하게)
try {
- if ($this->db->connID === false || !@$this->db->connID->ping()) {
+ // connID가 없거나, 가벼운 쿼리 실행 실패 시 재연결 시도
+ if ($this->db->connID === false || !$this->db->simpleQuery('SELECT 1')) {
$this->db->reconnect();
CLI::write(CLI::color('🔄 Database reconnected.', 'yellow'));
}
} catch (\Throwable $e) {
+ // 어떤 이유로든 에러 발생 시 재연결 시도
$this->db->reconnect();
}
diff --git a/app/Config/Routes.php b/app/Config/Routes.php
index e46662a..433a7b8 100644
--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -76,8 +76,10 @@ $routes->group('', ['namespace' => 'App\Controllers\Article'], static function (
$routes->post('chgStatus', 'Receipt::chgStatus'); // 상태변경
$routes->post('sendSms', 'Receipt::sendSms'); // 문자발송
$routes->post('saveRecInfo', 'Receipt::saveRecInfo'); // 거주인정보저장
+ $routes->get('getImages', 'Receipt::getImages'); // 이미지 목록 조회
$routes->post('uploadFile', 'Receipt::uploadFile'); // 파일업로드
$routes->post('removeUploadFile', 'Receipt::removeUploadFile'); // 파일삭제
+ $routes->post('updateImageOrder', 'Receipt::updateImageOrder'); // 이미지 순서 업데이트
$routes->get('downloadAllImages', 'Receipt::downloadAllImages'); // 이미지 일괄 다운로드
$routes->post('saveImgLocation', 'Receipt::saveImgLocation'); // 촬영위치 저장
});
diff --git a/app/Controllers/Article/Receipt.php b/app/Controllers/Article/Receipt.php
index b6b6927..143b420 100644
--- a/app/Controllers/Article/Receipt.php
+++ b/app/Controllers/Article/Receipt.php
@@ -23,6 +23,8 @@ class Receipt extends BaseController
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') ?: '';
@@ -54,6 +56,8 @@ class Receipt extends BaseController
public function getResultList()
{
+ error_log('========== Receipt::getResultList() CALLED ==========');
+
$start = (int) $this->request->getGet('start') ?: 0;
$end = (int) $this->request->getGet('length') ?: 10;
@@ -92,9 +96,13 @@ class Receipt extends BaseController
'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,
@@ -717,6 +725,26 @@ class Receipt extends BaseController
$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 . "/";
@@ -738,7 +766,7 @@ class Receipt extends BaseController
// 워터마크 이미지 조회
$watermark_info = [];
- if (!empty($receipt) && ($receipt['comp_sq'] ?? '') == '2') {
+ if (!empty($receipt) && !empty($receipt['rcpt_cpid'])) {
$watermark_info = $this->model->getWatermarkList($receipt['rcpt_cpid']);
}
@@ -758,15 +786,20 @@ class Receipt extends BaseController
if ($uploadData !== false) {
$arrUploadfile[] = $uploadData;
- log_message('info', '[Receipt::uploadFile] upload success name={name} stored={stored}', [
+ 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] do_upload2 failed name={name} error={error}:{errorString}', [
+ log_message('error', '[Receipt::uploadFile] Cloud upload failed name={name}', [
'name' => $file->getName(),
- 'error' => $file->getError(),
- 'errorString' => $file->getErrorString(),
+ ]);
+
+ // 클라우드 업로드 실패 시 즉시 에러 반환
+ return $this->response->setJSON([
+ 'code' => '9',
+ 'msg' => '클라우드 스토리지 업로드 실패: ' . $file->getName() . '\n네트워크 연결 또는 권한을 확인해주세요.'
]);
}
@@ -786,25 +819,79 @@ class Receipt extends BaseController
$dir = rtrim(dirname($uploadFile['object_key']), '/'); // upload/result/*
$thumbKey = $dir . '/' . $base . '_thumb.jpg';
- $imageDataBlob = file_get_contents($object_storage_url);
- $im = new \Imagick();
- $im->readImageBlob($imageDataBlob);
- $imgWidth = $im->getImageWidth();
- $imgHeight = $im->getImageHeight();
- $im->thumbnailImage(105, 80, false);
- $thumb_im = $im->getImageBlob();
- // 썸네일 s3 전송
- $lib->upload_object_storage_imagick2($thumbKey, $thumb_im);
- $im->destroy();
+ 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)) {
- if (!empty($watermark_info)) {
- $common->watermarking($object_storage_url, $watermark_info, '', $receipt['rcpt_cpid'], $uploadFile['object_key']);
- }
+ if (in_array($img_type, $watermark_imgs) && !empty($watermark_info)) {
+ $common->watermarking($object_storage_url, $watermark_info, '', $receipt['rcpt_cpid'], $uploadFile['object_key']);
}
// 촬영정보 가져오기
@@ -880,19 +967,34 @@ class Receipt extends BaseController
'meta_data' => $metaData, // 이미지메타데이터
'receipt' => $receipt,
'img_type' => $img_type,
+ 'cloud_upload_yn' => 'Y', // 클라우드 업로드 성공 (여기까지 왔으면 성공)
];
- $this->model->saveImg($uploadParam);
+ $img_sq = $this->model->saveImg($uploadParam);
}
}
+ // 성공 응답 - JavaScript에서 사용할 이미지 정보 포함
+ $lastUploadedFile = end($arrUploadfile);
return $this->response->setJSON([
'code' => '0',
- 'msg' => 'success'
+ '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(),
@@ -988,6 +1090,69 @@ class Receipt extends BaseController
}
}
+ // 업로드파일 목록 조회 (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()
{
@@ -1016,17 +1181,76 @@ class Receipt extends BaseController
$lib->deleteFile($thumbPath);
}
- $this->model->removeUploadFile($rcpt_sq, $img_sq);
+ $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',
- 'msg' => 'success'
+ 'success' => true,
+ 'msg' => '순서가 저장되었습니다.'
+ ]);
+
+ } catch (\Exception $e) {
+ log_message('error', 'updateImageOrder error: ' . $e->getMessage());
+ return $this->response->setJSON([
+ 'code' => '1',
+ 'msg' => '순서 업데이트 중 에러가 발생했습니다: ' . $e->getMessage()
]);
}
}
diff --git a/app/Libraries/Common.php b/app/Libraries/Common.php
index 5e90353..214a6f6 100644
--- a/app/Libraries/Common.php
+++ b/app/Libraries/Common.php
@@ -49,7 +49,6 @@ class Common
$wmTextAlpha = 0.5; // 워터마크 텍스트 투명도
try {
-
$img = new \Imagick();
if (is_string($imagePath) && is_file($imagePath)) {
$img->readImage($imagePath);
@@ -96,19 +95,23 @@ class Common
}
}
- if (empty($wmImagePath))
+ if (empty($wmImagePath)) {
return;
+ }
+ // 워터마크 이미지 경로 처리
if (substr($wmImagePath, 0, 1) == '/') {
$wmImagePath = substr($wmImagePath, 1);
}
+
+ // ROOTPATH와 결합하여 절대 경로 생성 (img 폴더는 프로젝트 루트에 있음)
+ $fullWmPath = ROOTPATH . $wmImagePath;
+
+ if (!is_file($fullWmPath)) {
+ return;
+ }
-
- // echo FCPATH.$wmImagePath;
-
-
-
- $wm = new \Imagick($wmImagePath);
+ $wm = new \Imagick($fullWmPath);
$hWm = $wm->getImageHeight();
$wWm = $wm->getImageWidth();
@@ -119,29 +122,34 @@ class Common
$img->compositeImage($wm, \Imagick::COMPOSITE_OVER, $wmImgLeft, $wmImgTop);
$wm->destroy();
- $draw = new \ImagickDraw();
+ // 워터마크 텍스트가 있는 경우에만 텍스트 그리기
+ if (!empty($wmText)) {
+ $draw = new \ImagickDraw();
- $basePath = defined('BASEPATH') ? BASEPATH : (defined('ROOTPATH') ? ROOTPATH . 'system/' : '');
- $wmFont = $basePath . 'fonts/' . $wmFont;
- if (is_file($wmFont)) {
- $draw->setFont($wmFont);
+ // 폰트 경로: img/watermark/fonts/
+ if (!empty($wmFont)) {
+ $fontPath = ROOTPATH . 'img/watermark/fonts/' . $wmFont;
+ if (is_file($fontPath)) {
+ $draw->setFont($fontPath);
+ }
+ }
+
+ $draw->setFontSize($wmFontSize);
+ $draw->setFillColor(new \ImagickPixel($wmTextColor));
+ // $draw->setFillAlpha( $wmTextAlpha );
+ $draw->setFillOpacity($wmTextAlpha); // 워터마크 텍스트 투명도 설정
+ $draw->setTextAlignment(2); // center
+ $draw->setStrokeAntialias(1);
+ $draw->setStrokeWidth(1);
+ $draw->setStrokeColor(new \ImagickPixel('#000000'));
+ // $draw->setStrokeAlpha(0.1);
+ $draw->setStrokeOpacity(0.1);
+ $draw->annotation($wImg / 2, $wmTxtTop, $wmText);
+
+ $img->drawImage($draw);
+ $draw->destroy();
}
- $draw->setFontSize($wmFontSize);
- $draw->setFillColor(new \ImagickPixel($wmTextColor));
- // $draw->setFillAlpha( $wmTextAlpha );
- $draw->setFillOpacity($wmTextAlpha); // 워터마크 텍스트 투명도 설정
- $draw->setTextAlignment(2); // center
- $draw->setStrokeAntialias(1);
- $draw->setStrokeWidth(1);
- $draw->setStrokeColor(new \ImagickPixel('#000000'));
- // $draw->setStrokeAlpha(0.1);
- $draw->setStrokeOpacity(0.1);
- $draw->annotation($wImg / 2, $wmTxtTop, $wmText);
-
- $img->drawImage($draw);
- $draw->destroy();
-
// $img->writeImage();
$watermark_img = $img->getImageBlob();
@@ -149,14 +157,9 @@ class Common
if (empty($key)) {
throw new \RuntimeException('Empty upload key');
}
+
$ok = $uploader->upload_object_storage_imagick2($key, $watermark_img);
- if ($ok) {
- log_message('info', '[watermarking] upload success key={key} size={size} cpid={cpid}', [
- 'key' => $key,
- 'size' => strlen($watermark_img),
- 'cpid' => $cpid,
- ]);
- } else {
+ if (!$ok) {
log_message('error', '[watermarking] upload failed key={key} cpid={cpid}', [
'key' => $key,
'cpid' => $cpid,
@@ -166,7 +169,11 @@ class Common
// $object_upload = $this->upload->upload_object_storage($imagePath , $imagePath , 'data');
} catch (\Throwable $e) {
- log_message('error', '[watermarking] ' . $e->getMessage());
+ log_message('error', '[watermarking] Exception: {message} at {file}:{line}', [
+ 'message' => $e->getMessage(),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ ]);
}
}
diff --git a/app/Libraries/MyUpload.php b/app/Libraries/MyUpload.php
index 00e566d..ecf5440 100644
--- a/app/Libraries/MyUpload.php
+++ b/app/Libraries/MyUpload.php
@@ -54,7 +54,7 @@ class MyUpload
}
/**
- * 파일 업로드 요청
+ * 파일 업로드 요청 (Object Storage 전용)
* 추가일 2025.12.24
* 작성자 - yangsh
*/
@@ -73,39 +73,40 @@ class MyUpload
$newName = $file->getRandomName();
- // ✅ PHP 임시 업로드 파일 경로 (writable로 move() 필요 없음)
+ // ✅ PHP 임시 업로드 파일 경로
$tmpFile = $file->getTempName();
if (!is_file($tmpFile)) {
$this->set_error('upload_temp_file_missing');
- log_message('error', 'do_upload2 temp file missing: ' . $tmpFile);
+ log_message('error', '[MyUpload] Temp file missing: ' . $tmpFile);
return false;
}
- // ✅ 클라우드에 올라갈 "Key"를 직접 만든다 (로컬 경로 절대 넣지 말기)
- // 예시: upload/tmp/랜덤파일명 또는 upload/apt_file/{rcpt_no}/...
+ // ✅ 클라우드에 올라갈 "Key"
$objectKey = $filePath . $newName;
+ // 클라우드 업로드 (필수)
$up = $this->upload_object_storage($objectKey, $tmpFile, 'file');
+
if ($up === false) {
- $this->set_error('upload_destination_cloud_error');
+ $this->set_error('cloud_upload_failed');
+ log_message('error', '[MyUpload] Cloud upload failed: ' . $objectKey);
+ @unlink($tmpFile);
return false;
}
- // (선택) tmp 파일 삭제
+ // 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
+ 'file_name' => basename($objectKey),
+ 'base_name' => pathinfo($objectKey, PATHINFO_FILENAME),
+ 'ext' => pathinfo($objectKey, PATHINFO_EXTENSION),
];
-
- log_message('debug', 's3_data=' . json_encode($this->s3_data ?? null, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
-
+ log_message('info', '[MyUpload] Cloud upload success: ' . $objectKey);
return $this->s3_data;
}
@@ -386,6 +387,7 @@ class MyUpload
'Key' => ltrim($object_storage_upload_path, '/'),
'Body' => $blobData,
'ACL' => 'public-read',
+ 'ContentType' => 'image/jpeg',
]);
diff --git a/app/Models/Entities/WatermarkModel.php b/app/Models/Entities/WatermarkModel.php
new file mode 100644
index 0000000..2be4fc8
--- /dev/null
+++ b/app/Models/Entities/WatermarkModel.php
@@ -0,0 +1,154 @@
+ 'required|max_length[20]',
+ 'wm_type' => 'required|integer',
+ 'wm_img_path' => 'required|max_length[300]',
+ 'wm_img_height' => 'required|integer',
+ 'wm_img_width' => 'required|integer',
+ 'wm_position' => 'required|max_length[2]',
+ 'wm_img_opacity' => 'required|integer',
+ 'wm_space' => 'required|integer',
+ 'img_width_min' => 'required|integer',
+ 'img_width_max' => 'required|integer'
+ ];
+
+ protected $validationMessages = [];
+ protected $skipValidation = false;
+
+ /**
+ * 워터마크 정보 조회 (복합키)
+ *
+ * @param string $cpid
+ * @param int $wm_type
+ * @return array|null
+ */
+ public function getWatermark($cpid, $wm_type)
+ {
+ return $this->where('cpid', $cpid)
+ ->where('wm_type', $wm_type)
+ ->first();
+ }
+
+ /**
+ * cpid로 워터마크 목록 조회
+ *
+ * @param string $cpid
+ * @return array
+ */
+ public function getWatermarksByCpid($cpid)
+ {
+ return $this->where('cpid', $cpid)->findAll();
+ }
+
+ /**
+ * 워터마크 타입별 조회
+ *
+ * @param int $wm_type
+ * @return array
+ */
+ public function getWatermarksByType($wm_type)
+ {
+ return $this->where('wm_type', $wm_type)->findAll();
+ }
+
+ /**
+ * 워터마크 정보 저장/업데이트
+ *
+ * @param array $data
+ * @return bool
+ */
+ public function saveWatermark($data)
+ {
+ if (!isset($data['cpid']) || !isset($data['wm_type'])) {
+ return false;
+ }
+
+ // 복합키로 기존 데이터 확인
+ $existing = $this->getWatermark($data['cpid'], $data['wm_type']);
+
+ if ($existing) {
+ // 업데이트
+ return $this->where('cpid', $data['cpid'])
+ ->where('wm_type', $data['wm_type'])
+ ->set($data)
+ ->update();
+ } else {
+ // 삽입
+ return $this->insert($data);
+ }
+ }
+
+ /**
+ * 워터마크 삭제 (복합키)
+ *
+ * @param string $cpid
+ * @param int $wm_type
+ * @return bool
+ */
+ public function deleteWatermark($cpid, $wm_type)
+ {
+ return $this->where('cpid', $cpid)
+ ->where('wm_type', $wm_type)
+ ->delete();
+ }
+
+ /**
+ * cpid로 모든 워터마크 삭제
+ *
+ * @param string $cpid
+ * @return bool
+ */
+ public function deleteAllByCpid($cpid)
+ {
+ return $this->where('cpid', $cpid)->delete();
+ }
+
+ /**
+ * 이미지 크기 범위로 워터마크 조회
+ *
+ * @param string $cpid
+ * @param int $img_width
+ * @return array|null
+ */
+ public function getWatermarkByImageSize($cpid, $img_width)
+ {
+ return $this->where('cpid', $cpid)
+ ->where('img_width_min <=', $img_width)
+ ->where('img_width_max >=', $img_width)
+ ->findAll();
+ }
+}
diff --git a/app/Models/article/ReceiptModel.php b/app/Models/article/ReceiptModel.php
index 3ca1bb5..80b6cfc 100644
--- a/app/Models/article/ReceiptModel.php
+++ b/app/Models/article/ReceiptModel.php
@@ -409,10 +409,9 @@ class ReceiptModel extends Model
}
- // log_message('debug', '[getTotalCount] SQL = ' . $builder->getCompiledSelect());
-
-
$row = $builder->get()->getRowArray();
+ error_log('[getTotalCount] SQL = ' . $this->db->getLastQuery());
+
return (int) ($row['cnt'] ?? 0);
}
@@ -538,10 +537,10 @@ class ReceiptModel extends Model
$builder->join('result b', 'b.rcpt_sq = a.rcpt_sq', 'inner');
$builder->join('region_codes c', 'a.rcpt_dong = c.region_cd', 'inner');
- $builder->join('departments d', 'b.dept_sq = d.dept_sq', 'left');
- $builder->join('users u', 'b.usr_sq = u.usr_sq', 'left');
- $builder->join('result_imgs e', "e.rsrv_sq = b.rsrv_sq AND e.img_type = 'I1' AND e.use_yn = 'Y'", 'left');
- $builder->join('receipt_transimage_log l', 'a.rcpt_key = l.rcpt_key', 'left');
+ $builder->join('departments d', 'b.dept_sq = d.dept_sq', 'left outer');
+ $builder->join('users u', 'b.usr_sq = u.usr_sq', 'left outer');
+ $builder->join('result_imgs e', "e.rsrv_sq = b.rsrv_sq AND e.img_type = 'I1' AND e.use_yn = 'Y'", 'left outer');
+ $builder->join('receipt_transimage_log l', 'a.rcpt_key = l.rcpt_key', 'left outer');
$login_dept_info = $this->getDeptDetail($dept_sq); // 로그인 사용자 소속부서정보
@@ -573,8 +572,8 @@ class ReceiptModel extends Model
$builder->where('a.rcpt_tm >=', $data['sdate'] . ' 00:00:00');
$builder->where('a.rcpt_tm <=', $data['edate'] . ' 23:59:59');
} else {
- $builder->where('b.rsrv_date >=', $data['sdate'] . ' 00:00:00');
- $builder->where('b.rsrv_date <=', $data['edate'] . ' 23:59:59');
+ $builder->where('b.rsrv_date >=', $data['sdate'] );
+ $builder->where('b.rsrv_date <=', $data['edate'] );
}
// 지역
@@ -753,9 +752,12 @@ class ReceiptModel extends Model
$builder->limit($end, $start);
- // log_message('debug', '[getResultList] SQL = ' . $builder->getCompiledSelect());
+ $result = $builder->get()->getResultArray();
+
+ error_log('[getResultList] SQL = ' . $this->db->getLastQuery());
+ error_log('[getResultList] Result count = ' . count($result));
- return $builder->get()->getResultArray();
+ return $result;
}
@@ -1837,7 +1839,8 @@ class ReceiptModel extends Model
$receipt = $param['receipt'];
- $cloud_upload_yn = 'Y';
+ // 실제 클라우드 업로드 성공 여부를 파라미터에서 받아옴 (기본값 'Y')
+ $cloud_upload_yn = $param['cloud_upload_yn'] ?? 'Y';
if ($param['img_type'] == 'I6' || $param['img_type'] == 'I7') {
$yn_sql = "update receipt " .
@@ -2011,103 +2014,161 @@ class ReceiptModel extends Model
// 이미지정보 조회
$row = $this->getUploadFileInfo($img_sq);
- if (!empty($row)) {
+ if (empty($row)) {
+ $this->db->transComplete();
+ // 파일이 이미 삭제된 경우 성공으로 처리 (중복 삭제 요청 방지)
+ log_message('info', "[removeUploadFile] 파일 정보 없음 (이미 삭제됨): img_sq={$img_sq}");
+ return [
+ 'success' => true,
+ 'msg' => '이미 삭제된 파일입니다',
+ ];
+ }
- if ($row['img_type'] == 'I6' || $row['img_type'] == 'I7') {
- $yn_sql = "update receipt " .
- " set exp_spc_yn = 'N' " .
- " where rcpt_sq = ? ";
- $yn_data = [$rcpt_sq];
- $this->db->query($yn_sql, $yn_data);
- } else if ($row['img_type'] == 'I8') {
- $yn_sql = "UPDATE receipt" .
- " SET parcel_out_yn = CASE (SELECT COUNT('x')" .
- " FROM result_imgs " .
- " WHERE rsrv_sq = (SELECT rsrv_sq FROM result_imgs WHERE img_sq = ? AND img_type = 'I8')" .
- " AND use_yn = 'Y'" .
- " AND img_type = 'I8') WHEN 0 THEN 'N' ELSE 'Y' END" .
- " WHERE rcpt_sq = ? ";
- $yn_data = [$rcpt_sq];
- $this->db->query($yn_sql, $yn_data);
- } else if ($row['img_type'] == 'I9') {
- $yn_sql = "UPDATE receipt" .
- " SET image_360_yn = (" .
- " CASE (SELECT COUNT(1)" .
- " FROM result_imgs" .
- " WHERE rsrv_sq = (SELECT rsrv_sq FROM result_imgs WHERE img_sq = ? AND img_type = 'I9')" .
- " AND img_type = 'I9' AND use_yn = 'Y')" .
- " WHEN 0 THEN 'N'" .
- " ELSE 'Y'" .
- " END" .
- " )" .
- " WHERE rcpt_sq = ?";
- $yn_data = [$rcpt_sq];
- $this->db->query($yn_sql, $yn_data);
- } else if ($row['img_type'] == 'I11') {
- $yn_sql = "update receipt " .
- " set check_list_img_yn = 'N' " .
- " where rcpt_sq = ? ";
- $yn_data = [$rcpt_sq];
- $this->db->query($yn_sql, $yn_data);
+ if ($row['img_type'] == 'I6' || $row['img_type'] == 'I7') {
+ $yn_sql = "update receipt " .
+ " set exp_spc_yn = 'N' " .
+ " where rcpt_sq = ? ";
+ $yn_data = [$rcpt_sq];
+ $this->db->query($yn_sql, $yn_data);
+ } else if ($row['img_type'] == 'I8') {
+ $yn_sql = "UPDATE receipt" .
+ " SET parcel_out_yn = CASE (SELECT COUNT('x')" .
+ " FROM result_imgs " .
+ " WHERE rsrv_sq = (SELECT rsrv_sq FROM result_imgs WHERE img_sq = ? AND img_type = 'I8')" .
+ " AND use_yn = 'Y'" .
+ " AND img_type = 'I8') WHEN 0 THEN 'N' ELSE 'Y' END" .
+ " WHERE rcpt_sq = ? ";
+ $yn_data = [$img_sq, $rcpt_sq];
+ $this->db->query($yn_sql, $yn_data);
+ } else if ($row['img_type'] == 'I9') {
+ $yn_sql = "UPDATE receipt" .
+ " SET image_360_yn = (" .
+ " CASE (SELECT COUNT(1)" .
+ " FROM result_imgs" .
+ " WHERE rsrv_sq = (SELECT rsrv_sq FROM result_imgs WHERE img_sq = ? AND img_type = 'I9')" .
+ " AND img_type = 'I9' AND use_yn = 'Y')" .
+ " WHEN 0 THEN 'N'" .
+ " ELSE 'Y'" .
+ " END" .
+ " )" .
+ " WHERE rcpt_sq = ?";
+ $yn_data = [$img_sq, $rcpt_sq];
+ $this->db->query($yn_sql, $yn_data);
+ } else if ($row['img_type'] == 'I11') {
+ $yn_sql = "update receipt " .
+ " set check_list_img_yn = 'N' " .
+ " where rcpt_sq = ? ";
+ $yn_data = [$rcpt_sq];
+ $this->db->query($yn_sql, $yn_data);
+ }
+ //삭제이미지보다 순번이 높은거는 순번 업데이트
+ $sql = "UPDATE result_imgs SET view_odr = view_odr - 1 WHERE rsrv_sq = ? AND img_type = ? AND view_odr > ? AND use_yn = 'Y'";
+ $data = [$row['rsrv_sq'], $row['img_type'], $row['view_odr']];
+
+ $this->db->query($sql, $data);
+
+ //이미지 삭제
+ $sql = "DELETE FROM result_imgs WHERE img_sq = ?";
+ $data = [$img_sq];
+
+ $deleteResult = $this->db->query($sql, $data);
+
+ if (in_array($row['img_type'], ['I1', 'I2', 'I8', 'I10', 'I11'])) {
+ $remark = "";
+ switch ($row['img_type']) {
+ case 'I1':
+ $remark = "홍보확인서 사진 삭제";
+ break;
+ case 'I2':
+ $remark = "현장확인 내역서 사진 삭제";
+ break;
+ case 'I8':
+ $remark = "분양권 파일 삭제";
+ break;
+ case 'I10':
+ $remark = "촬영동의서 사진 삭제";
+ break;
+ case 'I11':
+ $remark = "체크리스트 사진 삭제";
+ break;
}
+ // 상태값을 가져오기위한 쿼리 해오기
+ $sql = "SELECT rcpt_stat FROM receipt WHERE rcpt_sq = ?";
+ $data = [$rcpt_sq];
+ $query = $this->db->query($sql, $data);
+ $rowStat = $query->getRowArray();
- //삭제이미지보다 순번이 높은거는 순번 업데이트
- $sql = "UPDATE result_imgs SET view_odr = view_odr - 1 WHERE rsrv_sq = ? AND img_type = ? AND view_odr > ? AND use_yn = 'Y'";
- $data = [$row['rsrv_sq'], $row['img_type'], $row['view_odr']];
+ $this->saveChangedHistory($rcpt_sq, $rowStat['rcpt_stat'], 'C31', $usr_id, $remark);
+ }
- $this->db->query($sql, $data);
+ $this->db->transComplete();
+ if ($deleteResult === false || $this->db->transStatus() === false) {
+ return [
+ 'success' => false,
+ 'msg' => '삭제실패',
+ ];
+ }
- //이미지 삭제
- $sql = "DELETE FROM result_imgs WHERE img_sq = ?";
- $data = [$img_sq];
-
- if ($this->db->query($sql, $data) === false) {
+ return [
+ 'success' => true,
+ ];
+ }
+
+ // 이미지 순서 업데이트
+ public function updateImageOrder($rcpt_sq, $img_type, $orderData)
+ {
+ log_message('info', '[ReceiptModel::updateImageOrder] 시작 - rcpt_sq: ' . $rcpt_sq . ', img_type: ' . $img_type . ', 개수: ' . count($orderData));
+
+ $this->db->transStart();
+
+ try {
+ $updateCount = 0;
+ foreach ($orderData as $item) {
+ $img_sq = $item['img_sq'] ?? null;
+ $view_odr = $item['view_odr'] ?? null;
+
+ if (!$img_sq || !$view_odr) {
+ log_message('warning', '[ReceiptModel::updateImageOrder] 스킵 - img_sq 또는 view_odr 없음');
+ continue;
+ }
+
+ $sql = "UPDATE result_imgs SET view_odr = ? WHERE img_sq = ? AND img_type = ?";
+ $data = [$view_odr, $img_sq, $img_type];
+
+ log_message('debug', '[ReceiptModel::updateImageOrder] 업데이트 - img_sq: ' . $img_sq . ', view_odr: ' . $view_odr);
+
+ $result = $this->db->query($sql, $data);
+ if ($result) {
+ $updateCount++;
+ }
+ }
+
+ $this->db->transComplete();
+
+ if ($this->db->transStatus() === false) {
+ log_message('error', '[ReceiptModel::updateImageOrder] 트랜잭션 실패');
return [
'success' => false,
- 'msg' => '삭제실패',
+ 'msg' => '순서 업데이트 실패',
];
}
-
-
- if (in_array($row['img_type'], ['I1', 'I2', 'I8', 'I10', 'I11'])) {
- $remark = "";
- switch ($row['img_type']) {
- case 'I1':
- $remark = "홍보확인서 사진 삭제";
- break;
- case 'I2':
- $remark = "현장확인 내역서 사진 삭제";
- break;
- case 'I8':
- $remark = "분양권 파일 삭제";
- break;
- case 'I10':
- $remark = "촬영동의서 사진 삭제";
- break;
- case 'I11':
- $remark = "체크리스트 사진 삭제";
- break;
- }
-
- // 상태값을 가져오기위한 쿼리 해오기
- $sql = "SELECT rcpt_stat FROM receipt WHERE rcpt_sq = ?";
- $data = [$rcpt_sq];
- $query = $this->db->query($sql, $data);
- $row = $query->getRowArray();
-
- $this->saveChangedHistory($rcpt_sq, $row['rcpt_stat'], 'C31', $usr_id, $remark);
- }
-
- $this->db->transComplete();
-
+
+ log_message('info', '[ReceiptModel::updateImageOrder] 완료 - 업데이트 수: ' . $updateCount);
+
return [
'success' => true,
];
-
-
+
+ } catch (\Exception $e) {
+ log_message('error', '[ReceiptModel::updateImageOrder] Exception: ' . $e->getMessage());
+
+ return [
+ 'success' => false,
+ 'msg' => '순서 업데이트 중 오류 발생: ' . $e->getMessage(),
+ ];
}
}
diff --git a/app/Services/ParameterMapper/TypeSParameterMapper.php b/app/Services/ParameterMapper/TypeSParameterMapper.php
index d5f0ed8..08b8476 100644
--- a/app/Services/ParameterMapper/TypeSParameterMapper.php
+++ b/app/Services/ParameterMapper/TypeSParameterMapper.php
@@ -40,8 +40,8 @@ class TypeSParameterMapper extends BaseParameterMapper
'rcpt_product_info5' => $price['premiumAmount'] ?? '0',
'rcpt_living_yn' => ($rawData['site']['isRegistration'] ?? false) ? 'Y' : 'N',
'rcpt_agent' => $realtor['realtorName'] ?? null,
- 'rcpt_sido' => mb_substr($address['legalDivision']['cityNumber'] ?? '', 0, 5),
- 'rcpt_gugun' => mb_substr($address['legalDivision']['divisionNumber'] ?? '', 0, 10),
+ 'rcpt_sido' => $address['legalDivision']['cityNumber'] ?? null,
+ 'rcpt_gugun' => $address['legalDivision']['divisionNumber'] ?? null,
'rcpt_dong' => $address['legalDivision']['sectorNumber'] ?? null,
'rcpt_hscp_nm' => $address['complexName'] ?? null,
'rcpt_hscp_no' => $address['complexNumber'] ?? null,
diff --git a/app/Views/pages/article/receipt/detail.php b/app/Views/pages/article/receipt/detail.php
index dd32bf3..3706726 100644
--- a/app/Views/pages/article/receipt/detail.php
+++ b/app/Views/pages/article/receipt/detail.php
@@ -29,41 +29,22 @@ $usr_level = session('usr_level');
}
-
-
-