Compare commits

...

31 Commits

Author SHA1 Message Date
f8c26acea8 워커 수정 2026-04-27 15:03:36 +09:00
b0ec75ae56 트랜젝션 실패시 처리 2026-04-08 20:13:25 +09:00
a87fed538b json_encode 에러 날때 exception 처리 2026-04-07 16:03:53 +09:00
cbf7e85cf8 헤더 2026-04-07 15:58:24 +09:00
6a72ccebd5 공통화 작업 및 워커 해더 2026-04-07 15:39:41 +09:00
cba387de9d // 1. 응답 헤더 설정 (JSON)
header('Content-Type: application/json; charset=utf-8');

제거
2026-03-27 11:52:38 +09:00
930e690126 수정 2026-03-26 21:06:31 +09:00
0ef2ba77c6 현장확인 매물검증 결과관리 - 매물 입력시 제외 2026-03-26 18:17:53 +09:00
c22b023310 로그 리스트 항목 생성 2026-03-26 11:36:21 +09:00
0b6ed3df73 worker service 매물 등록 및 redis 다운시 처리 2026-03-25 20:51:38 +09:00
a170fdc774 다시 수정 2026-03-25 15:59:05 +09:00
e5a80aff33 세션 패스 수정 2026-03-25 15:52:13 +09:00
7139e0c095 세션 처리 2026-03-25 13:14:42 +09:00
129e2b4e69 왜 안되지? 2026-03-24 19:31:40 +09:00
fd187bb84a jenkins 테스트 2026-03-24 19:29:39 +09:00
3fbc57bedc jenkins 테스트 2026-03-24 19:26:39 +09:00
81b85ae25d redis connect 수정 2026-03-24 19:11:19 +09:00
e2f49ba77e redis 연결 수정 2026-03-24 18:56:01 +09:00
728b394063 api 주소 변경 2026-03-20 16:32:51 +09:00
1f445512f7 처리가능 수량관리 수정 2026-03-20 13:35:55 +09:00
b553310dc1 테스트 파일 삭제 2026-03-20 11:32:46 +09:00
bb07396abf 로그 생성이 안되어 테스트 및 수정본 2026-03-20 09:33:30 +09:00
80cb9451d2 result 입력시 usr_sq dept_sq 를 region_codes 에서 가져오는 로직 넣기 2026-03-18 16:59:16 +09:00
9138fa9c16 redis 설정 변경 2026-03-18 14:33:17 +09:00
a6ae1bd377 로그 생성 수정 2026-03-11 17:25:45 +09:00
6bb9dceec9 오류 처리 2026-03-11 15:51:17 +09:00
32efe755e6 호출시 정보 저장 2026-03-11 09:55:11 +09:00
141f526f91 수정 2026-03-10 19:38:13 +09:00
9650707caf 수정 본 2026-03-10 18:57:09 +09:00
13dfb3e112 현장확인 화면 및 기능 수정 2026-03-05 21:00:40 +09:00
9d9df394c5 현장확인매물 상세 페이지 기능 수정 2026-03-05 18:17:31 +09:00
100 changed files with 10568 additions and 7347 deletions

2
.gitignore vendored
View File

@@ -175,4 +175,4 @@ _modules/*
# 6. 기타 개인 설정 파일 (선택적)
.github/copilot-instructions.md
.github/copilot-instructions.mdworker/fallback_queue/*.json

360
SESSION_README.md Normal file
View File

@@ -0,0 +1,360 @@
# Session 관리 가이드
## 개요
본 애플리케이션은 **Redis**를 기본 세션 저장소로 사용하며, Redis 장애 시 **Database(MariaDB)**로 자동 폴백하는 이중화 구조를 가지고 있습니다.
## 아키텍처
### 세션 저장소
| 우선순위 | 핸들러 | 설명 | 성능 |
|---------|-------|------|------|
| 1순위 | **RedisHandler** | 메모리 기반 고속 세션 | ~0.03초 |
| 2순위 | **DatabaseHandler** | DB 기반 안정적 세션 | ~0.05초 (수동 전환 시) |
### 구성 요소
```
┌─────────────────────────────────────────┐
│ 사용자 로그인 요청 │
└──────────────┬──────────────────────────┘
┌─────────────────────────────────────────┐
│ Session.php 생성자 │
│ - Redis 연결 테스트 │
│ - SESSION_FORCE_DATABASE 확인 │
└──────────────┬──────────────────────────┘
┌──────┴──────┐
│ │
Redis 정상? Redis 장애?
│ │
▼ ▼
RedisHandler DatabaseHandler
(ci_sessions (ci_sessions
테이블) 테이블)
│ │
└──────┬──────┘
세션 데이터 저장/조회
```
## 설정 파일
### 1. Session.php (`src/app/Config/Session.php`)
```php
public string $driver = RedisHandler::class;
public function __construct()
{
// 환경변수로 강제 Database 모드 설정 가능
$forceDatabase = env('SESSION_FORCE_DATABASE', false);
if ($this->driver === RedisHandler::class && !$forceDatabase) {
try {
// Redis 연결 테스트 (타임아웃: 0.5초)
$testRedis = get_redis_connection('session');
if (!$testRedis) {
throw new \Exception('Redis connection failed');
}
// Redis 정상 - Redis 사용
$this->savePath = 'tcp://192.168.10.243:6379?database=0';
} catch (\Exception $e) {
// Redis 실패 - DatabaseHandler로 폴백
$this->driver = DatabaseHandler::class;
$this->savePath = 'ci_sessions';
}
} else {
// Database 모드
$this->savePath = 'ci_sessions';
}
}
```
### 2. 환경 설정 (`.env`)
```bash
# Redis 세션 설정
SESSION_REDIS_HOST = 192.168.10.243
SESSION_REDIS_PORT = 6379
SESSION_REDIS_DATABASE = 0
#SESSION_REDIS_PASSWORD =
# 세션 강제 Database 모드 (Redis 장애 시 true로 변경)
SESSION_FORCE_DATABASE = false
```
### 3. Redis Helper (`src/app/Helpers/redis_helper.php`)
```php
function get_redis_connection(string $type = 'worker')
{
$redis = new \Redis();
// 타임아웃 0.5초로 빠른 실패 감지
$timeout = 0.5;
$success = $redis->connect($host, $port, $timeout);
return $success ? $redis : false;
}
```
## 데이터베이스 테이블
### ci_sessions 테이블 구조
```sql
CREATE TABLE `ci_sessions` (
`session_id` VARCHAR(40) NOT NULL DEFAULT '0',
`ip_address` VARCHAR(16) NOT NULL DEFAULT '0',
`user_agent` VARCHAR(120) NOT NULL,
`last_activity` INT UNSIGNED NOT NULL DEFAULT 0,
`user_data` VARCHAR(4000) NOT NULL,
PRIMARY KEY (`session_id`)
) ENGINE = MEMORY;
CREATE INDEX `last_activity_idx`
ON `ci_sessions` (`last_activity` ASC);
```
**주요 특징:**
- **ENGINE = MEMORY**: 메모리 기반 테이블로 매우 빠른 읽기/쓰기 성능
- **장점**: Redis와 비슷한 속도 (디스크 I/O 없음)
- **단점**: 서버 재시작 시 세션 데이터 소실 (일반적으로 문제없음 - 세션은 휘발성 데이터)
- **용량**: VARCHAR(4000)으로 충분한 세션 데이터 저장 가능
## 운영 가이드
### 1. Redis 장애 발생 시
#### 자동 폴백 (기본 동작)
- Redis 연결 실패 시 자동으로 DatabaseHandler로 전환
- **단점**: 매 요청마다 Redis 연결 시도 → 타임아웃 대기 (약 0.5초 추가)
- 사용자는 약간 느린 응답 시간을 경험
#### 수동 전환 (권장)
Redis 장애 감지 즉시 다음 조치:
```bash
# 1. .env 파일 수정
vi /path/to/src/.env
# SESSION_FORCE_DATABASE를 true로 변경
SESSION_FORCE_DATABASE = true
# 2. PHP-FPM 재시작 (선택사항, 다음 요청부터 자동 적용)
docker exec projects-admin_confirms kill -USR2 1
```
**장점**: Redis 연결 시도 없이 즉시 Database 사용 → 빠른 응답 (0.05초)
### 2. Redis 복구 후
```bash
# 1. .env 파일 원복
SESSION_FORCE_DATABASE = false
# 2. PHP-FPM 재시작
docker exec projects-admin_confirms kill -USR2 1
```
### 3. 모니터링
#### 로그 확인
```bash
# 세션 관련 로그 확인
docker exec projects-admin_confirms tail -f /var/www/html/writable/logs/log-$(date +%Y-%m-%d).log | grep -i session
# Redis 폴백 경고 확인
docker exec projects-admin_confirms grep "Redis unavailable" /var/www/html/writable/logs/log-$(date +%Y-%m-%d).log
```
#### 예상 로그 메시지
**Redis 정상:**
```
DEBUG - Session: Class initialized using 'CodeIgniter\Session\Handlers\RedisHandler' driver.
```
**Redis 장애 (자동 폴백):**
```
WARNING - Session: Redis unavailable (Redis connection failed), falling back to DatabaseHandler
DEBUG - Session: Class initialized using 'CodeIgniter\Session\Handlers\DatabaseHandler' driver.
```
**Database 강제 모드:**
```
INFO - Session: Forced to use DatabaseHandler (SESSION_FORCE_DATABASE=true)
DEBUG - Session: Class initialized using 'CodeIgniter\Session\Handlers\DatabaseHandler' driver.
```
### 4. 성능 모니터링
```bash
# 로그인 응답 시간 측정
for i in {1..5}; do
curl -X POST http://localtest2-admin.confirms.co.kr/login/chkLogin \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "user_id=test&user_pw=test" \
-o /dev/null -s -w " Time: %{time_total}s\n"
done
```
**기대 성능:**
- Redis 정상: 0.03 ~ 0.05초
- Database (수동 전환): 0.05 ~ 0.08초
- 자동 폴백 (Redis 다운): 0.5 ~ 1.0초
## 사용자 알림
### 프론트엔드 경고 표시
Redis 장애 시 로그인 페이지에 자동으로 경고 배너가 표시됩니다:
```javascript
// login.php의 JavaScript
function checkSystemStatus(responseData) {
if (responseData.system && responseData.system.redis_fallback) {
// 경고 메시지: "세션 서버(Redis) 장애로 임시 모드로 운영 중입니다."
showRedisWarning(responseData.system.warning_message);
}
}
```
**API 응답 예시:**
```json
{
"code": "0",
"msg": "success",
"system": {
"redis_fallback": true,
"warning_message": "세션 서버(Redis) 장애로 임시 모드로 운영 중입니다. 시스템 관리자에게 문의하세요."
}
}
```
## 트러블슈팅
### 문제: 로그인이 느림 (2~3초)
**원인**: Redis 다운 상태에서 자동 폴백 모드 사용 중
**해결**:
```bash
# SESSION_FORCE_DATABASE=true로 수동 전환
echo "SESSION_FORCE_DATABASE = true" >> /path/to/src/.env
```
### 문제: 세션이 유지되지 않음
**원인**: ci_sessions 테이블이 없거나 권한 문제
**해결**:
```sql
-- 테이블 존재 확인
SHOW TABLES LIKE 'ci_sessions';
-- 권한 확인
SHOW GRANTS FOR 'confirms'@'%';
-- 테이블이 없는 경우 생성
CREATE TABLE `ci_sessions` (
`session_id` VARCHAR(40) NOT NULL DEFAULT '0',
`ip_address` VARCHAR(16) NOT NULL DEFAULT '0',
`user_agent` VARCHAR(120) NOT NULL,
`last_activity` INT UNSIGNED NOT NULL DEFAULT 0,
`user_data` VARCHAR(4000) NOT NULL,
PRIMARY KEY (`session_id`)
) ENGINE = MEMORY;
CREATE INDEX `last_activity_idx`
ON `ci_sessions` (`last_activity` ASC);
```
### 문제: 서버 재시작 후 모든 세션이 사라짐
**원인**: ci_sessions가 MEMORY 엔진을 사용 (정상 동작)
**설명**:
- MEMORY 엔진은 서버 재시작 시 데이터가 삭제됩니다
- 세션은 일시적 데이터이므로 일반적으로 문제가 되지 않습니다
- 사용자는 재로그인하면 됩니다
**영구 저장이 필요한 경우**:
```sql
-- InnoDB로 변경 (성능은 약간 느려짐)
ALTER TABLE ci_sessions ENGINE=InnoDB;
```
### 문제: Redis 연결 타임아웃이 너무 김
**원인**: redis_helper.php의 타임아웃 설정
**해결**:
```php
// src/app/Helpers/redis_helper.php
$timeout = 0.5; // 0.1 ~ 1.0 사이 값으로 조정
$redis->connect($host, $port, $timeout);
```
## 관련 파일
| 파일 | 역할 |
|------|------|
| `src/app/Config/Session.php` | 세션 설정 및 폴백 로직 |
| `src/app/Helpers/redis_helper.php` | Redis 연결 헬퍼 함수 |
| `src/app/Controllers/Login.php` | 로그인 및 시스템 상태 체크 |
| `src/app/Views/pages/login.php` | 프론트엔드 경고 표시 |
| `src/.env` | 환경 설정 (Redis 연결 정보, 강제 모드) |
## Worker 및 API (참고)
### Worker (NaverWorker.php)
- Redis 큐 사용 (DB 9)
- Redis 장애 시 파일 기반 폴백 (`worker/fallback_queue/*.json`)
### API (api_receiver.php, KisoController.php)
- Redis 큐 사용 (DB 9)
- Redis 장애 시 파일 기반 폴백 (`worker/fallback_queue/*.json`)
## 추가 개선 사항
### 고려 중인 기능
1. **Redis Sentinel 도입** (고가용성)
- 자동 장애 조치
- 마스터/슬레이브 구조
2. **APM 연동**
- New Relic, DataDog 등
- 실시간 성능 모니터링
3. **헬스체크 엔드포인트**
```php
// GET /health/session
{
"status": "ok",
"driver": "RedisHandler",
"redis_available": true,
"response_time_ms": 12
}
```
## 문의
세션 관련 문제 발생 시:
1. 로그 확인 (`/writable/logs/`)
2. Redis 서버 상태 확인
3. ci_sessions 테이블 상태 확인
4. 필요 시 수동 전환 수행
---
**Last Updated**: 2026-03-25
**Version**: 1.0.0

209
app/Commands/NaverRetry.php Normal file
View File

@@ -0,0 +1,209 @@
<?php
namespace App\Commands;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use App\Models\Entities\NaverWorkerLogModel;
class NaverRetry extends BaseCommand
{
protected $group = 'Workers';
protected $name = 'naver:retry';
protected $description = '실패한 Naver Worker 작업을 재처리합니다.';
protected $usage = 'naver:retry [log_id] [options]';
protected $arguments = [
'log_id' => '재처리할 특정 로그 ID (선택사항, 없으면 모든 실패 건 재처리)'
];
protected $options = [
'--limit' => '재처리할 최대 개수 (기본: 10)',
'--dry-run' => '실제 실행 없이 목록만 확인',
'--force' => '재시도 횟수 제한 무시 (3회 이상 실패한 건도 재처리)'
];
public function run(array $params)
{
helper(['log']);
$logId = $params[0] ?? null;
$limit = CLI::getOption('limit') ?? 10;
$isDryRun = CLI::getOption('dry-run') !== null;
$isForce = CLI::getOption('force') !== null;
$logModel = model(NaverWorkerLogModel::class);
$naverService = new \App\Services\NaverService();
// 1. 특정 ID 재처리
if ($logId) {
$this->retryOne($logModel, $naverService, $logId, $isDryRun, $isForce);
return;
}
// 2. 실패한 모든 작업 재처리
$this->retryFailed($logModel, $naverService, $limit, $isDryRun, $isForce);
}
/**
* 특정 로그 ID 재처리
*/
protected function retryOne($logModel, $naverService, $logId, $isDryRun, $isForce)
{
$log = $logModel->find($logId);
if (!$log) {
CLI::error("❌ Log ID {$logId} not found");
return;
}
CLI::write(CLI::color("📋 Log ID: {$logId}", 'cyan'));
CLI::write(" Status: {$log['status']}");
CLI::write(" Retry Count: " . ($log['retry_cnt'] ?? 0));
CLI::write(" Error: {$log['error_msg']}");
CLI::write(" Created: {$log['created_at']}");
// 재시도 횟수 체크
if (!$isForce && ($log['retry_cnt'] ?? 0) >= 3) {
CLI::write(CLI::color('⚠️ 이미 3회 이상 재시도했습니다. --force 옵션으로 강제 실행 가능', 'yellow'));
return;
}
// 재시도 불가능한 오류 체크
if (!$isForce && $this->isNonRetryableError($log['error_msg'])) {
CLI::write(CLI::color('⚠️ 재시도 불가능한 오류입니다. 데이터를 먼저 수정해주세요.', 'yellow'));
CLI::write(CLI::color(' (--force 옵션으로 강제 실행 가능)', 'yellow'));
return;
}
if ($isDryRun) {
CLI::write(CLI::color('🔍 DRY RUN - 재처리하지 않음', 'yellow'));
return;
}
$this->processLog($logModel, $naverService, $log);
}
/**
* 재시도 불가능한 오류인지 확인
*/
protected function isNonRetryableError($errorMsg)
{
if (!$errorMsg) return false;
// 데이터 수정이 필요한 오류 패턴
$nonRetryablePatterns = [
'foreign key constraint',
'Duplicate entry',
'빈 페이로드',
'usr_sq.*users 테이블에 없음', // 이미 폴백 적용된 건은 재시도 가능
];
foreach ($nonRetryablePatterns as $pattern) {
if (stripos($errorMsg, $pattern) !== false) {
return true;
}
}
return false;
}
/**
* 실패한 모든 작업 재처리
*/
protected function retryFailed($logModel, $naverService, $limit, $isDryRun, $isForce)
{
$query = $logModel->where('status', 'FAIL');
// force가 아닌 경우 재시도 횟수 3회 미만만 조회
if (!$isForce) {
$query->groupStart()
->where('retry_cnt IS NULL')
->orWhere('retry_cnt <', 3)
->groupEnd();
}
$failedLogs = $query->orderBy('created_at', 'ASC')->findAll($limit);
if (empty($failedLogs)) {
CLI::write(CLI::color('✅ 재처리할 실패 작업이 없습니다.', 'green'));
return;
}
CLI::write(CLI::color("📋 실패한 작업 {count}건 발견", 'cyan')
->replace('{count}', count($failedLogs)));
if ($isDryRun) {
foreach ($failedLogs as $log) {
CLI::write(" - ID: {$log['seq']} | Atcl: {$log['atcl_no']} | Error: {$log['error_msg']}");
}
CLI::write(CLI::color('🔍 DRY RUN - 재처리하지 않음', 'yellow'));
return;
}
// 실제 재처리
$successCount = 0;
$failCount = 0;
foreach ($failedLogs as $log) {
CLI::write(CLI::color("\n🔄 Retrying Log ID: {$log['seq']}", 'yellow'));
$result = $this->processLog($logModel, $naverService, $log);
if ($result) {
$successCount++;
} else {
$failCount++;
}
// 과부하 방지
sleep(1);
}
CLI::write(CLI::color("\n✅ 재처리 완료: 성공 {$successCount}건, 실패 {$failCount}", 'green'));
}
/**
* 로그 데이터 재처리
*/
protected function processLog($logModel, $naverService, $log)
{
try {
// 상태를 RETRY로 변경
$logModel->update($log['seq'], ['status' => 'RETRY']);
// JSON 파싱
$responseJson = json_decode($log['raw_payload'], true);
$payload = $responseJson['request_data'] ?? [];
if (empty($payload)) {
throw new \Exception("빈 페이로드 데이터");
}
CLI::write(" Article: {$payload['articleNumber']}");
// 재처리
$insertId = $naverService->processArticle($payload);
// 성공 시 로그 업데이트
$logModel->update($log['seq'], [
'atcl_no' => $payload['articleNumber'] ?? null,
'status' => 'SUCCESS',
'target_db_id' => $insertId,
'error_msg' => null
]);
CLI::write(CLI::color(" ✅ Success! DB ID: {$insertId}", 'green'));
return true;
} catch (\Exception $e) {
// 실패 시 로그 업데이트
$logModel->update($log['seq'], [
'status' => 'FAIL',
'error_msg' => $e->getMessage()
]);
CLI::error(" ❌ Failed: " . $e->getMessage());
return false;
}
}
}

View File

@@ -17,93 +17,281 @@ class NaverWorker extends BaseCommand
// DB 객체를 담을 변수 선언
protected $db;
protected $redisHost;
protected $redisPort;
protected $redisDatabase;
public function run(array $params)
{
helper('log'); // 여기서 로드 완료!
helper(['log', 'redis']); // redis helper 추가
$this->db = \Config\Database::connect();
// 워커 시작 시점에 선제적으로 연결 상태를 보정
try {
$this->db->initialize();
} catch (\Throwable $e) {
CLI::error('Database connection init failed: ' . $e->getMessage());
}
$logModel = model(NaverWorkerLogModel::class);
$naverService = new \App\Services\NaverService(); // 서비스 생성
$redis = new \Redis();
try {
$redis->connect('redis', 6379);
$redis->select(9);
CLI::write(CLI::color('🟢 Naver Worker running...', 'green'));
} catch (\Exception $e) {
CLI::error("Redis 연결 불가: " . $e->getMessage());
return;
// Redis 연결 (실패해도 계속 진행 - 파일 모드로 동작 가능)
$redis = get_redis_connection('worker');
$config = get_redis_config('worker');
if ($redis) {
CLI::write(CLI::color('🟢 Naver Worker running... (Redis: ' . $config['host'] . ':' . $config['port'] . ' DB:' . $config['database'] . ')', 'green'));
} else {
CLI::write(CLI::color('⚠️ Naver Worker running in FILE-ONLY mode (Redis unavailable)', 'yellow'));
}
while (true) {
// 1. DB 연결 상태 체크 (더 견고하게)
try {
// connID가 없거나, 가벼운 쿼리 실행 실패 시 재연결 시도
if ($this->db->connID === false || !$this->db->simpleQuery('SELECT 1')) {
$this->db->reconnect();
CLI::write(CLI::color('🔄 Database reconnected.', 'yellow'));
// Redis 또는 폴백 파일에서 데이터 읽기
$rawData = null;
$source = 'redis'; // 데이터 소스 추적
// 1. Redis에서 데이터 읽기 시도 (Redis가 있을 경우만)
if ($redis) {
$maxRetries = 2;
$retryCount = 0;
while ($retryCount < $maxRetries) {
try {
$result = $redis->brPop(['naver:raw_queue'], 5); // 5초 타임아웃
if ($result) {
$rawData = $result[1];
$source = 'redis';
}
break; // 성공하면 루프 탈출
} catch (\Exception $e) {
$retryCount++;
CLI::write(CLI::color("⚠️ Redis error (attempt {$retryCount}/{$maxRetries}): " . $e->getMessage(), 'yellow'));
if ($retryCount >= $maxRetries) {
CLI::write(CLI::color("⚠️ Redis unavailable, switching to file mode", 'yellow'));
$redis = null; // Redis를 비활성화
break;
}
// Redis 재연결 시도
try {
CLI::write(CLI::color('🔄 Reconnecting to Redis...', 'yellow'));
$redis->close();
$redis = get_redis_connection('worker');
if ($redis) {
CLI::write(CLI::color('✅ Redis reconnected', 'green'));
}
} catch (\Exception $reconnectError) {
CLI::error("Redis reconnection error: " . $reconnectError->getMessage());
}
}
}
} catch (\Throwable $e) {
// 어떤 이유로든 에러 발생 시 재연결 시도
$this->db->reconnect();
}
$result = $redis->brPop(['naver:raw_queue'], 30);
// 2. Redis에서 데이터 없으면 폴백 파일 확인
if (!$rawData) {
$rawData = $this->readFromFallbackFile();
if ($rawData) {
$source = 'file';
}
}
if (!$result) {
// 데이터가 없어서 타임아웃 난 경우.
// 굳이 sleep 안 해도 바로 다음 brPop이 다시 30초 대기를 시작함.
// 3. 데이터 없으면 다음 루프
if (!$rawData) {
continue;
}
if ($result) {
$rawData = $result[1];
// [1] 꺼내자마자 DB에 원문 저장 (2차 임시 저장)
// 4. 데이터 소스 로깅
CLI::write(CLI::color("📥 Data received from: " . strtoupper($source), 'cyan'));
// [1] 꺼내자마자 DB에 원문 저장 (2차 임시 저장) - 실패 시 재시도
try {
$logId = $logModel->insert([
'raw_payload' => $rawData,
'status' => 'INIT'
]);
} catch (\Throwable $e) {
// MySQL 연결 계열 에러 시 재연결 후 재시도
if ($this->isMySqlConnectionError($e)) {
CLI::write(CLI::color('⚠️ MySQL gone away, reconnecting...', 'yellow'));
$this->db->close();
$this->db = \Config\Database::connect();
$this->db->initialize();
$logModel = model(NaverWorkerLogModel::class);
// 재시도
$logId = $logModel->insert([
'raw_payload' => $rawData,
'status' => 'INIT'
]);
} else {
throw $e; // 다른 에러면 그대로 throw
}
}
try {
$responseJson = json_decode($rawData, true);
$payload = $responseJson['request_data'] ?? [];
if (empty($payload)) {
throw new \Exception("빈 페이로드 데이터");
}
// 서비스의 함수 하나로 모든 처리 완료
$insertId = $naverService->processArticle($payload);
// [3] 성공 시 로그 업데이트 (재연결 처리 포함)
$this->safeUpdateLog($logModel, $logId, [
'atcl_no' => $payload['articleNumber'] ?? null,
'status' => 'SUCCESS',
'target_db_id' => $insertId
]);
CLI::write("✅ Success! DB ID: $insertId | Source: $source", 'cyan');
} catch (\Exception $e) {
CLI::error("❌ Task Failed: " . $e->getMessage());
// payload에서 매물번호 추출 시도
$atclNo = null;
try {
$responseJson = json_decode($result[1], true);
$payload = $responseJson['request_data'] ?? [];
if (empty($payload)) {
throw new \Exception("빈 페이로드 데이터");
if (!empty($rawData)) {
$responseJson = json_decode($rawData, true);
$payload = $responseJson['request_data'] ?? [];
$atclNo = $payload['articleNumber'] ?? null;
}
} catch (\Exception $parseEx) {
// JSON 파싱 실패는 무시
}
// 실패 로그는 여기서 남김
// 1. DB 상태를 FAIL로 업데이트 (필수) (재연결 처리 포함)
$this->safeUpdateLog($logModel, $logId, [
'atcl_no' => $atclNo,
'status' => 'FAIL',
'error_msg' => $e->getMessage()
]);
// 서비스의 함수 하나로 모든 처리 완료
$insertId = $naverService->processArticle($payload);
// 2. Redis 실패 큐에 백업 (선택 - Redis가 있을 경우만)
if ($redis) {
try {
$redis->lPush('naver:failed_queue', $rawData);
} catch (\Exception $redisEx) {
// Redis 실패 시에도 에러 처리하지 않음 (이미 DB에 FAIL 로그 남김)
CLI::write(CLI::color('⚠️ Failed to push to failed_queue: ' . $redisEx->getMessage(), 'yellow'));
}
}
helper('log');
write_custom_log("FAILED_DATA | Error: " . $e->getMessage(), 'ERROR', 'failed');
// [3] 성공 시 로그 업데이트
$logModel->update($logId, [
'atcl_no' => $payload['articleNumber'] ?? null,
'status' => 'SUCCESS',
'target_db_id' => $insertId
]);
// 루프 과부하 방지 (연속 에러 시)
sleep(1);
}
}
}
/**
* MySQL 연결 계열 에러 발생 시 재연결 후 재시도하는 안전한 update
*/
protected function safeUpdateLog($logModel, $logId, $data)
{
try {
return $logModel->update($logId, $data);
} catch (\Throwable $e) {
if ($this->isMySqlConnectionError($e)) {
CLI::write(CLI::color('⚠️ MySQL gone away on update, reconnecting...', 'yellow'));
$this->db->close();
$this->db = \Config\Database::connect();
$this->db->initialize();
$logModel = model(\App\Models\Entities\NaverWorkerLogModel::class);
CLI::write("✅ Success! DB ID: $insertId", 'cyan');
} catch (\Exception $e) {
CLI::error("❌ Task Failed: " . $e->getMessage());
// 실패 로그는 여기서 남김
// 1. DB 상태를 FAIL로 업데이트 (필수)
$logModel->update($logId, ['status' => 'FAIL', 'error_msg' => $e->getMessage()]);
// 2. Redis 실패 큐에 백업 (선택 - 나중에 모아서 다시 던질 때 편함)
$redis->lPush('naver:failed_queue', $rawData);
helper('log');
write_custom_log("FAILED_DATA | Error: " . $e->getMessage(), 'ERROR', 'failed');
// 루프 과부하 방지 (연속 에러 시)
sleep(1);
// 재시도
return $logModel->update($logId, $data);
} else {
throw $e;
}
}
}
/**
* MySQL 연결 끊김 계열 에러 여부 판별
*/
protected function isMySqlConnectionError(\Throwable $e): bool
{
$message = strtolower($e->getMessage());
return str_contains($message, 'mysql server has gone away')
|| str_contains($message, 'lost connection to mysql server')
|| str_contains($message, 'server has gone away');
}
/**
* 폴백 파일에서 데이터 읽기 (Redis 장애 시 파일에서 직접 처리)
*
* @return string|null JSON 데이터 또는 null
*/
protected function readFromFallbackFile()
{
$fallbackDir = ROOTPATH . 'worker/fallback_queue';
// 폴백 디렉토리가 없으면 null 반환
if (!is_dir($fallbackDir)) {
return null;
}
// 폴백 파일 목록 가져오기 (오래된 순서대로)
$files = glob($fallbackDir . '/*.json');
if (empty($files)) {
return null;
}
sort($files); // 파일명(타임스탬프) 기준 정렬
// 가장 오래된 파일 하나 처리
$filePath = $files[0];
try {
// 파일 락을 사용하여 읽기 (동시 접근 방지)
$fp = fopen($filePath, 'r');
if (!$fp) {
CLI::write(CLI::color("⚠️ Failed to open fallback file: " . basename($filePath), 'yellow'));
return null;
}
// 배타적 락 획득 시도
if (!flock($fp, LOCK_EX | LOCK_NB)) {
// 락 획득 실패 (다른 프로세스가 처리 중)
fclose($fp);
return null;
}
// 파일 내용 읽기
$content = stream_get_contents($fp);
// 파일 삭제 (처리 완료로 간주)
flock($fp, LOCK_UN);
fclose($fp);
unlink($filePath);
CLI::write(CLI::color("📂 Processing fallback file: " . basename($filePath), 'green'));
return $content;
} catch (\Exception $e) {
CLI::write(CLI::color("❌ Error reading fallback file " . basename($filePath) . ": " . $e->getMessage(), 'red'));
if (isset($fp) && is_resource($fp)) {
flock($fp, LOCK_UN);
fclose($fp);
}
return null;
}
}

32
app/Commands/TestLog.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
namespace App\Commands;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
class TestLog extends BaseCommand
{
protected $group = 'Test';
protected $name = 'test:log';
protected $description = 'Test log_message function';
public function run(array $params)
{
CLI::write('Testing log_message()...', 'yellow');
log_message('error', '===== TEST LOG MESSAGE FROM CLI ===== ' . date('Y-m-d H:i:s'));
log_message('debug', 'Debug level test');
log_message('info', 'Info level test');
$logFile = WRITEPATH . 'logs/log-' . date('Y-m-d') . '.log';
CLI::write('Log file: ' . $logFile);
CLI::write('Exists: ' . (file_exists($logFile) ? 'YES' : 'NO'));
if (file_exists($logFile)) {
CLI::write('Last 10 lines:');
CLI::write(shell_exec('tail -10 ' . escapeshellarg($logFile)));
}
}
}

View File

@@ -159,13 +159,13 @@ class Cache extends BaseConfig
{
parent::__construct();
// Redis 설정에 .env 값을 할당 (이전 논의된 Docker 호스트 이름 'redis' 사용)
// Redis 설정에 환경 변수 값을 할당 (두 가지 환경 변수 형식 지원)
$this->redis = [
'host' => env('redis.default.host', '127.0.0.1'),
'password' => (env('redis.default.password') === '' || env('redis.default.password') === null) ? null : env('redis.default.password'),
'port' => (int)env('redis.default.port', 6379),
'timeout' => (int)env('redis.default.timeout', 0),
'database' => (int)env('redis.default.database', 0)
'host' => env('REDIS_HOST', env('redis.default.host', '127.0.0.1')),
'password' => env('REDIS_PASSWORD', env('redis.default.password')) ?: null,
'port' => (int)(env('REDIS_PORT', env('redis.default.port', 6379))),
'timeout' => (int)(env('REDIS_TIMEOUT', env('redis.default.timeout', 0))),
'database' => (int)(env('REDIS_DATABASE', env('redis.default.database', 0)))
];
// 필요하다면, 이 생성자에서 $handler나 $backupHandler 같은 다른 설정도

View File

@@ -39,7 +39,7 @@ class Logger extends BaseConfig
*
* @var int|list<int>
*/
public $threshold = (ENVIRONMENT === 'production') ? 4 : 9;
public $threshold = 9; // Always log everything for debugging
/**
* --------------------------------------------------------------------------

View File

@@ -28,6 +28,8 @@ $routes->group('common', ['namespace' => 'App\Controllers\Common'], function ($r
$routes->post('common/changeUserPass', 'Common::changeUserPass'); // 비밀번호변경
$routes->get('getComplexList', 'Common::getComplexList'); // 단지목록조회
$routes->get('getPyeongInfo', 'Common::getPyeongInfo'); // 평형정보조회
});
@@ -76,12 +78,16 @@ $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('getRecInfo', 'Receipt::getRecInfo'); // 거주인정보조회
$routes->get('getHistory', 'Receipt::getHistory'); // 정보변경이력조회
$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'); // 촬영위치 저장
$routes->post('modifyPriceInfo', 'Receipt::modifyPriceInfo'); // 가격정보 수정
});
/**
@@ -695,3 +701,12 @@ $routes->post('/login/chkLogin', 'Login::chkLogin');
if (is_file($filepath = APPPATH . 'Config/Routes/Api.php')) {
require $filepath;
}
/**
* Worker 관리
*/
$routes->group('manage/worker', ['namespace' => 'App\Controllers\Manage'], function ($routes) {
$routes->get('failed', 'WorkerLogController::failedList');
$routes->post('retry', 'WorkerLogController::retrySelected');
$routes->get('detail/(:num)', 'WorkerLogController::detail/$1');
});

View File

@@ -6,5 +6,5 @@ use CodeIgniter\Router\RouteCollection;
/** @var RouteCollection $routes */
$routes->group('kiso', function(RouteCollection $routes) {
$routes->match(['get', 'post'], 'api/vrfcReq', 'KisoController::vrfcReq');
$routes->match(['GET', 'POST'], 'api/vrfcReq', 'KisoController::vrfcReq');
});

View File

@@ -4,7 +4,7 @@ namespace Config;
use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Session\Handlers\BaseHandler;
use CodeIgniter\Session\Handlers\FileHandler;
use CodeIgniter\Session\Handlers\DatabaseHandler;
use CodeIgniter\Session\Handlers\RedisHandler;
class Session extends BaseConfig
@@ -15,14 +15,11 @@ class Session extends BaseConfig
* --------------------------------------------------------------------------
*
* The session storage driver to use:
* - `CodeIgniter\Session\Handlers\FileHandler`
* - `CodeIgniter\Session\Handlers\DatabaseHandler`
* - `CodeIgniter\Session\Handlers\MemcachedHandler`
* - `CodeIgniter\Session\Handlers\RedisHandler`
*
* @var class-string<BaseHandler>
*/
// public string $driver = FileHandler::class;
public string $driver = RedisHandler::class;
/**
@@ -51,16 +48,61 @@ class Session extends BaseConfig
*
* The location to save sessions to and is driver dependent.
*
* For the 'files' driver, it's a path to a writable directory.
* WARNING: Only absolute paths are supported!
*
* For the 'database' driver, it's a table name.
* Please read up the manual for the format with other session drivers.
* For Redis: tcp://host:port?database=n&password=xxx
*
* IMPORTANT: You are REQUIRED to set a valid save path!
*/
// public string $savePath = WRITEPATH . 'session';
public string $savePath = 'tcp://192.168.10.243:6379?database=0';
public string $savePath;
public function __construct()
{
parent::__construct();
// 환경변수로 강제로 Database 모드 설정 가능 (Redis 장애 시 수동 전환)
$forceDatabase = env('SESSION_FORCE_DATABASE', false);
// Redis 설정: Redis 연결 실패 시 Database로 폴백
if ($this->driver === RedisHandler::class && !$forceDatabase) {
helper('redis');
try {
// Redis 연결 테스트
$testRedis = get_redis_connection('session');
if (!$testRedis) {
throw new \Exception('Redis connection failed');
}
// Redis 정상 - Redis 설정 사용
$config = get_redis_config('session');
$this->savePath = sprintf(
'tcp://%s:%s?database=%s',
$config['host'],
$config['port'],
$config['database']
);
if (!empty($config['password'])) {
$this->savePath .= '&password=' . $config['password'];
}
} catch (\Exception $e) {
// Redis 실패 - DatabaseHandler로 폴백
log_message('warning', 'Session: Redis unavailable (' . $e->getMessage() . '), falling back to DatabaseHandler');
$this->driver = DatabaseHandler::class;
$this->savePath = 'ci_sessions'; // 테이블 이름
}
} else {
// Database 강제 모드 또는 기본 Database 설정
if ($forceDatabase && $this->driver === RedisHandler::class) {
log_message('info', 'Session: Forced to use DatabaseHandler (SESSION_FORCE_DATABASE=true)');
$this->driver = DatabaseHandler::class;
}
$this->savePath = 'ci_sessions';
}
}
/**
* --------------------------------------------------------------------------

View File

@@ -7,210 +7,242 @@ use App\Models\article\ProcessibleModel;
class Processible extends BaseController
{
private $model;
private $model;
public function __construct()
{
$this->model = new ProcessibleModel();
}
public function __construct()
{
$this->model = new ProcessibleModel();
}
public function datecount(): string
{
public function datecount(): string
{
$sido = $this->model->getAreaList(); // 지역조회
$sido = $this->model->getAreaList(); // 지역조회
$this->data['sido'] = $sido;
$this->data['sido'] = $sido;
return view("pages/article/processible/datecount", $this->data);
}
return view("pages/article/processible/datecount", $this->data);
}
public function getList1()
{
$start = (int) $this->request->getGet('start') ?: 0;
$end = (int) $this->request->getGet('length') ?: 10;
public function getList1()
{
$start = (int) $this->request->getGet('start') ?: 0;
$end = (int) $this->request->getGet('length') ?: 10;
$data = [
'sdate' => $this->request->getGet('sdate'), // 시작일
'edate' => $this->request->getGet('edate'), // 종료일
];
$data = [
'sdate' => $this->request->getGet('sdate'), // 시작일
'edate' => $this->request->getGet('edate'), // 종료일
];
$totalCount = $this->model->getTotal1($data);
$totalCount = $this->model->getTotal1($data);
$datas = $this->model->getList1($start, $end, $data);
$datas = $this->model->getList1($start, $end, $data);
return $this->response->setJSON(body: [
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
'data' => $datas,
]);
}
return $this->response->setJSON(body: [
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
'data' => $datas,
]);
}
public function getList2()
{
// $start = (int) $this->request->getGet('start') ?: 0;
// $end = (int) $this->request->getGet('length') ?: 10;
public function getList2()
{
// $start = (int) $this->request->getGet('start') ?: 0;
// $end = (int) $this->request->getGet('length') ?: 10;
$data = [
'sdate' => $this->request->getGet('sdate'), // 시작일
'region' => $this->request->getGet('region'), // 종료일
];
$data = [
'sdate' => $this->request->getGet('sdate'), // 시작일
'region' => $this->request->getGet('region'), // 종료일
];
$totalCount = $this->model->getTotal2($data);
$totalCount = $this->model->getTotal2($data);
$datas = $this->model->getList2($data);
$datas = $this->model->getList2($data);
return $this->response->setJSON(body: [
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
'data' => $datas,
]);
}
return $this->response->setJSON(body: [
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
'data' => $datas,
]);
}
public function getList3()
{
public function getList3()
{
$data = [
'region' => $this->request->getGet('region'), // 종료일
];
$data = [
'region' => $this->request->getGet('region'), // 종료일
];
$totalCount = $this->model->getTotal3($data);
$totalCount = $this->model->getTotal3($data);
$datas = $this->model->getList3($data);
$datas = $this->model->getList3($data);
return $this->response->setJSON(body: [
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
'data' => $datas,
]);
}
return $this->response->setJSON(body: [
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
'data' => $datas,
]);
}
// 엑셀다운로드
public function excel()
{
try {
public function excel()
{
try {
$data = [
'sdate' => $this->request->getGet('sdate'),
'edate' => $this->request->getGet('edate'),
];
$data = [
'sdate' => $this->request->getGet('sdate'),
'edate' => $this->request->getGet('edate'),
];
$datas = $this->model->getExcelList($data);
$datas = $this->model->getExcelList($data);
return $this->response->setJSON(body: [
'data' => $datas,
]);
return $this->response->setJSON(body: [
'data' => $datas,
]);
} catch (\Exception $e) {
$e->getPrevious()->getTraceAsString();
} catch (\Exception $e) {
$e->getPrevious()->getTraceAsString();
}
}
}
// 지역별 수량 저장
public function saveArea()
{
log_message('info', '[Processible::saveArea] 진입');
try {
$rows = $this->request->getPost('rows');
$rows = json_decode($rows, true);
if (count($rows) > 0) {
// API 형식으로 변환 및 DB 저장을 한 번에 처리
$syncSlotData = [];
foreach ($rows as $row) {
// DB 저장
$this->model->saveArea($row);
// 동시에 API 데이터 구성
$syncSlotData[] = [
'baseDate' => $row['sc_date'],
'legalDivisionNumber' => $row['region_cd'],
'slots' => [
'am' => [
'max' => (int) $row['am_cnt'],
'reserved' => 0
],
'pm' => [
'max' => (int) $row['pm_cnt'],
'reserved' => 0
]
]
];
}
log_message('info', '[Processible::saveArea] 슬롯 동기화 시작 | Count: ' . count($syncSlotData));
$naverClient = new \App\Libraries\NaverApiClient();
$apiResponse = $naverClient->syncSiteSlot($syncSlotData);
// API 응답 처리
if ($apiResponse === null) {
log_message('error', '[Processible::saveArea] Naver API 슬롯 동기화 실패 | Data: ' . json_encode($syncSlotData, JSON_UNESCAPED_UNICODE));
return $this->response->setJSON([
'code' => '1',
'msg' => '저장은 완료되었으나 네이버 슬롯 동기화에 실패했습니다. 로그를 확인하세요.',
'syncData' => $syncSlotData,
'hint' => '로그 위치: writable/logs/ 에서 ERROR 확인'
]);
}
log_message('info', '[Processible::saveArea] Naver API 슬롯 동기화 성공 | Response: ' . json_encode($apiResponse, JSON_UNESCAPED_UNICODE));
return $this->response->setJSON([
'code' => '0',
'msg' => 'success',
'syncData' => $syncSlotData,
'apiResponse' => $apiResponse
]);
} else {
if (empty($rows)) {
log_message('info', '[Processible::saveArea] 저장가능한 데이터가 없습니다.');
return $this->response->setJSON([
'code' => '9',
'msg' => '저장가능한 데이터가 없습니다.'
]);
}
} catch (\Exception $e) {
log_message('error', '[Processible::saveArea] 예외: ' . $e->getMessage());
return $this->response->setJSON([
'code' => '9',
'msg' => $e->getMessage(),
]);
}
}
public function saveCount()
{
try {
$rows = $this->request->getPost('rows');
$rows = json_decode($rows, true);
if (count($rows) > 0) {
// API 형식으로 변환 및 DB 저장
$syncSlotData = [];
foreach ($rows as $row) {
// DB 저장 (API 실패와 무관하게 저장됨)
$this->model->saveArea($row);
// API 데이터 구성
$syncSlotData[] = [
'baseDate' => $row['sc_date'],
'legalDivisionNumber' => $row['region_cd'],
'slots' => [
'am' => [
'max' => (int) $row['am_cnt'],
'reserved' => 0
],
'pm' => [
'max' => (int) $row['pm_cnt'],
'reserved' => 0
]
]
];
}
$results = [];
log_message('info', '[Processible::saveArea] DB 저장 완료 | Count: ' . count($rows));
// 네이버 API 슬롯 동기화 시도
log_message('info', '[Processible::saveArea] 슬롯 동기화 시작 | Count: ' . count($syncSlotData));
$naverClient = new \App\Libraries\NaverApiClient();
$apiResponse = $naverClient->syncSiteSlot($syncSlotData);
// API 응답 에러 체크
$hasError = false;
$errorMessage = '';
foreach ($rows as $row):
$result = $this->model->saveCount($row);
$results[] = $result;
if (!$result['success']) {
if ($apiResponse === null) {
$hasError = true;
}
endforeach;
$errorMessage = 'API 응답 없음 (null)';
} elseif (!empty($apiResponse['error'])) {
$hasError = true;
$errorType = $apiResponse['error_type'] ?? 'UNKNOWN';
$httpCode = $apiResponse['http_code'] ?? 'N/A';
$errorMessage = "API Error: {$errorType} (HTTP {$httpCode})";
} elseif (isset($apiResponse['http_code']) && $apiResponse['http_code'] >= 400) {
$hasError = true;
$errorMessage = "HTTP Error: {$apiResponse['http_code']}";
}
// API 에러 발생 시 (DB는 이미 저장됨)
if ($hasError) {
log_message('error', "[Processible::saveArea] {$errorMessage} | Response: " . json_encode($apiResponse, JSON_UNESCAPED_UNICODE));
return $this->response->setJSON([
'code' => '1',
'msg' => 'DB 저장은 완료되었으나 네이버 슬롯 동기화에 실패했습니다. 관리자에게 문의하세요.',
'error' => $errorMessage,
'dbSaved' => true,
'syncData' => $syncSlotData,
'apiResponse' => $apiResponse
]);
}
// 성공
log_message('info', '[Processible::saveArea] Naver API 슬롯 동기화 성공 | Response: ' . json_encode($apiResponse, JSON_UNESCAPED_UNICODE));
return $this->response->setJSON([
'code' => $hasError ? '9' : '0',
'msg' => $hasError ? '일부 저장 실패' : 'success',
'debug' => $results // 디버깅 정보 포함
'code' => '0',
'msg' => 'success',
'dbSaved' => true,
'syncData' => $syncSlotData,
'apiResponse' => $apiResponse
]);
} else {
} catch (\Exception $e) {
log_message('error', '[Processible::saveArea] 예외 발생: ' . $e->getMessage());
return $this->response->setJSON([
'code' => '9',
'msg' => '저장가능한 데이터가 없습니다.'
'code' => '9',
'msg' => '처리 중 오류가 발생했습니다: ' . $e->getMessage(),
]);
}
} catch (\Exception $e) {
return $this->response->setJSON([
'code' => '9',
'msg' => $e->getMessage(),
]);
}
}
public function saveCount()
{
try {
$rows = $this->request->getPost('rows');
$rows = json_decode($rows, true);
if (count($rows) > 0) {
$results = [];
$hasError = false;
foreach ($rows as $row):
$result = $this->model->saveCount($row);
$results[] = $result;
if (!$result['success']) {
$hasError = true;
}
endforeach;
return $this->response->setJSON([
'code' => $hasError ? '9' : '0',
'msg' => $hasError ? '일부 저장 실패' : 'success',
'debug' => $results // 디버깅 정보 포함
]);
} else {
return $this->response->setJSON([
'code' => '9',
'msg' => '저장가능한 데이터가 없습니다.'
]);
}
} catch (\Exception $e) {
return $this->response->setJSON([
'code' => '9',
'msg' => $e->getMessage(),
]);
}
}
}

View File

@@ -9,27 +9,64 @@ use App\Libraries\NaverApiClient;
use App\Models\article\DeptModel;
use App\Models\article\ReceiptModel;
use App\Models\common\CodeModel;
use App\Models\Entities\ReceiptModel as ReceiptEntity;
use App\Models\Entities\ChangedHistoryModel as ChangedHistoryEntity;
use Exception;
class Receipt extends BaseController
{
private $model, $codeModel;
private $model, $entityModel, $codeModel, $changedHistoryEntity;
private $naverApiClient;
private const SEARCH_FILTER_KEYS = [
'rcpt_atclno',
'schDateGb',
'sdate',
'edate',
'bonbu',
'team',
'user',
'sido',
'gugun',
'dong',
'rcpt_stat1',
'rcpt_stat2',
'rcpt_stat3',
'rcpt_product_info1',
'exp_movie_yn',
'conf_img_yn',
'parcel_out_yn',
'rcpt_cpid',
'rcpt_product',
'exp_spc_yn',
'check_list_img_yn',
'ground_plan_yn',
'ground_plan',
'direct_trad_yn',
'image_360_yn',
'srchType',
'srchTxt',
];
public function __construct()
{
$this->model = new ReceiptModel();
$this->entityModel = new ReceiptEntity();
$this->codeModel = new CodeModel();
$this->changedHistoryEntity = new ChangedHistoryEntity();
$this->naverApiClient = new NaverApiClient();
}
public function lists(): string
{
error_log('========== Receipt::lists() CALLED ==========');
log_message('info', '========== Receipt::lists() CALLED ==========');
$usr_id = $this->request->getGet('usr_id') ?: '';
$sBonbu = $this->request->getGet('bonbu') ?: '';
$sTeanm = $this->request->getGet('dept_sq') ?: '';
$sTeam = $this->request->getGet('dept_sq') ?: '';
$codes = $this->codeModel->getCodeLists(['NHN_DEAL_TYPE', 'CP_ID', 'ARTICLE_TYPE', 'VRFCREQ_WAY', 'STEP_VERIFICATION']); // 코드조회
$sido = $this->model->getAreaList(); // 지역조회
$bonbu = $this->model->getBonbuList();
$team = $this->model->getTeamList();
@@ -48,7 +85,8 @@ class Receipt extends BaseController
}
$this->data['sBonbu'] = $sBonbu;
$this->data['sTeanm'] = $sTeanm;
$this->data['sTeam'] = $sTeam;
$this->data['sTeanm'] = $sTeam;
return view("pages/article/receipt/lists", $this->data);
@@ -56,54 +94,21 @@ class Receipt extends BaseController
public function getResultList()
{
error_log('========== Receipt::getResultList() CALLED ==========');
log_message('info', '========== Receipt::getResultList() CALLED ==========');
$start = (int) $this->request->getGet('start') ?: 0;
$end = (int) $this->request->getGet('length') ?: 10;
$data = [
'rcpt_atclno' => $this->request->getGet('rcpt_atclno'), // 매물ID
'schDateGb' => $this->request->getGet('schDateGb'), // 일자유형
'sdate' => $this->request->getGet('sdate'), // 시작일
'edate' => $this->request->getGet('edate'), // 종료일
$data = $this->getSearchFilterData();
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'user' => $this->request->getGet('user'), // 담당자
'sido' => $this->request->getGet('sido'), // 시도
'gugun' => $this->request->getGet('gugun'), // 시군구
'dong' => $this->request->getGet('dong'), // 읍면동
'rcpt_stat1' => $this->request->getGet('rcpt_stat1'), // 상태1
'rcpt_stat2' => $this->request->getGet('rcpt_stat2'), // 상태2
'rcpt_stat3' => $this->request->getGet('rcpt_stat3'), // 상태3
'rcpt_product_info1' => $this->request->getGet('rcpt_product_info1'), // 거래구분
'exp_movie_yn' => $this->request->getGet('exp_movie_yn'), // 동영상촬영여부
'conf_img_yn' => $this->request->getGet('conf_img_yn'), // 홍보확인서여부
'parcel_out_yn' => $this->request->getGet('parcel_out_yn'), // 분양권
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // CPID
'rcpt_product' => $this->request->getGet('rcpt_product'), // 매물종류
'exp_spc_yn' => $this->request->getGet('exp_spc_yn'), // 면적확인
'check_list_img_yn' => $this->request->getGet('check_list_img_yn'), // 체크리스트
'ground_plan_yn' => $this->request->getGet('ground_plan_yn'), // 평면도유무
'ground_plan' => $this->request->getGet('ground_plan'), // 평면도요청
'direct_trad_yn' => $this->request->getGet('direct_trad_yn'), // 직거래
'image_360_yn' => $this->request->getGet('image_360_yn'), // 360촬영여부
'srchType' => $this->request->getGet('srchType'), // 검색유형
'srchTxt' => $this->request->getGet('srchTxt'), // 검색어
];
error_log('[Receipt::getResultList] START - start=' . $start . ' length=' . $end);
log_message('info', '[Receipt::getResultList] START - start=' . $start . ' length=' . $end);
$totalCount = $this->model->getTotalCount($data);
error_log('[Receipt::getResultList] TotalCount = ' . $totalCount);
log_message('info', '[Receipt::getResultList] TotalCount = ' . $totalCount);
$datas = $this->model->getResultList($start, $end, $data);
error_log('[Receipt::getResultList] END - returned ' . count($datas) . ' rows');
log_message('info', '[Receipt::getResultList] END - returned ' . count($datas) . ' rows');
// echo db_connect()->getLastQuery();
return $this->response->setJSON(body: [
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
@@ -116,41 +121,7 @@ class Receipt extends BaseController
public function excel()
{
try {
$data = [
'rcpt_atclno' => $this->request->getGet('rcpt_atclno'), // 매물ID
'schDateGb' => $this->request->getGet('schDateGb'), // 일자유형
'sdate' => $this->request->getGet('sdate'), // 시작일
'edate' => $this->request->getGet('edate'), // 종료일
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'user' => $this->request->getGet('user'), // 담당자
'sido' => $this->request->getGet('sido'), // 시도
'gugun' => $this->request->getGet('gugun'), // 시군구
'dong' => $this->request->getGet('dong'), // 읍면동
'rcpt_stat1' => $this->request->getGet('rcpt_stat1'), // 상태1
'rcpt_stat2' => $this->request->getGet('rcpt_stat2'), // 상태2
'rcpt_stat3' => $this->request->getGet('rcpt_stat3'), // 상태3
'rcpt_product_info1' => $this->request->getGet('rcpt_product_info1'), // 거래구분
'exp_movie_yn' => $this->request->getGet('exp_movie_yn'), // 동영상촬영여부
'conf_img_yn' => $this->request->getGet('conf_img_yn'), // 홍보확인서여부
'parcel_out_yn' => $this->request->getGet('parcel_out_yn'), // 분양권
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // CPID
'rcpt_product' => $this->request->getGet('rcpt_product'), // 매물종류
'exp_spc_yn' => $this->request->getGet('exp_spc_yn'), // 면적확인
'check_list_img_yn' => $this->request->getGet('check_list_img_yn'), // 체크리스트
'ground_plan_yn' => $this->request->getGet('ground_plan_yn'), // 평면도유무
'ground_plan' => $this->request->getGet('ground_plan'), // 평면도요청
'direct_trad_yn' => $this->request->getGet('direct_trad_yn'), // 직거래
'image_360_yn' => $this->request->getGet('image_360_yn'), // 360촬영여부
'srchType' => $this->request->getGet('srchType'), // 검색유형
'srchTxt' => $this->request->getGet('srchTxt'), // 검색어
];
$data = $this->getSearchFilterData();
$datas = $this->model->getExcelList($data);
@@ -159,15 +130,28 @@ class Receipt extends BaseController
]);
} catch (\Exception $e) {
$e->getPrevious()->getTraceAsString();
log_message('error', '[Receipt::excel] Error: ' . $e->getMessage());
return $this->response->setJSON([
'code' => '9',
'msg' => $e->getMessage(),
]);
}
}
private function getSearchFilterData(): array
{
$data = [];
foreach (self::SEARCH_FILTER_KEYS as $key) {
$data[$key] = $this->request->getGet($key);
}
return $data;
}
// 상세화면
public function detail($id)
{
$naver = new NaverApiClient();
$id = (string) $id;
if ($id === '') {
@@ -188,13 +172,16 @@ class Receipt extends BaseController
$damdang = $this->model->getUserList();
$complexList = $this->naverApiClient->complexList($id);
// sms 코드
$sms = [];
foreach ($codes as $c) {
if ($c['category'] === "SMS_MSG_TYPE2")
array_push($sms, $c);
foreach (($codes['SMS_MSG_TYPE2']['items'] ?? []) as $cd => $cdNm) {
$sms[] = [
'category' => 'SMS_MSG_TYPE2',
'cd' => $cd,
'cd_nm' => $cdNm,
];
}
$t3 = microtime(true);
@@ -202,7 +189,8 @@ class Receipt extends BaseController
log_message('info', '[Receipt::detail] getDetail {ms}ms', ['ms' => (int) ((microtime(true) - $t3) * 1000)]);
$t4 = microtime(true);
$history = $this->model->getHistory($id);
// rcpt_sq를 사용하여 이력 조회
$history = $this->model->getHistory($data['rcpt_sq'] ?? $id);
log_message('info', '[Receipt::detail] getHistory {ms}ms', ['ms' => (int) ((microtime(true) - $t4) * 1000)]);
$t5 = microtime(true);
@@ -264,10 +252,10 @@ class Receipt extends BaseController
if ($data['comp_sq'] == '2') {
// 아파트단지목록
$complexList = $naver->complexList($data['rcpt_dong']);
$complexList = $this->naverApiClient->complexList($data['rcpt_dong']);
// 평형목록
$ptpList = $naver->ptpList($data['rcpt_hscp_no']);
$ptpList = $this->naverApiClient->ptpList($data['rcpt_hscp_no']);
}
// print_r($ptpList);
@@ -312,10 +300,13 @@ class Receipt extends BaseController
public function saveTel()
{
try {
$tel = $this->request->getPost('agent_tel');
$this->model->saveTel($tel);
$tel = (string) $this->request->getPost('agent_tel');
$rcpt_sq = (string) $this->request->getPost('rcpt_sq');
if (empty($tel)) {
throw new Exception("전화번호가 입력되지 않았습니다.");
}
$this->model->saveTel($rcpt_sq , $tel);
return $this->response->setJSON([
@@ -334,8 +325,6 @@ class Receipt extends BaseController
// 거주여부 저장
public function resDbYn()
{
$naver = new NaverApiClient();
try {
$rcpt_key = $this->request->getPost('rcpt_key');
@@ -346,18 +335,46 @@ class Receipt extends BaseController
$this->model->saveResDB($rcpt_sq, $rsrv_sq, $res_yn, $dbUsageAgrYn);
$receipt = $this->getDetail($rcpt_key);
$receipt = $this->model->getDetail($rcpt_key);
if ($res_yn == 'Y') {
$isResidentsExist = true;
} else {
$isResidentsExist = false;
}
$api_result = $naver->residentsExistence($rcpt_key, $isResidentsExist);
$updateData = [
'residentsExistence' => $isResidentsExist
];
$charger = session('usr_id');
if (!isset($api_result['result'])) {
throw new \Exception('API 통신오류입니다.\n다시 저장하여 주십시요.');
log_message('info', '[resDbYn] 네이버 API 호출 시작 - rcpt_key: ' . $rcpt_key . ', charger: ' . $charger . ', updateData: ' . json_encode($updateData, JSON_UNESCAPED_UNICODE));
$api_result = $this->naverApiClient->updateArticleInfo($rcpt_key, $updateData, $charger);
log_message('info', '[resDbYn] 네이버 API 응답 - result: ' . json_encode($api_result, JSON_UNESCAPED_UNICODE) . ' (type: ' . gettype($api_result) . ')');
// API 호출 실패 체크
if (isset($api_result['error']) && $api_result['error'] === true) {
// 에러 정보를 상세하게 표시
if ($api_result['error_type'] === 'CURL_ERROR') {
$errorMsg = "네이버 API 통신 실패 (CURL 오류)\n";
$errorMsg .= "오류코드: {$api_result['curl_errno']}\n";
$errorMsg .= "오류내용: {$api_result['curl_error']}\n";
$errorMsg .= "URL: {$api_result['url']}";
} else {
$errorMsg = "네이버 API 응답 오류 (HTTP {$api_result['http_code']})\n";
$errorMsg .= "응답내용: {$api_result['response']}\n";
$errorMsg .= "URL: {$api_result['url']}";
}
throw new \Exception($errorMsg);
}
// 정상 응답이지만 성공이 아닌 경우
if (!isset($api_result['code']) || $api_result['code'] !== 'success') {
$errorMsg = "네이버 API 응답 오류\n";
$errorMsg .= "응답: " . json_encode($api_result, JSON_UNESCAPED_UNICODE);
throw new \Exception($errorMsg);
}
return $this->response->setJSON([
@@ -400,7 +417,6 @@ class Receipt extends BaseController
// 예약확정 저장
public function assignRegist()
{
$naver = new NaverApiClient();
$deptModel = new DeptModel();
try {
@@ -422,10 +438,10 @@ class Receipt extends BaseController
$receipt = $this->model->getDetail($rcpt_key);
/*** 네이버 연동[s] ***/
$na_result = $naver->reserveSuccess($rcpt_key, 'Y', $bonbuInfo['dept_nm'], $deptInfo['dept_nm'], $userInfo['usr_nm'], $userInfo['usr_tel1'], $rsrv_date, $rsrv_tm_ap);
$na_result = $this->naverApiClient->reserveSuccess($rcpt_key, 'Y', $bonbuInfo['dept_nm'], $deptInfo['dept_nm'], $userInfo['usr_nm'], $userInfo['usr_tel1'], $rsrv_date, $rsrv_tm_ap);
/*** 네이버 연동[e] ***/
if (array_key_exists('result', $na_result)) { //네이버연동 상태변경 완료
if (isset($na_result['code']) && $na_result['code'] === 'success') { //네이버연동 상태변경 완료
$result = $this->model->assignRegist($rcpt_sq, $rsrv_date, $rsrv_tm_ap, $rsrv_tm_hour, $dept_sq, $usr_sq, $receipt);
return $this->response->setJSON([
@@ -433,7 +449,7 @@ class Receipt extends BaseController
'msg' => 'success'
]);
} else {
throw new \Exception($na_result['message']);
throw new \Exception($na_result['message'] ?? '네이버 API 연동 실패');
}
@@ -498,8 +514,6 @@ class Receipt extends BaseController
// 예약취소
public function rsrvcancel()
{
$naver = new NaverApiClient();
try {
//전달받은 값
$rcpt_sq = $this->request->getPost('rcpt_sq');
@@ -514,27 +528,27 @@ class Receipt extends BaseController
/*** 네이버 연동[s] ***/
if ($result_cd2 == '9010' || $result_cd2 == '9020') { //예약취소
$na_result = $naver->reserveFail($rcpt_key, "E11", $result_msg);
$na_result = $this->naverApiClient->reserveFail($rcpt_key, "E11", $result_msg);
} else if ($result_cd2 == '9030') {
if ($rcpt_stat1 == '70') {
throw new \Exception('방문전 취소 할 수 없습니다.');
} else {
$na_result = $naver->shootFail($rcpt_key, "E21", $result_msg);
$na_result = $this->naverApiClient->shootFail($rcpt_key, "E21", $result_msg);
}
} else if ($result_cd2 == '9040') {
$na_result = $naver->shootFail($rcpt_key, "E22", $result_msg);
$na_result = $this->naverApiClient->shootFail($rcpt_key, "E22", $result_msg);
} else if ($result_cd2 == '9045') {
$na_result = $naver->shootFail($rcpt_key, "E23", $result_msg);
$na_result = $this->naverApiClient->shootFail($rcpt_key, "E23", $result_msg);
} else if ($result_cd2 == '9050') {
$na_result = $naver->inspectFail($rcpt_key, 'E31', $result_msg);
$na_result = $this->naverApiClient->inspectFail($rcpt_key, 'E31', $result_msg);
}
/*** 네이버 연동[e] ***/
if (array_key_exists('result', $na_result)) { //네이버연동 상태변경 완료
if (isset($na_result['code']) && $na_result['code'] === 'success') { //네이버연동 상태변경 완료
$result = $this->model->rsrvcancel($rcpt_sq, $rsrv_sq, $result_cd2, $result_cd3, $result_msg, $receipt);
} else {
throw new \Exception($na_result['message']);
throw new \Exception($na_result['message'] ?? '네이버 API 연동 실패');
}
return $this->response->setJSON([
@@ -570,7 +584,7 @@ class Receipt extends BaseController
$rletTypeCd = $this->request->getGet('rletTypeCd');
// 파라미터 디버그 로깅
// 파라미터 로깅
$p = [
'rcpt_sq' => $rcpt_sq,
'rcpt_key' => $rcpt_key,
@@ -586,9 +600,10 @@ class Receipt extends BaseController
'rletTypeCd' => $rletTypeCd,
];
log_message('info', '[Receipt::chgStatus] params: ' . json_encode($p, JSON_UNESCAPED_UNICODE));
print_r($p);
exit;
// TODO: 기존 비즈니스 로직 복원 필요
throw new \Exception('상태변경 로직 점검 중입니다. 잠시 후 다시 시도해주세요.');
} catch (\Exception $e) {
@@ -644,7 +659,6 @@ class Receipt extends BaseController
$lib = new MyUpload();
try {
$rcpt_sq = $this->request->getPost('rcpt_sq');
$rcpt_key = $this->request->getPost('rcpt_key');
$rsrv_sq = $this->request->getPost('rsrv_sq');
@@ -657,8 +671,6 @@ class Receipt extends BaseController
$rec_nm = $this->request->getPost('rec_nm');
$rec_remark = $this->request->getPost('rec_remark');
$file = $this->request->getFile('rec_file');
$data = [
'rcpt_sq' => $rcpt_sq,
'rsrv_sq' => $rsrv_sq,
@@ -668,43 +680,101 @@ class Receipt extends BaseController
'rec_remark' => $rec_remark,
];
// 파일 업로드 처리
$file = $this->request->getFile('rec_file');
if ($file && $file->isValid() && !$file->hasMoved()) {
$uploadPath = "/upload/result/" . $rsrv_sq . "/";
$uploadData = $lib->do_upload2($file, $uploadPath);
$arrUploadfile = [];
if ($file->isValid() && !$file->hasMoved()) {
$uploadData = $lib->do_upload2($file, $uploadPath);
if ($uploadData !== false) {
$arrUploadfile[] = $uploadData;
}
if ($uploadData !== false && is_array($uploadData)) {
// do_upload2 반환값 구조: object_key, object_storage_url, origin_name, file_name, base_name, ext
$data['file'] = [
'upload_path' => $uploadPath,
'file_name' => isset($uploadData['file_name']) ? $uploadData['file_name'] : '',
'origin_name' => isset($uploadData['origin_name']) ? $uploadData['origin_name'] : '',
'ext' => isset($uploadData['ext']) ? $uploadData['ext'] : '',
'size' => $file->getSize(),
];
} else {
throw new \Exception('파일 업로드에 실패했습니다.');
}
if (!empty($arrUploadfile)) {
foreach ($arrUploadfile as $key => $uploadFile) {
$data['file'] = [
'orig_name' => $uploadFile['origin_name'],
'new_name' => $uploadFile['file_name'],
'file_path' => $uploadPath, // 필요에 따라 상대경로로만 저장
'ext' => '.' . $uploadFile['ext'],
'size' => $file->getSize(),
];
}
}
}
$this->model->saveRecInfo($data);
$result = $this->model->saveRecInfo($data);
return $this->response->setJSON([
'code' => '0',
'msg' => 'success'
'msg' => 'success',
'data' => [
'rec_tel' => $result['rec_tel'] ?? '',
'rec_nm' => $result['rec_nm'] ?? '',
'remark' => $result['remark'] ?? '',
'record' => $result['record'] ?? null
]
]);
} catch (\Exception $e) {
log_message('error', '[Receipt::saveRecInfo] Error: ' . $e->getMessage());
return $this->response->setJSON([
'code' => '9',
'msg' => $e->getMessage(),
]);
}
}
// 거주인 정보 조회
public function getRecInfo()
{
try {
$rcpt_sq = $this->request->getGet('rcpt_sq');
if (empty($rcpt_sq)) {
throw new \Exception('rcpt_sq가 필요합니다.');
}
// 거주인 정보 조회
$recInfo = $this->model->getRecInfoByRcptSq($rcpt_sq);
if (empty($recInfo)) {
throw new \Exception('거주인 정보를 찾을 수 없습니다.');
}
return $this->response->setJSON([
'code' => '0',
'msg' => 'success',
'data' => $recInfo
]);
} catch (\Exception $e) {
log_message('error', '[Receipt::getRecInfo] Error: ' . $e->getMessage());
return $this->response->setJSON([
'code' => '9',
'msg' => $e->getMessage(),
]);
}
}
// 정보변경 이력 조회
public function getHistory()
{
try {
$rcpt_sq = $this->request->getGet('rcpt_sq');
if (empty($rcpt_sq)) {
throw new \Exception('rcpt_sq가 필요합니다.');
}
// 정보변경 이력 조회
$history = $this->model->getHistory($rcpt_sq);
return $this->response->setJSON([
'code' => '0',
'msg' => 'success',
'data' => $history ?? []
]);
} catch (\Exception $e) {
log_message('error', '[Receipt::getHistory] Error: ' . $e->getMessage());
return $this->response->setJSON([
'code' => '9',
'msg' => $e->getMessage(),
@@ -1254,4 +1324,136 @@ class Receipt extends BaseController
]);
}
}
public function modifyPriceInfo()
{
try {
$rcpt_sq = $this->request->getPost('rcpt_sq');
$rcpt_key = $this->request->getPost('rcpt_key');
$trade_type = $this->request->getPost('trade_type');
$rcpt_product_info2 = $this->request->getPost('rcpt_product_info2');
$rcpt_product_info3 = $this->request->getPost('rcpt_product_info3');
$rcpt_product_info4 = $this->request->getPost('rcpt_product_info4');
$rcpt_product_info5 = $this->request->getPost('rcpt_product_info5');
$rcpt_product_info6 = $this->request->getPost('rcpt_product_info6');
$rcpt_ptp_no = $this->request->getPost('rcpt_ptp_no');
$rcpt_hscp_no = $this->request->getPost('rcpt_hscp_no');
$dealAmount = $this->request->getPost('dealAmount') ?: 0;
$warrantyAmount = $this->request->getPost('warrantyAmount') ?: 0;
$leaseAmount = $this->request->getPost('leaseAmount') ?: 0;
$preSaleAmount = $this->request->getPost('preSaleAmount') ?: 0;
$premiumAmount = $this->request->getPost('premiumAmount') ?: 0;
$preSaleOptionAmount = $this->request->getPost('preSaleOptionAmount') ?: 0;
if (empty($rcpt_sq) || empty($rcpt_key) || empty($trade_type)) {
return $this->response->setJSON([
'code' => '1',
'msg' => '필수 파라미터가 누락되었습니다.'
]);
}
$receipt = $this->model->getDetail($rcpt_key);
if (empty($receipt)) {
return $this->response->setJSON([
'code' => '1',
'msg' => '매물 정보를 찾을 수 없습니다.'
]);
}
// 거래유형에 따른 가격 정보 제한
$limitCheck = limitHscpMarketPriceInfo($trade_type, $rcpt_hscp_no, $rcpt_ptp_no, $rcpt_product_info2);
if (!empty($limitCheck)) {
return $this->response->setJSON([
'code' => '1',
'msg' => $limitCheck['message'] ?? '가격 범위를 벗어났습니다.'
]);
}
$data = [
'trade_type' => $trade_type,
'rcpt_product_info2' => $rcpt_product_info2,
'rcpt_product_info3' => $rcpt_product_info3,
'rcpt_product_info4' => $rcpt_product_info4,
'rcpt_product_info5' => $rcpt_product_info5,
'rcpt_product_info6' => $rcpt_product_info6,
'rcpt_ptp_no' => $rcpt_ptp_no,
'rcpt_hscp_no' => $rcpt_hscp_no,
'dealAmount' => $dealAmount,
'warrantyAmount' => $warrantyAmount,
'leaseAmount' => $leaseAmount,
'preSaleAmount' => $preSaleAmount,
'premiumAmount' => $premiumAmount,
'preSaleOptionAmount' => $preSaleOptionAmount,
];
$updated = $this->entityModel->update($rcpt_sq, $data);
if (!$updated) {
$errors = $this->entityModel->errors();
log_message('error', 'Failed to update modifyPriceInfo info for rcpt_sq: ' . $rcpt_sq . '. Errors: ' . json_encode($errors));
return $this->response->setJSON([
'code' => '1',
'msg' => !empty($errors) ? implode(', ', $errors) : '가격 정보 수정 실패'
]);
}
$affectedRows = $this->entityModel->db->affectedRows();
$isChanged = ($affectedRows > 0);
if (!$isChanged) {
return $this->response->setJSON([
'code' => '0',
'changed' => false,
'msg' => '변경사항이 없습니다.'
]);
}
log_message('info', 'Price info updated for rcpt_sq: ' . $rcpt_sq . '. Affected rows: ' . $affectedRows);
$changed = what_is_changed($receipt, $data);
log_message('info', 'what_is_changed result for rcpt_sq: ' . $rcpt_sq . '. Changes: ' . $changed);
if (!empty($changed)) {
$this->changedHistoryEntity->addHistory(
$rcpt_sq,
$receipt['result_cd3'] ?? '',
'C25',
session('usr_id'),
$changed
);
}
$usr_id = session('usr_id');
$atcl_no = $receipt['rcpt_atclno'] ?? null;
if (!empty($atcl_no)) {
$params = [
'dealAmount' => $dealAmount,
'warrantyAmount' => $warrantyAmount,
'leaseAmount' => $leaseAmount,
];
if (in_array($receipt['rcpt_product'] ?? '', ['B01', 'B02', 'B03'], true)) {
$params['preSaleAmount'] = $preSaleAmount;
$params['premiumAmount'] = $premiumAmount;
$params['preSaleOptionAmount'] = $preSaleOptionAmount;
}
$priceInfo = $this->naverApiClient->postArticlePriceUpdate($atcl_no, $params, $usr_id);
log_message('info', 'postArticlePriceUpdate response for atcl_no: ' . $atcl_no . '. Response: ' . json_encode($priceInfo));
}
return $this->response->setJSON([
'code' => '0',
'changed' => true,
'msg' => '가격 정보가 수정되었습니다.'
]);
} catch (\Exception $e) {
log_message('error', 'modifyPriceInfo error: ' . $e->getMessage());
return $this->response->setJSON([
'code' => '1',
'msg' => '가격 정보 수정 중 에러가 발생했습니다: ' . $e->getMessage()
]);
}
}
}

View File

@@ -4,16 +4,19 @@ namespace App\Controllers\Common;
use App\Controllers\BaseController;
use App\Models\common\CommonModel;
use App\Models\manage\UserModel;
use App\Libraries\NaverApiClient;
class Common extends BaseController
{
private $model;
private $userModel;
private $naverApiClient;
public function __construct()
{
$this->model = new CommonModel();
$this->userModel = new UserModel();
$this->naverApiClient = new NaverApiClient();
}
public function getVrfcCode()
@@ -118,4 +121,54 @@ class Common extends BaseController
]);
}
}
// 단지 조회
public function getComplexList()
{
$legalDivisionNumber = $this->request->getGet("legalDivisionNumber");
$realEstateType = $this->request->getGet("realEstateType") ?? null;
try {
$complexList = $this->naverApiClient->getComplexList($legalDivisionNumber , $realEstateType);
return $this->response->setJSON($complexList);
} catch (\Exception $e) {
return $this->response->setJSON([
'code' => '9',
'msg' => $e->getMessage(),
]);
}
}
/**
* 평형 조회
*
* complexNumber : 단지 번호
* realEstateType : 매물 종류 (APT, OFFICETEL, VILLA 등)
*/
public function getPyeongInfo()
{
$complexNumber = $this->request->getGet("complexNumber");
$realEstateType = $this->request->getGet("realEstateType");
try {
if ( $realEstateType == 'A01' || $realEstateType == 'A02' || $realEstateType == 'A03' ) {
$pyeongInfo = $this->naverApiClient->getPyeongTypeList($complexNumber);
} else if ( $realEstateType == 'A05' || $realEstateType == 'A06' ) {
$pyeongInfo = $this->naverApiClient->getVillaPyeongTypeList($complexNumber);
} else {
return $this->response->setJSON([
'code' => '9',
'msg' => '지원하지 않는 매물 종류입니다.',
]);
}
return $this->response->setJSON($pyeongInfo);
} catch (\Exception $e) {
return $this->response->setJSON([
'code' => '9',
'msg' => $e->getMessage(),
]);
}
}
}

View File

@@ -13,6 +13,23 @@ class KisoController extends BaseController
/** @var ResponseInterface */
protected $response;
/**
* 네이버 검증 요청 API
*
* Required Parameters:
* - articleNumber: 기사 번호
* - requestType: 요청 타입 (verify, check, validate)
* - requestDatetime: 요청 일시 (YYYY-MM-DD HH:MM:SS)
*
* Error Codes:
* - E001: 필수 파라미터 누락
* - E002: requestType 값 오류
* - E003: requestDatetime 형식 오류
* - E005: HTTP 메소드 오류
* - E999: Redis 연결 오류
*
* @return ResponseInterface
*/
public function vrfcReq()
{
// 1. 요청 방식에 따라 데이터 파싱
@@ -25,56 +42,79 @@ class KisoController extends BaseController
} else {
// 지원하지 않는 메소드 처리 (예: PUT, DELETE 등)
return $this->response->setStatusCode(Response::HTTP_METHOD_NOT_ALLOWED)
->setJSON(['resultCode' => 'E005', 'resultMessage' => 'Method not allowed. Use GET or POST.']);
->setJSON(['code' => 'E005', 'message' => 'Method not allowed. Use GET or POST.']);
}
// 2. 필수 항목 검증
$requiredKeys = ['articleNumber', 'requestType', 'requestDatetime'];
foreach ($requiredKeys as $key) {
// 파싱된 데이터($data) 내에 키가 없거나 값이 비어있는지 확인
if (empty($data[$key])) {
// isset()과 trim()을 사용하여 '0' 값도 허용
if (!isset($data[$key]) || trim((string)$data[$key]) === '') {
return $this->response->setStatusCode(Response::HTTP_BAD_REQUEST)
->setJSON([
'resultCode' => 'E001',
'resultMessage' => "Missing required parameter: {$key}"
'code' => 'E001',
'message' => "Missing required parameter: {$key}"
]);
}
}
// 3. Redis 연결 및 예외 처리
// 3. Redis 연결 및 직접 푸시
try {
$redis = new \Redis();
// Docker 환경이므로 host를 'redis'로 설정
$success = $redis->connect('redis', 6379);
if (!$success) {
// 3. requestType 값 검증
$validRequestTypes = ['verify', 'check', 'validate']; // 허용되는 requestType 값
if (!in_array($data['requestType'], $validRequestTypes, true)) {
return $this->response->setStatusCode(Response::HTTP_BAD_REQUEST)
->setJSON([
'code' => 'E002',
'message' => "Invalid requestType. Allowed values: " . implode(', ', $validRequestTypes)
]);
}
// 4. requestDatetime 날짜 형식 검증 (Y-m-d H:i:s 형식)
$datetime = \DateTime::createFromFormat('Y-m-d H:i:s', $data['requestDatetime']);
if (!$datetime || $datetime->format('Y-m-d H:i:s') !== $data['requestDatetime']) {
return $this->response->setStatusCode(Response::HTTP_BAD_REQUEST)
->setJSON([
'code' => 'E003',
'message' => "Invalid requestDatetime format. Use: YYYY-MM-DD HH:MM:SS"
]);
}
// 5. Redis 연결 및 큐 저장
helper('redis');
try {
$redis = get_redis_connection('worker');
if (!$redis) {
throw new \Exception('Redis connection failed');
}
$redis->select(10); // 10번 DB 선택
// 데이터 준비
$data['retry_count'] = 0;
$data['received_at'] = date('Y-m-d H:i:s');
// 리스트에 데이터 삽입 (이 명령어가 실행되어야 monitor에 LPUSH가 뜹니다)
$redis->lPush('naver:queue', json_encode($data));
// 리스트에 데이터 삽입
$pushResult = $redis->lPush('naver:queue', json_encode($data));
if (!$pushResult) {
throw new \Exception('Failed to push data to Redis queue');
}
// 성공 로그 기록
log_message('info', "Request queued successfully - Article: {$data['articleNumber']}, Type: {$data['requestType']}");
} catch (\Exception $e) {
log_message('error', 'Redis Push Error: ' . $e->getMessage());
return $this->response->setStatusCode(500)->setJSON([
'resultCode' => 'E999',
'resultMessage' => 'Redis Connection Error'
'code' => 'E999',
'message' => 'Redis Connection Error'
]);
}
// 4. 성공 응답
return $this->response->setStatusCode(Response::HTTP_ACCEPTED) // 202 Accepted
// 6. 성공 응답
return $this->response->setStatusCode(Response::HTTP_OK) // 200 OK
->setJSON([
'resultCode' => 'S000',
'resultMessage' => 'Request successfully queued for processing',
'articleNumber' => $data['articleNumber']
'code' => 'success',
'message' => ''
]);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Controllers;
class LogTest extends BaseController
{
public function index()
{
log_message('error', '===== WEB LOG TEST ===== ' . date('Y-m-d H:i:s'));
log_message('info', 'Info from web');
log_message('debug', 'Debug from web');
$logFile = WRITEPATH . 'logs/log-' . date('Y-m-d') . '.log';
echo "ENVIRONMENT: " . ENVIRONMENT . "<br>";
echo "Log file: " . $logFile . "<br>";
echo "Exists: " . (file_exists($logFile) ? 'YES' : 'NO') . "<br>";
echo "Writable: " . (is_writable(dirname($logFile)) ? 'YES' : 'NO') . "<br>";
if (file_exists($logFile)) {
echo "<h3>Last 20 lines:</h3>";
echo "<pre>";
echo htmlspecialchars(shell_exec('tail -20 ' . escapeshellarg($logFile)));
echo "</pre>";
}
}
}

View File

@@ -3,6 +3,7 @@ namespace App\Controllers;
use App\Controllers\BaseController;
use App\Models\common\LoginModel;
use CodeIgniter\Session\Handlers\DatabaseHandler;
class Login extends BaseController
{
@@ -13,6 +14,37 @@ class Login extends BaseController
$this->loginModel = new LoginModel();
}
/**
* Redis 폴백 상태 체크 (세션이 DatabaseHandler로 동작 중인지)
*/
private function getSystemStatus(): array
{
$status = [
'redis_fallback' => false,
'warning_message' => ''
];
// Session 설정에서 현재 드라이버 확인
$sessionConfig = config('Session');
if ($sessionConfig->driver === DatabaseHandler::class) {
$status['redis_fallback'] = true;
$status['warning_message'] = '세션 서버(Redis) 장애로 임시 모드로 운영 중입니다. 시스템 관리자에게 문의하세요.';
}
return $status;
}
/**
* JSON 응답에 시스템 상태 추가
*/
private function jsonResponse(array $data): \CodeIgniter\HTTP\ResponseInterface
{
$systemStatus = $this->getSystemStatus();
$data['system'] = $systemStatus;
return $this->response->setJSON($data);
}
public function index(): string
{
$user_id = get_cookie('save_id');
@@ -59,7 +91,7 @@ class Login extends BaseController
];
if (!$this->validate($rules)) {
return $this->response->setJSON([
return $this->jsonResponse([
'code' => '1',
'errors' => $this->validator->getErrors()
]);
@@ -82,7 +114,7 @@ class Login extends BaseController
$this->loginModel->insertUserLog($logs);
return $this->response->setJSON([
return $this->jsonResponse([
'code' => '1',
'msg' => '존재하지 않는 아이디입니다.'
]);
@@ -96,7 +128,7 @@ class Login extends BaseController
$this->loginModel->insertUserLog($logs);
return $this->response->setJSON(body: [
return $this->jsonResponse([
'code' => '1',
'msg' => '잘못된 비밀번호 입니다.'
]);
@@ -135,7 +167,7 @@ class Login extends BaseController
$this->session->set($newdata);
return $this->response->setJSON([
return $this->jsonResponse([
'code' => '0',
'msg' => 'success'
]);
@@ -148,7 +180,17 @@ class Login extends BaseController
log_message('error', '[LOGIN ERROR] ' . $e->getMessage());
log_message('error', $e->getTraceAsString());
return $this->response->setJSON([
// 세션 관련 에러인지 확인
$errorMessage = $e->getMessage();
if (stripos($errorMessage, 'session') !== false ||
stripos($errorMessage, 'redis') !== false) {
return $this->jsonResponse([
'code' => '8',
'msg' => '세션 서비스 오류입니다. 시스템 관리자에게 문의해주세요.'
]);
}
return $this->jsonResponse([
'code' => '9',
'msg' => '서버 내부 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'
]);

View File

@@ -81,27 +81,35 @@ class WorkerLog extends BaseController
$lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
// 로그 포맷: [2025-12-22 10:30:45] [INFO] [NaverWorker::run:95] 메시지
if (preg_match('/\[(.+?)\]\s*\[(.+?)\]\s*(?:\[(.+?)\]\s*)?(.+)/', $line, $matches)) {
$logs[] = [
'timestamp' => $matches[1],
'level' => $matches[2],
'location' => $matches[3] ?? '',
'message' => $matches[4]
];
} else {
// 파싱 실패한 경우 원본 그대로
$logs[] = [
'timestamp' => date('Y-m-d H:i:s'),
'level' => 'UNKNOWN',
'location' => '',
'message' => $line
];
}
$logs[] = $this->parseLogLine($line);
}
return $logs;
}
/**
* 로그 한 줄 파싱
*/
private function parseLogLine($line)
{
// 로그 포맷: [2025-12-22 10:30:45] [INFO] [NaverWorker::run:95] 메시지
if (preg_match('/\[(.+?)\]\s*\[(.+?)\]\s*(?:\[(.+?)\]\s*)?(.+)/', $line, $matches)) {
return [
'timestamp' => $matches[1],
'level' => $matches[2],
'location' => $matches[3] ?? '',
'message' => $matches[4]
];
}
// 파싱 실패한 경우 원본 그대로
return [
'timestamp' => date('Y-m-d H:i:s'),
'level' => 'UNKNOWN',
'location' => '',
'message' => $line
];
}
/**
* 로그 파일이 존재하는 날짜 목록 가져오기
@@ -151,14 +159,7 @@ class WorkerLog extends BaseController
$newLines = array_slice($lines, $lastId);
foreach ($newLines as $line) {
if (preg_match('/\[(.+?)\]\s*\[(.+?)\]\s*(?:\[(.+?)\]\s*)?(.+)/', $line, $matches)) {
$newLogs[] = [
'timestamp' => $matches[1],
'level' => $matches[2],
'location' => $matches[3] ?? '',
'message' => $matches[4]
];
}
$newLogs[] = $this->parseLogLine($line);
}
}
}

View File

@@ -0,0 +1,366 @@
<?php
namespace App\Controllers\Manage;
use App\Controllers\BaseController;
use App\Models\Entities\NaverWorkerLogModel;
class WorkerLogController extends BaseController
{
protected $logModel;
public function __construct()
{
$this->logModel = new NaverWorkerLogModel();
}
/**
* 실패 로그 목록 (오류 유형별 분석)
*/
public function failedList()
{
$db = \Config\Database::connect();
// 1. 전체 통계
$stats = [
'total_fail' => $this->logModel->where('status', 'FAIL')->countAllResults(false),
'retry_available' => $this->logModel
->where('status', 'FAIL')
->groupStart()
->where('retry_cnt IS NULL')
->orWhere('retry_cnt <', 3)
->groupEnd()
->countAllResults(false),
'retry_exhausted' => $this->logModel
->where('status', 'FAIL')
->where('retry_cnt >=', 3)
->countAllResults(false),
];
// 2. 오류 유형별 분류
$errorTypes = $this->classifyErrors();
// 3. 최근 실패 목록
$recentFails = $this->logModel
->select('seq, atcl_no, status, error_msg, retry_cnt, created_at')
->where('status', 'FAIL')
->orderBy('created_at', 'DESC')
->findAll(50);
// 각 오류에 대한 해결 가이드 추가
foreach ($recentFails as &$log) {
$log['error_type'] = $this->detectErrorType($log['error_msg']);
$log['solution'] = $this->getSolution($log['error_type']);
$log['can_retry'] = $this->canRetry($log);
}
$data = [
'stats' => $stats,
'errorTypes' => $errorTypes,
'logs' => $recentFails
];
return view('manage/worker_log/failed_list', array_merge($this->data, $data));
}
/**
* 오류 유형 분류
*/
protected function classifyErrors()
{
$db = \Config\Database::connect();
$errorPatterns = [
'foreign_key' => [
'pattern' => 'foreign key constraint',
'label' => 'FK 제약조건 위반',
'severity' => 'high',
'count' => 0
],
'duplicate' => [
'pattern' => 'Duplicate entry',
'label' => '중복 데이터',
'severity' => 'medium',
'count' => 0
],
'missing_user' => [
'pattern' => 'usr_sq.*users 테이블에 없음',
'label' => '담당자 정보 없음',
'severity' => 'medium',
'count' => 0
],
'empty_payload' => [
'pattern' => '빈 페이로드',
'label' => '빈 데이터',
'severity' => 'low',
'count' => 0
],
'api_error' => [
'pattern' => 'API.*FAIL|HTTP.*40[0-9]|HTTP.*50[0-9]',
'label' => 'API 통신 오류',
'severity' => 'medium',
'count' => 0
],
'unknown' => [
'pattern' => '',
'label' => '기타 오류',
'severity' => 'low',
'count' => 0
]
];
$failedLogs = $this->logModel
->select('error_msg')
->where('status', 'FAIL')
->findAll();
foreach ($failedLogs as $log) {
$classified = false;
foreach ($errorPatterns as $key => &$pattern) {
if ($key === 'unknown') continue;
if ($pattern['pattern'] && preg_match('/' . $pattern['pattern'] . '/i', $log['error_msg'])) {
$pattern['count']++;
$classified = true;
break;
}
}
if (!$classified) {
$errorPatterns['unknown']['count']++;
}
}
return $errorPatterns;
}
/**
* 오류 유형 감지
*/
protected function detectErrorType($errorMsg)
{
if (!$errorMsg) return 'unknown';
if (stripos($errorMsg, 'foreign key constraint') !== false) {
return 'foreign_key';
}
if (stripos($errorMsg, 'Duplicate entry') !== false) {
return 'duplicate';
}
if (stripos($errorMsg, 'usr_sq') !== false && stripos($errorMsg, 'users 테이블') !== false) {
return 'missing_user';
}
if (stripos($errorMsg, '빈 페이로드') !== false) {
return 'empty_payload';
}
if (preg_match('/API.*FAIL|HTTP.*40[0-9]|HTTP.*50[0-9]/i', $errorMsg)) {
return 'api_error';
}
return 'unknown';
}
/**
* 오류 유형별 해결 방법
*/
protected function getSolution($errorType)
{
$solutions = [
'foreign_key' => [
'title' => 'DB 데이터 수정 필요',
'steps' => [
'1. 참조하는 테이블의 데이터가 존재하는지 확인',
'2. region 테이블의 orphaned usr_sq 값 수정',
'3. TypeSParameterMapper의 폴백 로직 확인'
],
'auto_retry' => false
],
'duplicate' => [
'title' => '중복 데이터 처리',
'steps' => [
'1. 기존 데이터 확인 (atcl_no로 검색)',
'2. 중복 원인 파악 (동일 요청 2회 수신?)',
'3. 필요시 기존 데이터 삭제 후 재처리'
],
'auto_retry' => false
],
'missing_user' => [
'title' => '담당자 정보 수정됨 - 재처리 가능',
'steps' => [
'✅ TypeSParameterMapper가 자동 폴백 적용됨',
'✅ 바로 재처리 가능 (usr_sq=1로 처리됨)'
],
'auto_retry' => true
],
'empty_payload' => [
'title' => '잘못된 요청 데이터',
'steps' => [
'1. raw_payload 확인',
'2. 네이버 API 응답 확인',
'3. 재처리 불가 - 데이터 삭제 권장'
],
'auto_retry' => false
],
'api_error' => [
'title' => 'API 통신 오류',
'steps' => [
'1. 일시적 오류인지 확인 (네트워크, 타임아웃)',
'2. 네이버 API 서버 상태 확인',
'3. 일시적 오류면 재처리 가능'
],
'auto_retry' => true
],
'unknown' => [
'title' => '원인 미상',
'steps' => [
'1. error_msg 상세 확인',
'2. 로그 파일 확인',
'3. 개발팀 검토 필요'
],
'auto_retry' => false
]
];
return $solutions[$errorType] ?? $solutions['unknown'];
}
/**
* 재시도 가능 여부
*/
protected function canRetry($log)
{
// 재시도 횟수 체크
if (($log['retry_cnt'] ?? 0) >= 3) {
return false;
}
// 오류 유형별 재시도 가능 여부
$errorType = $this->detectErrorType($log['error_msg']);
$solution = $this->getSolution($errorType);
return $solution['auto_retry'] ?? false;
}
/**
* 선택한 로그 재처리 (AJAX)
*/
public function retrySelected()
{
$logIds = $this->request->getPost('log_ids');
if (empty($logIds) || !is_array($logIds)) {
return $this->response->setJSON([
'success' => false,
'message' => '재처리할 로그를 선택해주세요.'
]);
}
$naverService = new \App\Services\NaverService();
$results = [
'success' => 0,
'fail' => 0,
'details' => []
];
foreach ($logIds as $logId) {
$log = $this->logModel->find($logId);
if (!$log) {
$results['details'][] = [
'id' => $logId,
'status' => 'error',
'message' => '로그를 찾을 수 없습니다.'
];
continue;
}
// 재시도 가능 여부 체크
if (!$this->canRetry($log)) {
$results['details'][] = [
'id' => $logId,
'atcl_no' => $log['atcl_no'],
'status' => 'skip',
'message' => '재시도 불가능 (횟수 초과 또는 수정 필요)'
];
continue;
}
try {
// 재처리
$this->logModel->update($logId, ['status' => 'RETRY']);
$responseJson = json_decode($log['raw_payload'], true);
$payload = $responseJson['request_data'] ?? [];
if (empty($payload)) {
throw new \Exception("빈 페이로드 데이터");
}
$insertId = $naverService->processArticle($payload);
$this->logModel->update($logId, [
'atcl_no' => $payload['articleNumber'] ?? null,
'status' => 'SUCCESS',
'target_db_id' => $insertId,
'error_msg' => null,
'retry_cnt' => ($log['retry_cnt'] ?? 0) + 1
]);
$results['success']++;
$results['details'][] = [
'id' => $logId,
'atcl_no' => $log['atcl_no'],
'status' => 'success',
'message' => "성공 (DB ID: {$insertId})"
];
} catch (\Exception $e) {
$this->logModel->update($logId, [
'status' => 'FAIL',
'error_msg' => $e->getMessage(),
'retry_cnt' => ($log['retry_cnt'] ?? 0) + 1
]);
$results['fail']++;
$results['details'][] = [
'id' => $logId,
'atcl_no' => $log['atcl_no'],
'status' => 'fail',
'message' => $e->getMessage()
];
}
}
return $this->response->setJSON([
'success' => true,
'results' => $results
]);
}
/**
* 특정 로그 상세 (AJAX)
*/
public function detail($id)
{
$log = $this->logModel->find($id);
if (!$log) {
return $this->response->setJSON([
'success' => false,
'message' => '로그를 찾을 수 없습니다.'
]);
}
// raw_payload JSON 파싱
$log['parsed_payload'] = json_decode($log['raw_payload'], true);
$log['error_type'] = $this->detectErrorType($log['error_msg']);
$log['solution'] = $this->getSolution($log['error_type']);
$log['can_retry'] = $this->canRetry($log);
return $this->response->setJSON([
'success' => true,
'log' => $log
]);
}
}

View File

@@ -31,6 +31,7 @@ class Assign extends BaseController
{
$start = (int) $this->request->getGet('start') ?: 0;
$end = (int) $this->request->getGet('length') ?: 10;
$draw = (int) $this->request->getGet('draw') ?: 1;
$data = [
'bonbu' => $this->request->getGet('bonbu'),
@@ -42,12 +43,18 @@ class Assign extends BaseController
'srchTxt' => $this->request->getGet('srchTxt'),
];
$totalCount = $this->assignModel->getTotalCount($data);
$datas = $this->assignModel->getUserList($start, $end, $data);
// 첫 번째 행에서 total_count 추출 (없으면 0)
$totalCount = !empty($datas) ? (int)$datas[0]['total_count'] : 0;
// 각 행에서 total_count 컬럼 제거
foreach ($datas as &$row) {
unset($row['total_count']);
}
return $this->response->setJSON(body: [
'draw' => $draw,
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
'data' => $datas,

View File

@@ -0,0 +1,163 @@
<?php
namespace App\Controllers\V2;
use App\Controllers\BaseController;
use App\Models\common\CodeModel;
/**
* BaseV2Controller
* V2 모듈 공통 로직: lists / getResultList / excel
*
* 하위 클래스에서 구현해야 하는 메서드:
* - createModel() : 모듈 전용 Model 인스턴스 반환
* - getCodeKeys() : lists()에서 로드할 코드 카테고리 배열 반환
* - getViewName() : lists()에서 렌더링할 뷰 경로 반환
* - getSearchKeys() : getResultList/excel에서 수집할 GET 파라미터 키 배열 반환
*/
abstract class BaseV2Controller extends BaseController
{
protected $model;
protected $codeModel;
public function __construct()
{
$this->codeModel = new CodeModel();
$this->model = $this->createModel();
}
abstract protected function createModel();
abstract protected function getCodeKeys(): array;
abstract protected function getViewName(): string;
abstract protected function getSearchKeys(): array;
public function lists(): string
{
$codes = $this->codeModel->getCodeLists($this->getCodeKeys());
$sido = $this->model->getAreaList();
$bonbu = $this->model->getBonbuList();
$team = $this->model->getTeamList();
$user = $this->model->getUserList();
$this->data['codes'] = $codes;
$this->data['sido'] = $sido;
$this->data['bonbu'] = $bonbu;
$this->data['team'] = $team;
$this->data['user'] = $user;
return view($this->getViewName(), $this->data);
}
public function getResultList()
{
$start = (int) $this->request->getGet('start') ?: 0;
$end = (int) $this->request->getGet('length') ?: 10;
$data = $this->buildSearchData();
$totalCount = $this->model->getTotalCount($data);
$datas = $this->model->getResultList($start, $end, $data);
return $this->response->setJSON([
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
'data' => $datas,
]);
}
public function excel()
{
try {
$data = $this->buildSearchData();
$datas = $this->model->getExcelList($data);
return $this->response->setJSON(['data' => $datas]);
} catch (\Exception $e) {
$e->getPrevious()->getTraceAsString();
}
}
/**
* getSearchKeys()에 정의된 키만 GET에서 수집해서 배열로 반환
*/
protected function buildSearchData(): array
{
$data = [];
foreach ($this->getSearchKeys() as $key) {
$rawValue = $this->request->getGet($key);
$data[$key] = $this->normalizeSearchValue($key, $rawValue);
}
return $data;
}
/**
* 공통 파라미터 정규화
* - null/공백은 빈 문자열로 통일
* - 문자열은 trim 처리
* - 날짜 키(date 포함)는 형식 검증 실패 시 빈 문자열로 처리
*/
protected function normalizeSearchValue(string $key, $value)
{
if (is_array($value)) {
return array_map(static function ($item) {
if ($item === null) {
return '';
}
if (is_string($item)) {
$item = trim($item);
return $item === '' ? '' : $item;
}
return $item;
}, $value);
}
if ($value === null) {
return '';
}
if (is_string($value)) {
$value = trim($value);
if ($value === '') {
return '';
}
}
if ($this->isDateKey($key) && !$this->isValidDateValue((string) $value)) {
return '';
}
return $value;
}
/**
* 날짜 성격 파라미터 판별
* 예: receipt_sdate, receipt_edate, complete_sdate, stat_complete_date 등
*/
protected function isDateKey(string $key): bool
{
return strpos($key, 'date') !== false;
}
/**
* 허용 날짜 형식 검증
* - YYYY-MM-DD
* - YYYYMMDD
*/
protected function isValidDateValue(string $value): bool
{
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) === 1) {
[$year, $month, $day] = explode('-', $value);
return checkdate((int) $month, (int) $day, (int) $year);
}
if (preg_match('/^\d{8}$/', $value) === 1) {
$year = substr($value, 0, 4);
$month = substr($value, 4, 2);
$day = substr($value, 6, 2);
return checkdate((int) $month, (int) $day, (int) $year);
}
return false;
}
}

View File

@@ -1,130 +1,60 @@
<?php
namespace App\Controllers\V2;
use App\Controllers\BaseController;
use App\Controllers\V2\BaseV2Controller;
use App\Libraries\FormValidation;
use App\Libraries\MyUpload;
use App\Libraries\NaverApiClient;
use App\Models\common\CodeModel;
use App\Models\results\M415Model;
use App\Models\v2\M701Model;
use App\Models\v2\M710Model;
use Exception;
use function PHPUnit\Framework\throwException;
use App\Models\v2\M701Model;
class M701 extends BaseController
class M701 extends BaseV2Controller
{
private $model, $codeModel;
public function __construct()
protected function createModel()
{
$this->model = new M701Model();
$this->codeModel = new CodeModel();
return new M701Model();
}
public function lists(): string
protected function getCodeKeys(): array
{
$codes = $this->codeModel->getCodeLists(['STEP_VERIFICATION', 'VRFCREQ_WAY', 'CP_ID', 'ARTICLE_TYPE']); // 코드조회
$sido = $this->model->getAreaList(); // 지역조회
$bonbu = $this->model->getBonbuList();
$team = $this->model->getTeamList();
$user = $this->model->getUserList();
$this->data['sido'] = $sido;
$this->data['bonbu'] = $bonbu;
$this->data['team'] = $team;
$this->data['user'] = $user;
$this->data['codes'] = $codes;
return view("pages/v2/m701/lists", $this->data);
return ['STEP_VERIFICATION', 'VRFCREQ_WAY', 'CP_ID', 'ARTICLE_TYPE'];
}
public function getResultList()
protected function getViewName(): string
{
$start = (int) $this->request->getGet('start') ?: 0;
$end = (int) $this->request->getGet('length') ?: 10;
return 'pages/v2/m701/lists';
}
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'complete_sdate' => $this->request->getGet('complete_sdate'), // 완료기간1
'complete_edate' => $this->request->getGet('complete_edate'), // 완료기간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'vrfcreq_way' => $this->request->getGet('vrfcreq_way'), // 검증방식1
'vrfc_type_sub' => $this->request->getGet('vrfc_type_sub'), // 검증방식2
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
'rlet_type_cd' => $this->request->getGet('rlet_type_cd'), // 매물종류
'reference_file_url_yn' => $this->request->getGet('reference_file_url_yn'), // 참고용
'corp_own' => $this->request->getGet('corp_own'), // 법인
protected function getSearchKeys(): array
{
return [
'atcl_no',
'stat_cd',
'realtor_nm',
'charger_gbn',
'assign_yn',
'receipt_sdate',
'receipt_edate',
'complete_sdate',
'complete_edate',
'srcSido',
'srcGugun',
'srcDong',
'bonbu',
'team',
'damdang',
'vrfcreq_way',
'vrfc_type_sub',
'rcpt_cpid',
'rlet_type_cd',
'reference_file_url_yn',
'corp_own',
];
$totalCount = $this->model->getTotalCount($data);
$datas = $this->model->getResultList($start, $end, $data);
return $this->response->setJSON(body: [
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
'data' => $datas,
]);
}
// 엑셀 다운로드
public function excel()
{
try {
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'complete_sdate' => $this->request->getGet('complete_sdate'), // 완료기간1
'complete_edate' => $this->request->getGet('complete_edate'), // 완료기간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'vrfcreq_way' => $this->request->getGet('vrfcreq_way'), // 검증방식1
'vrfc_type_sub' => $this->request->getGet('vrfc_type_sub'), // 검증방식2
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
'rlet_type_cd' => $this->request->getGet('rlet_type_cd'), // 매물종류
'reference_file_url_yn' => $this->request->getGet('reference_file_url_yn'), // 참고용
'corp_own' => $this->request->getGet('corp_own'), // 법인
];
$datas = $this->model->getExcelList($data);
return $this->response->setJSON(body: [
'data' => $datas,
]);
} catch (\Exception $e) {
$e->getPrevious()->getTraceAsString();
}
}
// 상세화면
public function detail($id = null)
{
$id = (int) $id;
@@ -1700,5 +1630,3 @@ class M701 extends BaseController
}
}
}

View File

@@ -1,123 +1,58 @@
<?php
namespace App\Controllers\V2;
use App\Controllers\BaseController;
use App\Controllers\V2\BaseV2Controller;
use App\Libraries\MyUpload;
use App\Libraries\NaverApiClient;
use App\Models\common\CodeModel;
use App\Models\results\M415Model;
use App\Models\v2\M702Model;
use App\Models\v2\M710Model;
use Exception;
use App\Models\v2\M702Model;
class M702 extends BaseController
class M702 extends BaseV2Controller
{
private $model;
private $codeModel;
public function __construct()
protected function createModel()
{
$this->model = new M702Model();
$this->codeModel = new CodeModel();
return new M702Model();
}
public function lists()
protected function getCodeKeys(): array
{
$codes = $this->codeModel->getCodeLists(['STEP_VERIFICATION', 'VRFCREQ_WAY', 'CP_ID', 'ARTICLE_TYPE']); // 코드조회
$sido = $this->model->getAreaList(); // 지역조회
$bonbu = $this->model->getBonbuList();
$team = $this->model->getTeamList();
$user = $this->model->getUserList();
$this->data['codes'] = $codes;
$this->data['sido'] = $sido;
$this->data['bonbu'] = $bonbu;
$this->data['team'] = $team;
$this->data['user'] = $user;
return view("pages/v2/m702/lists", $this->data);
return ['STEP_VERIFICATION', 'VRFCREQ_WAY', 'CP_ID', 'ARTICLE_TYPE'];
}
public function getResultList()
protected function getViewName(): string
{
$start = (int) $this->request->getGet('start') ?: 0;
$end = (int) $this->request->getGet('length') ?: 10;
return 'pages/v2/m702/lists';
}
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'complete_sdate' => $this->request->getGet('complete_sdate'), // 완료기간1
'complete_edate' => $this->request->getGet('complete_edate'), // 완료기간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'vrfcreq_way' => $this->request->getGet('vrfcreq_way'), // 검증방식1
'vrfc_type_sub' => $this->request->getGet('vrfc_type_sub'), // 검증방식2
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
'rlet_type_cd' => $this->request->getGet('rlet_type_cd'), // 매물종류
'corp_own' => $this->request->getGet('corp_own'), // 법인
protected function getSearchKeys(): array
{
return [
'atcl_no',
'stat_cd',
'realtor_nm',
'charger_gbn',
'assign_yn',
'receipt_sdate',
'receipt_edate',
'complete_sdate',
'complete_edate',
'srcSido',
'srcGugun',
'srcDong',
'bonbu',
'team',
'damdang',
'vrfcreq_way',
'vrfc_type_sub',
'rcpt_cpid',
'rlet_type_cd',
'corp_own',
];
$totalCount = $this->model->getTotalCount($data);
$datas = $this->model->getResultList($start, $end, $data);
return $this->response->setJSON(body: [
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
'data' => $datas,
]);
}
// 엑셀 다운로드
public function excel()
{
try {
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'complete_sdate' => $this->request->getGet('complete_sdate'), // 완료기간1
'complete_edate' => $this->request->getGet('complete_edate'), // 완료기간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'vrfcreq_way' => $this->request->getGet('vrfcreq_way'), // 검증방식1
'vrfc_type_sub' => $this->request->getGet('vrfc_type_sub'), // 검증방식2
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
'rlet_type_cd' => $this->request->getGet('rlet_type_cd'), // 매물종류
'corp_own' => $this->request->getGet('corp_own'), // 법인
];
$datas = $this->model->getExcelList($data);
return $this->response->setJSON(body: [
'data' => $datas,
]);
} catch (\Exception $e) {
$e->getPrevious()->getTraceAsString();
}
}
// 배정변경
public function updateAssign()
{
try {
@@ -1213,4 +1148,4 @@ class M702 extends BaseController
]);
}
}
}
}

View File

@@ -1,129 +1,59 @@
<?php
namespace App\Controllers\V2;
use App\Controllers\BaseController;
use App\Controllers\V2\BaseV2Controller;
use App\Libraries\NaverApiClient;
use App\Models\common\CodeModel;
use App\Models\results\M415Model;
use App\Models\v2\M703Model;
use App\Models\v2\M710Model;
use App\Models\v2\M703Model;
class M703 extends BaseController
class M703 extends BaseV2Controller
{
private $model, $codeModel;
public function __construct()
protected function createModel()
{
$this->model = new M703Model();
$this->codeModel = new CodeModel();
return new M703Model();
}
public function lists()
protected function getCodeKeys(): array
{
$codes = $this->codeModel->getCodeLists(['CP_ID', 'STEP_VERIFICATION', 'RECEIPT_STATUS3', 'FAX_CORP']); // 코드조회
$sido = $this->model->getAreaList(); // 지역조회
$bonbu = $this->model->getBonbuList();
$team = $this->model->getTeamList();
$user = $this->model->getUserList();
$this->data['sido'] = $sido;
$this->data['bonbu'] = $bonbu;
$this->data['team'] = $team;
$this->data['user'] = $user;
$this->data['codes'] = $codes;
return view("pages/v2/m703/lists", $this->data);
return ['CP_ID', 'STEP_VERIFICATION', 'RECEIPT_STATUS3', 'FAX_CORP'];
}
public function getResultList()
protected function getViewName(): string
{
$start = (int) $this->request->getGet('start') ?: 0;
$end = (int) $this->request->getGet('length') ?: 10;
return 'pages/v2/m703/lists';
}
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'chk_atcl_no' => $this->request->getGet('chk_atcl_no'), // 매물번호입력
'caller_no' => $this->request->getGet('caller_no'), // 발신팩스번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'complete_sdate' => $this->request->getGet('complete_sdate'), // 완료기간1
'complete_edate' => $this->request->getGet('complete_edate'), // 완료기간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'vrfcreq_way' => $this->request->getGet('vrfcreq_way'), // 검증방식1
'vrfc_type_sub' => $this->request->getGet('vrfc_type_sub'), // 검증방식2
'target_yn' => $this->request->getGet('target_yn'), // 홍보확인서여부
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
'chk_rec' => $this->request->getGet('chk_rec'), // 동의서 유무
'fax_corp' => $this->request->getGet('fax_corp'), // 팩스업체
protected function getSearchKeys(): array
{
return [
'atcl_no',
'chk_atcl_no',
'caller_no',
'stat_cd',
'realtor_nm',
'charger_gbn',
'assign_yn',
'receipt_sdate',
'receipt_edate',
'complete_sdate',
'complete_edate',
'srcSido',
'srcGugun',
'srcDong',
'bonbu',
'team',
'damdang',
'vrfcreq_way',
'vrfc_type_sub',
'target_yn',
'rcpt_cpid',
'chk_rec',
'fax_corp',
];
$totalCount = $this->model->getTotalCount($data);
$datas = $this->model->getResultList($start, $end, $data);
return $this->response->setJSON(body: [
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
'data' => $datas,
]);
}
// 엑셀 다운로드
public function excel()
{
try {
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'chk_atcl_no' => $this->request->getGet('chk_atcl_no'), // 매물번호입력
'caller_no' => $this->request->getGet('caller_no'), // 발신팩스번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'complete_sdate' => $this->request->getGet('complete_sdate'), // 완료기간1
'complete_edate' => $this->request->getGet('complete_edate'), // 완료기간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'vrfcreq_way' => $this->request->getGet('vrfcreq_way'), // 검증방식1
'vrfc_type_sub' => $this->request->getGet('vrfc_type_sub'), // 검증방식2
'target_yn' => $this->request->getGet('target_yn'), // 홍보확인서여부
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
'chk_rec' => $this->request->getGet('chk_rec'), // 동의서 유무
'fax_corp' => $this->request->getGet('fax_corp'), // 팩스업체
];
$datas = $this->model->getExcelList($data);
return $this->response->setJSON(body: [
'data' => $datas,
]);
} catch (\Exception $e) {
$e->getPrevious()->getTraceAsString();
}
}
// 상세화면
public function detail($id)
{
$naver = new NaverApiClient();
@@ -621,4 +551,4 @@ class M703 extends BaseController
return redirect()->to('/m703/m703a/detail/' . $fax['fax_sq']);
}
}
}

View File

@@ -1,128 +1,60 @@
<?php
namespace App\Controllers\V2;
use App\Controllers\BaseController;
use App\Controllers\V2\BaseV2Controller;
use App\Libraries\MyUpload;
use App\Libraries\NaverApiClient;
use App\Models\common\CodeModel;
use App\Models\results\M415Model;
use App\Models\v2\M704Model;
use App\Models\v2\M710Model;
use Exception;
use App\Models\v2\M704Model;
class M704 extends BaseController
class M704 extends BaseV2Controller
{
private $model, $codeModel;
public function __construct()
protected function createModel()
{
$this->model = new M704Model();
$this->codeModel = new CodeModel();
return new M704Model();
}
public function lists(): string
protected function getCodeKeys(): array
{
$codes = $this->codeModel->getCodeLists(['STEP_VERIFICATION', 'VRFCREQ_WAY', 'CP_ID', 'TEL_FAIL_CAUSE', 'ARTICLE_TYPE']); // 코드조회
$sido = $this->model->getAreaList(); // 지역조회
$bonbu = $this->model->getBonbuList();
$team = $this->model->getTeamList();
$user = $this->model->getUserList();
$this->data['sido'] = $sido;
$this->data['bonbu'] = $bonbu;
$this->data['team'] = $team;
$this->data['user'] = $user;
$this->data['codes'] = $codes;
return view("pages/v2/m704/lists", $this->data);
return ['STEP_VERIFICATION', 'VRFCREQ_WAY', 'CP_ID', 'TEL_FAIL_CAUSE', 'ARTICLE_TYPE'];
}
public function getResultList()
protected function getViewName(): string
{
$start = (int) $this->request->getGet('start') ?: 0;
$end = (int) $this->request->getGet('length') ?: 10;
return 'pages/v2/m704/lists';
}
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'stat_complete_date' => $this->request->getGet('stat_complete_date'), // 진행상태별시간유형
'complete_sdate' => $this->request->getGet('complete_sdate'), // 진행상태별시간1
'complete_edate' => $this->request->getGet('complete_edate'), // 진행상태별시간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'vrfcreq_way' => $this->request->getGet('vrfcreq_way'), // 검증방식1
'vrfc_type_sub' => $this->request->getGet('vrfc_type_sub'), // 검증방식2
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
'rlet_type_cd' => $this->request->getGet('rlet_type_cd'), // 매물종류
'reference_file_url_yn' => $this->request->getGet('reference_file_url_yn'), // 참고용
'corp_own' => $this->request->getGet('corp_own'), // 법인
protected function getSearchKeys(): array
{
return [
'atcl_no',
'stat_cd',
'realtor_nm',
'charger_gbn',
'assign_yn',
'receipt_sdate',
'receipt_edate',
'stat_complete_date',
'complete_sdate',
'complete_edate',
'srcSido',
'srcGugun',
'srcDong',
'bonbu',
'team',
'damdang',
'vrfcreq_way',
'vrfc_type_sub',
'rcpt_cpid',
'rlet_type_cd',
'reference_file_url_yn',
'corp_own',
];
$totalCount = $this->model->getTotalCount($data);
$datas = $this->model->getResultList($start, $end, $data);
return $this->response->setJSON(body: [
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
'data' => $datas,
]);
}
// 엑셀 다운로드
public function excel()
{
try {
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'stat_complete_date' => $this->request->getGet('stat_complete_date'), // 진행상태별시간유형
'complete_sdate' => $this->request->getGet('complete_sdate'), // 진행상태별시간1
'complete_edate' => $this->request->getGet('complete_edate'), // 진행상태별시간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'vrfcreq_way' => $this->request->getGet('vrfcreq_way'), // 검증방식1
'vrfc_type_sub' => $this->request->getGet('vrfc_type_sub'), // 검증방식2
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
'rlet_type_cd' => $this->request->getGet('rlet_type_cd'), // 매물종류
'reference_file_url_yn' => $this->request->getGet('reference_file_url_yn'), // 참고용
'corp_own' => $this->request->getGet('corp_own'), // 법인
];
$datas = $this->model->getExcelList($data);
return $this->response->setJSON(body: [
'data' => $datas,
]);
} catch (\Exception $e) {
$e->getPrevious()->getTraceAsString();
}
}
// 상세화면
public function detail($id)
{
$naver = new NaverApiClient();
@@ -865,4 +797,4 @@ class M704 extends BaseController
]);
}
}
}
}

View File

@@ -1,86 +1,60 @@
<?php
namespace App\Controllers\V2;
use App\Controllers\BaseController;
use App\Controllers\V2\BaseV2Controller;
use App\Libraries\Common;
use App\Libraries\MyUpload;
use App\Libraries\NaverApiClient;
use App\Models\common\CodeModel;
use App\Models\results\M415Model;
use App\Models\v2\M705Model;
use App\Models\v2\M710Model;
use App\Models\v2\M705Model;
class M705 extends BaseController
class M705 extends BaseV2Controller
{
private $model, $codeModel;
public function __construct()
protected function createModel()
{
$this->model = new M705Model();
$this->codeModel = new CodeModel();
return new M705Model();
}
public function lists(): string
protected function getCodeKeys(): array
{
$codes = $this->codeModel->getCodeLists(['STEP_VERIFICATION', 'VRFCREQ_WAY', 'CP_ID', 'TEL_FAIL_CAUSE', 'ARTICLE_TYPE']); // 코드조회
$sido = $this->model->getAreaList(); // 지역조회
$bonbu = $this->model->getBonbuList();
$team = $this->model->getTeamList();
$user = $this->model->getUserList();
$this->data['sido'] = $sido;
$this->data['bonbu'] = $bonbu;
$this->data['team'] = $team;
$this->data['user'] = $user;
$this->data['codes'] = $codes;
return view("pages/v2/m705/lists", $this->data);
return ['STEP_VERIFICATION', 'VRFCREQ_WAY', 'CP_ID', 'TEL_FAIL_CAUSE', 'ARTICLE_TYPE'];
}
public function getResultList()
protected function getViewName(): string
{
$start = (int) $this->request->getGet('start') ?: 0;
$end = (int) $this->request->getGet('length') ?: 10;
return 'pages/v2/m705/lists';
}
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'complete_sdate' => $this->request->getGet('complete_sdate'), // 완료기간1
'complete_edate' => $this->request->getGet('complete_edate'), // 완료기간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'vrfcreq_way' => $this->request->getGet('vrfcreq_way'), // 검증방식1
'vrfc_type_sub' => $this->request->getGet('vrfc_type_sub'), // 검증방식2
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
'rlet_type_cd' => $this->request->getGet('rlet_type_cd'), // 매물종류
'chk_spc_yn' => $this->request->getGet('chk_spc_yn'), // 면적여부
'reference_file_url_yn' => $this->request->getGet('reference_file_url_yn'), // 참고용
'corp_own' => $this->request->getGet('corp_own'), // 법인
protected function getSearchKeys(): array
{
return [
'atcl_no',
'stat_cd',
'realtor_nm',
'charger_gbn',
'assign_yn',
'receipt_sdate',
'receipt_edate',
'complete_sdate',
'complete_edate',
'srcSido',
'srcGugun',
'srcDong',
'bonbu',
'team',
'damdang',
'vrfcreq_way',
'vrfc_type_sub',
'rcpt_cpid',
'rlet_type_cd',
'chk_spc_yn',
'reference_file_url_yn',
'corp_own',
];
$totalCount = $this->model->getTotalCount($data);
$datas = $this->model->getResultList($start, $end, $data);
return $this->response->setJSON(body: [
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
'data' => $datas,
]);
}
// 배정확인
public function getNotAssign()
{
try {
@@ -750,4 +724,4 @@ class M705 extends BaseController
]);
}
}
}
}

View File

@@ -1,117 +1,54 @@
<?php
namespace App\Controllers\V2;
use App\Controllers\BaseController;
use App\Controllers\V2\BaseV2Controller;
use App\Libraries\NaverApiClient;
use App\Models\common\CodeModel;
use App\Models\results\M415Model;
use App\Models\v2\M706Model;
use App\Models\v2\M710Model;
use Exception;
use App\Models\v2\M706Model;
class M706 extends BaseController
class M706 extends BaseV2Controller
{
private $model, $codeModel;
public function __construct()
protected function createModel()
{
$this->model = new M706Model();
$this->codeModel = new CodeModel();
return new M706Model();
}
public function lists(): string
protected function getCodeKeys(): array
{
$codes = $this->codeModel->getCodeLists(['STEP_VERIFICATION', 'VRFCREQ_WAY', 'CP_ID']); // 코드조회
$sido = $this->model->getAreaList(); // 지역조회
$bonbu = $this->model->getBonbuList();
$team = $this->model->getTeamList();
$user = $this->model->getUserList();
$this->data['sido'] = $sido;
$this->data['bonbu'] = $bonbu;
$this->data['team'] = $team;
$this->data['user'] = $user;
$this->data['codes'] = $codes;
return view("pages/v2/m706/lists", $this->data);
return ['STEP_VERIFICATION', 'VRFCREQ_WAY', 'CP_ID'];
}
public function getResultList()
protected function getViewName(): string
{
$start = (int) $this->request->getGet('start') ?: 0;
$end = (int) $this->request->getGet('length') ?: 10;
return 'pages/v2/m706/lists';
}
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'complete_sdate' => $this->request->getGet('complete_sdate'), // 완료기간1
'complete_edate' => $this->request->getGet('complete_edate'), // 완료기간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'vrfcreq_way' => $this->request->getGet('vrfcreq_way'), // 검증방식1
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
protected function getSearchKeys(): array
{
return [
'atcl_no',
'stat_cd',
'realtor_nm',
'charger_gbn',
'assign_yn',
'receipt_sdate',
'receipt_edate',
'complete_sdate',
'complete_edate',
'srcSido',
'srcGugun',
'srcDong',
'bonbu',
'team',
'damdang',
'vrfcreq_way',
'rcpt_cpid',
];
$totalCount = $this->model->getTotalCount($data);
$datas = $this->model->getResultList($start, $end, $data);
return $this->response->setJSON(body: [
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
'data' => $datas,
]);
}
// 엑셀 다운로드
public function excel()
{
try {
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'complete_sdate' => $this->request->getGet('complete_sdate'), // 완료기간1
'complete_edate' => $this->request->getGet('complete_edate'), // 완료기간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'vrfcreq_way' => $this->request->getGet('vrfcreq_way'), // 검증방식1
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
];
$datas = $this->model->getExcelList($data);
return $this->response->setJSON(body: [
'data' => $datas,
]);
} catch (\Exception $e) {
$e->getPrevious()->getTraceAsString();
}
}
// 상세화면
public function detail($id)
{
$naver = new NaverApiClient();
@@ -619,4 +556,4 @@ class M706 extends BaseController
]);
}
}
}
}

View File

@@ -1,125 +1,59 @@
<?php
namespace App\Controllers\V2;
use App\Controllers\BaseController;
use App\Controllers\V2\BaseV2Controller;
use App\Libraries\Common;
use App\Libraries\NaverApiClient;
use App\Models\common\CodeModel;
use App\Models\results\M415Model;
use App\Models\v2\M708Model;
use App\Models\v2\M710Model;
use Exception;
use App\Models\v2\M708Model;
class M708 extends BaseController
class M708 extends BaseV2Controller
{
private $model, $codeModel;
public function __construct()
protected function createModel()
{
$this->model = new M708Model();
$this->codeModel = new CodeModel();
return new M708Model();
}
public function lists(): string
protected function getCodeKeys(): array
{
$codes = $this->codeModel->getCodeLists(['CP_ID', 'STEP_VERIFICATION', 'RECEIPT_STATUS3', 'ARTICLE_TYPE']); // 코드조회
$sido = $this->model->getAreaList(); // 지역조회
$bonbu = $this->model->getBonbuList();
$team = $this->model->getTeamList();
$user = $this->model->getUserList();
$this->data['sido'] = $sido;
$this->data['bonbu'] = $bonbu;
$this->data['team'] = $team;
$this->data['user'] = $user;
$this->data['codes'] = $codes;
return view("pages/v2/m708/lists", $this->data);
return ['CP_ID', 'STEP_VERIFICATION', 'RECEIPT_STATUS3', 'ARTICLE_TYPE'];
}
public function getResultList()
protected function getViewName(): string
{
$start = (int) $this->request->getGet('start') ?: 0;
$end = (int) $this->request->getGet('length') ?: 10;
return 'pages/v2/m708/lists';
}
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'chk_atcl_no' => $this->request->getGet('chk_atcl_no'), // 매물번호입력
'caller_no' => $this->request->getGet('caller_no'), // 발신팩스번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'complete_sdate' => $this->request->getGet('complete_sdate'), // 완료기간1
'complete_edate' => $this->request->getGet('complete_edate'), // 완료기간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'target_yn' => $this->request->getGet('target_yn'), // 홍보확인서여부
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
'rlet_type_cd' => $this->request->getGet('rlet_type_cd'), // 매물종류
'rcpt_v2' => $this->request->getGet('rcpt_v2'), // 검증방식
protected function getSearchKeys(): array
{
return [
'atcl_no',
'chk_atcl_no',
'caller_no',
'stat_cd',
'realtor_nm',
'charger_gbn',
'assign_yn',
'receipt_sdate',
'receipt_edate',
'complete_sdate',
'complete_edate',
'srcSido',
'srcGugun',
'srcDong',
'bonbu',
'team',
'damdang',
'target_yn',
'rcpt_cpid',
'rlet_type_cd',
'rcpt_v2',
];
$totalCount = $this->model->getTotalCount($data);
$datas = $this->model->getResultList($start, $end, $data);
return $this->response->setJSON(body: [
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
'data' => $datas,
]);
}
// 엑셀 다운로드
public function excel()
{
try {
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'chk_atcl_no' => $this->request->getGet('chk_atcl_no'), // 매물번호입력
'caller_no' => $this->request->getGet('caller_no'), // 발신팩스번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'complete_sdate' => $this->request->getGet('complete_sdate'), // 완료기간1
'complete_edate' => $this->request->getGet('complete_edate'), // 완료기간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'target_yn' => $this->request->getGet('target_yn'), // 홍보확인서여부
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
'rlet_type_cd' => $this->request->getGet('rlet_type_cd'), // 매물종류
'rcpt_v2' => $this->request->getGet('rcpt_v2'), // 검증방식
];
$datas = $this->model->getExcelList($data);
return $this->response->setJSON(body: [
'data' => $datas,
]);
} catch (\Exception $e) {
$e->getPrevious()->getTraceAsString();
}
}
// 상세화면
public function detail($id)
{
$id = (string) $id;
@@ -643,4 +577,4 @@ class M708 extends BaseController
}
}
}
}

View File

@@ -1,120 +1,55 @@
<?php
namespace App\Controllers\V2;
use App\Controllers\BaseController;
use App\Controllers\V2\BaseV2Controller;
use App\Libraries\Common;
use App\Models\common\CodeModel;
use App\Models\v2\M709Model;
class M709 extends BaseController
class M709 extends BaseV2Controller
{
private $model, $codeModel;
public function __construct()
protected function createModel()
{
$this->model = new M709Model();
$this->codeModel = new CodeModel();
return new M709Model();
}
public function lists(): string
protected function getCodeKeys(): array
{
$codes = $this->codeModel->getCodeLists(['CP_ID', 'STEP_VERIFICATION', 'RECEIPT_STATUS3', 'ARTICLE_TYPE']); // 코드조회
$sido = $this->model->getAreaList(); // 지역조회
$bonbu = $this->model->getBonbuList();
$team = $this->model->getTeamList();
$user = $this->model->getUserList();
$this->data['sido'] = $sido;
$this->data['bonbu'] = $bonbu;
$this->data['team'] = $team;
$this->data['user'] = $user;
$this->data['codes'] = $codes;
return view("pages/v2/m709/lists", $this->data);
return ['CP_ID', 'STEP_VERIFICATION', 'RECEIPT_STATUS3', 'ARTICLE_TYPE'];
}
public function getResultList()
protected function getViewName(): string
{
$start = (int) $this->request->getGet('start') ?: 0;
$end = (int) $this->request->getGet('length') ?: 10;
return 'pages/v2/m709/lists';
}
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'chk_atcl_no' => $this->request->getGet('chk_atcl_no'), // 매물번호입력
'caller_no' => $this->request->getGet('caller_no'), // 발신팩스번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'complete_sdate' => $this->request->getGet('complete_sdate'), // 완료기간1
'complete_edate' => $this->request->getGet('complete_edate'), // 완료기간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'target_yn' => $this->request->getGet('target_yn'), // 홍보확인서여부
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
'rlet_type_cd' => $this->request->getGet('rlet_type_cd'), // 매물종류
'rcpt_v2' => $this->request->getGet('rcpt_v2'), // 검증방식
protected function getSearchKeys(): array
{
return [
'atcl_no',
'chk_atcl_no',
'caller_no',
'stat_cd',
'realtor_nm',
'charger_gbn',
'assign_yn',
'receipt_sdate',
'receipt_edate',
'complete_sdate',
'complete_edate',
'srcSido',
'srcGugun',
'srcDong',
'bonbu',
'team',
'damdang',
'target_yn',
'rcpt_cpid',
'rlet_type_cd',
'rcpt_v2',
];
$totalCount = $this->model->getTotalCount($data);
$datas = $this->model->getResultList($start, $end, $data);
return $this->response->setJSON(body: [
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
'data' => $datas,
]);
}
public function excel()
{
try {
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'chk_atcl_no' => $this->request->getGet('chk_atcl_no'), // 매물번호입력
'caller_no' => $this->request->getGet('caller_no'), // 발신팩스번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'complete_sdate' => $this->request->getGet('complete_sdate'), // 완료기간1
'complete_edate' => $this->request->getGet('complete_edate'), // 완료기간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'target_yn' => $this->request->getGet('target_yn'), // 홍보확인서여부
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
'rlet_type_cd' => $this->request->getGet('rlet_type_cd'), // 매물종류
'rcpt_v2' => $this->request->getGet('rcpt_v2'), // 검증방식
];
$datas = $this->model->getExcelList($data);
return $this->response->setJSON(body: [
'data' => $datas,
]);
} catch (\Exception $e) {
$e->getPrevious()->getTraceAsString();
}
}
// 상세화면
public function detail($id)
{
$id = (string) $id;
@@ -395,4 +330,4 @@ class M709 extends BaseController
]);
}
}
}
}

View File

@@ -18,6 +18,7 @@ class M710 extends BaseController
public function lists(): string
{
$codes = $this->codeModel->getCodeLists(['STEP_VERIFICATION', 'VRFCREQ_WAY', 'CP_ID', 'ARTICLE_TYPE']); // 코드조회
$sido = $this->model->getAreaList(); // 지역조회
$bonbu = $this->model->getBonbuList();
$team = $this->model->getTeamList();

View File

@@ -1,128 +1,59 @@
<?php
namespace App\Controllers\V2;
use App\Controllers\BaseController;
use App\Controllers\V2\BaseV2Controller;
use App\Libraries\Common;
use App\Libraries\MyUpload;
use App\Libraries\NaverApiClient;
use App\Models\common\CodeModel;
use App\Models\results\M415Model;
use App\Models\v2\M712Model;
class M712 extends BaseController
class M712 extends BaseV2Controller
{
private $model, $codeModel;
public function __construct()
protected function createModel()
{
$this->model = new M712Model();
$this->codeModel = new CodeModel();
return new M712Model();
}
public function lists(): string
protected function getCodeKeys(): array
{
$codes = $this->codeModel->getCodeLists(['STEP_VERIFICATION', 'VRFCREQ_WAY', 'CP_ID', 'ARTICLE_TYPE']); // 코드조회
$sido = $this->model->getAreaList(); // 지역조회
$bonbu = $this->model->getBonbuList();
$team = $this->model->getTeamList();
$user = $this->model->getUserList();
$this->data['sido'] = $sido;
$this->data['bonbu'] = $bonbu;
$this->data['team'] = $team;
$this->data['user'] = $user;
$this->data['codes'] = $codes;
return view("pages/v2/m712/lists", $this->data);
return ['STEP_VERIFICATION', 'VRFCREQ_WAY', 'CP_ID', 'ARTICLE_TYPE'];
}
public function getResultList()
protected function getViewName(): string
{
$start = (int) $this->request->getGet('start') ?: 0;
$end = (int) $this->request->getGet('length') ?: 10;
return 'pages/v2/m712/lists';
}
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'complete_sdate' => $this->request->getGet('complete_sdate'), // 완료기간1
'complete_edate' => $this->request->getGet('complete_edate'), // 완료기간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'vrfcreq_way' => $this->request->getGet('vrfcreq_way'), // 검증방식1
'vrfc_type_sub' => $this->request->getGet('vrfc_type_sub'), // 검증방식2
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
'rlet_type_cd' => $this->request->getGet('rlet_type_cd'), // 매물종류
'reference_file_url_yn' => $this->request->getGet('reference_file_url_yn'), // 참고용
'ownerTypeCode' => $this->request->getGet('ownerTypeCode'), // 소유자 구분
'document_not_received_yn' => $this->request->getGet('document_not_received_yn'), // 서류미수취
protected function getSearchKeys(): array
{
return [
'atcl_no',
'stat_cd',
'realtor_nm',
'charger_gbn',
'assign_yn',
'receipt_sdate',
'receipt_edate',
'complete_sdate',
'complete_edate',
'srcSido',
'srcGugun',
'srcDong',
'bonbu',
'team',
'damdang',
'vrfcreq_way',
'vrfc_type_sub',
'rcpt_cpid',
'rlet_type_cd',
'reference_file_url_yn',
'ownerTypeCode',
'document_not_received_yn',
];
$totalCount = $this->model->getTotalCount($data);
$datas = $this->model->getResultList($start, $end, $data);
return $this->response->setJSON(body: [
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
'data' => $datas,
]);
}
// 엑셀 다운로드
public function excel()
{
try {
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'complete_sdate' => $this->request->getGet('complete_sdate'), // 완료기간1
'complete_edate' => $this->request->getGet('complete_edate'), // 완료기간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'vrfcreq_way' => $this->request->getGet('vrfcreq_way'), // 검증방식1
'vrfc_type_sub' => $this->request->getGet('vrfc_type_sub'), // 검증방식2
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
'rlet_type_cd' => $this->request->getGet('rlet_type_cd'), // 매물종류
'reference_file_url_yn' => $this->request->getGet('reference_file_url_yn'), // 참고용
'ownerTypeCode' => $this->request->getGet('ownerTypeCode'), // 소유자 구분
'document_not_received_yn' => $this->request->getGet('document_not_received_yn'), // 서류미수취
];
$datas = $this->model->getExcelList($data);
return $this->response->setJSON(body: [
'data' => $datas,
]);
} catch (\Exception $e) {
$e->getPrevious()->getTraceAsString();
}
}
// 상세화면
public function detail($id): string
{
@@ -738,4 +669,4 @@ class M712 extends BaseController
]);
}
}
}
}

View File

@@ -1,127 +1,59 @@
<?php
namespace App\Controllers\V2;
use App\Controllers\BaseController;
use App\Controllers\V2\BaseV2Controller;
use App\Libraries\MyUpload;
use App\Libraries\NaverApiClient;
use App\Models\common\CodeModel;
use App\Models\results\M415Model;
use App\Models\v2\M710Model;
use App\Models\v2\M713Model;
class M713 extends BaseController
class M713 extends BaseV2Controller
{
private $model, $codeModel;
public function __construct()
protected function createModel()
{
$this->model = new M713Model();
$this->codeModel = new CodeModel();
return new M713Model();
}
public function lists(): string
protected function getCodeKeys(): array
{
$codes = $this->codeModel->getCodeLists(['STEP_VERIFICATION', 'VRFCREQ_WAY', 'CP_ID', 'ARTICLE_TYPE']); // 코드조회
$sido = $this->model->getAreaList(); // 지역조회
$bonbu = $this->model->getBonbuList();
$team = $this->model->getTeamList();
$user = $this->model->getUserList();
$this->data['sido'] = $sido;
$this->data['bonbu'] = $bonbu;
$this->data['team'] = $team;
$this->data['user'] = $user;
$this->data['codes'] = $codes;
return view("pages/v2/m713/lists", $this->data);
return ['STEP_VERIFICATION', 'VRFCREQ_WAY', 'CP_ID', 'ARTICLE_TYPE'];
}
public function getResultList()
protected function getViewName(): string
{
$start = (int) $this->request->getGet('start') ?: 0;
$end = (int) $this->request->getGet('length') ?: 10;
return 'pages/v2/m713/lists';
}
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'complete_sdate' => $this->request->getGet('complete_sdate'), // 완료기간1
'complete_edate' => $this->request->getGet('complete_edate'), // 완료기간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'vrfcreq_way' => $this->request->getGet('vrfcreq_way'), // 검증방식1
'vrfc_type_sub' => $this->request->getGet('vrfc_type_sub'), // 검증방식2
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
'rlet_type_cd' => $this->request->getGet('rlet_type_cd'), // 매물종류
'chk_spc_yn' => $this->request->getGet('chk_spc_yn'), // 면적확인
'reference_file_url_yn' => $this->request->getGet('reference_file_url_yn'), // 참고용
'corp_own' => $this->request->getGet('corp_own'), // 법인소유
protected function getSearchKeys(): array
{
return [
'atcl_no',
'stat_cd',
'realtor_nm',
'charger_gbn',
'assign_yn',
'receipt_sdate',
'receipt_edate',
'complete_sdate',
'complete_edate',
'srcSido',
'srcGugun',
'srcDong',
'bonbu',
'team',
'damdang',
'vrfcreq_way',
'vrfc_type_sub',
'rcpt_cpid',
'rlet_type_cd',
'chk_spc_yn',
'reference_file_url_yn',
'corp_own',
];
$totalCount = $this->model->getTotalCount($data);
$datas = $this->model->getResultList($start, $end, $data);
return $this->response->setJSON(body: [
'recordsTotal' => $totalCount,
'recordsFiltered' => $totalCount,
'data' => $datas,
]);
}
// 엑셀 다운로드
public function excel()
{
try {
$data = [
'atcl_no' => $this->request->getGet('atcl_no'), // 매물번호
'stat_cd' => $this->request->getGet('stat_cd'), // 현재상태
'realtor_nm' => $this->request->getGet('realtor_nm'), // 중개소
'charger_gbn' => $this->request->getGet('charger_gbn'), // 배정여부
'assign_yn' => $this->request->getGet('assign_yn'), // 배정여부2
'receipt_sdate' => $this->request->getGet('receipt_sdate'), // 접수기간1
'receipt_edate' => $this->request->getGet('receipt_edate'), // 접수기간2
'complete_sdate' => $this->request->getGet('complete_sdate'), // 완료기간1
'complete_edate' => $this->request->getGet('complete_edate'), // 완료기간2
'srcSido' => $this->request->getGet('srcSido'), // 시도
'srcGugun' => $this->request->getGet('srcGugun'), // 시군구
'srcDong' => $this->request->getGet('srcDong'), // 읍면동
'bonbu' => $this->request->getGet('bonbu'), // 본부
'team' => $this->request->getGet('team'), // 팀
'damdang' => $this->request->getGet('damdang'), // 담당
'vrfcreq_way' => $this->request->getGet('vrfcreq_way'), // 검증방식1
'vrfc_type_sub' => $this->request->getGet('vrfc_type_sub'), // 검증방식2
'rcpt_cpid' => $this->request->getGet('rcpt_cpid'), // 매체사
'rlet_type_cd' => $this->request->getGet('rlet_type_cd'), // 매물종류
'chk_spc_yn' => $this->request->getGet('chk_spc_yn'), // 면적확인
'reference_file_url_yn' => $this->request->getGet('reference_file_url_yn'), // 참고용
'corp_own' => $this->request->getGet('corp_own'), // 법인소유
];
$datas = $this->model->getExcelList($data);
return $this->response->setJSON(body: [
'data' => $datas,
]);
} catch (\Exception $e) {
$e->getPrevious()->getTraceAsString();
}
}
// 상세화면
public function detail($id): string
{
$naver = new NaverApiClient();
@@ -760,4 +692,4 @@ class M713 extends BaseController
]);
}
}
}
}

View File

@@ -13,10 +13,31 @@ class AuthCheck implements FilterInterface
$session = session();
log_message('debug', 'URI PATH: ' . service('uri')->getPath());
// 로그인 체크
if (!$session->get('logged_in')) {
// 로그인 안 되어 있으면 로그인 페이지로
return redirect()->to('/login');
try {
// 세션 읽기 시도
$loggedIn = $session->get('logged_in');
// 로그인 체크
if (!$loggedIn) {
// 로그인 안 되어 있으면 로그인 페이지로
return redirect()->to('/login');
}
} catch (\Exception $e) {
// 세션 오류 (Redis 다운 등) - 모든 예외 catch
log_message('error', '[SESSION ERROR in AuthCheck] ' . $e->getMessage());
// AJAX 요청인 경우 JSON 응답
if ($request->isAJAX()) {
return service('response')
->setStatusCode(503)
->setJSON([
'code' => '8',
'msg' => '세션 서비스 오류입니다. 페이지를 새로고침 해주세요. (Redis)'
]);
}
// 일반 요청인 경우 로그인 페이지로 리다이렉트 (에러 메시지 포함)
return redirect()->to('/login')->with('error', '세션 서비스 오류입니다. 시스템 관리자에게 문의해주세요.');
}
}

View File

@@ -7,40 +7,33 @@ if (!function_exists('limitHscpMarketPriceInfo')) {
* @param string $ptp_no 평형코드
* @param string $atcl_amt 가격
*/
function limitHscpMarketPriceInfo($CI, $trade_type, $hscp_no, $ptp_no, $atcl_amt)
function limitHscpMarketPriceInfo($trade_type, $hscp_no, $ptp_no, $atcl_amt)
{
$return = array();
if (!empty($hscp_no) && !empty($ptp_no)) {
$hscpMarketPriceInfo = $CI->call_kiso_api->hscpMarketPriceInfo($hscp_no, $ptp_no);
$naver = new \App\Libraries\NaverApiClient();
$hscpMarketPriceInfo = $naver->getComplexPriceByUnitType((int)$hscp_no, (int)$ptp_no);
if (isset($hscpMarketPriceInfo['error'])) { //결과값 확인
if ($hscpMarketPriceInfo['error']['code'] == 'VC027') {
$return = array();
} else {
$return = $hscpMarketPriceInfo['error'];
}
if (isset($hscpMarketPriceInfo['error']) && $hscpMarketPriceInfo['error']) { //결과값 확인
log_message('error', '네이버 시세 API 호출 실패: ' . json_encode($hscpMarketPriceInfo));
return array();
} else {
$limitH = 0;
$limitL = 0;
$sise = array();
$sise = $hscpMarketPriceInfo['data'];
// 상한가, 하한가 체크 ( 상한가 * 2, 하한가 * 0.7) 이내의 범위에 가격이 있어야 함.
if ($trade_type == 'A1') {
// 매매
if (isset($hscpMarketPriceInfo['result']['deal_uplmt_prc'])) {
$limitH = $hscpMarketPriceInfo['result']['deal_uplmt_prc'];
}
if (isset($hscpMarketPriceInfo['result']['deal_lwlmt_prc'])) {
$limitL = $hscpMarketPriceInfo['result']['deal_lwlmt_prc'];
}
$limitH = $sise['dealCeilingPrice'] ?? 0;
$limitL = $sise['dealFloorPrice'] ?? 0;
} elseif ($trade_type == 'B1') {
// 전세
if (isset($hscpMarketPriceInfo['result']['lease_uplmt_prc'])) {
$limitH = $hscpMarketPriceInfo['result']['lease_uplmt_prc'];
}
if (isset($hscpMarketPriceInfo['result']['lease_lwlmt_prc'])) {
$limitL = $hscpMarketPriceInfo['result']['lease_lwlmt_prc'];
}
$limitH = $sise['leaseCeilingPrice'] ?? 0;
$limitL = $sise['leaseFloorPrice'] ?? 0;
}
if (!empty($limitH)) {
@@ -316,3 +309,146 @@ function getOwnerTypeCodeNo($code)
break;
}
}
/**
* 무엇이 변경되었는지 확인한다.
* $table(DB의 원래 데이터), data (변경된 내용) ==> array
*/
function what_is_changed($table, $data){
$return = '';
if (empty($table) || empty($data)){
log_message('warning', 'what_is_changed 함수에 빈 데이터 전달 | table: ' . json_encode($table) . ', data: ' . json_encode($data));
return $return;
}
foreach ( $data as $key => $value ) {
if (!array_key_exists($key, $table)) {
$table[$key] = '';
}
if (strcmp(trim((string) $table[$key]), trim((string) $value)) != 0){
switch ( $key ) {
case 'trade_type': // 거래구분
$return .= ', 거래구분 : ';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'rcpt_product_info2': // 매매, 전세, 월세보증금
$return .= ', 거래가 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'rcpt_product_info3': // 월세
$return .= ', 월세 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'rcpt_dtl_addr': // 상세주소
$return .= ', 상세주소 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'rcpt_li_addr': //리 주소
$return .= ', 리 주소 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'rcpt_jibun_addr': //지번 주소
$return .= ', 지번 주소 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'rcpt_etc_addr': //기타 주소
$return .= ', 기타 주소 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'rcpt_ref_addr': // 상세주소
$return .= ', 기타주소2 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'rcpt_ho': // 기타주소
$return .= ', 기타주소 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'rcpt_hscp_no': // 단지
$return .= ', 단지번호 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'rcpt_ptp_no': // 평형
$return .= ', 평형 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'rcpt_floor': // 층
$return .= ', 층 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'exp_spc_yn': // 층
$return .= ', 면적확인여부 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'excls_spc1': // 층
$return .= ', 전용면적 첫번째 값 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'excls_spc2': // 층
$return .= ', 전용면적 두번째 값 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'room_cnt': // 층
$return .= ', 방개수 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'sply_spc': // 층
$return .= ', 공급면적 값 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'share_spc': // 층
$return .= ', 공용면적 값 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'tot_spc': // 층
$return .= ', 연면적 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'tot_spc1': // 층
$return .= ', 연면적 첫번째 값 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'tot_spc2': // 층
$return .= ', 연면적 두번째 값 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'grnd_spc1': // 층
$return .= ', 대지면적 첫번째 값 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'grnd_spc2': // 층
$return .= ', 대지면적 두번째 값 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'grnd_spc3': // 층
$return .= ', 대지면적 세번째 값 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'grnd_spc4': // 층
$return .= ', 대지면적 네번째 값 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'grnd_spc5': // 층
$return .= ', 대지면적 다섯번째 값 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'spc_stat': // 층
$return .= ', 면적구분 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
case 'request_msg': // 층
$return .= ', 메모변경 :';
$return .= $table[$key] . ' => ' . $value . "\n";
break;
// case 'rcpt_x': // 좌표 x 12x.xxxxx (경도: longitude)
// $return .= '거래구분 :';
// break;
// case 'rcpt_y': // 좌표 y 3x.xxxx (위도: latitude)
// $return .= '거래구분 :';
// break;
}
}
}
return trim(substr($return, 1));
}

View File

@@ -6,7 +6,7 @@ if (! function_exists('write_custom_log')) {
*/
function write_custom_log($message, $level = 'INFO', $type = 'service')
{
$logDir = WRITEPATH . 'logs/worker';
$logDir = WRITEPATH . 'logs';
if (!is_dir($logDir)) {
@mkdir($logDir, 0777, true);
}
@@ -31,8 +31,8 @@ if (! function_exists('write_custom_log')) {
}
// ----------------------------
$suffix = ($type === 'failed') ? '_failed' : '';
$logFile = $logDir . '/' . date('Y-m-d') . $suffix . '.log';
$suffix = ($type === 'failed') ? '-failed' : '';
$logFile = $logDir . '/log-' . date('Y-m-d') . $suffix . '.log';
$timestamp = date('Y-m-d H:i:s');
$singleLine = str_replace(["\r", "\n", "\t"], " ", $message);

View File

@@ -0,0 +1,35 @@
<?php
if (!function_exists('write_query_log')) {
/**
* SQL 쿼리 로그를 writable/logs/sql-query-{date}.log에 기록
*
* @param string $message 로그 메시지
* @param string $sql SQL 쿼리 (선택)
*/
function write_query_log(string $message, string $sql = '')
{
$logDir = WRITEPATH . 'logs';
$logFile = $logDir . '/sql-query-' . date('Y-m-d') . '.log';
$timestamp = date('Y-m-d H:i:s');
$logMessage = "[{$timestamp}] {$message}";
if (!empty($sql)) {
$logMessage .= "\nSQL: {$sql}";
}
$logMessage .= "\n" . str_repeat('-', 80) . "\n";
// 로그 디렉토리 확인 및 생성
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
// 파일에 쓰기
file_put_contents($logFile, $logMessage, FILE_APPEND | LOCK_EX);
// log_message도 호출 (작동하면 좋고, 안되도 파일에는 기록됨)
log_message('error', $message);
}
}

View File

@@ -0,0 +1,82 @@
<?php
if (!function_exists('get_redis_connection')) {
/**
* Redis 연결을 반환하는 헬퍼 함수
* .env 환경변수에서 설정을 읽어 Redis에 연결합니다.
*
* @param string $type 'worker' 또는 'session' (기본값: 'worker')
* @return Redis|false Redis 객체 또는 연결 실패 시 false
* @throws RedisException
*/
function get_redis_connection(string $type = 'worker')
{
$redis = new \Redis();
try {
// 타입에 따라 다른 환경변수 사용
if ($type === 'session') {
$host = env('SESSION_REDIS_HOST', '127.0.0.1');
$port = env('SESSION_REDIS_PORT', '6379');
$database = env('SESSION_REDIS_DATABASE', '0');
$password = env('SESSION_REDIS_PASSWORD', '');
} else {
// worker, default
$host = env('REDIS_HOST', '127.0.0.1');
$port = env('REDIS_PORT', '6379');
$database = env('REDIS_DATABASE', '9');
$password = env('REDIS_PASSWORD', '');
}
// Redis 연결 (타임아웃: 0.5초)
$timeout = 0.5;
$success = $redis->connect($host, (int)$port, $timeout);
if (!$success) {
log_message('error', "Redis connection failed: {$host}:{$port}");
return false;
}
// 비밀번호 인증 (있는 경우)
if (!empty($password)) {
$redis->auth($password);
}
// 데이터베이스 선택
$redis->select((int)$database);
return $redis;
} catch (\RedisException $e) {
log_message('error', "Redis connection error: " . $e->getMessage());
return false;
}
}
}
if (!function_exists('get_redis_config')) {
/**
* Redis 설정 정보를 배열로 반환
*
* @param string $type 'worker' 또는 'session'
* @return array Redis 설정 배열
*/
function get_redis_config(string $type = 'worker'): array
{
if ($type === 'session') {
return [
'host' => env('SESSION_REDIS_HOST', '127.0.0.1'),
'port' => env('SESSION_REDIS_PORT', '6379'),
'database' => env('SESSION_REDIS_DATABASE', '0'),
'password' => env('SESSION_REDIS_PASSWORD', ''),
];
}
return [
'host' => env('REDIS_HOST', '127.0.0.1'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DATABASE', '9'),
'password' => env('REDIS_PASSWORD', ''),
];
}
}

View File

@@ -41,12 +41,34 @@ class NaverApiClient
* object $space 면적정보[비공동] (supplySpace,exclusiveSpace,totalSpace,groundSpace,buildingSpace)
* object $facilities 비공동시설정보 (roomCount)
*/
/** 현장확인 수정시 */
public function updateArticleInfo(string $articleNumber, array $updateData, string $charger = 'admin'): ?array
{
$this->charger = $charger;
$url = "{$this->baseUrl}/kiso/center/verification-article/{$articleNumber}?charger={$this->charger}";
$url = "{$this->baseUrl}/kiso/center/verification-site/{$articleNumber}?charger={$this->charger}";
log_message('info', "[updateArticleInfo] 호출됨 - URL: {$url}, 데이터: " . json_encode($updateData, JSON_UNESCAPED_UNICODE));
$result = $this->request('PUT', $url, $updateData);
log_message('info', "[updateArticleInfo] 결과 - " . ($result === null ? 'NULL' : json_encode($result, JSON_UNESCAPED_UNICODE)));
return $result;
}
return $this->request('PUT', $url, $updateData);
/** 일반매물 수정시 */
public function v2updateArticleInfo(string $articleNumber, array $updateData, string $charger = 'admin'): ?array
{
$this->charger = $charger;
$url = "{$this->baseUrl}/kiso/center/verification-article/{$articleNumber}?charger={$this->charger}";
log_message('info', "[v2updateArticleInfo] 호출됨 - URL: {$url}, 데이터: " . json_encode($updateData, JSON_UNESCAPED_UNICODE));
$result = $this->request('PUT', $url, $updateData);
log_message('info', "[v2updateArticleInfo] 결과 - " . ($result === null ? 'NULL' : json_encode($result, JSON_UNESCAPED_UNICODE)));
return $result;
}
/**
@@ -109,9 +131,132 @@ class NaverApiClient
$this->charger = $charger;
$url = "{$this->baseUrl}/kiso/center/verification-article/price/{$articleNumber}?charger={$this->charger}";
return $this->request('POST', $url, $priceData);
return $this->request('PATCH', $url, $priceData);
}
/**
* 현장확인 슬롯 동기화
* API (가) - /kiso/center/site-slot
* 신규/변경 일자별 데이터 전송
* @syncData array
* @param string $baseDate 기준일자(ISO 8601 형식: YYYY-MM-DD 예: 2024-01-31)
* @param string $legalDivisionNumber 구역번호 "1111000000"
* @param object slots 오전/오후/ 슬롯 정보
[
{
"baseDate": "2025-04-30",
"legalDivisionNumber": "1111000000",
"slots": {
"am": { "max": 10, "reserved": 0 },
"pm": { "max": 100, "reserved": 0 }
}
}
]
*/
public function syncSiteSlot($syncData){
$url = $this->baseUrl . "/kiso/center/verification-site/slots";
return $this->request('POST', $url, $syncData);
}
/**
* 현장확인 매물검증 결과 관리
* API: POST /kiso/center/verification-site/{매물번호}/report?charger={담당자명}
* @param string $type 현장확인 S11: 예약, S21: 방문, S31: 검수
* @param string $code 결과코드 10000: 성공, 20000: 실패, 30000: 지연, 20121: 방문전취소, 20122:방문후취소, 20123:촬영후취소
* @param string $message 내용
* @param string $reserveDate 예약 년월일 2005-07-31
*/
public function verificationSiteReport(string $articleNumber, array $reportData, string $charger = 'admin'): ?array
{
$this->charger = $charger;
$url = "{$this->baseUrl}/kiso/center/verification-site/{$articleNumber}/report?charger={$this->charger}";
return $this->request('POST', $url, $reportData);
}
/**
* 특정 단지/평형 시세조회()
* @param Long hscpNo 단지번호
* @param int ptpNo 평형번호
* API: GET /confirms/hscpMarketPriceInfo.nhn?hscpNo={단지번호}&ptpNo={평형번호}
*/
// public function hscpMarketPriceInfo($hscpNo, $ptpNo){
// $url = '/confirms/hscpMarketPriceInfo.nhn';
// $url = $this->commonModel->getCompanyInfo(3);
// $url = $url['api_server'] . $url;
// $data = [
// 'hscpNo' => $hscpNo,
// 'ptpNo' => $ptpNo
// ];
// return $this->request('GET', $url, $data);
// }
public function getComplexPriceByUnitType(int $hscpNo, int $ptpNo): ?array
{
$url = $this->baseUrl . "/kiso/marketprice/complex/{$hscpNo}/pyeong-type/{$ptpNo}";
return $this->request('GET', $url);
}
/**
* 법정동 기준 단지 목록 조회
* API: GET /kiso/complex/legal-division/{법정동코드}
* legalDivisionNumber : 법정동 코드
*/
public function getComplexList($legalDivisionNumber, $realEstateType = null)
{
$url = $this->baseUrl . "/kiso/complex/legal-division/{$legalDivisionNumber}";
if ($realEstateType !== null) {
$url .= "?realEstateType={$realEstateType}";
}
return $this->request('GET', $url);
}
/**
* 단지평형목록 조회(아파트/오피스텔)
*/
public function getPyeongTypeList($complexNumber){
$url = $this->baseUrl . "/kiso/complex/{$complexNumber}/pyeongs";
return $this->request('GET', $url);
}
/**
* 빌라 단지 평형 목록 조회
*/
public function getVillaPyeongTypeList($complexNumber){
$url = $this->baseUrl . "/kiso/complex/villa/{$complexNumber}/pyeongs";
return $this->request('GET', $url);
}
/**
* 단지 상세 조회
*/
public function getComplexDetail($complexNumber){
$url = $this->baseUrl . "/kiso/complex/{$complexNumber}";
return $this->request('GET', $url);
}
/**
* 빌라 단지 상세 조회
*/
public function getVillaComplexDetail($complexNumber){
$url = $this->baseUrl . "/kiso/complex/villa/{$complexNumber}";
return $this->request('GET', $url);
}
/**
* 빌라 단지 동호수 조회
*/
public function getVillaBuildingList($complexNumber){
$url = $this->baseUrl . "/kiso/complex/villa/{$complexNumber}/buildings";
return $this->request('GET', $url);
}
/**
* 현장확인 동기화 결과 전송
* API: GET /site/submitSyncResult.nhn?reserveNoList={예약번호리스트}
* @param string reserveNoList 예약번호 리스트 (쉼표로 구분된 문자열, 예: "12345,67890,54321")
*/
public function submitSyncResult(string $reserveNoList): ?array
{
$url = "{$this->baseUrl}/site/submitSyncResult.nhn";
@@ -435,31 +580,6 @@ class NaverApiClient
return $this->request('POST', $url, $postData);
}
/**
* 현장확인 슬롯 동기화
* API (가) - /kiso/center/site-slot
* 신규/변경 일자별 데이터 전송
* @syncData array
* @param string $baseDate 기준일자(ISO 8601 형식: YYYY-MM-DD 예: 2024-01-31)
* @param string $legalDivisionNumber 구역번호 "1111000000"
* @param object slots 오전/오후/ 슬롯 정보
[
{
"baseDate": "2025-04-30",
"legalDivisionNumber": "1111000000",
"slots": {
"am": { "max": 10, "reserved": 0 },
"pm": { "max": 100, "reserved": 0 }
}
}
]
*/
public function syncSiteSlot($syncData){
$url = $this->baseUrl . "/kiso/center/site-verification/slots";
return $this->request('POST', $url, $syncData);
}
/**
* CURL 공통 실행 함수
*/
@@ -525,7 +645,13 @@ class NaverApiClient
// CURL 오류 체크
if ($curlErrno !== 0) {
log_message('error', "[Naver API $method CURL ERROR] URL: $url | Error ($curlErrno): $curlError");
return null;
return [
'error' => true,
'error_type' => 'CURL_ERROR',
'curl_errno' => $curlErrno,
'curl_error' => $curlError,
'url' => $url
];
}
// 결과 로그 기록 (성공/실패 모두 기록하여 추적 가능하게 함)
@@ -533,7 +659,13 @@ class NaverApiClient
log_message('info', "[Naver API $method SUCCESS] URL: $url | Code: $httpCode | Response: $response");
} else {
log_message('error', "[Naver API $method FAIL] URL: $url | Code: $httpCode | Response: $response");
return null;
return [
'error' => true,
'error_type' => 'HTTP_ERROR',
'http_code' => $httpCode,
'response' => $response,
'url' => $url
];
}
return json_decode($response, true);

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Models\Entities;
use CodeIgniter\Model;
class ChangedHistoryModel extends Model
{
protected $table = 'changed_history';
protected $primaryKey = 'seq';
protected $useAutoIncrement = true;
protected $returnType = 'array';
protected $protectFields = true;
protected $allowedFields = [
'rcpt_sq',
'rcpt_stat',
'changed_type',
'changed_id',
'changed_tm',
'remark',
];
/**
* 정보변경 이력 1건 저장
*/
public function addHistory(
int $rcptSq,
?string $rcptStat,
?string $changedType,
?string $changedId,
?string $remark,
?string $changedTm = null
): bool {
$data = [
'rcpt_sq' => $rcptSq,
'rcpt_stat' => $rcptStat,
'changed_type' => $changedType,
'changed_id' => $changedId,
'changed_tm' => $changedTm ?? date('Y-m-d H:i:s'),
'remark' => $remark,
];
return $this->insert($data) !== false;
}
}

View File

@@ -13,7 +13,10 @@ class ReceiptModel extends Model
protected $allowedFields = [
'comp_sq', 'rcpt_rating', 'rcpt_key', 'rcpt_atclno', 'rcpt_type',
'rcpt_product', 'rcpt_product_nm', 'rcpt_product_info1', 'rcpt_product_info2',
'rcpt_product_info3', 'rcpt_office', 'rcpt_agent', 'rcpt_sido', 'rcpt_hscp_nm',
'rcpt_product_info3', 'rcpt_product_info4', 'rcpt_product_info5', 'rcpt_product_info6',
'trade_type', 'rcpt_ptp_no', 'rcpt_hscp_no',
'dealAmount', 'warrantyAmount', 'leaseAmount', 'preSaleAmount', 'premiumAmount', 'preSaleOptionAmount',
'rcpt_office', 'rcpt_agent', 'rcpt_sido', 'rcpt_hscp_nm',
'rcpt_dtl_addr', 'rcpt_etc_addr', 'rcpt_floor', 'rcpt_floor2', 'rcpt_tm',
'rcpt_stat', 'rcpt_x', 'rcpt_y', 'agent_nm', 'agent_head_tel', 'rsrv_date',
'insert_tm', 'rcpt_cpid', 'room_cnt', 'isSiteVRVerification'
@@ -104,4 +107,5 @@ class ReceiptModel extends Model
'total' => $totalCount
];
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Models\Entities;
use CodeIgniter\Model;
class RegionModel extends Model
{
protected $table = 'region_codes';
protected $primaryKey = 'region_cd';
protected $returnType = 'array';
protected $useSoftDeletes = false;
protected $useTimestamps = false;
protected $allowedFields = [
'region_cd', 'region_nm', 'use_yn', 'dept_sq', 'usr_sq'
];
/**
* 지역 코드로 담당자 정보 조회
*
* @param string $regionCd 지역 코드 (sectorNumber)
* @return array|null ['dept_sq' => 26, 'usr_sq' => 1] 또는 null
*/
public function getChargeByRegionCd($regionCd)
{
if (empty($regionCd)) {
return null;
}
return $this->select('dept_sq, usr_sq')
->where('region_cd', $regionCd)
->where('use_yn', 'Y')
->first();
}
}

View File

@@ -6,318 +6,294 @@ use CodeIgniter\Model;
class ProcessibleModel extends Model
{
// 지역 목록 조회
public function getAreaList($sido = '', $gugun = '')
{
public function getAreaList($sido = '', $gugun = '')
{
if (!empty($gugun)) {
$gugun = substr($gugun, '0', '5');
if (!empty($gugun)) {
$gugun = substr($gugun, '0', '5');
$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,5),'00000')" .
" WHERE a.region_cd LIKE concat(?, '%')" .
" AND a.region_cd NOT LIKE '%00000'" .
" AND a.region_cd LIKE '%00'" .
" AND a.use_yn = 'Y'" .
" ORDER BY a.region_nm ASC";
$query = $this->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";
}
" FROM region_codes a" .
" LEFT JOIN region_codes b ON b.region_cd = CONCAT(SUBSTR(a.region_cd,1,5),'00000')" .
" WHERE a.region_cd LIKE concat(?, '%')" .
" AND a.region_cd NOT LIKE '%00000'" .
" AND a.region_cd LIKE '%00'" .
" AND a.use_yn = 'Y'" .
" 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, [$gugun]);
$query = $this->db->query($sql);
} 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();
}
return $query->getResultArray();
}
/**
* 일자별 처리가능 수량
*/
public function getTotal1($data)
{
$sql = "
SELECT
COUNT(*) AS cnt
FROM
(
SELECT
a.sol_date sc_date, DATE_FORMAT(a.sol_date, '%w') day_week
, sum(ifnull(c.am_cnt, b.am_cnt)) am_cnt
, sum(ifnull(c.pm_cnt,b.pm_cnt)) pm_cnt
, sum(ifnull(c.day_cnt, b.day_cnt)) day_cnt
, case count(b.region_cd) when count(c.region_cd) then 'Y' ELSE 'N' END svc_check
, sum(c.day_cnt) svc_count
FROM calendar a
LEFT JOIN service_count b on b.sc_date = '0000-00-00'
LEFT JOIN service_count c on c.sc_date = a.sol_date AND c.region_cd = b.region_cd
WHERE a.sol_date BETWEEN ? AND ?
GROUP BY a.sol_date ASC
) AS a";
$query = $this->db->query($sql, [$data['sdate'], $data['edate']]);
/**
* 일자별 처리가능 수량
*/
public function getTotal1($data)
{
$sql = "
SELECT
COUNT(*) AS cnt
FROM
(
SELECT
a.sol_date sc_date, DATE_FORMAT(a.sol_date, '%w') day_week
, sum(ifnull(c.am_cnt, b.am_cnt)) am_cnt
, sum(ifnull(c.pm_cnt,b.pm_cnt)) pm_cnt
, sum(ifnull(c.day_cnt, b.day_cnt)) day_cnt
, case count(b.region_cd) when count(c.region_cd) then 'Y' ELSE 'N' END svc_check
, sum(c.day_cnt) svc_count
FROM calendar a
LEFT JOIN service_count b on b.sc_date = '0000-00-00'
LEFT JOIN service_count c on c.sc_date = a.sol_date AND c.region_cd = b.region_cd
WHERE a.sol_date BETWEEN ? AND ?
GROUP BY a.sol_date ASC
) AS a";
return $query->getRow()->cnt;
}
$query = $this->db->query($sql, [$data['sdate'], $data['edate']]);
public function getList1($start, $end, $data)
{
$sql = "SELECT
a.sol_date sc_date, DATE_FORMAT(a.sol_date, '%w') day_week
, sum(ifnull(c.am_cnt, b.am_cnt)) am_cnt
, sum(ifnull(c.pm_cnt,b.pm_cnt)) pm_cnt
, sum(ifnull(c.day_cnt, b.day_cnt)) day_cnt
, case count(b.region_cd) when count(c.region_cd) then 'Y' ELSE 'N' END svc_check
, sum(c.day_cnt) svc_count
FROM calendar a
LEFT JOIN service_count b on b.sc_date = '0000-00-00'
LEFT JOIN service_count c on c.sc_date = a.sol_date AND c.region_cd = b.region_cd
WHERE a.sol_date BETWEEN ? AND ?
GROUP BY a.sol_date ASC ";
return $query->getRow()->cnt;
}
$sql .= "LIMIT {$start}, {$end}";
public function getList1($start, $end, $data)
{
$sql = "SELECT
a.sol_date sc_date, DATE_FORMAT(a.sol_date, '%w') day_week
, sum(ifnull(c.am_cnt, b.am_cnt)) am_cnt
, sum(ifnull(c.pm_cnt,b.pm_cnt)) pm_cnt
, sum(ifnull(c.day_cnt, b.day_cnt)) day_cnt
, case count(b.region_cd) when count(c.region_cd) then 'Y' ELSE 'N' END svc_check
, sum(c.day_cnt) svc_count
FROM calendar a
LEFT JOIN service_count b on b.sc_date = '0000-00-00'
LEFT JOIN service_count c on c.sc_date = a.sol_date AND c.region_cd = b.region_cd
WHERE a.sol_date BETWEEN ? AND ?
GROUP BY a.sol_date ASC ";
$query = $this->db->query($sql, [$data['sdate'], $data['edate']]);
$sql .= "LIMIT {$start}, {$end}";
$query = $this->db->query($sql, [$data['sdate'], $data['edate']]);
return $query->getResultArray();
}
return $query->getResultArray();
}
/**
* 지역별 수량
*/
public function getTotal2($data)
{
$sql = "SELECT
COUNT(*) AS cnt
FROM calendar a
LEFT JOIN service_count b on b.sc_date = '0000-00-00' and b.region_cd LIKE CONCAT(SUBSTR(?,1,2),'%')
LEFT JOIN service_count c on c.sc_date = a.sol_date and c.region_cd = b.region_cd
JOIN region_codes d on d.region_cd = b.region_cd and d.use_yn = 'Y'
WHERE a.sol_date = ?
ORDER BY d.region_nm";
public function getTotal2($data)
{
$sql = "SELECT
COUNT(*) AS cnt
FROM calendar a
LEFT JOIN service_count b on b.sc_date = '0000-00-00' and b.region_cd LIKE CONCAT(SUBSTR(?,1,2),'%')
LEFT JOIN service_count c on c.sc_date = a.sol_date and c.region_cd = b.region_cd
JOIN region_codes d on d.region_cd = b.region_cd and d.use_yn = 'Y'
WHERE a.sol_date = ?
ORDER BY d.region_nm";
$query = $this->db->query($sql, [$data['region'], $data['sdate']]);
$query = $this->db->query($sql, [$data['region'], $data['sdate']]);
return $query->getRow()->cnt;
}
return $query->getRow()->cnt;
}
public function getList2($data)
{
$sql = "SELECT
a.sol_date
, b.region_cd, d.region_nm
, c.am_cnt, b.am_cnt default_am_cnt
, c.pm_cnt, b.pm_cnt default_pm_cnt
, c.day_cnt, b.day_cnt default_day_cnt
FROM calendar a
LEFT JOIN service_count b on b.sc_date = '0000-00-00' and b.region_cd LIKE CONCAT(SUBSTR(?,1,2),'%')
LEFT JOIN service_count c on c.sc_date = a.sol_date and c.region_cd = b.region_cd
JOIN region_codes d on d.region_cd = b.region_cd and d.use_yn = 'Y'
WHERE a.sol_date = ?
ORDER BY d.region_nm";
public function getList2($data)
{
$sql = "SELECT
a.sol_date
, b.region_cd, d.region_nm
, c.am_cnt, b.am_cnt default_am_cnt
, c.pm_cnt, b.pm_cnt default_pm_cnt
, c.day_cnt, b.day_cnt default_day_cnt
FROM calendar a
LEFT JOIN service_count b on b.sc_date = '0000-00-00' and b.region_cd LIKE CONCAT(SUBSTR(?,1,2),'%')
LEFT JOIN service_count c on c.sc_date = a.sol_date and c.region_cd = b.region_cd
JOIN region_codes d on d.region_cd = b.region_cd and d.use_yn = 'Y'
WHERE a.sol_date = ?
ORDER BY d.region_nm";
$query = $this->db->query($sql, [$data['region'], $data['sdate']]);
$query = $this->db->query($sql, [$data['region'], $data['sdate']]);
return $query->getResultArray();
}
return $query->getResultArray();
}
/**
* 기본수량
*/
public function getTotal3($data)
{
$sql = "SELECT
COUNT(*) AS cnt
FROM (SELECT DISTINCT CONCAT(SUBSTR(aa.region_cd, 1, 5), '00000') region_cd FROM region_codes aa WHERE aa.region_cd like CONCAT(SUBSTR(?,1,2),'%') AND aa.region_cd NOT LIKE '%00000000' AND aa.dept_sq != 0 AND aa.use_yn = 'Y') a
INNER JOIN region_codes b ON b.region_cd = a.region_cd
LEFT JOIN service_count c ON c.sc_date = '0000-00-00' AND c.region_cd = a.region_cd
ORDER BY b.region_nm";
public function getTotal3($data)
{
$sql = "SELECT
COUNT(*) AS cnt
FROM (SELECT DISTINCT CONCAT(SUBSTR(aa.region_cd, 1, 5), '00000') region_cd FROM region_codes aa WHERE aa.region_cd like CONCAT(SUBSTR(?,1,2),'%') AND aa.region_cd NOT LIKE '%00000000' AND aa.dept_sq != 0 AND aa.use_yn = 'Y') a
INNER JOIN region_codes b ON b.region_cd = a.region_cd
LEFT JOIN service_count c ON c.sc_date = '0000-00-00' AND c.region_cd = a.region_cd
ORDER BY b.region_nm";
$query = $this->db->query($sql, [$data['region']]);
$query = $this->db->query($sql, [$data['region']]);
return $query->getRow()->cnt;
}
return $query->getRow()->cnt;
}
public function getList3($data)
{
$sql = "SELECT
b.region_cd, b.region_nm, IFNULL(c.am_cnt,0)am_cnt, IFNULL(c.pm_cnt,0) pm_cnt
FROM (SELECT DISTINCT CONCAT(SUBSTR(aa.region_cd, 1, 5), '00000') region_cd FROM region_codes aa WHERE aa.region_cd like CONCAT(SUBSTR(?,1,2),'%') AND aa.region_cd NOT LIKE '%00000000' AND aa.dept_sq != 0 AND aa.use_yn = 'Y') a
INNER JOIN region_codes b ON b.region_cd = a.region_cd
LEFT JOIN service_count c ON c.sc_date = '0000-00-00' AND c.region_cd = a.region_cd
ORDER BY b.region_nm";
public function getList3($data)
{
$sql = "SELECT
b.region_cd, b.region_nm, IFNULL(c.am_cnt,0)am_cnt, IFNULL(c.pm_cnt,0) pm_cnt
FROM (SELECT DISTINCT CONCAT(SUBSTR(aa.region_cd, 1, 5), '00000') region_cd FROM region_codes aa WHERE aa.region_cd like CONCAT(SUBSTR(?,1,2),'%') AND aa.region_cd NOT LIKE '%00000000' AND aa.dept_sq != 0 AND aa.use_yn = 'Y') a
INNER JOIN region_codes b ON b.region_cd = a.region_cd
LEFT JOIN service_count c ON c.sc_date = '0000-00-00' AND c.region_cd = a.region_cd
ORDER BY b.region_nm";
$query = $this->db->query($sql, [$data['region']]);
$query = $this->db->query($sql, [$data['region']]);
return $query->getResultArray();
}
return $query->getResultArray();
}
// 일자별 수량 엑셀
public function getExcelList($data)
{
$sql = "SELECT
a.sol_date sc_date
, CASE WHEN DATE_FORMAT(a.sol_date, '%w') = 'N' THEN '기본지정' ELSE '별도지정' END AS sc_type
, sum(ifnull(c.am_cnt, b.am_cnt)) am_cnt
, sum(ifnull(c.pm_cnt,b.pm_cnt)) pm_cnt
, sum(ifnull(c.day_cnt, b.day_cnt)) day_cnt
, case count(b.region_cd) when count(c.region_cd) then 'Y' ELSE 'N' END svc_check
, sum(c.day_cnt) svc_count
FROM calendar a
LEFT JOIN service_count b on b.sc_date = '0000-00-00'
LEFT JOIN service_count c on c.sc_date = a.sol_date AND c.region_cd = b.region_cd
WHERE a.sol_date BETWEEN ? AND ?
GROUP BY a.sol_date ASC";
public function getExcelList($data)
{
$sql = "SELECT
a.sol_date sc_date
, CASE WHEN DATE_FORMAT(a.sol_date, '%w') = 'N' THEN '기본지정' ELSE '별도지정' END AS sc_type
, sum(ifnull(c.am_cnt, b.am_cnt)) am_cnt
, sum(ifnull(c.pm_cnt,b.pm_cnt)) pm_cnt
, sum(ifnull(c.day_cnt, b.day_cnt)) day_cnt
, case count(b.region_cd) when count(c.region_cd) then 'Y' ELSE 'N' END svc_check
, sum(c.day_cnt) svc_count
FROM calendar a
LEFT JOIN service_count b on b.sc_date = '0000-00-00'
LEFT JOIN service_count c on c.sc_date = a.sol_date AND c.region_cd = b.region_cd
WHERE a.sol_date BETWEEN ? AND ?
GROUP BY a.sol_date ASC";
$query = $this->db->query($sql, [$data['sdate'], $data['edate']]);
$query = $this->db->query($sql, [$data['sdate'], $data['edate']]);
return $query->getResultArray();
}
return $query->getResultArray();
}
// 지역별 수량 저장
public function saveArea($data)
{
$this->db->transStart();
public function saveArea($data)
{
$usr_sq = session('usr_sq');
$now = date('Y-m-d H:i:s');
$usr_sq = session('usr_sq');
$insertData = [
'sc_date' => $data['sc_date'],
'region_cd' => $data['region_cd'],
'am_cnt' => $data['am_cnt'],
'pm_cnt' => $data['pm_cnt'],
'day_cnt' => (int)$data['am_cnt'] + (int)$data['pm_cnt'],
'insert_usr' => $usr_sq,
'insert_tm' => $now,
'update_usr' => $usr_sq,
'update_tm' => $now
];
$sql = "INSERT INTO service_count
(sc_date, region_cd, am_cnt, pm_cnt, day_cnt, insert_usr, insert_tm, update_usr, update_tm)
VALUES
(?, ?, ?, ?, ?, ?, NOW(), ?, NOW())
ON DUPLICATE KEY UPDATE am_cnt=values(am_cnt), pm_cnt=values(pm_cnt), day_cnt=values(day_cnt), update_usr=values(update_usr)
";
// CI4 Query Builder upsert (INSERT ... ON DUPLICATE KEY UPDATE)
$result = $this->db->table('service_count')->upsert($insertData);
$datas = [
$data['sc_date'],
$data['region_cd'],
$data['am_cnt'],
$data['pm_cnt'],
((int) $data['am_cnt'] + (int) $data['pm_cnt']),
$usr_sq,
$usr_sq
];
if ($this->db->query($sql, $datas) === false) {
return [
'success' => false,
'msg' => '저장실패',
];
return [
'success' => $result !== false,
'msg' => $result !== false ? '성공' : '저장실패'
];
}
$this->db->transComplete();
public function saveCount($data)
{
$usr_sq = session('usr_sq');
$now = date('Y-m-d H:i:s');
return [
'success' => true,
];
}
$insertData = [
'sc_date' => '0000-00-00',
'region_cd' => $data['region_cd'],
'am_cnt' => $data['am_cnt'],
'pm_cnt' => $data['pm_cnt'],
'day_cnt' => (int)$data['am_cnt'] + (int)$data['pm_cnt'],
'insert_usr' => $usr_sq,
'insert_tm' => $now,
'update_usr' => $usr_sq,
'update_tm' => $now
];
public function saveCount($data)
{
// $this->db->transStart();
// CI4 Query Builder upsert
$result = $this->db->table('service_count')->upsert($insertData);
// 실행된 쿼리 로그 출력
$lastQuery = $this->db->getLastQuery();
log_message('info', '[ProcessibleModel::saveCount] Query: ' . $lastQuery);
log_message('info', '[ProcessibleModel::saveCount] Affected Rows: ' . $this->db->affectedRows());
if ($result === false) {
$error = $this->db->error();
log_message('error', '[ProcessibleModel::saveCount] Error: ' . json_encode($error));
return [
'success' => false,
'msg' => '저장실패: ' . ($error['message'] ?? '알 수 없는 오류'),
'query' => (string) $lastQuery,
'error' => $error
];
}
$usr_sq = session('usr_sq');
$sql = "INSERT INTO service_count
(sc_date, region_cd, am_cnt, pm_cnt, day_cnt, insert_usr, insert_tm, update_usr, update_tm)
VALUES
(?, ?, ?, ?, ?, ?, NOW(), ?, NOW())
ON DUPLICATE KEY UPDATE
am_cnt = VALUES(am_cnt),
pm_cnt = VALUES(pm_cnt),
day_cnt = VALUES(day_cnt),
update_usr = VALUES(update_usr),
update_tm = NOW()
";
$datas = [
'0000-00-00',
$data['region_cd'],
$data['am_cnt'],
$data['pm_cnt'],
((int) $data['am_cnt'] + (int) $data['pm_cnt']),
$usr_sq,
$usr_sq
];
// 쿼리 실행
$result = $this->db->query($sql, $datas);
// 실행된 쿼리 로그 출력
$lastQuery = $this->db->getLastQuery();
log_message('info', '[ProcessibleModel::saveCount] Query: ' . $lastQuery);
log_message('info', '[ProcessibleModel::saveCount] Affected Rows: ' . $this->db->affectedRows());
if ($result === false) {
$error = $this->db->error();
log_message('error', '[ProcessibleModel::saveCount] Error: ' . json_encode($error));
return [
'success' => false,
'msg' => '저장실패: ' . ($error['message'] ?? '알 수 없는 오류'),
'query' => (string) $lastQuery,
'error' => $error
];
return [
'success' => true,
'query' => (string) $lastQuery,
'affected_rows' => $this->db->affectedRows()
];
}
// $this->db->transComplete();
return [
'success' => true,
'query' => (string) $lastQuery,
'affected_rows' => $this->db->affectedRows()
];
}
/**
* 슬롯 동기화
*
*/
public function getSyncSlotData($baseDate, $region_cd)
{
$sql = "SELECT sc_date, region_cd, am_cnt, pm_cnt, day_cnt FROM service_count WHERE sc_date = ? AND region_cd IN ?";
$datas = [
$baseDate,
$region_cd
];
$query = $this->db->query($sql, $datas);
return $query->getResultArray();
}
public function getSyncSlotData($baseDate, $region_cd)
{
$sql = "SELECT sc_date, region_cd, am_cnt, pm_cnt, day_cnt FROM service_count WHERE sc_date = ? AND region_cd IN ?";
$datas = [
$baseDate,
$region_cd
];
$query = $this->db->query($sql, $datas);
return $query->getResultArray();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -8,64 +8,85 @@ class CodeModel extends Model
/**
* 코드목록 읽어오기(Y만)
*/
public function getCodeList($category)
{
$sql = "SELECT category, category_nm, cd, cd_nm FROM codes" .
" WHERE category = ?" .
" AND use_yn = 'Y'" .
" ORDER BY view_odr";
$data = [$category];
$query = $this->db->query($sql, $data);
return $query->getResultArray();
}
public function getCodeLists($data): array
public function getCodeList(string $category): array
{
return $this->db->table('codes')
->select('category, category_nm, cd, cd_nm')
->whereIn('category', $data)
->where('category', $category)
->where('use_yn', 'Y')
->orderBy('view_odr')
->orderBy('view_odr', 'asc')
->get()
->getResultArray();
}
public function getCategoryCodeList($category = [], $useYn = '')
public function getCodeLists(array $categories): array
{
$this->db->select('category, cd, cd_nm, use_yn');
$this->db->from('codes');
$this->db->where_in('category', $category);
if (!empty($useYn)) {
$this->db->where('use_yn', $useYn);
if (empty($categories)) {
return [];
}
$this->db->order_by('category', 'asc');
$this->db->order_by('view_odr', 'asc');
$query = $this->db->get();
$result = $this->db->table('codes')
->select('category, category_nm, cd, cd_nm')
->whereIn('category', $categories)
->where('use_yn', 'Y')
->orderBy('category', 'asc')
->orderBy('view_odr', 'asc')
->get()
->getResultArray();
//echo $this->db->last_query()."<br>";
$groupedData = [];
foreach ($result as $item) {
$category = $item['category'];
if (!isset($groupedData[$category])) {
$groupedData[$category] = [
'name' => $item['category_nm'],
'items' => []
];
}
$groupedData[$category]['items'][$item['cd']] = $item['cd_nm'];
}
return $groupedData;
}
public function getCategoryCodeList(array $category = [], string $useYn = ''): array
{
if (empty($category)) {
return [];
}
$builder = $this->db->table('codes');
$builder->select('category, cd, cd_nm, use_yn');
$builder->whereIn('category', $category);
if (!empty($useYn)) {
$builder->where('use_yn', $useYn);
}
$builder->orderBy('category', 'asc');
$builder->orderBy('view_odr', 'asc');
$query = $builder->get();
//여기 아래부분을 해줘야 배열을 카테고리로 뽑아쓸수있다 위에는 배열에 배열이담김
$codes = [];
foreach ($query->getResultArray() as $row) {
$codes[$row['category']][] = ['cd' => $row['cd'], 'cd_nm' => $row['cd_nm']];
}
return $codes;
}
/**
* 코드 상세
*/
public function getCodeDetail($category, $code)
public function getCodeDetail(string $category, string $code): array
{
$sql = "SELECT category, category_nm, cd, cd_nm FROM codes" .
" WHERE category = ? and cd = ?";
$data = [$category, $code];
$query = $this->db->query($sql, $data);
$row = $query->getResultArray();
return $row;
return $this->db->table('codes')
->select('category, category_nm, cd, cd_nm')
->where('category', $category)
->where('cd', $code)
->get()
->getResultArray();
}
}

View File

@@ -99,10 +99,11 @@ class AssignModel extends Model
public function getTotalCount($data)
{
$sql = "SELECT
COUNT(*) AS cnt
COUNT(DISTINCT b.usr_sq) AS cnt
FROM result a
INNER JOIN users b ON b.usr_sq = a.usr_sq
INNER JOIN receipt d ON d.rcpt_sq = a.rcpt_sq
INNER JOIN departments c ON c.dept_sq = a.dept_sq
WHERE 1=1 ";
@@ -138,13 +139,13 @@ class AssignModel extends Model
if (!empty($data['srchTxt'])) {
if ($data['srchType'] === "1") {
$sql .= "AND usr_id like CONCAT('%', '{$data['srchTxt']}', '%' ) ";
$sql .= "AND b.usr_id like CONCAT('%', '{$data['srchTxt']}', '%' ) ";
} else if ($data['srchType'] === "2") {
$sql .= "AND usr_nm like CONCAT('%', '{$data['srchTxt']}', '%' ) ";
$sql .= "AND b.usr_nm like CONCAT('%', '{$data['srchTxt']}', '%' ) ";
} else {
$sql .= "AND (
usr_id like CONCAT('%', '{$data['srchTxt']}', '%' )
OR usr_nm like CONCAT('%', '{$data['srchTxt']}', '%' )
b.usr_id like CONCAT('%', '{$data['srchTxt']}', '%' )
OR b.usr_nm like CONCAT('%', '{$data['srchTxt']}', '%' )
) ";
}
}
@@ -157,26 +158,31 @@ class AssignModel extends Model
public function getUserList($start, $end, $data)
{
$sql = "SELECT
b.usr_nm, b.usr_id, b.usr_sq
, SUM(CASE WHEN a.rsrv_tm_hour IN ('00','01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24') THEN 1 ELSE 0 END) TODAY
, SUM(CASE WHEN a.rsrv_tm_ap = 'AM' AND a.rsrv_tm_hour = '09' THEN 1 ELSE 0 END) AM09
, SUM(CASE WHEN a.rsrv_tm_ap = 'AM' AND a.rsrv_tm_hour = '10' THEN 1 ELSE 0 END) AM10
, SUM(CASE WHEN a.rsrv_tm_ap = 'AM' AND a.rsrv_tm_hour = '11' THEN 1 ELSE 0 END) AM11
, SUM(CASE WHEN a.rsrv_tm_ap = 'AM' AND a.rsrv_tm_hour = '12' THEN 1 ELSE 0 END) AM12
, SUM(CASE WHEN a.rsrv_tm_ap = 'AM' AND a.rsrv_tm_hour IN ('00','01','02','03','04','05','06','07','08') THEN 1 ELSE 0 END) AMETC
, SUM(CASE WHEN a.rsrv_tm_ap = 'PM' AND a.rsrv_tm_hour IN ('01', '13') THEN 1 ELSE 0 END) PM01
, SUM(CASE WHEN a.rsrv_tm_ap = 'PM' AND a.rsrv_tm_hour IN ('02', '14') THEN 1 ELSE 0 END) PM02
, SUM(CASE WHEN a.rsrv_tm_ap = 'PM' AND a.rsrv_tm_hour IN ('03', '15') THEN 1 ELSE 0 END) PM03
, SUM(CASE WHEN a.rsrv_tm_ap = 'PM' AND a.rsrv_tm_hour IN ('04', '16') THEN 1 ELSE 0 END) PM04
, SUM(CASE WHEN a.rsrv_tm_ap = 'PM' AND a.rsrv_tm_hour IN ('05', '17') THEN 1 ELSE 0 END) PM05
, SUM(CASE WHEN a.rsrv_tm_ap = 'PM' AND a.rsrv_tm_hour IN ('06', '18') THEN 1 ELSE 0 END) PM06
, SUM(CASE WHEN a.rsrv_tm_ap = 'PM' AND a.rsrv_tm_hour IN ('07', '19') THEN 1 ELSE 0 END) PM07
, SUM(CASE WHEN a.rsrv_tm_ap = 'PM' AND a.rsrv_tm_hour IN ('00','08','09','10','11','12','20','21','22','23','24') THEN 1 ELSE 0 END) PMETC
FROM result a
INNER JOIN users b ON b.usr_sq = a.usr_sq
sub.*,
COUNT(*) OVER() as total_count
FROM (
SELECT
b.usr_nm, b.usr_id, b.usr_sq
, SUM(CASE WHEN a.rsrv_tm_hour IN ('00','01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24') THEN 1 ELSE 0 END) TODAY
, SUM(CASE WHEN a.rsrv_tm_ap = 'AM' AND a.rsrv_tm_hour = '09' THEN 1 ELSE 0 END) AM09
, SUM(CASE WHEN a.rsrv_tm_ap = 'AM' AND a.rsrv_tm_hour = '10' THEN 1 ELSE 0 END) AM10
, SUM(CASE WHEN a.rsrv_tm_ap = 'AM' AND a.rsrv_tm_hour = '11' THEN 1 ELSE 0 END) AM11
, SUM(CASE WHEN a.rsrv_tm_ap = 'AM' AND a.rsrv_tm_hour = '12' THEN 1 ELSE 0 END) AM12
, SUM(CASE WHEN a.rsrv_tm_ap = 'AM' AND a.rsrv_tm_hour IN ('00','01','02','03','04','05','06','07','08') THEN 1 ELSE 0 END) AMETC
, SUM(CASE WHEN a.rsrv_tm_ap = 'PM' AND a.rsrv_tm_hour IN ('01', '13') THEN 1 ELSE 0 END) PM01
, SUM(CASE WHEN a.rsrv_tm_ap = 'PM' AND a.rsrv_tm_hour IN ('02', '14') THEN 1 ELSE 0 END) PM02
, SUM(CASE WHEN a.rsrv_tm_ap = 'PM' AND a.rsrv_tm_hour IN ('03', '15') THEN 1 ELSE 0 END) PM03
, SUM(CASE WHEN a.rsrv_tm_ap = 'PM' AND a.rsrv_tm_hour IN ('04', '16') THEN 1 ELSE 0 END) PM04
, SUM(CASE WHEN a.rsrv_tm_ap = 'PM' AND a.rsrv_tm_hour IN ('05', '17') THEN 1 ELSE 0 END) PM05
, SUM(CASE WHEN a.rsrv_tm_ap = 'PM' AND a.rsrv_tm_hour IN ('06', '18') THEN 1 ELSE 0 END) PM06
, SUM(CASE WHEN a.rsrv_tm_ap = 'PM' AND a.rsrv_tm_hour IN ('07', '19') THEN 1 ELSE 0 END) PM07
, SUM(CASE WHEN a.rsrv_tm_ap = 'PM' AND a.rsrv_tm_hour IN ('00','08','09','10','11','12','20','21','22','23','24') THEN 1 ELSE 0 END) PMETC
FROM result a
INNER JOIN users b ON b.usr_sq = a.usr_sq
INNER JOIN receipt d ON d.rcpt_sq = a.rcpt_sq
INNER JOIN departments c ON c.dept_sq = a.dept_sq
WHERE 1=1 ";
WHERE 1=1 ";
if (!empty($data['bonbu'])) {
$sql .= "AND c.pdept_sq = {$data['bonbu']} ";
@@ -210,20 +216,20 @@ class AssignModel extends Model
if (!empty($data['srchTxt'])) {
if ($data['srchType'] === "1") {
$sql .= "AND usr_id like CONCAT('%', '{$data['srchTxt']}', '%' ) ";
$sql .= "AND b.usr_id like CONCAT('%', '{$data['srchTxt']}', '%' ) ";
} else if ($data['srchType'] === "2") {
$sql .= "AND usr_nm like CONCAT('%', '{$data['srchTxt']}', '%' ) ";
$sql .= "AND b.usr_nm like CONCAT('%', '{$data['srchTxt']}', '%' ) ";
} else {
$sql .= "AND (
usr_id like CONCAT('%', '{$data['srchTxt']}', '%' )
OR usr_nm like CONCAT('%', '{$data['srchTxt']}', '%' )
b.usr_id like CONCAT('%', '{$data['srchTxt']}', '%' )
OR b.usr_nm like CONCAT('%', '{$data['srchTxt']}', '%' )
) ";
}
}
$sql .= "GROUP BY b.usr_id, b.usr_nm ";
$sql .= "LIMIT {$start}, {$end}";
$sql .= "GROUP BY b.usr_id, b.usr_nm
) sub
LIMIT {$start}, {$end}";
$query = $this->db->query($sql);

View File

@@ -0,0 +1,119 @@
<?php
namespace App\Models\v2;
use CodeIgniter\Model;
/**
* BaseV2Model
* V2 모듈 공통 메서드: 지역/본부/팀/담당자 조회
* 각 모듈 Model은 이 클래스를 상속받아 getTotalCount/getResultList/getExcelList 등 모듈 고유 메서드만 구현합니다.
*/
abstract class BaseV2Model extends Model
{
// 지역 목록 조회
public function getAreaList($sido = '', $gugun = '')
{
if (!empty($gugun)) {
$gugun = substr($gugun, '0', '5');
$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,5),'00000')" .
" WHERE a.region_cd LIKE concat(?, '%')" .
" AND a.region_cd NOT LIKE '%00000'" .
" AND a.region_cd LIKE '%00'" .
" AND a.use_yn = 'Y'" .
" ORDER BY a.region_nm ASC";
$query = $this->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();
}
}

View File

@@ -2,121 +2,11 @@
namespace App\Models\v2;
use App\Models\webfax\FaxModel;
use CodeIgniter\Model;
use App\Models\v2\BaseV2Model;
class M701Model extends Model
class M701Model extends BaseV2Model
{
// 지역 목록 조회
public function getAreaList($sido = '', $gugun = '')
{
if (!empty($gugun)) {
$gugun = substr($gugun, '0', '5');
$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,5),'00000')" .
" WHERE a.region_cd LIKE concat(?, '%')" .
" AND a.region_cd NOT LIKE '%00000'" .
" AND a.region_cd LIKE '%00'" .
" AND a.use_yn = 'Y'" .
" ORDER BY a.region_nm ASC";
$query = $this->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

View File

@@ -2,119 +2,10 @@
namespace App\Models\v2;
use App\Models\webfax\FaxModel;
use CodeIgniter\Model;
use App\Models\v2\BaseV2Model;
class M702Model extends Model
class M702Model extends BaseV2Model
{
// 지역 목록 조회
public function getAreaList($sido = '', $gugun = '')
{
if (!empty($gugun)) {
$gugun = substr($gugun, '0', '5');
$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,5),'00000')" .
" WHERE a.region_cd LIKE concat(?, '%')" .
" AND a.region_cd NOT LIKE '%00000'" .
" AND a.region_cd LIKE '%00'" .
" AND a.use_yn = 'Y'" .
" ORDER BY a.region_nm ASC";
$query = $this->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)
{

View File

@@ -3,119 +3,10 @@ namespace App\Models\v2;
use App\Models\receipt\ReceiptModel;
use App\Models\webfax\FaxModel;
use CodeIgniter\Model;
use App\Models\v2\BaseV2Model;
class M703Model extends Model
class M703Model extends BaseV2Model
{
// 지역 목록 조회
public function getAreaList($sido = '', $gugun = '')
{
if (!empty($gugun)) {
$gugun = substr($gugun, '0', '5');
$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,5),'00000')" .
" WHERE a.region_cd LIKE concat(?, '%')" .
" AND a.region_cd NOT LIKE '%00000'" .
" AND a.region_cd LIKE '%00'" .
" AND a.use_yn = 'Y'" .
" ORDER BY a.region_nm ASC";
$query = $this->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)
{

View File

@@ -2,119 +2,10 @@
namespace App\Models\v2;
use App\Models\webfax\FaxModel;
use CodeIgniter\Model;
use App\Models\v2\BaseV2Model;
class M704Model extends Model
class M704Model extends BaseV2Model
{
// 지역 목록 조회
public function getAreaList($sido = '', $gugun = '')
{
if (!empty($gugun)) {
$gugun = substr($gugun, '0', '5');
$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,5),'00000')" .
" WHERE a.region_cd LIKE concat(?, '%')" .
" AND a.region_cd NOT LIKE '%00000'" .
" AND a.region_cd LIKE '%00'" .
" AND a.use_yn = 'Y'" .
" ORDER BY a.region_nm ASC";
$query = $this->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)
{

View File

@@ -1,119 +1,10 @@
<?php
namespace App\Models\v2;
use CodeIgniter\Model;
use App\Models\v2\BaseV2Model;
class M705Model extends Model
class M705Model extends BaseV2Model
{
// 지역 목록 조회
public function getAreaList($sido = '', $gugun = '')
{
if (!empty($gugun)) {
$gugun = substr($gugun, '0', '5');
$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,5),'00000')" .
" WHERE a.region_cd LIKE concat(?, '%')" .
" AND a.region_cd NOT LIKE '%00000'" .
" AND a.region_cd LIKE '%00'" .
" AND a.use_yn = 'Y'" .
" ORDER BY a.region_nm ASC";
$query = $this->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)
{

View File

@@ -2,119 +2,10 @@
namespace App\Models\v2;
use App\Models\webfax\FaxModel;
use CodeIgniter\Model;
use App\Models\v2\BaseV2Model;
class M706Model extends Model
class M706Model extends BaseV2Model
{
// 지역 목록 조회
public function getAreaList($sido = '', $gugun = '')
{
if (!empty($gugun)) {
$gugun = substr($gugun, '0', '5');
$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,5),'00000')" .
" WHERE a.region_cd LIKE concat(?, '%')" .
" AND a.region_cd NOT LIKE '%00000'" .
" AND a.region_cd LIKE '%00'" .
" AND a.use_yn = 'Y'" .
" ORDER BY a.region_nm ASC";
$query = $this->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)
{

View File

@@ -3,119 +3,10 @@ namespace App\Models\v2;
use App\Models\receipt\ReceiptModel;
use App\Models\webfax\FaxModel;
use CodeIgniter\Model;
use App\Models\v2\BaseV2Model;
class M708Model extends Model
class M708Model extends BaseV2Model
{
// 지역 목록 조회
public function getAreaList($sido = '', $gugun = '')
{
if (!empty($gugun)) {
$gugun = substr($gugun, '0', '5');
$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,5),'00000')" .
" WHERE a.region_cd LIKE concat(?, '%')" .
" AND a.region_cd NOT LIKE '%00000'" .
" AND a.region_cd LIKE '%00'" .
" AND a.use_yn = 'Y'" .
" ORDER BY a.region_nm ASC";
$query = $this->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)
{

View File

@@ -1,119 +1,10 @@
<?php
namespace App\Models\v2;
use CodeIgniter\Model;
use App\Models\v2\BaseV2Model;
class M709Model extends Model
class M709Model extends BaseV2Model
{
// 지역 목록 조회
public function getAreaList($sido = '', $gugun = '')
{
if (!empty($gugun)) {
$gugun = substr($gugun, '0', '5');
$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,5),'00000')" .
" WHERE a.region_cd LIKE concat(?, '%')" .
" AND a.region_cd NOT LIKE '%00000'" .
" AND a.region_cd LIKE '%00'" .
" AND a.use_yn = 'Y'" .
" ORDER BY a.region_nm ASC";
$query = $this->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 getDepartmentPath($usr_sq)

View File

@@ -1,121 +1,11 @@
<?php
namespace App\Models\v2;
use CodeIgniter\Model;
use App\Models\v2\BaseV2Model;
class M710Model extends Model
class M710Model extends BaseV2Model
{
// 지역 목록 조회
public function getAreaList($sido = '', $gugun = '')
{
if (!empty($gugun)) {
$gugun = substr($gugun, '0', '5');
$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,5),'00000')" .
" WHERE a.region_cd LIKE concat(?, '%')" .
" AND a.region_cd NOT LIKE '%00000'" .
" AND a.region_cd LIKE '%00'" .
" AND a.use_yn = 'Y'" .
" ORDER BY a.region_nm ASC";
$query = $this->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

View File

@@ -1,9 +1,9 @@
<?php
namespace App\Models\v2;
use CodeIgniter\Model;
use App\Models\v2\BaseV2Model;
class M711Model extends Model
class M711Model extends BaseV2Model
{
}

View File

@@ -1,119 +1,10 @@
<?php
namespace App\Models\v2;
use CodeIgniter\Model;
use App\Models\v2\BaseV2Model;
class M712Model extends Model
class M712Model extends BaseV2Model
{
// 지역 목록 조회
public function getAreaList($sido = '', $gugun = '')
{
if (!empty($gugun)) {
$gugun = substr($gugun, '0', '5');
$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,5),'00000')" .
" WHERE a.region_cd LIKE concat(?, '%')" .
" AND a.region_cd NOT LIKE '%00000'" .
" AND a.region_cd LIKE '%00'" .
" AND a.use_yn = 'Y'" .
" ORDER BY a.region_nm ASC";
$query = $this->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)
{

View File

@@ -1,119 +1,10 @@
<?php
namespace App\Models\v2;
use CodeIgniter\Model;
use App\Models\v2\BaseV2Model;
class M713Model extends Model
class M713Model extends BaseV2Model
{
// 지역 목록 조회
public function getAreaList($sido = '', $gugun = '')
{
if (!empty($gugun)) {
$gugun = substr($gugun, '0', '5');
$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,5),'00000')" .
" WHERE a.region_cd LIKE concat(?, '%')" .
" AND a.region_cd NOT LIKE '%00000'" .
" AND a.region_cd LIKE '%00'" .
" AND a.use_yn = 'Y'" .
" ORDER BY a.region_nm ASC";
$query = $this->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)
{

View File

@@ -39,44 +39,80 @@ class TypeSHandler
public function handle(string $articleNumber, array $rawData, array $payload): int
{
CLI::write(CLI::color('🟢 Type S 처리 시작 :: ' . $articleNumber, 'green'));
// CI 트랜잭션 strict 모드에서는 이전 실패 상태가 누적될 수 있으므로 시작 전에 초기화
if (method_exists($this->db, 'resetTransStatus')) {
$this->db->resetTransStatus();
}
$this->db->transBegin();
try {
// 1. Receipt 데이터 저장
$receiptData = $this->parameterMapper->mapReceipt($articleNumber, $rawData, $payload);
write_custom_log("Result Insert 데이터: " . json_encode($receiptData, JSON_UNESCAPED_UNICODE), 'INFO', 'service');
if (!$this->receiptModel->insert($receiptData)) {
$lastQuery = (string)$this->receiptModel->getLastQuery();
$errors = json_encode($this->receiptModel->errors());
write_custom_log("Receipt Insert 실패 | SQL: $lastQuery | Errors: $errors", 'ERROR', 'service');
throw new Exception("Receipt Insert 실패: " . json_encode($this->receiptModel->errors()));
}
$rcptSq = $this->receiptModel->getInsertID();
$receiptInsertSql = (string)$this->receiptModel->getLastQuery(); // Receipt SQL 캡처
CLI::write(CLI::color("✅ Receipt 저장 성공 (ID: $rcptSq)", 'blue'));
// 2. Result 데이터 저장
$resultData = $this->parameterMapper->mapResult($rcptSq, $rawData);
write_custom_log("Result Insert 데이터: " . json_encode($resultData, JSON_UNESCAPED_UNICODE), 'INFO', 'service');
if (!$this->resultModel->insert($resultData)) {
throw new Exception("Result Insert 실패");
$lastQuery = (string)$this->resultModel->getLastQuery();
$errors = json_encode($this->resultModel->errors());
write_custom_log("Result Insert 실패 | SQL: $lastQuery | Errors: $errors", 'ERROR', 'service');
throw new Exception("Result Insert 실패: $errors");
}
$resultInsertSql = (string)$this->resultModel->getLastQuery(); // Result SQL 캡처
write_custom_log("Result Insert 성공 | SQL: $resultInsertSql", 'INFO', 'service');
CLI::write(CLI::color('✅ Result 저장 성공', 'blue'));
// 3. 트랜잭션 커밋
$this->db->transComplete();
if ($this->db->transStatus() === false) {
write_custom_log("Type S DB 트랜잭션 최종 실패", 'ERROR', 'service');
$dbError = $this->db->error();
$errorJson = json_encode($dbError, JSON_UNESCAPED_UNICODE);
$receiptLastQuery = (string)$this->receiptModel->getLastQuery();
$resultLastQuery = (string)$this->resultModel->getLastQuery();
write_custom_log(
"Type S DB 트랜잭션 최종 실패 | DB Error: {$errorJson} | ReceiptLastQuery: {$receiptLastQuery} | ResultLastQuery: {$resultLastQuery}",
'ERROR',
'service'
);
throw new Exception("Type S DB 트랜잭션 최종 실패");
}
// 4. 로그 기록
write_custom_log("Type S 처리 성공 | Atcl: $articleNumber | Rcpt_sq: $rcptSq", 'INFO', 'service');
write_custom_log("Receipt Insert SQL: " . (string)$this->receiptModel->getLastQuery(), 'INFO', 'service');
write_custom_log("Result Insert SQL: " . (string)$this->resultModel->getLastQuery(), 'INFO', 'service');
write_custom_log("Receipt Insert SQL: " . $receiptInsertSql, 'INFO', 'service');
write_custom_log("Result Insert SQL: " . $resultInsertSql, 'INFO', 'service');
// 5. 네이버 예약 정보 동기화 (비동기)
try {
$syncResult = $this->naverClient->submitSyncResult($rawData['reserveNo'] ?? '');
write_custom_log("Naver Sync Result Response: " . json_encode($syncResult), 'INFO', 'service');
} catch (Exception $e) {
write_custom_log("Naver Sync 실패 (계속 진행): " . $e->getMessage(), 'WARN', 'service');
}
// try {
// // $syncResult = $this->naverClient->submitSyncResult($rawData['reserveNo'] ?? '');
// $rsrv_date = $receiptData['rsrv_date'] ?? null;
// $reportData = [
// 'type' => 'S11',
// 'code' => '10000',
// 'message' => '현장확인 예약',
// 'reserveDate' => $rsrv_date
// ];
// $syncResult = $this->naverClient->verificationSiteReport($articleNumber, $reportData);
// write_custom_log("Naver Sync Result Response: " . json_encode($syncResult), 'INFO', 'service');
// } catch (Exception $e) {
// write_custom_log("Naver Sync 실패 (계속 진행): " . $e->getMessage(), 'WARN', 'service');
// }
return $rcptSq;

View File

@@ -46,9 +46,11 @@ class TypeV2Handler
public function handle(string $articleNumber, array $rawData, array $payload): int
{
CLI::write(CLI::color('🟢 Type V2 처리 시작 :: ' . $articleNumber, 'green'));
write_custom_log("TypeV2Handler 진입 | Atcl: $articleNumber | Payload: " . json_encode($payload), 'INFO', 'service');
try {
$requestType = $payload['requestType'] ?? 'REG';
CLI::write(CLI::color("🔵 RequestType: $requestType", 'cyan'));
switch ($requestType) {
case 'REG':
@@ -62,7 +64,9 @@ class TypeV2Handler
}
} catch (Exception $e) {
write_custom_log("Type V2 처리 실패: " . $e->getMessage(), 'ERROR', 'service');
$errorDetail = "Type V2 처리 실패 | Atcl: $articleNumber | Error: " . $e->getMessage() . " | File: " . $e->getFile() . ":" . $e->getLine();
write_custom_log($errorDetail, 'ERROR', 'service');
CLI::write(CLI::color("" . $errorDetail, 'red'));
throw $e;
}
}
@@ -136,7 +140,9 @@ class TypeV2Handler
// 기존 검증 요청 확인
$existing = $this->vrfcReqModel->where('atcl_no', $articleNumber)->first();
if (!$existing) {
throw new Exception("수정할 기존 데이터가 없습니다. Atcl: $articleNumber");
CLI::write(CLI::color("⚠️ 수정할 기존 데이터가 없음 → 신규 등록으로 전환 (Atcl: $articleNumber)", 'yellow'));
write_custom_log("MOD → REG 자동 전환 | Atcl: $articleNumber | 사유: 기존 데이터 없음", 'WARNING', 'service');
return $this->handleRegister($articleNumber, $rawData, $payload);
}
$vrSq = $existing['vr_sq'];
$stat_cd = $existing['stat_cd'];
@@ -199,7 +205,9 @@ class TypeV2Handler
// 기존 검증 요청 확인
$existing = $this->vrfcReqModel->where('atcl_no', $articleNumber)->first();
if (!$existing) {
throw new Exception("취소할 기존 데이터가 없습니다. Atcl: $articleNumber");
CLI::write(CLI::color("⚠️ 취소할 데이터가 없음 → 무시 (Atcl: $articleNumber)", 'yellow'));
write_custom_log("CNC 무시 | Atcl: $articleNumber | 사유: 기존 데이터 없음", 'WARNING', 'service');
return 0; // 취소할 데이터가 없으므로 0 반환
}
$vrSq = $existing['vr_sq'];

View File

@@ -59,19 +59,33 @@ class NaverService
$vType = $rawData['verificationTypeCode'] ?? '';
// 2. 원본 데이터 Staging 저장
$this->rawStagingModel->insert([
$insertResult = $this->rawStagingModel->insert([
'atcl_no' => $articleNumber,
'verification_type' => $vType,
'request_type' => $requestType,
'raw_json' => $rawData
]);
if (!$insertResult) {
$dbError = $this->db->error();
write_custom_log("naver_raw_staging Insert 실패 | Atcl: $articleNumber | Error: " . json_encode($dbError), 'ERROR', 'service');
throw new Exception("naver_raw_staging 저장 실패: " . json_encode($dbError));
}
CLI::write(CLI::color('🟢 임시테이블 저장 완료', 'green'));
// 3. 타입별 분기 처리
CLI::write(CLI::color("🔵 검증타입 확인: vType = $vType", 'cyan'));
write_custom_log("타입별 분기 | Atcl: $articleNumber | vType: $vType | requestType: $requestType", 'INFO', 'service');
if ($vType === 'S' || $vType === 'S_VR') {
CLI::write(CLI::color('🟢 Type S Handler 호출', 'green'));
return $this->typeSHandler->handle($articleNumber, $rawData, $payload);
} else {
return $this->typeV2Handler->handle($articleNumber, $rawData, $payload);
CLI::write(CLI::color('🟢 Type V2 Handler 호출', 'green'));
$result = $this->typeV2Handler->handle($articleNumber, $rawData, $payload);
write_custom_log("TypeV2Handler 완료 | Atcl: $articleNumber | Result: $result", 'INFO', 'service');
return $result;
}
} catch (Exception $e) {

View File

@@ -2,12 +2,20 @@
namespace App\Services\ParameterMapper;
use App\Models\Entities\RegionModel;
/**
* Type S 파라미터 매퍼
* 현장확인 매물 (A01 등) 데이터를 Receipt 테이블용 파라미터로 변환
*/
class TypeSParameterMapper extends BaseParameterMapper
{
private $regionModel;
public function __construct()
{
$this->regionModel = new RegionModel();
}
/**
* Receipt 테이블용 파라미터 생성
*/
@@ -24,8 +32,8 @@ class TypeSParameterMapper extends BaseParameterMapper
// 평면도 여부 결정
$groundPlan = in_array($rawData['realEstateTypeCode'] ?? '', ['C01', 'C02']) ? 'N' : 'Y';
// 거래 유형 매핑
$tradeType = $this->mapTradeType($rawData['tradeType'] ?? null);
// 거래 유형 코드 (tradeTypeCode 우선, 없으면 tradeType 한글을 변환)
$tradeType = $rawData['tradeTypeCode'] ?? $this->mapTradeType($rawData['tradeType'] ?? null);
return [
'comp_sq' => '2',
@@ -38,6 +46,7 @@ class TypeSParameterMapper extends BaseParameterMapper
'rcpt_product_info2' => $price['dealAmount'] ?? '0',
'rcpt_product_info4' => $price['preSaleAmount'] ?? '0',
'rcpt_product_info5' => $price['premiumAmount'] ?? '0',
'rcpt_product_info6' => $price['preSaleOptionAmount'] ?? '0',
'rcpt_living_yn' => ($rawData['site']['isRegistration'] ?? false) ? 'Y' : 'N',
'rcpt_agent' => $realtor['realtorName'] ?? null,
'rcpt_sido' => $address['legalDivision']['cityNumber'] ?? null,
@@ -87,6 +96,11 @@ class TypeSParameterMapper extends BaseParameterMapper
'rcpt_cpid' => $rawData['cpId'] ?? 'naver',
'isSiteVRVerification' => ($rawData['site']['isVrVerification'] ?? false) ? 'Y' : 'N',
'isPromotionApply' => ($rawData['site']['isVrRepresentativeApply'] ?? false) ? 'Y' : 'N',
'dealAmount' => $price['dealAmount'] ?? null,
'warrantyAmount' => $price['warrantyAmount'] ?? null,
'leaseAmount' => $price['leaseAmount'] ?? null,
'preSaleAmount' => $price['preSaleAmount'] ?? null,
'premiumAmount' => $price['premiumAmount'] ?? null
];
}
@@ -96,10 +110,52 @@ class TypeSParameterMapper extends BaseParameterMapper
public function mapResult(int $rcptSq, array $rawData): array
{
$now = db_now();
$address = $rawData['address'] ?? [];
$sectorNumber = $address['legalDivision']['sectorNumber'] ?? null;
// VR 검증 여부에 따른 담당자 설정
$deptSq = ($rawData['site']['isVrVerification'] ?? false) ? '29' : null;
$usrSq = ($rawData['site']['isVrVerification'] ?? false) ? '1993' : null;
// VR 검증 여부 확인
$isVrVerification = ($rawData['site']['isVrVerification'] ?? false);
// 1. 지역별 담당자 조회
$charge = null;
if ($sectorNumber) {
$charge = $this->regionModel->getChargeByRegionCd($sectorNumber);
log_message('info', "[TypeSParameterMapper] 지역코드: {$sectorNumber}, 조회 결과: " . json_encode($charge, JSON_UNESCAPED_UNICODE));
}
// 2. 기본 담당자 설정 (지역별 담당자가 없으면)
$deptSq = $charge['dept_sq'] ?? '26';
$usrSq = $charge['usr_sq'] ?? '1';
// 담당자 존재 여부 검증 (users 테이블 확인)
if ($usrSq && $usrSq !== '1') {
$db = \Config\Database::connect();
$userExists = $db->table('users')->where('usr_sq', $usrSq)->countAllResults() > 0;
if (!$userExists) {
log_message('warning', "[TypeSParameterMapper] usr_sq={$usrSq}가 users 테이블에 없음. 기본값(1)으로 폴백");
$usrSq = '1';
$deptSq = '26';
}
}
log_message('info', "[TypeSParameterMapper] 기본 담당자 - dept_sq: {$deptSq}, usr_sq: {$usrSq}, VR검증: " . ($isVrVerification ? 'Y' : 'N'));
// 3. VR 검증인 경우 환경별 담당자로 덮어쓰기
if ($isVrVerification) {
log_message('info', "[TypeSParameterMapper] ENVIRONMENT 상수 값: " . ENVIRONMENT);
if (ENVIRONMENT === 'development') {
$deptSq = '29';
$usrSq = '472'; // vradmin
log_message('info', "[TypeSParameterMapper] VR 검증 (DEV) - dept_sq: {$deptSq}, usr_sq: {$usrSq}");
} else {
// production - TODO: 실제 프로덕션 VR 담당자로 변경 필요
$deptSq = '29';
$usrSq = '472'; // 임시: vradmin (프로덕션 VR 담당자 확인 후 수정)
log_message('warning', "[TypeSParameterMapper] VR 검증 (PROD) - 프로덕션 VR 담당자 미설정, 임시 담당자 사용: usr_sq={$usrSq}");
}
}
return [
'rcpt_sq' => $rcptSq,

View File

@@ -90,6 +90,7 @@ class TypeV2ParameterMapper extends BaseParameterMapper
'lease_amt' => $price['leaseAmount'] ?? 0,
'isale_amt' => $price['preSaleAmount'] ?? 0,
'prem_amt' => $price['premiumAmount'] ?? 0,
'preopt_amt' => $price['preSaleOptionAmount'] ?? 0,
'sise' => null,
'floor' => $floor['correspondenceFloorCount'] ?? null,
'floor2' => $floor['totalFloorCount'] ?? null,

View File

@@ -0,0 +1,2 @@
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.0.7/css/dataTables.bootstrap5.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

View File

@@ -0,0 +1,3 @@
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.min.js"></script>
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.bootstrap5.min.js"></script>
<script defer src="/architectui/assets/js/datatable.kor.js"></script>

View File

@@ -0,0 +1,2 @@
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.0.7/css/dataTables.dataTables.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

View File

@@ -0,0 +1,2 @@
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.min.js"></script>
<script defer src="/architectui/assets/js/datatable.kor.js"></script>

View File

@@ -0,0 +1,67 @@
<script type="text/javascript">
/**
* DataTables v2 layout topEnd 버튼 헬퍼
* @param {Object} options - 옵션 객체
* @param {boolean} options.showSendButton - 등기부등본 전송 버튼 표시 여부 (기본값: false)
* @param {string} options.sendButtonText - 전송 버튼 텍스트
* @param {boolean} options.showExcelButton - 엑셀 버튼 표시 여부 (기본값: true)
* @param {string} options.excelButtonId - 엑셀 버튼 id
* @param {string} options.excelButtonClass - 엑셀 버튼 class
* @param {string} options.excelButtonHtml - 엑셀 버튼 innerHTML
* @returns {Function} DataTable layout.topEnd 콜백 함수
*/
function v2TopEndButtons(options) {
const opts = options || {};
return function () {
const container = document.createElement('div');
container.className = 'd-flex';
container.style.gap = '8px';
container.style.justifyContent = 'flex-end';
if (opts.showSendButton === true) {
const btnSend = document.createElement('button');
btnSend.className = 'btn btn-sm btn-outline-light';
btnSend.textContent = opts.sendButtonText || '등기부등본 전송';
container.append(btnSend);
}
if (opts.showExcelButton !== false) {
const btnExcel = document.createElement('button');
btnExcel.id = opts.excelButtonId || 'excel-download';
btnExcel.className = opts.excelButtonClass || 'btn btn-sm btn-outline-success';
btnExcel.innerHTML = opts.excelButtonHtml || '<i class="fa fa-fw fa-file-excel-o" aria-hidden="true"></i> 엑셀다운로드';
container.append(btnExcel);
}
return container;
};
}
/**
* DataTables v2 layout bottomStart/bottomEnd 통합 설정
* @param {Object} options - 옵션 객체
* @param {boolean} options.hasPageLength - pageLength 표시 여부 (기본값: true)
* @param {boolean} options.hasInfo - info 표시 여부 (기본값: true)
* @returns {Object} { bottomStart, bottomEnd } 객체
*/
function v2BottomLayout(options) {
const opts = options || {};
const hasPageLength = opts.hasPageLength !== false;
const hasInfo = opts.hasInfo !== false;
const config = {};
if (hasPageLength && hasInfo) {
config.bottomStart = ['pageLength', 'info'];
} else if (hasPageLength) {
config.bottomStart = 'pageLength';
} else if (hasInfo) {
config.bottomStart = 'info';
}
config.bottomEnd = 'paging';
return config;
}
</script>

View File

@@ -0,0 +1,410 @@
<?= $this->extend('layouts/main') ?>
<?= $this->section('page_styles') ?>
<style>
.stats-card {
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.stats-card h3 {
margin: 0 0 10px 0;
font-size: 14px;
color: #666;
}
.stats-card .number {
font-size: 32px;
font-weight: bold;
margin: 0;
}
.stats-card.total { background: #f8f9fa; border-left: 4px solid #6c757d; }
.stats-card.retryable { background: #d1ecf1; border-left: 4px solid #17a2b8; }
.stats-card.exhausted { background: #f8d7da; border-left: 4px solid #dc3545; }
.error-type-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
margin-right: 8px;
}
.severity-high { background: #dc3545; color: white; }
.severity-medium { background: #ffc107; color: #000; }
.severity-low { background: #28a745; color: white; }
.solution-box {
background: #e7f3ff;
border-left: 4px solid #007bff;
padding: 15px;
margin-top: 10px;
border-radius: 4px;
}
.solution-box h5 {
margin: 0 0 10px 0;
color: #004085;
}
.solution-box ul {
margin: 0;
padding-left: 20px;
}
.log-table {
font-size: 13px;
}
.log-table th {
background: #f8f9fa;
font-weight: 600;
}
.log-table .error-msg {
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
}
.log-table .error-msg:hover {
overflow: visible;
white-space: normal;
}
.badge-retry-count {
background: #6c757d;
color: white;
padding: 2px 8px;
border-radius: 10px;
font-size: 11px;
}
.btn-retry-selected {
position: sticky;
top: 20px;
z-index: 100;
}
</style>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<div class="app-page-title">
<div class="page-title-wrapper">
<div class="page-title-heading">
<div class="page-title-icon">
<i class="pe-7s-attention icon-gradient bg-mean-fruit"></i>
</div>
<div>
Worker 실패 로그 관리
<div class="page-title-subheading">
처리 실패한 작업을 분석하고 재처리합니다.
</div>
</div>
</div>
</div>
</div>
<!-- 통계 카드 -->
<div class="row mb-4">
<div class="col-md-4">
<div class="stats-card total">
<h3>전체 실패 건수</h3>
<p class="number"><?= number_format($stats['total_fail']) ?></p>
</div>
</div>
<div class="col-md-4">
<div class="stats-card retryable">
<h3>재시도 가능</h3>
<p class="number"><?= number_format($stats['retry_available']) ?></p>
</div>
</div>
<div class="col-md-4">
<div class="stats-card exhausted">
<h3>재시도 횟수 초과</h3>
<p class="number"><?= number_format($stats['retry_exhausted']) ?></p>
</div>
</div>
</div>
<!-- 오류 유형별 분석 -->
<div class="main-card mb-3 card">
<div class="card-header">오류 유형별 분석</div>
<div class="card-body">
<div class="row">
<?php foreach ($errorTypes as $key => $type): ?>
<?php if ($type['count'] > 0): ?>
<div class="col-md-4 mb-3">
<span class="error-type-badge severity-<?= $type['severity'] ?>">
<?= $type['label'] ?>
</span>
<strong><?= number_format($type['count']) ?>건</strong>
</div>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
</div>
<!-- 실패 로그 목록 -->
<div class="main-card mb-3 card">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<span>최근 실패 로그 (50건)</span>
<button class="btn btn-primary btn-sm" id="retrySelected" disabled>
<i class="fa fa-refresh"></i> 선택 항목 재처리
</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover log-table">
<thead>
<tr>
<th width="40">
<input type="checkbox" id="selectAll">
</th>
<th width="80">ID</th>
<th width="120">매물번호</th>
<th width="100">오류 유형</th>
<th>오류 메시지</th>
<th width="80">재시도</th>
<th width="150">발생시각</th>
<th width="100">액션</th>
</tr>
</thead>
<tbody>
<?php foreach ($logs as $log): ?>
<tr>
<td>
<?php if ($log['can_retry']): ?>
<input type="checkbox" class="log-checkbox" value="<?= $log['seq'] ?>">
<?php else: ?>
<i class="fa fa-ban text-muted" title="재시도 불가"></i>
<?php endif; ?>
</td>
<td><?= $log['seq'] ?></td>
<td><?= $log['atcl_no'] ?? '-' ?></td>
<td>
<?php
$typeInfo = $errorTypes[$log['error_type']] ?? ['label' => '기타', 'severity' => 'low'];
?>
<span class="error-type-badge severity-<?= $typeInfo['severity'] ?>">
<?= $typeInfo['label'] ?>
</span>
</td>
<td>
<div class="error-msg" title="<?= esc($log['error_msg']) ?>">
<?= esc($log['error_msg']) ?>
</div>
</td>
<td class="text-center">
<?php if ($log['retry_cnt'] > 0): ?>
<span class="badge-retry-count"><?= $log['retry_cnt'] ?>회</span>
<?php else: ?>
-
<?php endif; ?>
</td>
<td><?= date('m-d H:i', strtotime($log['created_at'])) ?></td>
<td>
<button class="btn btn-sm btn-info view-detail" data-id="<?= $log['seq'] ?>">
상세
</button>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($logs)): ?>
<tr>
<td colspan="8" class="text-center text-muted py-4">
실패한 로그가 없습니다.
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('modals') ?>
<!-- 상세 모달 -->
<div class="modal fade" id="detailModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">로그 상세 정보</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<div class="modal-body" id="detailContent">
<div class="text-center py-4">
<div class="spinner-border" role="status"></div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">닫기</button>
<button type="button" class="btn btn-primary" id="retryOne" style="display:none;">
<i class="fa fa-refresh"></i> 재처리
</button>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<?= $this->section('page_scripts') ?>
<script>
$(document).ready(function() {
// 전체 선택
$('#selectAll').on('change', function() {
$('.log-checkbox').prop('checked', this.checked);
updateRetryButton();
});
// 개별 선택
$('.log-checkbox').on('change', function() {
updateRetryButton();
});
// 재처리 버튼 활성화/비활성화
function updateRetryButton() {
const checked = $('.log-checkbox:checked').length;
$('#retrySelected').prop('disabled', checked === 0);
}
// 선택 항목 재처리
$('#retrySelected').on('click', function() {
const logIds = $('.log-checkbox:checked').map(function() {
return $(this).val();
}).get();
if (logIds.length === 0) {
Swal.fire('알림', '재처리할 항목을 선택해주세요.', 'warning');
return;
}
Swal.fire({
title: '재처리 확인',
text: `선택한 ${logIds.length}건을 재처리하시겠습니까?`,
icon: 'question',
showCancelButton: true,
confirmButtonText: '재처리',
cancelButtonText: '취소'
}).then((result) => {
if (result.isConfirmed) {
retryLogs(logIds);
}
});
});
// 재처리 실행
function retryLogs(logIds) {
const btn = $('#retrySelected');
btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> 처리 중...');
$.ajax({
url: '<?= base_url('manage/worker/retry') ?>',
method: 'POST',
data: { log_ids: logIds },
dataType: 'json',
success: function(response) {
if (response.success) {
const results = response.results;
let message = `성공: ${results.success}건, 실패: ${results.fail}건\n\n`;
results.details.forEach(detail => {
const icon = detail.status === 'success' ? '✅' :
detail.status === 'skip' ? '⏭' : '❌';
message += `${icon} [${detail.atcl_no}] ${detail.message}\n`;
});
Swal.fire({
title: '재처리 완료',
text: message,
icon: results.success > 0 ? 'success' : 'warning',
preConfirm: () => location.reload()
});
} else {
Swal.fire('오류', response.message, 'error');
}
},
error: function() {
Swal.fire('오류', '재처리 중 오류가 발생했습니다.', 'error');
},
complete: function() {
btn.prop('disabled', false).html('<i class="fa fa-refresh"></i> 선택 항목 재처리');
}
});
}
// 상세 보기
$('.view-detail').on('click', function() {
const logId = $(this).data('id');
$('#detailContent').html('<div class="text-center py-4"><div class="spinner-border"></div></div>');
$('#detailModal').modal('show');
$.ajax({
url: `<?= base_url('manage/worker/detail') ?>/${logId}`,
method: 'GET',
dataType: 'json',
success: function(response) {
if (response.success) {
displayDetail(response.log);
} else {
$('#detailContent').html('<div class="alert alert-danger">' + response.message + '</div>');
}
},
error: function() {
$('#detailContent').html('<div class="alert alert-danger">로그를 불러오는 중 오류가 발생했습니다.</div>');
}
});
});
// 상세 정보 표시
function displayDetail(log) {
const solution = log.solution;
const errorType = log.error_type;
let html = `
<div class="mb-3">
<strong>로그 ID:</strong> ${log.seq}<br>
<strong>매물번호:</strong> ${log.atcl_no || '-'}<br>
<strong>상태:</strong> <span class="badge badge-danger">${log.status}</span><br>
<strong>재시도 횟수:</strong> ${log.retry_cnt || 0}회<br>
<strong>발생시각:</strong> ${log.created_at}
</div>
<div class="mb-3">
<strong>오류 메시지:</strong>
<div class="alert alert-danger">${log.error_msg}</div>
</div>
<div class="solution-box mb-3">
<h5>${solution.title}</h5>
<ul>
${solution.steps.map(step => `<li>${step}</li>`).join('')}
</ul>
</div>
<div class="mb-3">
<strong>원본 Payload:</strong>
<pre class="bg-light p-3" style="max-height: 300px; overflow-y: auto;">${JSON.stringify(log.parsed_payload, null, 2)}</pre>
</div>
`;
$('#detailContent').html(html);
// 재처리 버튼 표시
if (log.can_retry) {
$('#retryOne').show().off('click').on('click', function() {
$('#detailModal').modal('hide');
retryLogs([log.seq]);
});
} else {
$('#retryOne').hide();
}
}
});
</script>
<?= $this->endSection() ?>

View File

@@ -2675,9 +2675,10 @@ $usr_level = session('usr_level');
title: "정상 처리되었습니다.",
icon: "success",
draggable: true
})
location.reload();
}).then(() => {
// 녹취파일 체크박스 활성화 및 체크
$('#chk_record').prop('disabled', false).prop('checked', true);
});
} else {
Swal.fire({
title: result.msg,

View File

@@ -7,434 +7,370 @@ $usr_nm = session('usr_nm');
<?= $this->extend('layouts/main') ?>
<?= $this->section('content') ?>
<style>
th {
font-size: 11px;
text-align: center;
}
th {
font-size: 11px;
text-align: center;
}
td {
text-align: center;
}
/* 테이블 헤더 좌우 여백 조정 */
#resultList thead th {
padding-left: 4px !important;
padding-right: 4px !important;
}
#resultList tbody tr {
cursor: pointer;
}
td {
text-align: center;
}
.blockUI {
z-index: 1500 !important;
}
#resultList tbody tr {
cursor: pointer;
}
.ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
}
.blockUI {
z-index: 1500 !important;
}
.card-header {
display: flex !important;
align-items: center;
}
.ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
}
.card-header-tab {
justify-content: flex-start !important;
}
.card-header {
display: flex !important;
align-items: center;
}
.table-scroll {
max-height: 300px;
overflow-y: scroll;
}
.card-header-tab {
justify-content: flex-start !important;
}
.swal2-cancel {
background-color: #ff0000 !important;
color: #fff !important;
}
.table-scroll {
max-height: 300px;
overflow-y: scroll;
}
#resultList.dataTable {
width: max-content !important;
}
.swal2-cancel {
background-color: #ff0000 !important;
color: #fff !important;
}
table.dataTable td,
table.dataTable th {
white-space: nowrap;
}
#resultList.dataTable {
width: max-content !important;
}
/* 테이블이 내용만큼 커지고, wrapper가 스크롤 담당 */
.table-responsive {
overflow-x: auto !important;
}
table.dataTable td,
table.dataTable th {
white-space: nowrap;
}
/* 테이블 폭: 내용 기준으로 커지, 최소는 100% */
.table-responsive #resultList {
width: max-content !important;
min-width: 100% !important;
table-layout: auto !important;
}
/* 테이블이 내용만큼 커지, wrapper가 스크롤 담당 */
.table-responsive {
overflow-x: auto !important;
}
/* 줄바꿈 금지 */
#resultList th,
#resultList td {
white-space: nowrap;
}
/* 테이블 폭: 내용에 따라 자동 조정, 스크롤 필요시 표시 */
.table-responsive #resultList {
width: 100% !important;
table-layout: auto !important;
}
/* PC에서 가로 스크롤이 잘리는 대표 구간들 강제 해제 */
.main-card,
.card,
.card-body {
overflow-x: visible !important;
}
/* 줄바꿈 허용 - 공백 기준으로 줄바꿈 */
#resultList th,
#resultList td {
white-space: normal !important;
word-wrap: break-word;
}
/* 만약 레이아웃 wrapper가 숨기고 있으면 이것도 */
.app-main__outer,
.app-main__inner,
.app-main {
overflow-x: visible !important;
}
/* tw-* 클래스가 DataTable의 inline 스타일보다 우선순위 높이기 */
#resultList th.tw-30,
#resultList td.tw-30 { width: 30px !important; max-width: 30px !important; min-width: 30px !important; }
#resultList th.tw-50,
#resultList td.tw-50 { width: 50px !important; max-width: 50px !important; min-width: 50px !important; }
#resultList th.tw-70,
#resultList td.tw-70 { width: 70px !important; max-width: 70px !important; min-width: 70px !important; }
#resultList th.tw-80,
#resultList td.tw-80 { width: 80px !important; max-width: 80px !important; min-width: 80px !important; }
#resultList th.tw-90,
#resultList td.tw-90 { width: 90px !important; max-width: 90px !important; min-width: 90px !important; }
#resultList th.tw-100,
#resultList td.tw-100 { width: 100px !important; max-width: 100px !important; min-width: 100px !important; }
#resultList th.tw-120,
#resultList td.tw-120 { width: 120px !important; max-width: 120px !important; min-width: 120px !important; }
#resultList th.tw-130,
#resultList td.tw-130 { width: 130px !important; max-width: 130px !important; min-width: 130px !important; }
#resultList th.tw-140,
#resultList td.tw-140 { width: 140px !important; max-width: 140px !important; min-width: 140px !important; }
#resultList th.tw-150,
#resultList td.tw-150 { width: 150px !important; max-width: 150px !important; min-width: 150px !important; }
#resultList th.tw-180,
#resultList td.tw-180 { width: 180px !important; max-width: 180px !important; min-width: 180px !important; }
#resultList th.tw-200,
#resultList td.tw-200 { width: 200px !important; max-width: 200px !important; min-width: 200px !important; }
#resultList th.tw-250,
#resultList td.tw-250 { width: 250px !important; max-width: 250px !important; min-width: 250px !important; }
/* flex 환경에서 필수 */
.app-main,
.app-main__outer,
.app-main__inner,
.card,
.card-body {
min-width: 0;
}
/* PC에서 가로 스크롤이 잘리는 대표 구간들 강제 해제 */
.main-card,
.card,
.card-body {
overflow-x: visible !important;
}
/* 만약 레이아웃 wrapper가 숨기고 있으면 이것도 */
.app-main__outer,
.app-main__inner,
.app-main {
overflow-x: visible !important;
}
/* flex 환경에서 필수 */
.app-main,
.app-main__outer,
.app-main__inner,
.card,
.card-body {
min-width: 0;
}
/* 검색 영역 행마다 구분선 추가 */
#frm_srch_info .row.g-3 {
padding: 12px 0;
border-bottom: 1px solid #e9ecef;
}
#frm_srch_info .row.g-3:last-child {
border-bottom: none;
}
</style>
<h1>조직별 배정 현황</h1>
<div class="col-md-12 col-xl-12">
<div class="main-card mb-3 card">
<div class="card-body">
<ul class="nav nav-tabs">
<li class="nav-item"><a data-bs-toggle="tab" href="#tab-eg10-0" class="active nav-link">검색</a></li>
<li class="nav-item"><a data-bs-toggle="tab" href="#tab-eg10-1" class="nav-link">조직별 통계</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tab-eg10-0" role="tabpanel">
<form id="frm_srch_info" method="get" onsubmit="return false;">
<input type="hidden" name="m" id="m" value="M801">
<input type="hidden" name="todo" id="todo" value="inq">
<input type="hidden" name="usr_id" value="">
<div class="card mb-3">
<div class="card-body">
<div class="row g-3 align-items-end">
<!-- 매물ID -->
<div class="col-12 col-md-2 col-lg-1">
<label class="form-label mb-1">매물ID</label>
<input type="text" class="form-control" name="rcpt_atclno" id="rcpt_atclno"
onkeypress="hscp_no_enter(event)">
</div>
<!-- 지역구분 -->
<div class="col-12 col-md-6 col-lg-3">
<label class="form-label mb-1">지역구분</label>
<div class="row g-2">
<div class="col-4">
<select class="form-select" name="srcSido" id="srcSido">
<option value="">시/도</option>
<?php foreach ($sido as $s): ?>
<option value="<?= $s['region_cd'] ?>"><?= $s['region_nm'] ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-4">
<select class="form-select" name="srcGugun" id="srcGugun">
<option value="">시/군/구</option>
</select>
</div>
<div class="col-4">
<select class="form-select" name="srcDong" id="srcDong">
<option value="">읍/면/동</option>
</select>
</div>
<div class="main-card mb-3 card">
<div class="card-header bg-white border-bottom shadow-sm">
<div class="d-flex flex-wrap align-items-center gap-3 card-header-tab">
<div>
<h4 class="mb-0 fw-bold text-dark">조직별 배정 현황</h4>
</div>
</div>
</div>
<div class="card-body">
<form id="frm_srch_info" method="get" onsubmit="return false;">
<!-- 검색 폼 -->
<div class="row g-3">
<div class="col-md-1">
<label class="form-label mb-1">매물ID</label>
<input type="text" class="form-control form-control-sm" name="rcpt_atclno" id="rcpt_atclno"
onkeypress="hscp_no_enter(event)">
</div>
</div>
<!-- 접수일자 -->
<div class="col-12 col-md-6 col-lg-3">
<label class="form-label mb-1">접수일자</label>
<div class="input-group">
<input type="date" class="form-control" name="sdate" id="sdate" placeholder="시작일">
<span class="input-group-text">~</span>
<input type="date" class="form-control" name="date" id="edate" placeholder="종료일">
<div class="col-md-4">
<label class="form-label mb-1">예약일자</label>
<div class="input-group input-group-sm">
<select class="form-select form-select-sm" name="rsrv_tm_ap">
<option value="">오전/오후</option>
<option value="AM">오전</option>
<option value="PM">오후</option>
</select>
<input type="date" class="form-control form-control-sm" name="rsrv_sdate" id="rsrv_sdate" placeholder="시작일">
<span class="input-group-text">~</span>
<input type="date" class="form-control form-control-sm" name="rsrv_edate" id="rsrv_edate" placeholder="종료일">
</div>
</div>
</div>
<!-- 예약일자 -->
<div class="col-12 col-md-6 col-lg-4">
<label class="form-label mb-1">예약일자</label>
<div class="input-group">
<select class="form-select" name="rsrv_tm_ap">
<option value="">오전/오후</option>
<option value="AM">오전</option>
<option value="PM">오후</option>
</select>
<input type="date" class="form-control" name="rsrv_sdate" id="rsrv_sdate" placeholder="시작일">
<span class="input-group-text">~</span>
<input type="date" class="form-control" name="rsrv_edate" id="rsrv_edate" placeholder="종료일">
<div class="col-md-3">
<label class="form-label mb-1">관할조직</label>
<div class="d-flex gap-1">
<select name="bonbu" id="bonbu" class="form-select form-select-sm">
<option value="">-본부-</option>
<?php foreach ($bonbu as $d): ?>
<option value="<?= $d['dept_sq'] ?>">
<?= $d['dept_nm'] ?>
</option>
<?php endforeach; ?>
</select>
<select name="team" id="team" class="form-select form-select-sm">
<option value="">-팀-</option>
</select>
<select name="damdang" id="damdang" class="form-select form-select-sm">
<option value="">-담당자-</option>
</select>
</div>
</div>
</div>
<!-- 방문담당 -->
<div class="col-12 col-lg-4">
<label class="form-label mb-1">관할조직</label>
<div class="row g-2">
<div class="col-4">
<select class="form-select" name="bonbu" id="bonbu">
<option value="">-본부-</option>
<?php foreach ($bonbu as $d): ?>
<option value="<?= $d['dept_sq'] ?>"><?= $d['dept_nm'] ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-4">
<select class="form-select" name="team" id="team">
<option value="">-팀-</option>
</select>
</div>
<div class="col-4">
<select class="form-select" name="damdang" id="damdang">
<option value="">-담당자-</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label mb-1">지역별조회</label>
<div class="d-flex gap-1">
<select name="srcSido" id="srcSido" class="form-select form-select-sm">
<option value="">-시/도-</option>
<?php foreach ($sido as $s): ?>
<option value="<?= $s['region_cd'] ?>">
<?= $s['region_nm'] ?>
</option>
<?php endforeach; ?>
</select>
<select name="srcGugun" id="srcGugun" class="form-select form-select-sm">
<option value="">-시/군/구-</option>
</select>
<select name="srcDong" id="srcDong" class="form-select form-select-sm">
<option value="">-읍/면/동-</option>
</select>
</div>
</div>
</div>
<div class="col-12 col-md-2 col-lg-1">
<label class="form-label mb-1">평면도 유무</label>
<select class="form-select" name="ground_plan_yn">
<option value="">선택</option>
<option value="Y">Y</option>
<option value="N">N</option>
<div class="col-md-1">
<label class="form-label mb-1">평면도유무</label>
<select class="form-select form-select-sm" name="ground_plan_yn">
<option value="">전체</option>
<option value="Y">Y</option>
<option value="N">N</option>
</select>
</div>
</div>
<div class="row g-3">
<div class="col-md-1">
<label class="form-label mb-1">평면도요청</label>
<select class="form-select form-select-sm" name="ground_plan">
<option value="">전체</option>
<option value="Y">Y</option>
<option value="N">N</option>
</select>
</div>
<div class="col-md-1">
<label class="form-label mb-1">직거래</label>
<select class="form-select form-select-sm" name="direct_trad_yn">
<option value="">전체</option>
<option value="Y">Y</option>
<option value="N">N</option>
</select>
</div>
<!-- 검색유형 -->
<div class="col-md-1">
<label class="form-label mb-1">검색유형</label>
<select class="form-select form-select-sm" name="srchType">
<option value="">선택</option>
<option value="1">중개사명</option>
<option value="2">중개사연락처</option>
</select>
</div>
<!-- 검색어 -->
<div class="col-md-2">
<label class="form-label mb-1">검색어</label>
<input type="text" class="form-control form-control-sm" name="srchTxt" placeholder="검색어 입력">
</div>
<div class="col-md-6">
<label class="form-label mb-1">진행상태</label>
<div class="d-flex flex-wrap gap-2">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="rcpt_stat_all" value="Y"
id="rcpt_stat_all" onclick="progressStatAll(this, this.checked);">
<label class="form-check-label" for="rcpt_stat_all">전체</label>
</div>
<?php foreach ($codes as $code): ?>
<?php if ($code['category'] === "RECEIPT_STATUS1"): ?>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="stat[]" id="stat_<?= $code['cd'] ?>"
value="<?= $code['cd'] ?>">
<label class="form-check-label" for="stat_<?= $code['cd'] ?>"><?= $code['cd_nm'] ?></label>
</div>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
<div class="col-md-1 d-grid">
<label class="form-label mb-1 invisible">검색</label>
<button type="button" class="btn btn-primary" id="btnSearch">
<i class="pe-7s-search me-1"></i>검색
</button>
</div>
</div>
</form>
</div>
</div>
<div class="main-card mb-3 card">
<div class="card-header bg-white border-bottom shadow-sm">
<div class="d-flex flex-wrap align-items-center w-100 justify-content-between card-header-tab">
<h5 class="mb-0 fw-bold text-dark">검색 결과</h5>
<div class="d-flex align-items-center gap-2 ms-auto">
<!-- 배정변경 조직 선택 -->
<select class="form-select form-select-sm" id="bonbu2" style="width:120px;">
<option value="">-본부-</option>
<?php foreach ($bonbu as $d): ?>
<option value="<?= $d['dept_sq'] ?>">
<?= $d['dept_nm'] ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-12 col-md-2 col-lg-1">
<label class="form-label mb-1">평면도 요청</label>
<select class="form-select" name="ground_plan">
<option value="">선택</option>
<option value="Y">Y</option>
<option value="N">N</option>
<select class="form-select form-select-sm" id="team2" style="width:140px;">
<option value="">-팀-</option>
</select>
</div>
<div class="col-12 col-md-2 col-lg-1">
<label class="form-label mb-1">직거래</label>
<select class="form-select" name="direct_trad_yn">
<option value="">선택</option>
<option value="Y">Y</option>
<option value="N">N</option>
<select class="form-select form-select-sm" id="damdang2" style="width:140px;">
<option value="">-담당자-</option>
</select>
</div>
<!-- 검색유형 -->
<div class="col-md-1">
<label class="form-label mb-1">검색유형</label>
<select class="form-select" name="srchType">
<option value="">선택</option>
<option value="1">중개사명</option>
<option value="2">중개사연락처</option>
</select>
</div>
<!-- 검색어 -->
<div class="col-md-2">
<label class="form-label mb-1">검색어</label>
<input type="text" class="form-control" name="srchTxt" placeholder="검색어 입력">
</div>
<!-- 진행상태 + 검색버튼 -->
<div class="col-12 col-lg-10">
<label class="form-label mb-1">진행상태</label>
<div class="d-flex flex-wrap gap-2 align-items-center border rounded p-2">
<div class="form-check me-2">
<input class="form-check-input" type="checkbox" name="rcpt_stat_all" value="Y"
id="rcpt_stat_all" onclick="progressStatAll(this, this.checked);">
<label class="form-check-label" for="rcpt_stat_all">전체</label>
</div>
<?php foreach ($codes as $code): ?>
<?php if ($code['category'] === "RECEIPT_STATUS1"): ?>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="stat[]" id="stat_<?= $code['cd'] ?>"
value="<?= $code['cd'] ?>">
<label class="form-check-label" for="stat_<?= $code['cd'] ?>"><?= $code['cd_nm'] ?></label>
</div>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
<div class="col-12 col-lg-1 d-grid">
<button type="button" class="btn btn-primary" id="btnSearch">
<i class="pe-7s-search me-1"></i>검색
<button type="button" class="btn btn-sm btn-outline-primary" id="btn_part_change" onclick="updateAssign()">
배정변경
</button>
</div>
</div><!-- /row -->
</div><!-- /card-body -->
</div><!-- /card -->
</form>
<button class="btn btn-sm btn-outline-success" id="excel-download">
<i class="fa fa-fw" aria-hidden="true" title="file-excel-o"></i>
엑셀다운로드
</button>
<button class="btn btn-sm btn-outline-secondary" id="excel-download2">
<i class="fa fa-fw" aria-hidden="true" title="file-excel-o"></i>
배정내역출력
</button>
</div>
</div>
</div>
<div class="tab-pane" id="tab-eg10-1" role="tabpanel">
<div class="row">
<div class="col-md-3">
<div class="card mb-3" style="max-width: 380px;">
<div class="card-body p-0">
<div class="table-scroll">
<table class="table table-sm table-hover table-striped mb-0 align-middle text-center">
<thead class="table-light">
<div class="card-body">
<div class="table-responsive">
<table id="resultList" class="table table-hover table-striped table-bordered">
<thead>
<tr>
<th style="width:42px;">
<input class="form-check-input" type="checkbox" id="depChkAll">
</th>
<th>관할본부</th>
<th>방문담당</th>
<th style="width:90px;">배정건수</th>
<th>
<input type="checkbox" class="form-check-input" name="chkAll" id="chkAll" />
</th>
<th>진행상태</th>
<th>매물ID</th>
<th>접수(등록)일시</th>
<th>중개사</th>
<th>주소</th>
<th>연락처</th>
<th>예약일자</th>
<th>매물종류</th>
<th>관할조직</th>
<th>방문담당</th>
<th>평면도유무</th>
<th>평면도요청</th>
<th>면적확인</th>
</tr>
</thead>
<tbody id="deptArea"></tbody>
</table>
</div>
</div>
<div class="card-footer d-flex justify-content-end gap-1">
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="printMap()">
<i class="pe-7s-print"></i> 인쇄하기
</button>
<button type="button" class="btn btn-primary btn-sm" onclick="deptMap()">
<i class="pe-7s-map-2"></i> 지도로 보기
</button>
</div>
</div>
</thead>
<tbody>
<!-- 여기는 비워둠: AJAX로 채움 -->
</tbody>
</table>
</div>
<div class="col-md-3">
<div class="card mb-3" style="max-width: 380px;">
<div class="card-body p-0">
<div class="table-scroll">
<table class="table table-sm table-hover table-striped mb-0 align-middle text-center">
<thead class="table-light">
<tr>
<th>지역구분</th>
<th style="width:90px;">요청건수</th>
</tr>
</thead>
<tbody id="statsArea"></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-12 col-xl-12">
<div class="main-card mb-3 card">
<div class="card-header d-flex align-items-center">
<div class="d-flex align-items-center flex-wrap" style="gap:8px; flex:1;">
<select class="form-select form-select-sm" id="bonbu2" style="width:140px;">
<option value="">-본부-</option>
<?php foreach ($bonbu as $d): ?>
<option value="<?= $d['dept_sq'] ?>">
<?= $d['dept_nm'] ?>
</option>
<?php endforeach; ?>
</select>
<select class="form-select form-select-sm" id="team2" style="width:160px;">
<option value="">-팀-</option>
</select>
<select class="form-select form-select-sm" id="damdang2" style="width:160px;">
<option value="">-담당자-</option>
</select>
<button type="button" class="btn btn-sm btn-outline-primary" id="btn_part_change">
배정변경
</button>
</div>
<div class="ml-auto">
<button class="btn btn-sm btn-outline-success" id="excel-download">
<i class="fa fa-fw" aria-hidden="true" title="file-excel-o"></i> 엑셀다운로드
</button>
<button class="btn btn-sm btn-outline-secondary" id="excel-download2">
<i class="fa fa-fw" aria-hidden="true" title="file-excel-o"></i> 배정내역출력
</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="resultList" class="table table-hover table-striped table-bordered">
<thead>
<th class="text-center">
<input type="checkbox" class="form-check-input" name="chkAll" id="chkAll" />
</th>
<th>진행상태</th>
<th>매물ID</th>
<th>접수(등록)일시</th>
<th>중개사</th>
<th>주소</th>
<th>연락처</th>
<th>예약일자</th>
<th>매물종류</th>
<th>관할조직</th>
<th>방문담당</th>
<th>평면도유무</th>
<th>평면도요청</th>
<th>면적확인</th>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div class="card-footer justify-content-start gap-1">
<button type="button" class="btn btn-light">
예약 확인
</button>
<button type="button" class="btn btn-outline-light">
검수지연
</button>
<button type="button" class="btn btn-outline-light">
검수지연 삭제
</button>
</div>
</div>
</div>
<?= $this->section('modals') ?>
<div class="modal fade" id="deptMapModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">지도보기</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-0">
<div id="dialog-deptmap-content"></div>
</div>
</div>
</div>
</div>
<?= $this->endSection() ?>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css" />
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script defer src="/architectui/assets/js/datatable.kor.js"></script>
@@ -622,61 +558,6 @@ $usr_nm = session('usr_nm');
d.start = d.start || 0
d.length = d.length || 10
},
dataSrc: function (d) {
const deptList = d.widgets?.deptList;
const areaStats = d.widgets?.areaStats ?? [];
if (deptList.length > 0) {
var str = "";
for (var i = 0; i < deptList.length; i++) {
str += `
<tr>
<td><input class="form-check-input depChk" type="checkbox" name="depChk[]" value="${deptList[i].dept_sq}"></td>
<td>${deptList[i].bonbu_nm}</td>
<td>${deptList[i].team_nm}</td>
<td><span class="badge bg-primary">${deptList[i].cnt} 건</span></td>
</tr>
`;
}
$("#deptArea").html(str);
} else {
str = `
<tr>
<td colspan="4">조회 가능한 데이터가 없습니다.</td>
</tr>
`;
$("#deptArea").html(str);
}
if (areaStats.length > 0) {
var str = "";
for (var i = 0; i < areaStats.length; i++) {
str += `
<tr>
<td>${areaStats[i].rcpt_dong}</td>
<td><span class="badge bg-warning">${areaStats[i].cnt} 건</span></td>
</tr>
`;
}
$("#statsArea").html(str);
} else {
str = `
<tr>
<td colspan="2">조회 가능한 데이터가 없습니다.</td>
</tr>
`;
$("#statsArea").html(str);
}
return d.data;
}
},
columnDefs: [
@@ -700,7 +581,7 @@ $usr_nm = session('usr_nm');
{ data: 'exp_spc_yn' },
],
// 옵션들 예시
pdestroy: true,
destroy: true,
deferRender: true,
scrollX: false,
autoWidth: false,
@@ -737,18 +618,6 @@ $usr_nm = session('usr_nm');
$('#chkAll').prop('checked', total > 0 && total === checkedCnt);
});
// 전체 선택
$(document).on('change', '#depChkAll', function () {
$('.depChk').prop('checked', this.checked);
});
// 개별 체크 시 전체 체크 동기화
$(document).on('change', '.depChk', function () {
const total = $('.depChk').length;
const checked = $('.depChk:checked').length;
$('#depChkAll').prop('checked', total === checked);
});
table.on('draw', function () {
$('#chkAll').prop('checked', false);
});
@@ -815,6 +684,15 @@ $usr_nm = session('usr_nm');
}
function hscp_no_enter(event) {
if (event.keyCode == 13) {
table.ajax.reload()
}
}
function progressStatAll(el, checked) {
$('input[name="stat[]"]').prop('checked', checked);
}
/** datatable render */
function fn_chk_render(data, type, row, meta) {
@@ -890,56 +768,9 @@ $usr_nm = session('usr_nm');
/** datatable render */
// 인쇄하기
function printMap() {
var data = $('input[name="depChk[]"]').serialize();
var usr_id = $('input[name="usr_id"]').serialize();
if (data == '') {
alert('팀을 선택해주세요');
return;
}
var url = '/article/dept/print?' + data;
window.open(url, '', '');
}
function deptMap() {
const data = $('input[name="depChk[]"]:checked').serialize();
if (!data) {
alert('팀을 선택해주세요');
return;
}
const url = '/article/dept/print?' + data;
$('#dialog-deptmap-content').html(
`<iframe style="border:0;width:100%;height:650px;" src="${url}"></iframe>`
);
const modal = new bootstrap.Modal(document.getElementById('deptMapModal'));
modal.show();
}
// 엑셀 다운로드
function downloadExcel(data) {
const ws = XLSX.utils.json_to_sheet(data);
// ws['!cols'] = [
// { wpx: 100 },
// { wpx: 100 },
// { wpx: 100 },
// { wpx: 100 },
// { wpx: 150 },
// { wpx: 120 },
// { wpx: 100 },
// { wpx: 100 },
// { wpx: 100 },
// { wpx: 100 },
// { wpx: 100 },
// { wpx: 100 },
// ];
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
@@ -1047,7 +878,7 @@ $usr_nm = session('usr_nm');
var arr = new Array();
const bonbu = $("#bonbu2").val();
const team = $("#team2").val();
const user = $("#user2").val();
const user = $("#damdang2").val();
$('#resultList tbody .row-chk:checked').each(function () {
const rowData = table.row($(this).closest('tr')).data();
@@ -1088,14 +919,12 @@ $usr_nm = session('usr_nm');
return;
}
swal.fire({
Swal.fire({
text: "배정을 변경하시겠습니까?",
type: "warning",
icon: "warning",
showCancelButton: true,
confirmButtonText: "예",
cancelButtonText: "아니오",
closeOnConfirm: false,
closeOnCancel: true,
confirmButtonColor: "#3085d6",
cancelButtonColor: "#d33",
}).then((result) => {
@@ -1155,4 +984,4 @@ $usr_nm = session('usr_nm');
}
</script>
<?= $this->endSection() ?>
<?= $this->endSection() ?>

File diff suppressed because it is too large Load Diff

View File

@@ -8,111 +8,160 @@ $usr_nm = session('usr_nm');
<?= $this->section('content') ?>
<style>
th {
font-size: 11px;
text-align: center;
}
th {
font-size: 11px;
text-align: center;
}
td {
text-align: center;
}
/* 테이블 헤더 좌우 여백 조정 */
#resultList thead th {
padding-left: 4px !important;
padding-right: 4px !important;
}
#resultList tbody tr {
cursor: pointer;
}
td {
text-align: center;
}
.blockUI {
z-index: 1500 !important;
}
#resultList tbody tr {
cursor: pointer;
}
.ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
}
.blockUI {
z-index: 1500 !important;
}
.card-header {
display: flex !important;
align-items: center;
}
.ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
}
.card-header-tab {
justify-content: flex-start !important;
}
.card-header {
display: flex !important;
align-items: center;
}
.table-scroll {
max-height: 300px;
overflow-y: scroll;
}
.card-header-tab {
justify-content: flex-start !important;
}
.swal2-cancel {
background-color: #ff0000 !important;
color: #fff !important;
}
.table-scroll {
max-height: 300px;
overflow-y: scroll;
}
#resultList.dataTable {
width: max-content !important;
}
.swal2-cancel {
background-color: #ff0000 !important;
color: #fff !important;
}
table.dataTable td,
table.dataTable th {
white-space: nowrap;
}
#resultList.dataTable {
width: max-content !important;
}
/* 테이블이 내용만큼 커지고, wrapper가 스크롤 담당 */
.table-responsive {
overflow-x: auto !important;
}
table.dataTable td,
table.dataTable th {
white-space: nowrap;
}
/* 테이블 폭: 내용 기준으로 커지, 최소는 100% */
.table-responsive #resultList {
width: max-content !important;
min-width: 100% !important;
table-layout: auto !important;
}
/* 테이블이 내용만큼 커지, wrapper가 스크롤 담당 */
.table-responsive {
overflow-x: auto !important;
}
/* 줄바꿈 금지 */
#resultList th,
#resultList td {
white-space: nowrap;
}
/* 테이블 폭: 내용에 따라 자동 조정, 스크롤 필요시 표시 */
.table-responsive #resultList {
width: 100% !important;
table-layout: auto !important;
}
/* PC에서 가로 스크롤이 잘리는 대표 구간들 강제 해제 */
.main-card,
.card,
.card-body {
overflow-x: visible !important;
}
/* 줄바꿈 허용 - 공백 기준으로 줄바꿈 */
#resultList th,
#resultList td {
white-space: normal !important;
word-wrap: break-word;
}
/* 만약 레이아웃 wrapper가 숨기고 있으면 이것도 */
.app-main__outer,
.app-main__inner,
.app-main {
overflow-x: visible !important;
}
/* tw-* 클래스가 DataTable의 inline 스타일보다 우선순위 높이기 */
#resultList th.tw-30,
#resultList td.tw-30 { width: 30px !important; max-width: 30px !important; min-width: 30px !important; }
#resultList th.tw-50,
#resultList td.tw-50 { width: 50px !important; max-width: 50px !important; min-width: 50px !important; }
#resultList th.tw-70,
#resultList td.tw-70 { width: 70px !important; max-width: 70px !important; min-width: 70px !important; }
#resultList th.tw-80,
#resultList td.tw-80 { width: 80px !important; max-width: 80px !important; min-width: 80px !important; }
#resultList th.tw-90,
#resultList td.tw-90 { width: 90px !important; max-width: 90px !important; min-width: 90px !important; }
#resultList th.tw-100,
#resultList td.tw-100 { width: 100px !important; max-width: 100px !important; min-width: 100px !important; }
#resultList th.tw-120,
#resultList td.tw-120 { width: 120px !important; max-width: 120px !important; min-width: 120px !important; }
#resultList th.tw-130,
#resultList td.tw-130 { width: 130px !important; max-width: 130px !important; min-width: 130px !important; }
#resultList th.tw-140,
#resultList td.tw-140 { width: 140px !important; max-width: 140px !important; min-width: 140px !important; }
#resultList th.tw-150,
#resultList td.tw-150 { width: 150px !important; max-width: 150px !important; min-width: 150px !important; }
#resultList th.tw-180,
#resultList td.tw-180 { width: 180px !important; max-width: 180px !important; min-width: 180px !important; }
#resultList th.tw-200,
#resultList td.tw-200 { width: 200px !important; max-width: 200px !important; min-width: 200px !important; }
#resultList th.tw-250,
#resultList td.tw-250 { width: 250px !important; max-width: 250px !important; min-width: 250px !important; }
/* flex 환경에서 필수 */
.app-main,
.app-main__outer,
.app-main__inner,
.card,
.card-body {
min-width: 0;
}
/* PC에서 가로 스크롤이 잘리는 대표 구간들 강제 해제 */
.main-card,
.card,
.card-body {
overflow-x: visible !important;
}
/* 만약 레이아웃 wrapper가 숨기고 있으면 이것도 */
.app-main__outer,
.app-main__inner,
.app-main {
overflow-x: visible !important;
}
/* flex 환경에서 필수 */
.app-main,
.app-main__outer,
.app-main__inner,
.card,
.card-body {
min-width: 0;
}
/* 검색 영역 행마다 구분선 추가 */
#frm_srch_info .row.g-3 {
padding: 12px 0;
border-bottom: 1px solid #e9ecef;
}
#frm_srch_info .row.g-3:last-child {
border-bottom: none;
}
</style>
<h1>현장확인V2 조직별 배정 현황</h1>
<div class="col-md-12 col-xl-12">
<div class="main-card mb-3 card">
<div class="card-body">
<ul class="nav nav-tabs">
<li class="nav-item"><a data-bs-toggle="tab" href="#tab-eg10-0" class="active nav-link">검색</a></li>
<li class="nav-item"><a data-bs-toggle="tab" href="#tab-eg10-1" class="nav-link">조직별 통계</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tab-eg10-0" role="tabpanel">
<div class="main-card mb-3 card">
<div class="card-header bg-white border-bottom shadow-sm">
<div class="d-flex flex-wrap align-items-center gap-3 card-header-tab">
<div>
<h4 class="mb-0 fw-bold text-dark">현장확인V2 조직별 배정 현황</h4>
</div>
</div>
</div>
<div class="card-body">
<ul class="nav nav-tabs">
<li class="nav-item"><a data-bs-toggle="tab" href="#tab-eg10-0" class="active nav-link">검색</a></li>
<li class="nav-item"><a data-bs-toggle="tab" href="#tab-eg10-1" class="nav-link">조직별 통계</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tab-eg10-0" role="tabpanel">
<form id="frm_srch_info" method="get" onsubmit="return false;">
<input type="hidden" name="m" id="m" value="M801">
<input type="hidden" name="todo" id="todo" value="inq">

View File

@@ -14,6 +14,12 @@ $usr_nm = session('usr_nm');
text-align: center;
}
/* 테이블 헤더 좌우 여백 조정 */
#resultList thead th {
padding-left: 4px !important;
padding-right: 4px !important;
}
td {
text-align: center;
}
@@ -66,19 +72,47 @@ $usr_nm = session('usr_nm');
overflow-x: auto !important;
}
/* 테이블 폭: 내용 기준으로 커지되, 최소는 100% */
/* 테이블 폭: 내용에 따라 자동 조정, 스크롤 필요시 표시 */
.table-responsive #resultList {
width: max-content !important;
min-width: 100% !important;
width: 100% !important;
table-layout: auto !important;
}
/* 줄바꿈 금지 */
/* 줄바꿈 허용 - 공백 기준으로 줄바꿈 */
#resultList th,
#resultList td {
white-space: nowrap;
white-space: normal !important;
word-wrap: break-word;
}
/* tw-* 클래스가 DataTable의 inline 스타일보다 우선순위 높이기 */
#resultList th.tw-30,
#resultList td.tw-30 { width: 30px !important; max-width: 30px !important; min-width: 30px !important; }
#resultList th.tw-50,
#resultList td.tw-50 { width: 50px !important; max-width: 50px !important; min-width: 50px !important; }
#resultList th.tw-70,
#resultList td.tw-70 { width: 70px !important; max-width: 70px !important; min-width: 70px !important; }
#resultList th.tw-80,
#resultList td.tw-80 { width: 80px !important; max-width: 80px !important; min-width: 80px !important; }
#resultList th.tw-90,
#resultList td.tw-90 { width: 90px !important; max-width: 90px !important; min-width: 90px !important; }
#resultList th.tw-100,
#resultList td.tw-100 { width: 100px !important; max-width: 100px !important; min-width: 100px !important; }
#resultList th.tw-120,
#resultList td.tw-120 { width: 120px !important; max-width: 120px !important; min-width: 120px !important; }
#resultList th.tw-130,
#resultList td.tw-130 { width: 130px !important; max-width: 130px !important; min-width: 130px !important; }
#resultList th.tw-140,
#resultList td.tw-140 { width: 140px !important; max-width: 140px !important; min-width: 140px !important; }
#resultList th.tw-150,
#resultList td.tw-150 { width: 150px !important; max-width: 150px !important; min-width: 150px !important; }
#resultList th.tw-180,
#resultList td.tw-180 { width: 180px !important; max-width: 180px !important; min-width: 180px !important; }
#resultList th.tw-200,
#resultList td.tw-200 { width: 200px !important; max-width: 200px !important; min-width: 200px !important; }
#resultList th.tw-250,
#resultList td.tw-250 { width: 250px !important; max-width: 250px !important; min-width: 250px !important; }
/* PC에서 가로 스크롤이 잘리는 대표 구간들 강제 해제 */
.main-card,
.card,
@@ -101,12 +135,27 @@ $usr_nm = session('usr_nm');
.card-body {
min-width: 0;
}
</style>
<h1>평면도 관리</h1>
/* 검색 영역 행마다 구분선 추가 */
#frm_srch_info .row.g-3 {
padding: 12px 0;
border-bottom: 1px solid #e9ecef;
}
#frm_srch_info .row.g-3:last-child {
border-bottom: none;
}
</style>
<div class="col-md-12 col-xl-12">
<div class="main-card mb-3 card">
<div class="card-header bg-white border-bottom shadow-sm">
<div class="d-flex flex-wrap align-items-center gap-3 card-header-tab">
<div>
<h4 class="mb-0 fw-bold text-dark">평면도 관리</h4>
</div>
</div>
</div>
<div class="card-body">
<form id="frm_srch_info" method="get" onsubmit="return false;">
<div class="alert alert-warning py-2 mb-3">

View File

@@ -4,104 +4,38 @@
table th {
text-align: center;
}
.tab-header {
display: inline;
}
</style>
<h4 class="mb-3">처리가능 수량관리</h4>
<div class="col-12">
<div class="main-card mb-3 card">
<!-- 탭은 card-header 안에 -->
<div class="card-header tab-header pb-0">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#tab-eg10-0">일자별 처리가능 수량</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#tab-eg10-1">지역별 수량</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#tab-eg10-2">기본 수량</a>
</li>
</ul>
<!-- 타이틀 -->
<div class="card-header bg-white border-bottom shadow-sm">
<h4 class="mb-0 fw-bold text-dark">처리가능 수량관리</h4>
</div>
<!-- 탭 -->
<ul class="nav nav-tabs px-3 pt-3 bg-white border-bottom">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#tab-eg10-0">일자별 처리가능 수량</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#tab-eg10-1">지역별 수량</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#tab-eg10-2">기본 수량</a>
</li>
</ul>
<div class="card-body">
<!-- 공통 검색/필터 영역 -->
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2 mb-3">
<div class="d-flex flex-wrap align-items-end gap-2">
<label class="form-label mb-1 small me-2">조회조건</label>
<!-- 첫번째탭 -->
<div id="form1" class="d-flex flex-wrap align-items-end gap-2">
<div class="input-group input-group-sm" style="min-width: 320px;">
<input type="date" class="form-control" name="sdate" id="sdate">
<span class="input-group-text">~</span>
<input type="date" class="form-control" name="edate" id="edate">
</div>
</div>
<!-- 두번째탭 -->
<div id="form2" class="d-none">
<div class="d-flex align-items-end gap-1">
<select class="form-select form-select-sm" name="region2" id="region2" style="min-width: 180px;">
<option value="">지역 선택</option>
<?php foreach ($sido as $s): ?>
<option value="<?= $s['region_cd'] ?>" <?php if ($s['region_cd'] == "1100000000") {
echo "selected";
} ?>>
<?= $s['region_nm'] ?>
</option>
<?php endforeach; ?>
</select>
<div class="input-group input-group-sm" style="min-width: 180px;">
<input type="date" class="form-control" name="sdate2" id="sdate2">
</div>
</div>
</div>
<!-- 세번째탭 -->
<div id="form3" class="d-none">
<div class="d-flex align-items-end gap-1">
<select class="form-select form-select-sm" name="region3" id="region3" style="min-width: 180px;">
<option value="">지역 선택</option>
<?php foreach ($sido as $s): ?>
<option value="<?= $s['region_cd'] ?>" <?php if ($s['region_cd'] == "1100000000") {
echo "selected";
} ?>>
<?= $s['region_nm'] ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
</div>
<div class="d-flex gap-1 ms-auto">
<button class="btn btn-sm btn-outline-success" id="excel-download" type="button">
<i class="fa fa-fw" aria-hidden="true" title="file-excel-o"></i> 엑셀다운로드
</button>
<button class="btn btn-sm btn-outline-light" type="button" id="btnSearch">
조회
</button>
</div>
</div>
<!-- 탭 컨텐츠는 tab-content 바로 아래에 -->
<div class="tab-content">
<!-- 1) 일자별 -->
<div class="tab-pane fade show active" id="tab-eg10-0" role="tabpanel">
<div class="border rounded p-3 bg-white">
<table class="table table-sm table-hover table-striped mb-0 align-middle text-center w-100" id="tbl1">
<table class="table table-sm table-hover table-striped mb-0 align-middle text-center w-100" id="tbl1" >
<thead>
<tr>
<th rowspan="2" class="align-middle">날짜</th>
@@ -121,6 +55,36 @@
<!-- 2) 지역별 -->
<div class="tab-pane fade" id="tab-eg10-1" role="tabpanel">
<!-- 조회조건 -->
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2 mb-3">
<div class="d-flex flex-wrap align-items-end gap-2">
<label class="form-label mb-1 small me-2">조회조건</label>
<div id="form2" class="d-flex align-items-end gap-1">
<select class="form-select form-select-sm" name="region2" id="region2" style="min-width: 180px;">
<option value="">지역 선택</option>
<?php foreach ($sido as $s): ?>
<option value="<?= $s['region_cd'] ?>" <?php if ($s['region_cd'] == "1100000000") {
echo "selected";
} ?>>
<?= $s['region_nm'] ?>
</option>
<?php endforeach; ?>
</select>
<div class="input-group input-group-sm" style="min-width: 180px;">
<input type="date" class="form-control" name="sdate2" id="sdate2">
</div>
</div>
</div>
<div class="d-flex gap-1 ms-auto">
<button class="btn btn-sm btn-outline-success" id="excel-download" type="button">
<i class="fa fa-fw" aria-hidden="true" title="file-excel-o"></i> 엑셀다운로드
</button>
<button class="btn btn-sm btn-outline-light" type="button" id="btnSearch">
조회
</button>
</div>
</div>
<div class="border rounded p-3 bg-white">
<table class="table table-sm table-hover table-striped mb-0 align-middle text-center w-100" id="tbl2">
<thead>
@@ -142,6 +106,33 @@
<!-- 3) 기본 수량 -->
<div class="tab-pane fade" id="tab-eg10-2" role="tabpanel">
<!-- 조회조건 -->
<div class="d-flex flex-wrap align-items-end justify-content-between gap-2 mb-3">
<div class="d-flex flex-wrap align-items-end gap-2">
<label class="form-label mb-1 small me-2">조회조건</label>
<div id="form3" class="d-flex align-items-end gap-1">
<select class="form-select form-select-sm" name="region3" id="region3" style="min-width: 180px;">
<option value="">지역 선택</option>
<?php foreach ($sido as $s): ?>
<option value="<?= $s['region_cd'] ?>" <?php if ($s['region_cd'] == "1100000000") {
echo "selected";
} ?>>
<?= $s['region_nm'] ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="d-flex gap-1 ms-auto">
<button class="btn btn-sm btn-outline-success" id="excel-download" type="button">
<i class="fa fa-fw" aria-hidden="true" title="file-excel-o"></i> 엑셀다운로드
</button>
<button class="btn btn-sm btn-outline-light" type="button" id="btnSearch">
조회
</button>
</div>
</div>
<div class="border rounded p-3 bg-white">
<table class="table table-sm table-hover table-striped mb-0 align-middle text-center w-100" id="tbl3">
<thead>
@@ -193,9 +184,12 @@
initForm();
$("#sdate, #edate").on("change", function () {
table1.ajax.reload();
});
// DataTable 초기화 후 날짜 변경 이벤트 등록
setTimeout(() => {
$("#sdate-dt, #edate-dt").on("change", function () {
if (table1) table1.ajax.reload();
});
}, 500);
$("#btnSearch").on("click", function () {
@@ -216,6 +210,47 @@
language: lang_kor,
serverSide: true,
processing: true,
dom: '<"d-flex flex-wrap align-items-end justify-content-between gap-2 mb-2"<"#tbl1-filter">r>tip',
initComplete: function () {
// DataTable 생성 완료 후 필터 영역에 조회조건 삽입
const fmt = d => d.toISOString().slice(0, 10);
const lastDate = getLastDateOfMonth(date.getFullYear(), date.getMonth() + 1);
$('#tbl1-filter').html(`
<div class="d-flex flex-wrap align-items-end gap-2">
<label class="form-label mb-1 small me-2">조회조건</label>
<div class="input-group input-group-sm" style="min-width: 320px;">
<input type="date" class="form-control" id="sdate-dt" value="${fmt(date)}">
<span class="input-group-text">~</span>
<input type="date" class="form-control" id="edate-dt" value="${fmt(lastDate)}">
</div>
</div>
<div class="d-flex gap-1 ms-auto">
<button id="excel-download-dt" class="btn btn-sm btn-outline-success" type="button">
<i class="fa fa-fw fa-file-excel-o"></i> 엑셀다운로드
</button>
<button id="btnSearch1" class="btn btn-sm btn-outline-light" type="button">조회</button>
</div>
`);
// 이벤트 바인딩
$('#sdate-dt, #edate-dt').on('change', function () {
table1.ajax.reload();
});
$('#btnSearch1').on('click', function () {
table1.ajax.reload();
});
$('#excel-download-dt').on('click', function () {
$.ajax({
url: "/article/processible/excel",
method: "GET",
data: { sdate: $("#sdate-dt").val(), edate: $("#edate-dt").val() },
beforeSend: function () { blockUI.blockPage({ message: tpl }) },
complete: function () { blockUI.unblockPage() },
success: function (result) { downloadExcelWithHeader(result.data); }
});
});
},
ajax: {
url: '/article/processible/getList1',
type: 'GET',
@@ -228,11 +263,15 @@
blockUI.unblockPage()
},
data: function (d) {
d.sdate = $("#sdate").val(); // 시작일
d.edate = $("#edate").val(); // 종료일
const fmt = d2 => d2.toISOString().slice(0, 10);
const lastDate = getLastDateOfMonth(date.getFullYear(), date.getMonth() + 1);
d.start = d.start || 0
d.length = d.length || 10
// input이 DOM에 있으면 그 값, 없으면 오늘/말일 기본값
d.sdate = $("#sdate-dt").val() || fmt(date);
d.edate = $("#edate-dt").val() || fmt(lastDate);
d.start = d.start || 0;
d.length = d.length || 10;
},
},
"columnDefs": [
@@ -357,10 +396,10 @@
const fmt = d => d.toISOString().slice(0, 10);
$('#sdate').val(fmt(date));
$('#sdate-dt').val(fmt(date));
const lastDate = getLastDateOfMonth(date.getFullYear(), date.getMonth() + 1);
$('#edate').val(fmt(lastDate));
$('#edate-dt').val(fmt(lastDate));
$('#sdate2').val(fmt(date));
}

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,12 @@ $usr_nm = session('usr_nm');
text-align: center;
}
/* 테이블 헤더 좌우 여백 조정 */
#resultList thead th {
padding-left: 4px !important;
padding-right: 4px !important;
}
td {
text-align: center;
}
@@ -66,19 +72,47 @@ $usr_nm = session('usr_nm');
overflow-x: auto !important;
}
/* 테이블 폭: 내용 기준으로 커지되, 최소는 100% */
/* 테이블 폭: 내용에 따라 자동 조정, 스크롤 필요시 표시 */
.table-responsive #resultList {
width: max-content !important;
min-width: 100% !important;
width: 100% !important;
table-layout: auto !important;
}
/* 줄바꿈 금지 */
/* 줄바꿈 허용 - 공백 기준으로 줄바꿈 */
#resultList th,
#resultList td {
white-space: nowrap;
white-space: normal !important;
word-wrap: break-word;
}
/* tw-* 클래스가 DataTable의 inline 스타일보다 우선순위 높이기 */
#resultList th.tw-30,
#resultList td.tw-30 { width: 30px !important; max-width: 30px !important; min-width: 30px !important; }
#resultList th.tw-50,
#resultList td.tw-50 { width: 50px !important; max-width: 50px !important; min-width: 50px !important; }
#resultList th.tw-70,
#resultList td.tw-70 { width: 70px !important; max-width: 70px !important; min-width: 70px !important; }
#resultList th.tw-80,
#resultList td.tw-80 { width: 80px !important; max-width: 80px !important; min-width: 80px !important; }
#resultList th.tw-90,
#resultList td.tw-90 { width: 90px !important; max-width: 90px !important; min-width: 90px !important; }
#resultList th.tw-100,
#resultList td.tw-100 { width: 100px !important; max-width: 100px !important; min-width: 100px !important; }
#resultList th.tw-120,
#resultList td.tw-120 { width: 120px !important; max-width: 120px !important; min-width: 120px !important; }
#resultList th.tw-130,
#resultList td.tw-130 { width: 130px !important; max-width: 130px !important; min-width: 130px !important; }
#resultList th.tw-140,
#resultList td.tw-140 { width: 140px !important; max-width: 140px !important; min-width: 140px !important; }
#resultList th.tw-150,
#resultList td.tw-150 { width: 150px !important; max-width: 150px !important; min-width: 150px !important; }
#resultList th.tw-180,
#resultList td.tw-180 { width: 180px !important; max-width: 180px !important; min-width: 180px !important; }
#resultList th.tw-200,
#resultList td.tw-200 { width: 200px !important; max-width: 200px !important; min-width: 200px !important; }
#resultList th.tw-250,
#resultList td.tw-250 { width: 250px !important; max-width: 250px !important; min-width: 250px !important; }
/* PC에서 가로 스크롤이 잘리는 대표 구간들 강제 해제 */
.main-card,
.card,
@@ -101,6 +135,16 @@ $usr_nm = session('usr_nm');
.card-body {
min-width: 0;
}
/* 검색 영역 행마다 구분선 추가 */
#frm_srch_info .row.g-3 {
padding: 12px 0;
border-bottom: 1px solid #e9ecef;
}
#frm_srch_info .row.g-3:last-child {
border-bottom: none;
}
</style>
<div class="col-md-12 col-xl-12">
@@ -181,10 +225,8 @@ $usr_nm = session('usr_nm');
<select name="rcpt_stat1" class="form-select form-select-sm">
<option value="">선택</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "STEP_VERIFICATION"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['STEP_VERIFICATION']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
<!-- <select name="rcpt_stat2" id="srcGugun" class="form-select form-select-sm">
@@ -200,10 +242,8 @@ $usr_nm = session('usr_nm');
<label class="form-label mb-1">거래구분</label>
<select class="form-select form-select-sm" name="rcpt_product_info1">
<option value="">전체</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "NHN_DEAL_TYPE"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['NHN_DEAL_TYPE']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cdNm ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -239,10 +279,8 @@ $usr_nm = session('usr_nm');
<label class="form-label mb-1">CP ID</label>
<select class="form-select form-select-sm" name="rcpt_cpid">
<option value="">전체</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "CP_ID"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['CP_ID']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -251,10 +289,8 @@ $usr_nm = session('usr_nm');
<label class="form-label mb-1">매물종류</label>
<select class="form-select form-select-sm" name="rcpt_product">
<option value="">전체</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "ARTICLE_TYPE"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['ARTICLE_TYPE']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -408,13 +444,12 @@ $usr_nm = session('usr_nm');
const bonbuArr = <?= json_encode($bonbu, JSON_UNESCAPED_UNICODE); ?>;
const teamArr = <?= json_encode($team, JSON_UNESCAPED_UNICODE); ?>;
const userArr = <?= json_encode($user, JSON_UNESCAPED_UNICODE); ?>;
<?php if (isset($srchUser) && !empty($srchUser)): ?>
const srchUser = <?= json_encode($srchUser, JSON_UNESCAPED_UNICODE); ?>;
<?php else: ?>
const srchUser = null;
<?php endif; ?>
const srchUser = <?= json_encode((!empty($srchUser) ? $srchUser : null), JSON_UNESCAPED_UNICODE); ?>;
const sBonbu = "<?= $sBonbu ?? '' ?>";
const sTeam = "<?= $sTeanm ?? '' ?>";
const sTeam = "<?= $sTeam ?? '' ?>";
const userColumns = <?= ($usr_level != '45')
? "[{ data: 'dept_nm' }, { data: 'usr_nm' }]"
: "[]" ?>;
const date = new Date();
var table;
@@ -592,6 +627,7 @@ $usr_nm = session('usr_nm');
language: lang_kor,
serverSide: true,
processing: true,
autoWidth: false,
ajax: {
url: '/article/receipt/getResultList',
type: 'GET',
@@ -650,27 +686,23 @@ $usr_nm = session('usr_nm');
columns: [
{ data: 'rcpt_stat_nm' },
{ data: 'rcpt_atclno' },
{ data: 'insert_tm' },
{ data: 'insert_tm', className: 'tw-80' },
{ data: null, render: fn_rsrv_render },
{ data: 'rsrv_tm_hour' },
{ data: 'photo_save_dt' },
{ data: 'rcpt_cpid' },
{ data: null, render: fn_agent_render },
{ data: null, render: fn_agent_render , className: 'tw-150' },
{ data: null, render: fn_addr_render },
{ data: null, render: fn_prd_render },
{ data: 'rcpt_product_info1' },
<?php if ($usr_level != "45"): ?>
{ data: 'dept_nm' },
{ data: 'usr_nm' },
<?php endif; ?>
{ data: 'rcpt_product_info1' }
].concat(userColumns, [
{ data: 'parcel_out_yn' },
{ data: 'conf_img_yn' },
{ data: 'exp_movie_yn' },
{ data: 'ground_plan_yn' },
{ data: 'ground_plan' },
{ data: 'exp_spc_yn' },
],
{ data: 'exp_spc_yn' }
]),
// 옵션들 예시
destroy: true,
deferRender: true,
@@ -689,7 +721,7 @@ $usr_nm = session('usr_nm');
if (!rowData) return;
const rcpt_atclno = rowData.rcpt_atclno;
window.open("<?= site_url('article/receipt/detail') ?>/" + rcpt_atclno, '_blank');
window.location.href = "<?= site_url('article/receipt/detail') ?>/" + rcpt_atclno;
});
@@ -835,14 +867,16 @@ $usr_nm = session('usr_nm');
function fn_addr_render(data, type, row) {
var str = "";
var addr = row.addr || '';
var lineBreak = addr ? "<br/>" : "";
if (row.rcpt_jibun_addr == null || row.rcpt_jibun_addr == "") {
str = row.addr + "<br/>" + row.rcpt_hscp_nm + " " + row.rcpt_dtl_addr + " " + row.rcpt_ho;
str = addr + lineBreak + (row.rcpt_hscp_nm || '') + " " + (row.rcpt_dtl_addr || '') + " " + (row.rcpt_ho || '');
} else {
str = row.addr + "<br/>" + row.rcpt_hscp_nm + " " + row.rcpt_li_addr + " " + row.rcpt_jibun_addr + " " + row.rcpt_etc_addr;
str = addr + lineBreak + (row.rcpt_hscp_nm || '') + " " + (row.rcpt_li_addr || '') + " " + (row.rcpt_jibun_addr || '') + " " + (row.rcpt_etc_addr || '');
}
return str;
return str.trim();
}
function fn_prd_render(data, type, row) {

View File

@@ -9,105 +9,154 @@ $usr_nm = session('usr_nm');
<?= $this->section('content') ?>
<style>
th {
font-size: 11px;
text-align: center;
}
th {
font-size: 11px;
text-align: center;
}
td {
text-align: center;
}
/* 테이블 헤더 좌우 여백 조정 */
#resultList thead th {
padding-left: 4px !important;
padding-right: 4px !important;
}
#resultList tbody tr {
cursor: pointer;
}
td {
text-align: center;
}
.blockUI {
z-index: 1500 !important;
}
#resultList tbody tr {
cursor: pointer;
}
.ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
}
.blockUI {
z-index: 1500 !important;
}
.card-header {
display: flex !important;
align-items: center;
}
.ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
}
.card-header-tab {
justify-content: flex-start !important;
}
.card-header {
display: flex !important;
align-items: center;
}
.table-scroll {
max-height: 300px;
overflow-y: scroll;
}
.card-header-tab {
justify-content: flex-start !important;
}
.swal2-cancel {
background-color: #ff0000 !important;
color: #fff !important;
}
.table-scroll {
max-height: 300px;
overflow-y: scroll;
}
#resultList.dataTable {
width: max-content !important;
}
.swal2-cancel {
background-color: #ff0000 !important;
color: #fff !important;
}
table.dataTable td,
table.dataTable th {
white-space: nowrap;
}
#resultList.dataTable {
width: max-content !important;
}
/* 테이블이 내용만큼 커지고, wrapper가 스크롤 담당 */
.table-responsive {
overflow-x: auto !important;
}
table.dataTable td,
table.dataTable th {
white-space: nowrap;
}
/* 테이블 폭: 내용 기준으로 커지, 최소는 100% */
.table-responsive #resultList {
width: max-content !important;
min-width: 100% !important;
table-layout: auto !important;
}
/* 테이블이 내용만큼 커지, wrapper가 스크롤 담당 */
.table-responsive {
overflow-x: auto !important;
}
/* 줄바꿈 금지 */
#resultList th,
#resultList td {
white-space: nowrap;
}
/* 테이블 폭: 내용에 따라 자동 조정, 스크롤 필요시 표시 */
.table-responsive #resultList {
width: 100% !important;
table-layout: auto !important;
}
/* PC에서 가로 스크롤이 잘리는 대표 구간들 강제 해제 */
.main-card,
.card,
.card-body {
overflow-x: visible !important;
}
/* 줄바꿈 허용 - 공백 기준으로 줄바꿈 */
#resultList th,
#resultList td {
white-space: normal !important;
word-wrap: break-word;
}
/* 만약 레이아웃 wrapper가 숨기고 있으면 이것도 */
.app-main__outer,
.app-main__inner,
.app-main {
overflow-x: visible !important;
}
/* tw-* 클래스가 DataTable의 inline 스타일보다 우선순위 높이기 */
#resultList th.tw-30,
#resultList td.tw-30 { width: 30px !important; max-width: 30px !important; min-width: 30px !important; }
#resultList th.tw-50,
#resultList td.tw-50 { width: 50px !important; max-width: 50px !important; min-width: 50px !important; }
#resultList th.tw-70,
#resultList td.tw-70 { width: 70px !important; max-width: 70px !important; min-width: 70px !important; }
#resultList th.tw-80,
#resultList td.tw-80 { width: 80px !important; max-width: 80px !important; min-width: 80px !important; }
#resultList th.tw-90,
#resultList td.tw-90 { width: 90px !important; max-width: 90px !important; min-width: 90px !important; }
#resultList th.tw-100,
#resultList td.tw-100 { width: 100px !important; max-width: 100px !important; min-width: 100px !important; }
#resultList th.tw-120,
#resultList td.tw-120 { width: 120px !important; max-width: 120px !important; min-width: 120px !important; }
#resultList th.tw-130,
#resultList td.tw-130 { width: 130px !important; max-width: 130px !important; min-width: 130px !important; }
#resultList th.tw-140,
#resultList td.tw-140 { width: 140px !important; max-width: 140px !important; min-width: 140px !important; }
#resultList th.tw-150,
#resultList td.tw-150 { width: 150px !important; max-width: 150px !important; min-width: 150px !important; }
#resultList th.tw-180,
#resultList td.tw-180 { width: 180px !important; max-width: 180px !important; min-width: 180px !important; }
#resultList th.tw-200,
#resultList td.tw-200 { width: 200px !important; max-width: 200px !important; min-width: 200px !important; }
#resultList th.tw-250,
#resultList td.tw-250 { width: 250px !important; max-width: 250px !important; min-width: 250px !important; }
/* flex 환경에서 필수 */
.app-main,
.app-main__outer,
.app-main__inner,
.card,
.card-body {
min-width: 0;
}
/* PC에서 가로 스크롤이 잘리는 대표 구간들 강제 해제 */
.main-card,
.card,
.card-body {
overflow-x: visible !important;
}
/* 만약 레이아웃 wrapper가 숨기고 있으면 이것도 */
.app-main__outer,
.app-main__inner,
.app-main {
overflow-x: visible !important;
}
/* flex 환경에서 필수 */
.app-main,
.app-main__outer,
.app-main__inner,
.card,
.card-body {
min-width: 0;
}
/* 검색 영역 행마다 구분선 추가 */
#frm_srch_info .row.g-3 {
padding: 12px 0;
border-bottom: 1px solid #e9ecef;
}
#frm_srch_info .row.g-3:last-child {
border-bottom: none;
}
</style>
<h1>현장확인V2 매물 접수 현황</h1>
<div class="col-md-12 col-xl-12">
<div class="main-card mb-3 card">
<div class="card-body">
<div class="main-card mb-3 card">
<div class="card-header bg-white border-bottom shadow-sm">
<div class="d-flex flex-wrap align-items-center gap-3 card-header-tab">
<div>
<h4 class="mb-0 fw-bold text-dark">현장확인V2 매물 접수 현황</h4>
</div>
</div>
</div>
<div class="card-body">
<form id="frm_srch_info" method="get" onsubmit="return false;">
<!-- 검색 폼 -->
@@ -176,10 +225,8 @@ $usr_nm = session('usr_nm');
<div class="d-flex gap-1">
<select name="rcpt_stat1" class="form-select form-select-sm">
<option value="">예약확인지연</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "STEP_VERIFICATION"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['STEP_VERIFICATION']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
<select name="rcpt_stat2" id="srcGugun" class="form-select form-select-sm">
@@ -195,10 +242,8 @@ $usr_nm = session('usr_nm');
<label class="form-label mb-1">거래구분</label>
<select class="form-select" name="rcpt_product_info1">
<option value="">전체</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "NHN_DEAL_TYPE"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['NHN_DEAL_TYPE']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cdNm ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -234,10 +279,8 @@ $usr_nm = session('usr_nm');
<label class="form-label mb-1">CP ID</label>
<select class="form-select" name="rcpt_cpid">
<option value="">전체</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "CP_ID"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['CP_ID']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -246,10 +289,8 @@ $usr_nm = session('usr_nm');
<label class="form-label mb-1">매물종류</label>
<select class="form-select" name="rcpt_product">
<option value="">전체</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "ARTICLE_TYPE"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['ARTICLE_TYPE']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -420,6 +461,9 @@ $usr_nm = session('usr_nm');
const bonbuArr = <?= json_encode($bonbu, JSON_UNESCAPED_UNICODE); ?>;
const teamArr = <?= json_encode($team, JSON_UNESCAPED_UNICODE); ?>;
const userArr = <?= json_encode($user, JSON_UNESCAPED_UNICODE); ?>;
const userColumns = <?= ($usr_level != '45')
? "[{ data: 'dept_nm' }, { data: 'usr_nm' }]"
: "[]" ?>;
const date = new Date();
var table;
@@ -660,14 +704,11 @@ $usr_nm = session('usr_nm');
{ data: null, render: fn_agent_render },
{ data: null, render: fn_addr_render },
{ data: null, render: fn_prd_render },
{ data: 'rcpt_product_info1' },
<?php if ($usr_level != "45"): ?>
{ data: 'dept_nm' },
{ data: 'usr_nm' },
<?php endif; ?>
{ data: 'rcpt_product_info1' }
].concat(userColumns, [
{ data: 'parcel_out_yn' },
{ data: 'conf_img_yn' },
],
{ data: 'conf_img_yn' }
]),
// 옵션들 예시
destroy: true,
deferRender: true,

View File

@@ -2276,6 +2276,7 @@ $usr_level = session('usr_level');
}
}
}
// 가격수정 저장
function modifyPriceInfo() {
@@ -2675,9 +2676,10 @@ $usr_level = session('usr_level');
title: "정상 처리되었습니다.",
icon: "success",
draggable: true
})
location.reload();
}).then(() => {
// 녹취파일 체크박스 활성화 및 체크
$('#chk_record').prop('disabled', false).prop('checked', true);
});
} else {
Swal.fire({
title: result.msg,

View File

@@ -9,105 +9,154 @@ $usr_nm = session('usr_nm');
<?= $this->section('content') ?>
<style>
th {
font-size: 11px;
text-align: center;
}
th {
font-size: 11px;
text-align: center;
}
td {
text-align: center;
}
/* 테이블 헤더 좌우 여백 조정 */
#resultList thead th {
padding-left: 4px !important;
padding-right: 4px !important;
}
#resultList tbody tr {
cursor: pointer;
}
td {
text-align: center;
}
.blockUI {
z-index: 1500 !important;
}
#resultList tbody tr {
cursor: pointer;
}
.ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
}
.blockUI {
z-index: 1500 !important;
}
.card-header {
display: flex !important;
align-items: center;
}
.ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 180px;
}
.card-header-tab {
justify-content: flex-start !important;
}
.card-header {
display: flex !important;
align-items: center;
}
.table-scroll {
max-height: 300px;
overflow-y: scroll;
}
.card-header-tab {
justify-content: flex-start !important;
}
.swal2-cancel {
background-color: #ff0000 !important;
color: #fff !important;
}
.table-scroll {
max-height: 300px;
overflow-y: scroll;
}
#resultList.dataTable {
width: max-content !important;
}
.swal2-cancel {
background-color: #ff0000 !important;
color: #fff !important;
}
table.dataTable td,
table.dataTable th {
white-space: nowrap;
}
#resultList.dataTable {
width: max-content !important;
}
/* 테이블이 내용만큼 커지고, wrapper가 스크롤 담당 */
.table-responsive {
overflow-x: auto !important;
}
table.dataTable td,
table.dataTable th {
white-space: nowrap;
}
/* 테이블 폭: 내용 기준으로 커지, 최소는 100% */
.table-responsive #resultList {
width: max-content !important;
min-width: 100% !important;
table-layout: auto !important;
}
/* 테이블이 내용만큼 커지, wrapper가 스크롤 담당 */
.table-responsive {
overflow-x: auto !important;
}
/* 줄바꿈 금지 */
#resultList th,
#resultList td {
white-space: nowrap;
}
/* 테이블 폭: 내용에 따라 자동 조정, 스크롤 필요시 표시 */
.table-responsive #resultList {
width: 100% !important;
table-layout: auto !important;
}
/* PC에서 가로 스크롤이 잘리는 대표 구간들 강제 해제 */
.main-card,
.card,
.card-body {
overflow-x: visible !important;
}
/* 줄바꿈 허용 - 공백 기준으로 줄바꿈 */
#resultList th,
#resultList td {
white-space: normal !important;
word-wrap: break-word;
}
/* 만약 레이아웃 wrapper가 숨기고 있으면 이것도 */
.app-main__outer,
.app-main__inner,
.app-main {
overflow-x: visible !important;
}
/* tw-* 클래스가 DataTable의 inline 스타일보다 우선순위 높이기 */
#resultList th.tw-30,
#resultList td.tw-30 { width: 30px !important; max-width: 30px !important; min-width: 30px !important; }
#resultList th.tw-50,
#resultList td.tw-50 { width: 50px !important; max-width: 50px !important; min-width: 50px !important; }
#resultList th.tw-70,
#resultList td.tw-70 { width: 70px !important; max-width: 70px !important; min-width: 70px !important; }
#resultList th.tw-80,
#resultList td.tw-80 { width: 80px !important; max-width: 80px !important; min-width: 80px !important; }
#resultList th.tw-90,
#resultList td.tw-90 { width: 90px !important; max-width: 90px !important; min-width: 90px !important; }
#resultList th.tw-100,
#resultList td.tw-100 { width: 100px !important; max-width: 100px !important; min-width: 100px !important; }
#resultList th.tw-120,
#resultList td.tw-120 { width: 120px !important; max-width: 120px !important; min-width: 120px !important; }
#resultList th.tw-130,
#resultList td.tw-130 { width: 130px !important; max-width: 130px !important; min-width: 130px !important; }
#resultList th.tw-140,
#resultList td.tw-140 { width: 140px !important; max-width: 140px !important; min-width: 140px !important; }
#resultList th.tw-150,
#resultList td.tw-150 { width: 150px !important; max-width: 150px !important; min-width: 150px !important; }
#resultList th.tw-180,
#resultList td.tw-180 { width: 180px !important; max-width: 180px !important; min-width: 180px !important; }
#resultList th.tw-200,
#resultList td.tw-200 { width: 200px !important; max-width: 200px !important; min-width: 200px !important; }
#resultList th.tw-250,
#resultList td.tw-250 { width: 250px !important; max-width: 250px !important; min-width: 250px !important; }
/* flex 환경에서 필수 */
.app-main,
.app-main__outer,
.app-main__inner,
.card,
.card-body {
min-width: 0;
}
/* PC에서 가로 스크롤이 잘리는 대표 구간들 강제 해제 */
.main-card,
.card,
.card-body {
overflow-x: visible !important;
}
/* 만약 레이아웃 wrapper가 숨기고 있으면 이것도 */
.app-main__outer,
.app-main__inner,
.app-main {
overflow-x: visible !important;
}
/* flex 환경에서 필수 */
.app-main,
.app-main__outer,
.app-main__inner,
.card,
.card-body {
min-width: 0;
}
/* 검색 영역 행마다 구분선 추가 */
#frm_srch_info .row.g-3 {
padding: 12px 0;
border-bottom: 1px solid #e9ecef;
}
#frm_srch_info .row.g-3:last-child {
border-bottom: none;
}
</style>
<h1>녹취매물 내역</h1>
<div class="col-md-12 col-xl-12">
<div class="main-card mb-3 card">
<div class="card-body">
<div class="main-card mb-3 card">
<div class="card-header bg-white border-bottom shadow-sm">
<div class="d-flex flex-wrap align-items-center gap-3 card-header-tab">
<div>
<h4 class="mb-0 fw-bold text-dark">녹취매물 내역</h4>
</div>
</div>
</div>
<div class="card-body">
<form id="frm_srch_info" method="get" onsubmit="return false;">
<!-- 검색 폼 -->
@@ -227,16 +276,15 @@ $usr_nm = session('usr_nm');
</div>
<div class="main-card mb-3 card">
<div class="card-header d-flex align-items-center">
<div class="d-flex align-items-center flex-wrap" style="gap: 8px; flex: 1">
</div>
<div class="ml-auto">
<button class="btn btn-sm btn-outline-success" id="excel-download">
<i class="fa fa-fw" aria-hidden="true" title="file-excel-o"></i>
엑셀다운로드
</button>
<div class="card-header bg-white border-bottom shadow-sm">
<div class="d-flex flex-wrap align-items-center w-100 justify-content-between card-header-tab">
<h5 class="mb-0 fw-bold text-dark">검색 결과</h5>
<div class="d-flex align-items-center gap-2 ms-auto">
<button class="btn btn-sm btn-outline-success" id="excel-download">
<i class="fa fa-fw" aria-hidden="true" title="file-excel-o"></i>
엑셀다운로드
</button>
</div>
</div>
</div>
<div class="card-body">

View File

@@ -69,6 +69,15 @@
<div class="h-100 bg-plum-plate bg-animation">
<div class="d-flex h-100 justify-content-center align-items-center py-4">
<div class="mx-auto col-sm-10 col-md-8 col-lg-6 col-xl-5">
<!-- Redis 장애 경고 영역 -->
<div id="redis-warning" class="alert alert-warning alert-dismissible fade d-none mb-3"
style="border-radius: 16px; background: rgba(255, 193, 7, 0.95); backdrop-filter: blur(10px); box-shadow: 0 4px 12px rgba(0,0,0,0.15);">
<strong><i class="fa fa-exclamation-triangle me-2"></i>시스템 알림</strong>
<p id="redis-warning-message" class="mb-0 mt-2"></p>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<div class="card border-0"
style="border-radius: 24px; background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(20px); box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);">
<div class="card-body p-5">
@@ -202,6 +211,27 @@
const tpl = document.querySelector('.my-loader-template');
var table;
// Redis 장애 경고 표시 함수
function showRedisWarning(message) {
const warningDiv = $('#redis-warning');
const messageEl = $('#redis-warning-message');
messageEl.text(message);
warningDiv.removeClass('d-none').addClass('show');
// 10초 후 자동 숨김 (선택사항)
setTimeout(function() {
warningDiv.fadeOut();
}, 15000);
}
// 시스템 상태 체크 함수
function checkSystemStatus(responseData) {
if (responseData.system && responseData.system.redis_fallback) {
showRedisWarning(responseData.system.warning_message);
}
}
$(function () {
$("#btnSearch").on("click", function () {
@@ -231,6 +261,9 @@
console.log(xhr.responseText);
},
success: function (result) {
// 시스템 상태 체크 (Redis 장애 여부)
checkSystemStatus(result);
if (result.code === "0") {
location.href = '/'
} else {

View File

@@ -4,6 +4,17 @@
<style>
th {
font-size: 11px;
text-align: center;
}
/* 테이블 헤더 좌우 여백 조정 */
#userList thead th {
padding-left: 4px !important;
padding-right: 4px !important;
}
td {
text-align: center;
}
#userList tbody tr {
@@ -18,161 +29,198 @@
background-color: #ff0000 !important;
color: #fff !important;
}
/* PC에서 가로 스크롤이 잘리는 대표 구간들 강제 해제 */
.main-card,
.card,
.card-body {
overflow-x: visible !important;
}
/* 만약 레이아웃 wrapper가 숨기고 있으면 이것도 */
.app-main__outer,
.app-main__inner,
.app-main {
overflow-x: visible !important;
}
/* flex 환경에서 필수 */
.app-main,
.app-main__outer,
.app-main__inner,
.card,
.card-body {
min-width: 0;
}
/* 검색 영역 행마다 구분선 추가 */
#frm_srch_info .row.g-3 {
padding: 12px 0;
border-bottom: 1px solid #e9ecef;
}
#frm_srch_info .row.g-3:last-child {
border-bottom: none;
}
</style>
<h1>현장확인인원별배정현황</h1>
<div class="row">
<div class="col-md-12 col-xl-12">
<div class="main-card mb-3 card">
<div class="card-body">
<form class="row align-items-end" id="frm_srch_info" onsubmit="return false;">
<!-- 1줄 -->
<div class="row mb-3">
<!-- 관할조직 -->
<div class="col-md-2">
<label class="form-label mb-1">관할조직</label>
<div class="row g-2">
<div class="col-6">
<select class="form-control" name="bonbu" id="bonbu">
<option value="">선택</option>
<?php foreach ($bonbu as $d): ?>
<option value="<?= $d['dept_sq'] ?>"><?= $d['dept_nm'] ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-6">
<select class="form-control" name="dept_sq" id="dept_sq">
<option value="">선택</option>
</select>
</div>
</div>
</div>
<!-- 유형 -->
<div class="col-md-1">
<label class="form-label mb-1">유형</label>
<select class="form-control" name="schDateGb">
<option value="1">예약일자</option>
<option value="2">등록일자</option>
</select>
</div>
<!-- 기준일자 -->
<div class="col-md-3">
<label class="form-label mb-1">기준일자</label>
<div class="row g-2">
<div class="col-5">
<input type="date" class="form-control" id="sdate" name="sdate">
</div>
<div class="col-2 d-flex align-items-center justify-content-center">~</div>
<div class="col-5">
<input type="date" class="form-control" id="edate" name="edate">
</div>
</div>
</div>
<!-- 지역검색 -->
<div class="col-md-4">
<label class="form-label mb-1">지역검색</label>
<div class="row g-2">
<div class="col-4">
<select class="form-control" name="srcSido" id="srcSido">
<option value="">시/도</option>
<?php foreach ($sido as $s): ?>
<option value="<?= $s['region_cd'] ?>"><?= $s['region_nm'] ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-4">
<select class="form-control" name="srcGugun" id="srcGugun">
<option value="">시/군/구</option>
</select>
</div>
<div class="col-4">
<select class="form-control" name="srcDong" id="srcDong">
<option value="">읍/면/동</option>
</select>
</div>
</div>
</div>
</div>
<!-- 2줄 -->
<div class="row mb-3">
<!-- 검색유형 -->
<div class="col-md-1">
<label class="form-label mb-1">검색유형</label>
<select class="form-control" name="srchType">
<option value="">선택</option>
<option value="1">아이디</option>
<option value="2">이름</option>
</select>
</div>
<!-- 검색어 -->
<div class="col-md-2">
<label class="form-label mb-1">검색어</label>
<input type="text" class="form-control" name="srchTxt" placeholder="검색어 입력">
</div>
<!-- 검색버튼 -->
<div class="col-md-1 d-grid align-items-end">
<button type="button" class="btn btn-primary" id="btnSearch">검색</button>
</div>
</div>
</form>
<div class="col-md-12 col-xl-12">
<div class="main-card mb-3 card">
<div class="card-header bg-white border-bottom shadow-sm">
<div class="d-flex flex-wrap align-items-center gap-3 card-header-tab">
<div>
<h4 class="mb-0 fw-bold text-dark">현장확인인원별배정현황</h4>
</div>
</div>
</div>
<div class="card-body">
<form id="frm_srch_info" onsubmit="return false;">
<!-- 1줄 -->
<div class="row g-3 mb-3">
<!-- 관할조직 -->
<div class="col-md-2">
<label class="form-label mb-1 text-sm fw-semibold text-muted">관할조직</label>
<div class="row g-2">
<div class="col-6">
<select class="form-control form-control-sm" name="bonbu" id="bonbu">
<option value="">선택</option>
<?php foreach ($bonbu as $d): ?>
<option value="<?= $d['dept_sq'] ?>"><?= $d['dept_nm'] ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-6">
<select class="form-control form-control-sm" name="dept_sq" id="dept_sq">
<option value="">선택</option>
</select>
</div>
</div>
</div>
<!-- 유형 -->
<div class="col-md-1">
<label class="form-label mb-1 text-sm fw-semibold text-muted">유형</label>
<select class="form-control form-control-sm" name="schDateGb">
<option value="1">예약일자</option>
<option value="2">등록일자</option>
</select>
</div>
<!-- 기준일자 -->
<div class="col-md-3">
<label class="form-label mb-1 text-sm fw-semibold text-muted">기준일자</label>
<div class="row g-2">
<div class="col-5">
<input type="date" class="form-control form-control-sm" id="sdate" name="sdate">
</div>
<div class="col-2 d-flex align-items-center justify-content-center">~</div>
<div class="col-5">
<input type="date" class="form-control form-control-sm" id="edate" name="edate">
</div>
</div>
</div>
<!-- 지역검색 -->
<div class="col-md-4">
<label class="form-label mb-1 text-sm fw-semibold text-muted">지역검색</label>
<div class="row g-2">
<div class="col-4">
<select class="form-control form-control-sm" name="srcSido" id="srcSido">
<option value="">시/도</option>
<?php foreach ($sido as $s): ?>
<option value="<?= $s['region_cd'] ?>"><?= $s['region_nm'] ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-4">
<select class="form-control form-control-sm" name="srcGugun" id="srcGugun">
<option value="">시/군/구</option>
</select>
</div>
<div class="col-4">
<select class="form-control form-control-sm" name="srcDong" id="srcDong">
<option value="">읍/면/동</option>
</select>
</div>
</div>
</div>
</div>
<!-- 2줄 -->
<div class="row g-3">
<!-- 검색유형 -->
<div class="col-md-1">
<label class="form-label mb-1 text-sm fw-semibold text-muted">검색유형</label>
<select class="form-control form-control-sm" name="srchType">
<option value="">선택</option>
<option value="1">아이디</option>
<option value="2">이름</option>
</select>
</div>
<!-- 검색어 -->
<div class="col-md-2">
<label class="form-label mb-1 text-sm fw-semibold text-muted">검색어</label>
<input type="text" class="form-control form-control-sm" name="srchTxt" placeholder="검색어 입력">
</div>
<!-- 검색버튼 -->
<div class="col-md-1 d-flex align-items-end">
<button type="button" class="btn btn-primary btn-sm w-100" id="btnSearch">검색</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-12 col-xl-12">
<div class="main-card mb-3 card">
<div class="card-header d-flex align-items-center">
<h3 class="card-title mb-0">사용자 목록</h3>
<div class="ms-auto d-flex align-items-center gap-3">
<button class="mb-2 me-2 border-0 btn-transition btn btn-shadow btn-outline-success"
id="excel-download">
<i class="fa fa-fw" aria-hidden="true" title="file-excel-o"></i>엑셀다운로드
<div class="col-md-12 col-xl-12">
<div class="main-card mb-3 card">
<div class="card-header bg-white border-bottom shadow-sm">
<div class="d-flex flex-wrap align-items-center w-100 justify-content-between card-header-tab">
<h4 class="mb-0 fw-bold text-dark">사용자 목록</h4>
<div class="d-flex align-items-center gap-2 ms-auto">
<button class="btn btn-sm btn-outline-success" id="excel-download">
<i class="fa fa-fw" aria-hidden="true" title="file-excel-o"></i>
엑셀다운로드
</button>
</div>
</div>
<div class="card-body">
<table id="userList" class="table table-hover table-striped table-bordered">
<thead>
<tr>
<th rowspan="2">순번</th>
<th rowspan="2">아이디</th>
<th rowspan="2">이름</th>
<th colspan="5" style="text-align: center;">오전</th>
<th colspan="8" style="text-align: center;">오</th>
<th rowspan="2">합계</th>
</tr>
<tr>
<th width="50">무관</th>
<th width="50">9</th>
<th width="50">10</th>
<th width="50">11</th>
<th width="50">12</th>
</div>
<div class="card-body">
<table id="userList" class="table table-hover table-striped table-bordered">
<thead>
<tr>
<th rowspan="2">순번</th>
<th rowspan="2">아이디</th>
<th rowspan="2">이름</th>
<th colspan="5" style="text-align: center;">오</th>
<th colspan="8" style="text-align: center;">오후</th>
<th rowspan="2">합계</th>
</tr>
<tr>
<th width="50">무관</th>
<th width="50">9</th>
<th width="50">10</th>
<th width="50">11</th>
<th width="50">12</th>
<th width="50">무관</th>
<th width="50">1</th>
<th width="50">2</th>
<th width="50">3</th>
<th width="50">4</th>
<th width="50">5</th>
<th width="50">6</th>
<th width="50">7</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<th width="50">무관</th>
<th width="50">1</th>
<th width="50">2</th>
<th width="50">3</th>
<th width="50">4</th>
<th width="50">5</th>
<th width="50">6</th>
<th width="50">7</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
@@ -269,7 +317,8 @@
let table = $('#userList').DataTable({
language: lang_kor,
processing: true,
serverSide: false,
serverSide: true,
pageLength: 25,
ajax: {
url: '/results/assign/getUserList',
type: 'GET',
@@ -291,20 +340,17 @@
d.srchType = $("#frm_srch_info [name=srchType]").val()
d.srchTxt = $("#frm_srch_info [name=srchTxt]").val()
d.start = d.start || 0
d.length = d.length || 10
}
},
"columnDefs": [
{ 'targets': '_all', "defaultContent": "" },
{ 'className': 'text-center', 'targets': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] },
columnDefs: [
{ targets: '_all', defaultContent: "" },
{ className: 'text-center', targets: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] },
],
columns: [
{
"data": null,
"width": "50px",
"render": function (data, type, row, meta) {
data: null,
width: "50px",
render: function (data, type, row, meta) {
return meta.row + meta.settings._iDisplayStart + 1;
}
},
@@ -325,11 +371,9 @@
{ data: 'PM07' },
{ data: 'TODAY' },
],
// 옵션들 예시
paging: true,
searching: false,
ordering: false,
serverSide: true,
});
@@ -531,4 +575,4 @@
});
</script>
<?= $this->endSection() ?>
<?= $this->endSection() ?>

View File

@@ -0,0 +1,534 @@
<?= $this->extend('layouts/main') ?>
<?= $this->section('content') ?>
<style>
th {
font-size: 11px;
}
#userList tbody tr {
cursor: pointer;
}
.blockUI {
z-index: 1500 !important;
}
.swal2-cancel {
background-color: #ff0000 !important;
color: #fff !important;
}
</style>
<h1>현장확인인원별배정현황</h1>
<div class="row">
<div class="col-md-12 col-xl-12">
<div class="main-card mb-3 card">
<div class="card-body">
<form class="row align-items-end" id="frm_srch_info" onsubmit="return false;">
<!-- 1줄 -->
<div class="row mb-3">
<!-- 관할조직 -->
<div class="col-md-2">
<label class="form-label mb-1">관할조직</label>
<div class="row g-2">
<div class="col-6">
<select class="form-control" name="bonbu" id="bonbu">
<option value="">선택</option>
<?php foreach ($bonbu as $d): ?>
<option value="<?= $d['dept_sq'] ?>"><?= $d['dept_nm'] ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-6">
<select class="form-control" name="dept_sq" id="dept_sq">
<option value="">선택</option>
</select>
</div>
</div>
</div>
<!-- 유형 -->
<div class="col-md-1">
<label class="form-label mb-1">유형</label>
<select class="form-control" name="schDateGb">
<option value="1">예약일자</option>
<option value="2">등록일자</option>
</select>
</div>
<!-- 기준일자 -->
<div class="col-md-3">
<label class="form-label mb-1">기준일자</label>
<div class="row g-2">
<div class="col-5">
<input type="date" class="form-control" id="sdate" name="sdate">
</div>
<div class="col-2 d-flex align-items-center justify-content-center">~</div>
<div class="col-5">
<input type="date" class="form-control" id="edate" name="edate">
</div>
</div>
</div>
<!-- 지역검색 -->
<div class="col-md-4">
<label class="form-label mb-1">지역검색</label>
<div class="row g-2">
<div class="col-4">
<select class="form-control" name="srcSido" id="srcSido">
<option value="">시/도</option>
<?php foreach ($sido as $s): ?>
<option value="<?= $s['region_cd'] ?>"><?= $s['region_nm'] ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-4">
<select class="form-control" name="srcGugun" id="srcGugun">
<option value="">시/군/구</option>
</select>
</div>
<div class="col-4">
<select class="form-control" name="srcDong" id="srcDong">
<option value="">읍/면/동</option>
</select>
</div>
</div>
</div>
</div>
<!-- 2줄 -->
<div class="row mb-3">
<!-- 검색유형 -->
<div class="col-md-1">
<label class="form-label mb-1">검색유형</label>
<select class="form-control" name="srchType">
<option value="">선택</option>
<option value="1">아이디</option>
<option value="2">이름</option>
</select>
</div>
<!-- 검색어 -->
<div class="col-md-2">
<label class="form-label mb-1">검색어</label>
<input type="text" class="form-control" name="srchTxt" placeholder="검색어 입력">
</div>
<!-- 검색버튼 -->
<div class="col-md-1 d-grid align-items-end">
<button type="button" class="btn btn-primary" id="btnSearch">검색</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-12 col-xl-12">
<div class="main-card mb-3 card">
<div class="card-header d-flex align-items-center">
<h3 class="card-title mb-0">사용자 목록</h3>
<div class="ms-auto d-flex align-items-center gap-3">
<button class="mb-2 me-2 border-0 btn-transition btn btn-shadow btn-outline-success"
id="excel-download">
<i class="fa fa-fw" aria-hidden="true" title="file-excel-o"></i>엑셀다운로드
</button>
</div>
</div>
<div class="card-body">
<table id="userList" class="table table-hover table-striped table-bordered">
<thead>
<tr>
<th rowspan="2">순번</th>
<th rowspan="2">아이디</th>
<th rowspan="2">이름</th>
<th colspan="5" style="text-align: center;">오전</th>
<th colspan="8" style="text-align: center;">오후</th>
<th rowspan="2">합계</th>
</tr>
<tr>
<th width="50">무관</th>
<th width="50">9</th>
<th width="50">10</th>
<th width="50">11</th>
<th width="50">12</th>
<th width="50">무관</th>
<th width="50">1</th>
<th width="50">2</th>
<th width="50">3</th>
<th width="50">4</th>
<th width="50">5</th>
<th width="50">6</th>
<th width="50">7</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css" />
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script defer src="/architectui/assets/js/datatable.kor.js"></script>
<script type="text/javascript">
// const tpl = document.querySelector('.my-loader-template');
var teamArr = <?= json_encode($team, JSON_UNESCAPED_UNICODE); ?>;
let date = new Date();
$(function () {
$("#bonbu").on("change", function (e) {
var dept_sq = e.target.value
$("#dept_sq").empty()
var str = "<option value=''>선택</option>"
if (teamArr != null) {
for (var i = 0; i < teamArr.length; i++) {
if (dept_sq === teamArr[i].pdept_sq) {
str += "<option value='" + teamArr[i].dept_sq + "'>" + teamArr[i].dept_nm + "</option>"
}
}
}
$("#dept_sq").append(str)
});
$("#srcSido, #srcGugun").on("change", function (e) {
const targetId = this.id;
$.ajax({
url: "/manage/areas/getAreaList",
method: "POST",
dataType: "json",
data: {
'srcSido': $("#frm_srch_info [name=srcSido]").val(),
'srcGugun': $("#frm_srch_info [name=srcGugun]").val(),
},
beforeSend: function () {
blockUI.blockPage({
message: tpl
})
},
complete: function () {
blockUI.unblockPage()
},
success: function (result) {
switch (targetId) {
case "srcSido":
$("#srcGugun").empty()
var str = "";
str += "<option value=''>시/군/구</option>";
if (result.length > 0) {
for (var i = 0; i < result.length; i++) {
str += "<option value='" + result[i]['region_cd'] + "'>" + result[i].region_nm + "</option>";
}
}
$("#srcGugun").append(str);
break;
case "srcGugun":
$("#srcDong").empty()
var str = "";
str += "<option value=''>읍/면/동</option>";
if (result.length > 0) {
for (var i = 0; i < result.length; i++) {
str += "<option value='" + result[i]['region_cd'] + "'>" + result[i].region_nm + "</option>";
}
}
$("#srcDong").append(str);
break;
}
}
});
});
let table = $('#userList').DataTable({
language: lang_kor,
processing: true,
serverSide: false,
ajax: {
url: '/results/assign/getUserList',
type: 'GET',
beforeSend: function () {
blockUI.blockPage({
message: tpl
})
},
complete: function () {
blockUI.unblockPage()
},
data: function (d) {
d.schDateGb = $("#frm_srch_info [name=schDateGb]").val()
d.sdate = $("#frm_srch_info [name=sdate]").val()
d.edate = $("#frm_srch_info [name=edate]").val()
d.bonbu = $("#frm_srch_info [name=bonbu]").val()
d.team = $("#frm_srch_info [name=dept_sq]").val()
d.srchType = $("#frm_srch_info [name=srchType]").val()
d.srchTxt = $("#frm_srch_info [name=srchTxt]").val()
d.start = d.start || 0
d.length = d.length || 10
}
},
"columnDefs": [
{ 'targets': '_all', "defaultContent": "" },
{ 'className': 'text-center', 'targets': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] },
],
columns: [
{
"data": null,
"width": "50px",
"render": function (data, type, row, meta) {
return meta.row + meta.settings._iDisplayStart + 1;
}
},
{ data: 'usr_id' },
{ data: 'usr_nm' },
{ data: 'AMETC' },
{ data: 'AM09' },
{ data: 'AM10' },
{ data: 'AM11' },
{ data: 'AM12' },
{ data: 'PMETC' },
{ data: 'PM01' },
{ data: 'PM02' },
{ data: 'PM03' },
{ data: 'PM04' },
{ data: 'PM05' },
{ data: 'PM06' },
{ data: 'PM07' },
{ data: 'TODAY' },
],
// 옵션들 예시
paging: true,
searching: false,
ordering: false,
serverSide: true,
});
$('#userList tbody').on('click', 'tr', function () {
const row = table.row(this).data()
if (!row) return
location.href = '/article/receipt/lists?usr_id=' + row.usr_id;
});
// [검색] 버튼 눌렀을 때 다시 조회
$('#btnSearch').on('click', function () {
table.ajax.reload()
});
// 유저 등록 모달
$("#addUser").on("click", function () {
// $("#frm_user_info")[0].reset()
// $("#frm_user_info [name=usr_sq]").val("")
// $("#frm_user_info [name=type]").val("create")
// $("#frm_user_info [name=addUserId]").prop("readonly", false)
// const modalEl = document.getElementById('userModal');
// const myModal = new bootstrap.Modal(modalEl);
// myModal.show();
});
// 엑셀다운 click
$("#excel-download").on("click", function () {
$.ajax({
url: "/results/assign/excel",
method: "GET",
dataType: "json",
data: $("#frm_srch_info").serialize(),
beforeSend: function () {
blockUI.blockPage({
message: tpl
})
},
complete: function () {
blockUI.unblockPage()
},
success: function (result) {
// downloadExcel(result.data);
downloadExcelWithHeader(result.data);
}
});
});
});
// 엑셀 다운로드
function downloadExcelWithHeader(dataRows) {
// 1) 헤더 (16컬럼)
const header1 = [
"아이디", "이름",
"오전", "", "", "", "",
"오후", "", "", "", "", "", "", "",
"합계"
];
const header2 = [
"", "",
"무관", "9", "10", "11", "12",
"무관", "1", "2", "3", "4", "5", "6", "7",
""
];
// 2) 바디 (16컬럼 정확히 맞춤)
const body = dataRows.map(r => ([
r.usr_id,
r.usr_nm,
r.AMETC, r.AM09, r.AM10, r.AM11, r.AM12,
r.PMETC, r.PM01, r.PM02, r.PM03, r.PM04, r.PM05, r.PM06, r.PM07,
r.TODAY
]));
const aoa = [header1, header2, ...body];
// 3) 시트 생성
const ws = XLSX.utils.aoa_to_sheet(aoa);
// 4) 병합(colspan / rowspan)
ws["!merges"] = [
// 아이디, 이름 (rowspan 2)
{ s: { r: 0, c: 0 }, e: { r: 1, c: 0 } }, // A1:A2
{ s: { r: 0, c: 1 }, e: { r: 1, c: 1 } }, // B1:B2
// 오전 (colspan 5)
{ s: { r: 0, c: 2 }, e: { r: 0, c: 6 } }, // C1:G1
// 오후 (colspan 8)
{ s: { r: 0, c: 7 }, e: { r: 0, c: 14 } }, // H1:O1
// 합계 (rowspan 2)
{ s: { r: 0, c: 15 }, e: { r: 1, c: 15 } }, // P1:P2
];
ws['!cols'] = [
{ wpx: 80 }, // A: 아이디
{ wpx: 100 }, // B: 이름
{ wpx: 50 }, // C: 오전-무관
{ wpx: 50 }, // D: 오전-9
{ wpx: 50 }, // E: 오전-10
{ wpx: 50 }, // F: 오전-11
{ wpx: 50 }, // G: 오전-12
{ wpx: 50 }, // H: 오후-무관
{ wpx: 50 }, // I: 오후-1
{ wpx: 50 }, // J: 오후-2
{ wpx: 50 }, // K: 오후-3
{ wpx: 50 }, // L: 오후-4
{ wpx: 50 }, // M: 오후-5
{ wpx: 50 }, // N: 오후-6
{ wpx: 50 }, // O: 오후-7
{ wpx: 60 }, // P: 합계
];
// 6) 저장
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
XLSX.writeFile(
wb,
"현장확인인원별배정현황" + getDateTimeString() + ".xlsx"
);
}
// 엑셀 다운로드
function downloadExcel(data) {
const ws = XLSX.utils.json_to_sheet(data);
ws['!cols'] = [
{ wpx: 80 },
{ wpx: 80 },
{ wpx: 80 },
{ wpx: 80 },
{ wpx: 80 },
{ wpx: 80 },
{ wpx: 80 },
{ wpx: 80 },
{ wpx: 80 },
{ wpx: 80 },
{ wpx: 80 },
{ wpx: 80 },
{ wpx: 80 },
{ wpx: 80 },
{ wpx: 80 },
{ wpx: 80 },
];
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
const blob = new Blob([wbout], { type: 'application/octet-stream' });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = "현장확인인원별배정현황" + getDateTimeString() + ".xlsx";
link.click();
URL.revokeObjectURL(link.href);
}
function getDateTimeString() {
const d = new Date();
const yyyy = d.getFullYear();
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
const hh = String(d.getHours()).padStart(2, '0');
const mi = String(d.getMinutes()).padStart(2, '0');
const ss = String(d.getSeconds()).padStart(2, '0');
return `${yyyy}${mm}${dd}${hh}${mi}${ss}`;
}
window.addEventListener("DOMContentLoaded", function () {
// 오늘 날짜
const today = new Date();
// 이번 달 1일
const firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
// 이번 달 말일 (다음달 0일 = 이번달 말일)
const lastDay = new Date(today.getFullYear(), today.getMonth() + 1, 0);
// yyyy-mm-dd 형태로 변환
const toDate = (d) => d.toISOString().split("T")[0];
document.getElementById("sdate").value = toDate(firstDay);
document.getElementById("edate").value = toDate(lastDay);
});
</script>
<?= $this->endSection() ?>

View File

@@ -160,12 +160,10 @@ if (!empty($data['cert_register']) && $data['cert_register_save_yn'] != 'Y') { /
<select class="form-select form-select-sm" name="atcl_vrtc_way"
id="atcl_vrtc_way" disabled>
<option value="">-선택-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "VRFCREQ_WAY"): ?>
<option value="<?= $c['cd'] ?>" <?= ($c['cd'] === $data['vrfc_type_cd']) ? 'selected' : '' ?>>
<?= $c['cd_nm'] ?>
<?php foreach (($codes['VRFCREQ_WAY']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>" <?= ($cd === $data['vrfc_type_cd']) ? 'selected' : '' ?>>
<?= $cdNm ?>
</option>
<?php endif; ?>
<?php endforeach; ?>
</select>
<?= $data['update_res_tm'] ?>
@@ -597,11 +595,9 @@ if (!empty($data['cert_register']) && $data['cert_register_save_yn'] != 'Y') { /
<div class="main-card mb-2 card">
<div class="card-body ">
<h5 class="card-title">상태변경</h5>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "STEP_VERIFICATION"): ?>
<button class="mb-2 me-2 btn btn-light"
onclick="fn_status_change(<?= $data['vr_sq'] ?>, '<?= $c['cd'] ?>', '<?= $c['cd_nm'] ?>');"><?= $c['cd_nm'] ?></button>
<?php endif; ?>
<?php foreach (($codes['STEP_VERIFICATION']['items'] ?? []) as $cd => $cdNm): ?>
<button class="mb-2 me-2 btn btn-light"
onclick="fn_status_change(<?= $data['vr_sq'] ?>, '<?= $cd ?>', '<?= $cdNm ?>');"><?= $cdNm ?></button>
<?php endforeach; ?>
</div>
</div>
@@ -706,12 +702,10 @@ if (!empty($data['cert_register']) && $data['cert_register_save_yn'] != 'Y') { /
<select class="form-select" id="fax_conf_res_d11"
name="fax_conf_res_d11">
<option value="">-선택-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "CONFIRM_RESULT_D11"): ?>
<option value="<?= $c['cd'] ?>" <?= ($c['cd'] === $data['result_d11']) ? 'selected' : '' ?>>
<?= $c['cd_nm'] ?>
<?php foreach (($codes['CONFIRM_RESULT_D11']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>" <?= ($cd === $data['result_d11']) ? 'selected' : '' ?>>
<?= $cdNm ?>
</option>
<?php endif; ?>
<?php endforeach; ?>
</select>
</td>
@@ -735,12 +729,7 @@ if (!empty($data['cert_register']) && $data['cert_register_save_yn'] != 'Y') { /
];
$nCnt = 0;
$code_comment = [];
foreach ($codes as $c) {
if ($c['category'] === "CONSULTANT_COMMENT") {
array_push($code_comment, $c);
}
}
$code_comment = $codes['CONSULTANT_COMMENT']['items'] ?? [];
foreach ($code_comment as $key => $value) {
if ($nCnt % 2 == 0 && $nCnt != 0) {
@@ -760,7 +749,7 @@ if (!empty($data['cert_register']) && $data['cert_register_save_yn'] != 'Y') { /
id="comment_<?= $key ?>" disabled>
<label class="form-check-label ms-1 small"
for="price_ignore1">
<?= $value['cd_nm'] ?>
<?= $value ?>
</label>
</div>
</td>
@@ -883,12 +872,10 @@ if (!empty($data['cert_register']) && $data['cert_register_save_yn'] != 'Y') { /
<td>
<select class="form-select" name="tel_agree" id="tel_agree">
<option value="">-선택-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "CONFIRM_RESULT_T11"): ?>
<option value="<?= $c['cd'] ?>" <?php if ($c['cd'] === $data['tel_agree']) {
<?php foreach (($codes['CONFIRM_RESULT_T11']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>" <?php if ($cd === $data['tel_agree']) {
echo "selected";
} ?>><?= $c['cd_nm'] ?></option>
<?php endif; ?>
} ?>><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</td>
@@ -1031,7 +1018,7 @@ if (!empty($data['cert_register']) && $data['cert_register_save_yn'] != 'Y') { /
<tr>
<th>메모</th>
<td>
<div class="d-flex flex-column gap-1" style="">
<div class="d-flex flex-column gap-1">
<textarea class="form-control" name="memo_tel" id="memo_tel" rows="2"
style="resize: none;"><?= $memo['memo'] ?></textarea>
@@ -1045,14 +1032,11 @@ if (!empty($data['cert_register']) && $data['cert_register_save_yn'] != 'Y') { /
<th>통화실패사유</th>
<td class="d-flex gap-2">
<select class="form-select" name="tel_fail_cause" id="tel_fail_cause">
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "TEL_FAIL_CAUSE"): ?>
<option value="<?= $c['cd'] ?>" <?php if ($c['cd'] === $data['tel_fail_cause']) {
echo 'selected';
} ?>><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['TEL_FAIL_CAUSE']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>" <?php if ($cd === $data['tel_fail_cause']) {
echo 'selected';
} ?>><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</td>
</tr>

View File

@@ -38,10 +38,8 @@
<label class="form-label mb-1">현재상태</label>
<select name="stat_cd" class="form-select form-select-sm">
<option value="">-선택-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "STEP_VERIFICATION"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['STEP_VERIFICATION']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -134,11 +132,9 @@
<div class="d-flex gap-2">
<select name="vrfcreq_way" id="vrfcreq_way" class="form-select form-select-sm">
<option value="">-검증방식-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "VRFCREQ_WAY"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['VRFCREQ_WAY']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
<select name="vrfc_type_sub" id="vrfc_type_sub" class="form-select form-select-sm">
<option value="">-선택-</option>
@@ -151,10 +147,8 @@
<label class="form-label mb-1">매체사</label>
<select name="rcpt_cpid" class="form-select form-select-sm">
<option value="">-전체-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "CP_ID"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['CP_ID']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -164,10 +158,8 @@
<label class="form-label mb-1">매물종류</label>
<select name="rlet_type_cd" class="form-select form-select-sm">
<option value="">-매물종류-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "ARTICLE_TYPE"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['ARTICLE_TYPE']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -237,14 +229,15 @@
<?= $this->endSection() ?>
<?= $this->section('page_styles') ?>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.0.7/css/dataTables.dataTables.min.css" />
<?= $this->include('layouts/partials/datatables_css') ?>
<link href="https://unpkg.com/dropzone@6.0.0-beta.1/dist/dropzone.css" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
#resultList_wrapper .dt-start { display: flex; align-items: center; gap: 1rem; align-items: baseline;}
</style>
<?= $this->endSection() ?>
<?= $this->section('page_scripts') ?>
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.min.js"></script>
<script defer src="/architectui/assets/js/datatable.kor.js"></script>
<?= $this->include('layouts/partials/datatables_js') ?>
<script type="text/javascript" src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=dtounkwjc5"></script>
<script type="text/javascript">

View File

@@ -36,10 +36,8 @@
<label class="form-label mb-1">현재상태</label>
<select name="stat_cd" class="form-select form-select-sm">
<option value="">-선택-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "STEP_VERIFICATION"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['STEP_VERIFICATION']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -132,11 +130,9 @@
<div class="d-flex gap-2">
<select name="vrfcreq_way" id="vrfcreq_way" class="form-select form-select-sm">
<option value="">-검증방식-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "VRFCREQ_WAY"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['VRFCREQ_WAY']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
<select name="vrfc_type_sub" id="vrfc_type_sub" class="form-select form-select-sm">
<option value="">-선택-</option>
@@ -149,10 +145,8 @@
<label class="form-label mb-1">매체사</label>
<select name="rcpt_cpid" class="form-select form-select-sm">
<option value="">-전체-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "CP_ID"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['CP_ID']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -162,10 +156,8 @@
<label class="form-label mb-1">매물종류</label>
<select name="rlet_type_cd" class="form-select form-select-sm">
<option value="">-매물종류-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "ARTICLE_TYPE"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['ARTICLE_TYPE']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -229,14 +221,16 @@
<?= $this->endSection() ?>
<?= $this->section('page_styles') ?>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.0.7/css/dataTables.dataTables.min.css" />
<?= $this->include('layouts/partials/datatables_css') ?>
<link href="https://unpkg.com/dropzone@6.0.0-beta.1/dist/dropzone.css" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
#resultList_wrapper .dt-start { display: flex; align-items: center; gap: 1rem; align-items: baseline;}
</style>
<?= $this->endSection() ?>
<?= $this->section('page_scripts') ?>
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.min.js"></script>
<script defer src="/architectui/assets/js/datatable.kor.js"></script>
<?= $this->include('layouts/partials/datatables_js') ?>
<?= $this->include('layouts/partials/datatables_v2_layout_helpers') ?>
<script type="text/javascript" src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=dtounkwjc5"></script>
<script type="text/javascript">
@@ -508,26 +502,7 @@
container.append(damdangT, bonbu2, team2, damdang2, btnChange, btnOmit);
return container;
},
topEnd: function () {
const container = document.createElement('div');
container.className = 'd-flex';
container.style.gap = '8px';
container.style.justifyContent = 'flex-end';
// 등기부등본 전송 버튼
const btnSend = document.createElement('button');
btnSend.className = 'btn btn-sm btn-outline-light';
btnSend.textContent = '등기부등본 전송';
// 엑셀 다운로드 버튼
const btnExcel = document.createElement('button');
btnExcel.id = 'excel-download';
btnExcel.className = 'btn btn-sm btn-outline-success';
btnExcel.innerHTML = '<i class="fa fa-fw fa-file-excel-o" aria-hidden="true"></i> 엑셀다운로드';
container.append(btnSend, btnExcel);
return container;
}
topEnd: v2TopEndButtons({ showSendButton: true })
},
language: lang_kor,
serverSide: true,

View File

@@ -53,11 +53,9 @@
<label class="form-label mb-1">현재상태</label>
<select name="stat_cd" class="form-select form-select-sm">
<option value="">-선택-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "STEP_VERIFICATION"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['STEP_VERIFICATION']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -159,11 +157,9 @@
<label class="form-label mb-1">매체사</label>
<select name="rcpt_cpid" class="form-select form-select-sm">
<option value="">-전체-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "CP_ID"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['CP_ID']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -183,11 +179,9 @@
<label class="form-label mb-1">팩스업체</label>
<select name="fax_corp" class="form-select form-select-sm">
<option value="">-전체-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "FAX_CORP"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['FAX_CORP']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -208,21 +202,6 @@
<div class="col-md-12 col-xl-12">
<div class="main-card mb-3 card">
<div class="card-header d-flex align-items-center">
<div class="d-flex align-items-center flex-wrap" style="gap: 8px; flex: 1">
</div>
<div class="ml-auto">
<button class="btn btn-sm btn-outline-success" id="excel-download">
<i class="fa fa-fw" aria-hidden="true" title="file-excel-o"></i>
엑셀다운로드
</button>
</div>
</div>
<div class="card-body">
<table id="resultList" class="table table-hover table-striped table-bordered">
<thead>
@@ -254,14 +233,16 @@
<?= $this->endSection() ?>
<?= $this->section('page_styles') ?>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.0.7/css/dataTables.dataTables.min.css" />
<?= $this->include('layouts/partials/datatables_css') ?>
<link href="https://unpkg.com/dropzone@6.0.0-beta.1/dist/dropzone.css" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
#resultList_wrapper .dt-start { display: flex; align-items: center; gap: 1rem; align-items: baseline;}
</style>
<?= $this->endSection() ?>
<?= $this->section('page_scripts') ?>
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.min.js"></script>
<script defer src="/architectui/assets/js/datatable.kor.js"></script>
<?= $this->include('layouts/partials/datatables_js') ?>
<?= $this->include('layouts/partials/datatables_v2_layout_helpers') ?>
<script type="text/javascript" src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=dtounkwjc5"></script>
<script type="text/javascript">
@@ -281,6 +262,7 @@
// 검색 조건 복원
function restoreSearchForm() {
const saved = localStorage.getItem("m703_search");
// console.log(saved);
if (!saved) return;
const data = JSON.parse(saved);
data.forEach(function(item) {
@@ -450,6 +432,12 @@ $(function () {
initReceiptDate();
table = $('#resultList').DataTable({
layout: {
topStart: '',
topEnd: v2TopEndButtons(),
bottomStart: ['pageLength', 'info'],
bottomEnd: 'paging'
},
language: lang_kor,
serverSide: true,
processing: true,
@@ -533,7 +521,7 @@ $(function () {
$('#btnSearch').on('click', function () {
saveSearchForm();
table.ajax.reload()
table.ajax.reload();
});
// 엑셀 다운로드 click
@@ -769,4 +757,6 @@ $(function () {
return str;
}
</script>
<?= $this->endSection() ?>
<?= $this->endSection() ?>

View File

@@ -38,10 +38,8 @@
<label class="form-label mb-1">현재상태</label>
<select name="stat_cd" class="form-select form-select-sm">
<option value="">-선택-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "STEP_VERIFICATION"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['STEP_VERIFICATION']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -85,12 +83,10 @@
<div class="input-group input-group-sm">
<select name="stat_complete_date" class="form-select form-select-sm">
<option value="">-선택-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "STEP_VERIFICATION"): ?>
<option value="<?= $c['cd'] ?>">
<?= $c['cd_nm'] ?>
</option>
<?php endif; ?>
<?php foreach (($codes['STEP_VERIFICATION']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>">
<?= $cdNm ?>
</option>
<?php endforeach; ?>
</select>
<input type="date" class="form-control" name="complete_sdate" id="complete_sdate" placeholder="시작일">
@@ -145,10 +141,8 @@
<label class="form-label mb-1">매체사</label>
<select name="rcpt_cpid" class="form-select form-select-sm">
<option value="">-전체-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "CP_ID"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['CP_ID']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -158,10 +152,8 @@
<label class="form-label mb-1">매물종류</label>
<select name="rlet_type_cd" class="form-select form-select-sm">
<option value="">-매물종류-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "ARTICLE_TYPE"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['ARTICLE_TYPE']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -187,14 +179,6 @@
</div>
<div class="ml-auto">
<button class="btn btn-sm btn-outline-success" id="excel-download">
<i class="fa fa-fw" aria-hidden="true" title="file-excel-o"></i>
엑셀다운로드
</button>
</div>
</div>
<div class="card-body">
@@ -227,14 +211,16 @@
<?= $this->endSection() ?>
<?= $this->section('page_styles') ?>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.0.7/css/dataTables.bootstrap5.min.css" />
<?= $this->include('layouts/partials/datatables_bs5_css') ?>
<link href="https://unpkg.com/dropzone@6.0.0-beta.1/dist/dropzone.css" rel="stylesheet" type="text/css" />
<style>
#resultList_wrapper .dt-start { display: flex; align-items: center; gap: 1rem; align-items: baseline;}
</style>
<?= $this->endSection() ?>
<?= $this->section('page_scripts') ?>
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.min.js"></script>
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.bootstrap5.min.js"></script>
<script defer src="/architectui/assets/js/datatable.kor.js"></script>
<?= $this->include('layouts/partials/datatables_bs5_js') ?>
<?= $this->include('layouts/partials/datatables_v2_layout_helpers') ?>
<script type="text/javascript" src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=dtounkwjc5"></script>
<script type="text/javascript">
@@ -429,6 +415,10 @@
initReceiptDate();
table = $('#resultList').DataTable({
layout: {
topEnd: v2TopEndButtons(),
...v2BottomLayout()
},
language: lang_kor,
serverSide: true,
processing: true,

View File

@@ -38,13 +38,11 @@
<label class="form-label mb-1">현재상태</label>
<select name="stat_cd" class="form-select form-select-sm">
<option value="">-선택-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "STEP_VERIFICATION"): ?>
<option value="<?= $c['cd'] ?>" <?php if ($c['cd'] == "40") {
echo "selected";
} ?>><?= $c['cd_nm'] ?>
</option>
<?php endif; ?>
<?php foreach (($codes['STEP_VERIFICATION']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>" <?php if ($cd == "40") {
echo "selected";
} ?>><?= $cdNm ?>
</option>
<?php endforeach; ?>
</select>
</div>
@@ -137,10 +135,8 @@
<div class="d-flex gap-2">
<select name="vrfcreq_way" id="vrfcreq_way" class="form-select form-select-sm">
<option value="">-검증방식-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "VRFCREQ_WAY"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['VRFCREQ_WAY']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
<select name="vrfc_type_sub" id="vrfc_type_sub" class="form-select form-select-sm">
@@ -154,10 +150,8 @@
<label class="form-label mb-1">매체사</label>
<select name="rcpt_cpid" class="form-select form-select-sm">
<option value="">-전체-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "CP_ID"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['CP_ID']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -167,10 +161,8 @@
<label class="form-label mb-1">매물종류</label>
<select name="rlet_type_cd" class="form-select form-select-sm">
<option value="">-매물종류-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "ARTICLE_TYPE"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['ARTICLE_TYPE']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -221,20 +213,6 @@
<div class="col-md-12 col-xl-12">
<div class="main-card mb-3 card">
<div class="card-header d-flex align-items-center">
<div class="d-flex align-items-center flex-wrap" style="gap: 8px; flex: 1">
<button class="btn btn-sm btn-outline-secondary" onclick="ajax_getNotAssign();">
배정확인
</button>
</div>
<div class="ml-auto">
<button class="btn btn-sm btn-outline-success" id="excel-download">
<i class="fa fa-fw" aria-hidden="true" title="file-excel-o"></i>
엑셀다운로드
</button>
</div>
</div>
<div class="card-body">
<table id="resultList" class="table table-hover table-striped table-bordered">
@@ -266,14 +244,16 @@
<?= $this->endSection() ?>
<?= $this->section('page_styles') ?>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.0.7/css/dataTables.dataTables.min.css" />
<?= $this->include('layouts/partials/datatables_css') ?>
<link href="https://unpkg.com/dropzone@6.0.0-beta.1/dist/dropzone.css" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
#resultList_wrapper .dt-start { display: flex; align-items: center; gap: 1rem; align-items: baseline;}
</style>
<?= $this->endSection() ?>
<?= $this->section('page_scripts') ?>
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.min.js"></script>
<script defer src="/architectui/assets/js/datatable.kor.js"></script>
<?= $this->include('layouts/partials/datatables_js') ?>
<?= $this->include('layouts/partials/datatables_v2_layout_helpers') ?>
<script type="text/javascript" src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=dtounkwjc5"></script>
<script type="text/javascript">
@@ -490,6 +470,17 @@ $(function () {
initReceiptDate();
table = $('#resultList').DataTable({
layout: {
topStart: function() {
const btn = document.createElement('button');
btn.className = 'btn btn-sm btn-outline-secondary';
btn.textContent = '배정확인';
btn.onclick = function() { ajax_getNotAssign(); };
return btn;
},
topEnd: v2TopEndButtons(),
...v2BottomLayout()
},
language: lang_kor,
serverSide: true,
processing: true,

View File

@@ -45,6 +45,8 @@
background-color: #ff0000 !important;
color: #fff !important;
}
#resultList_wrapper .dt-start { display: flex; align-items: center; gap: 1rem; align-items: baseline;}
</style>
<h1>1차 재검증 매물현황</h1>
@@ -57,13 +59,6 @@
<input type="hidden" name="todo" id="todo" value="inq" />
<input type="hidden" name="usr_id" value="" />
<!-- 안내 -->
<div class="alert alert-warning py-2 mb-3">
<small class="mb-0">
매물번호를 입력하면 <b>다른 조건은 무시</b>됩니다.
</small>
</div>
<!-- 검색 폼 -->
<div class="row g-3">
@@ -79,11 +74,9 @@
<label class="form-label mb-1">현재상태</label>
<select name="stat_cd" class="form-select form-select-sm">
<option value="">-선택-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "STEP_VERIFICATION"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['STEP_VERIFICATION']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -175,10 +168,8 @@
<div class="d-flex gap-2">
<select name="vrfcreq_way" id="vrfcreq_way" class="form-select form-select-sm">
<option value="">-검증방식-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "VRFCREQ_WAY"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['VRFCREQ_WAY']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -189,11 +180,9 @@
<label class="form-label mb-1">매체사</label>
<select name="rcpt_cpid" class="form-select form-select-sm">
<option value="">-전체-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "CP_ID"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['CP_ID']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -212,19 +201,6 @@
<div class="col-md-12 col-xl-12">
<div class="main-card mb-3 card">
<div class="card-header d-flex align-items-center">
<div class="d-flex align-items-center flex-wrap" style="gap: 8px; flex: 1">
</div>
<div class="ml-auto">
<button class="btn btn-sm btn-outline-success" id="excel-download">
<i class="fa fa-fw" aria-hidden="true" title="file-excel-o"></i>
엑셀다운로드
</button>
</div>
</div>
<div class="card-body">
<table id="resultList" class="table table-hover table-striped table-bordered">
<thead>
@@ -252,10 +228,10 @@
</div>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css" />
<?= $this->include('layouts/partials/datatables_css') ?>
<link href="https://unpkg.com/dropzone@6.0.0-beta.1/dist/dropzone.css" rel="stylesheet" type="text/css" />
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script defer src="/architectui/assets/js/datatable.kor.js"></script>
<?= $this->include('layouts/partials/datatables_js') ?>
<?= $this->include('layouts/partials/datatables_v2_layout_helpers') ?>
<script type="text/javascript" src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=dtounkwjc5"></script>
<script type="text/javascript">
@@ -411,6 +387,12 @@
initReceiptDate();
table = $('#resultList').DataTable({
layout: {
topStart: '',
topEnd: v2TopEndButtons(),
bottomStart: ['pageLength', 'info'],
bottomEnd: 'paging'
},
language: lang_kor,
serverSide: true,
processing: true,

View File

@@ -45,6 +45,8 @@
background-color: #ff0000 !important;
color: #fff !important;
}
#resultList_wrapper .dt-start { display: flex; align-items: center; gap: 1rem; align-items: baseline;}
</style>
<?= $this->endSection() ?>
@@ -101,11 +103,9 @@
<label class="form-label mb-1">현재상태</label>
<select name="stat_cd" class="form-select form-select-sm">
<option value="">-선택-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "STEP_VERIFICATION"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['STEP_VERIFICATION']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -210,11 +210,9 @@
<label class="form-label mb-1">매체사</label>
<select name="rcpt_cpid" class="form-select form-select-sm">
<option value="">-전체-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "CP_ID"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['CP_ID']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -223,11 +221,9 @@
<label class="form-label mb-1">매물종류</label>
<select name="rlet_type_cd" class="form-select form-select-sm">
<option value="">-매물종류-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "ARTICLE_TYPE"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['ARTICLE_TYPE']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -259,19 +255,6 @@
<div class="col-md-12 col-xl-12">
<div class="main-card mb-3 card">
<div class="card-header d-flex align-items-center">
<div class="d-flex align-items-center flex-wrap" style="gap: 8px; flex: 1">
</div>
<div class="ml-auto">
<button class="btn btn-sm btn-outline-success" id="excel-download">
<i class="fa fa-fw" aria-hidden="true" title="file-excel-o"></i>
엑셀다운로드
</button>
</div>
</div>
<div class="card-body">
<table id="resultList" class="table table-hover table-striped table-bordered">
<thead>
@@ -302,9 +285,9 @@
<?= $this->endSection() ?>
<?= $this->section('page_scripts') ?>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.0.7/css/dataTables.dataTables.min.css" />
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.min.js"></script>
<script defer src="/architectui/assets/js/datatable.kor.js"></script>
<?= $this->include('layouts/partials/datatables_css') ?>
<?= $this->include('layouts/partials/datatables_js') ?>
<?= $this->include('layouts/partials/datatables_v2_layout_helpers') ?>
<script type="text/javascript">
const date = new Date();
@@ -499,6 +482,12 @@
initReceiptDate();
table = $('#resultList').DataTable({
layout: {
topStart: '',
topEnd: v2TopEndButtons(),
bottomStart: ['pageLength', 'info'],
bottomEnd: 'paging'
},
language: lang_kor,
serverSide: true,
processing: true,

View File

@@ -18,13 +18,6 @@
<input type="hidden" name="todo" id="todo" value="inq" />
<input type="hidden" name="usr_id" value="" />
<!-- 안내 -->
<div class="alert alert-warning py-2 mb-3">
<small class="mb-0">
매물번호 또는 발신팩스번호를 입력하면 <b>다른 조건은 무시</b>됩니다.
</small>
</div>
<!-- 검색 폼 -->
<div class="row g-3">
@@ -55,11 +48,9 @@
<label class="form-label mb-1">현재상태</label>
<select name="stat_cd" class="form-select form-select-sm">
<option value="">-선택-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "STEP_VERIFICATION"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['STEP_VERIFICATION']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -164,11 +155,9 @@
<label class="form-label mb-1">매체사</label>
<select name="rcpt_cpid" class="form-select form-select-sm">
<option value="">-전체-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "CP_ID"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['CP_ID']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -177,11 +166,9 @@
<label class="form-label mb-1">매물종류</label>
<select name="rlet_type_cd" class="form-select form-select-sm">
<option value="">-매물종류-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "ARTICLE_TYPE"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['ARTICLE_TYPE']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -256,14 +243,15 @@
<?= $this->endSection() ?>
<?= $this->section('page_styles') ?>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.0.7/css/dataTables.bootstrap5.min.css" />
<?= $this->include('layouts/partials/datatables_bs5_css') ?>
<link href="https://unpkg.com/dropzone@6.0.0-beta.1/dist/dropzone.css" rel="stylesheet" type="text/css" />
<style>
#resultList_wrapper .dt-start { display: flex; align-items: center; gap: 1rem; align-items: baseline;}
</style>
<?= $this->endSection() ?>
<?= $this->section('page_scripts') ?>
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.min.js"></script>
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.bootstrap5.min.js"></script>
<script defer src="/architectui/assets/js/datatable.kor.js"></script>
<?= $this->include('layouts/partials/datatables_bs5_js') ?>
<script type="text/javascript" src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=dtounkwjc5"></script>
<script type="text/javascript">

View File

@@ -79,11 +79,9 @@
<label class="form-label mb-1">현재상태</label>
<select name="stat_cd" class="form-select form-select-sm">
<option value="">-선택-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "STEP_VERIFICATION"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['STEP_VERIFICATION']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -175,10 +173,8 @@
<div class="d-flex gap-2">
<select name="vrfcreq_way" id="vrfcreq_way" class="form-select form-select-sm">
<option value="">-검증방식-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "VRFCREQ_WAY"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['VRFCREQ_WAY']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
<select name="vrfc_type_sub" id="vrfc_type_sub" class="form-select form-select-sm">
@@ -192,11 +188,9 @@
<label class="form-label mb-1">매체사</label>
<select name="rcpt_cpid" class="form-select form-select-sm">
<option value="">-전체-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "CP_ID"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['CP_ID']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -205,11 +199,9 @@
<label class="form-label mb-1">매물종류</label>
<select name="rlet_type_cd" class="form-select form-select-sm">
<option value="">-매물종류-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "ARTICLE_TYPE"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['ARTICLE_TYPE']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -296,10 +288,9 @@
</div>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css" />
<?= $this->include('layouts/partials/datatables_css') ?>
<link href="https://unpkg.com/dropzone@6.0.0-beta.1/dist/dropzone.css" rel="stylesheet" type="text/css" />
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script defer src="/architectui/assets/js/datatable.kor.js"></script>
<?= $this->include('layouts/partials/datatables_js') ?>
<script type="text/javascript" src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=dtounkwjc5"></script>
<script type="text/javascript">

View File

@@ -73,13 +73,11 @@
<?= $this->endSection() ?>
<?= $this->section('page_styles') ?>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.0.7/css/dataTables.bootstrap5.min.css" />
<?= $this->include('layouts/partials/datatables_bs5_css') ?>
<?= $this->endSection() ?>
<?= $this->section('page_scripts') ?>
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.min.js"></script>
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.bootstrap5.min.js"></script>
<script defer src="/architectui/assets/js/datatable.kor.js"></script>
<?= $this->include('layouts/partials/datatables_bs5_js') ?>
<script type="text/javascript">
const codes = [];

View File

@@ -40,11 +40,9 @@
<label class="form-label mb-1">현재상태</label>
<select name="stat_cd" class="form-select form-select-sm">
<option value="">-선택-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "STEP_VERIFICATION"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['STEP_VERIFICATION']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -135,11 +133,9 @@
<label class="form-label mb-1">매체사</label>
<select name="rcpt_cpid" class="form-select form-select-sm">
<option value="">-전체-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "CP_ID"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['CP_ID']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -148,11 +144,9 @@
<label class="form-label mb-1">매물종류</label>
<select name="rlet_type_cd" class="form-select form-select-sm">
<option value="">-매물종류-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "ARTICLE_TYPE"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php endforeach; ?>
<?php foreach (($codes['ARTICLE_TYPE']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -252,14 +246,12 @@
<?= $this->endSection() ?>
<?= $this->section('page_styles') ?>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.0.7/css/dataTables.bootstrap5.min.css" />
<?= $this->include('layouts/partials/datatables_bs5_css') ?>
<link href="https://unpkg.com/dropzone@6.0.0-beta.1/dist/dropzone.css" rel="stylesheet" type="text/css" />
<?= $this->endSection() ?>
<?= $this->section('page_scripts') ?>
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.min.js"></script>
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.bootstrap5.min.js"></script>
<script defer src="/architectui/assets/js/datatable.kor.js"></script>
<?= $this->include('layouts/partials/datatables_bs5_js') ?>
<script type="text/javascript" src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=dtounkwjc5"></script>
<script type="text/javascript">

View File

@@ -40,10 +40,8 @@
<label class="form-label mb-1">현재상태</label>
<select name="stat_cd" class="form-select form-select-sm">
<option value="">-선택-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "STEP_VERIFICATION"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['STEP_VERIFICATION']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -76,12 +74,10 @@
<div class="d-flex gap-2">
<select name="vrfcreq_way" id="vrfcreq_way" class="form-select form-select-sm">
<option value="">-검증방식-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "VRFCREQ_WAY"): ?>
<option value="<?= $c['cd'] ?>">
<?= $c['cd_nm'] ?>
</option>
<?php endif; ?>
<?php foreach (($codes['VRFCREQ_WAY']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>">
<?= $cdNm ?>
</option>
<?php endforeach; ?>
</select>
<select name="vrfc_type_sub" id="vrfc_type_sub" class="form-select form-select-sm">
@@ -155,10 +151,8 @@
<label class="form-label mb-1">매체사</label>
<select name="rcpt_cpid" class="form-select form-select-sm">
<option value="">-전체-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "CP_ID"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['CP_ID']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -168,10 +162,8 @@
<label class="form-label mb-1">매물종류</label>
<select name="rlet_type_cd" class="form-select form-select-sm">
<option value="">-매물종류-</option>
<?php foreach ($codes as $c): ?>
<?php if ($c['category'] === "ARTICLE_TYPE"): ?>
<option value="<?= $c['cd'] ?>"><?= $c['cd_nm'] ?></option>
<?php endif; ?>
<?php foreach (($codes['ARTICLE_TYPE']['items'] ?? []) as $cd => $cdNm): ?>
<option value="<?= $cd ?>"><?= $cdNm ?></option>
<?php endforeach; ?>
</select>
</div>
@@ -276,14 +268,12 @@
<?= $this->endSection() ?>
<?= $this->section('page_styles') ?>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.0.7/css/dataTables.bootstrap5.min.css" />
<?= $this->include('layouts/partials/datatables_bs5_css') ?>
<link href="https://unpkg.com/dropzone@6.0.0-beta.1/dist/dropzone.css" rel="stylesheet" type="text/css" />
<?= $this->endSection() ?>
<?= $this->section('page_scripts') ?>
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.min.js"></script>
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.bootstrap5.min.js"></script>
<script defer src="/architectui/assets/js/datatable.kor.js"></script>
<?= $this->include('layouts/partials/datatables_bs5_js') ?>
<script type="text/javascript" src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=dtounkwjc5"></script>
<script type="text/javascript">

2
public/check_env.php Normal file
View File

@@ -0,0 +1,2 @@
<?php
phpinfo(INFO_ENVIRONMENT);

File diff suppressed because it is too large Load Diff

29
public/test_ci_log.php Normal file
View File

@@ -0,0 +1,29 @@
<?php
// CodeIgniter 로그 테스트
require __DIR__ . '/../vendor/autoload.php';
$app = require_once FCPATH . '../app/Config/App.php';
$paths = require_once FCPATH . '../app/Config/Paths.php';
// Bootstrap the application
require_once $paths->systemDirectory . 'bootstrap.php';
$app = Config\Services::codeigniter();
$app->initialize();
echo "ENVIRONMENT: " . ENVIRONMENT . "<br>\n";
// 로그 테스트
log_message('error', '===== TEST CI LOG MESSAGE ===== ' . date('Y-m-d H:i:s'));
echo "log_message() 호출 완료<br>\n";
$logFile = WRITEPATH . 'logs/log-' . date('Y-m-d') . '.log';
echo "로그 파일: " . $logFile . "<br>\n";
echo "로그 파일 존재: " . (file_exists($logFile) ? 'YES' : 'NO') . "<br>\n";
if (file_exists($logFile)) {
echo "<pre>";
echo "마지막 10줄:\n";
echo shell_exec("tail -10 " . escapeshellarg($logFile));
echo "</pre>";
}

20
public/test_log.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
// 간단한 테스트
$writablePath = realpath(__DIR__ . '/../writable/logs');
echo "writable/logs 경로: " . $writablePath . "<br>";
echo "writable/logs 쓰기 가능: " . (is_writable($writablePath) ? 'YES' : 'NO') . "<br>";
echo "writable/logs 소유자: ";
system("ls -ld " . $writablePath);
echo "<br>";
// 직접 파일 쓰기 테스트
$testFile = $writablePath . '/test-' . date('Y-m-d') . '.log';
$result = file_put_contents($testFile, date('Y-m-d H:i:s') . " - Direct write test\n", FILE_APPEND);
echo "직접 쓰기 결과: " . ($result !== false ? 'SUCCESS' : 'FAILED') . "<br>";
echo "테스트 파일: " . $testFile . "<br>";
if (file_exists($testFile)) {
echo "파일 내용:<pre>" . file_get_contents($testFile) . "</pre>";
}
// 파일 변경

View File

@@ -2,78 +2,75 @@
/**
* [A 작업] 네이버 검증 요청 실시간 수신 리시버
* - 프레임워크를 로드하지 않아 매우 빠르고 안전함
* - 받은 데이터를 Redis 큐에 넣고 즉시 응답
* - 받은 데이터를 Redis 큐에 넣고 즉시 응답 테스트
*/
// 1. 응답 헤더 설정 (JSON)
header('Content-Type: application/json; charset=utf-8');
// 2. 보안 키 체크 (URL 파라미터 key=값)
$configKey = "7EE868F4B36D36B3D86736828F4729EAC4992083"; // 실제 사용할 키값으로 변경하세요
$receivedKey = $_GET['key'] ?? '';
$logDir = __DIR__ . '/logs/';
if ($receivedKey !== $configKey) {
http_response_code(403);
echo apiResponse([
'code' => '-1',
'message' => 'Unregistered key'
]);
exit;
// .env 파일 로드 함수 (프레임워크 미사용 환경)
function loadEnvFile($filePath) {
if (!file_exists($filePath)) {
return;
}
$lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
// 주석 제거
if (strpos(trim($line), '#') === 0) {
continue;
}
// KEY = VALUE 파싱
if (strpos($line, '=') !== false) {
list($key, $value) = explode('=', $line, 2);
$key = trim($key);
$value = trim($value);
// 따옴표 제거
$value = trim($value, '"\'');
// 환경변수 설정 (이미 있으면 덮어쓰지 않음)
if (!getenv($key)) {
putenv("$key=$value");
}
}
}
}
try {
// 3. 데이터 수신 (POST JSON 또는 GET 파라미터)
$rawData = file_get_contents('php://input');
$data = json_decode($rawData, true);
// .env 파일 로드 (한 단계 상위 디렉토리)
loadEnvFile(__DIR__ . '/../.env');
if (empty($data)) {
throw new Exception("Empty data received");
/**
* X-Forwarded-For 헤더에서 실제 클라이언트 IP 추출
* @return string 클라이언트 IP
*/
function getRealClientIP() {
// 1. X-Forwarded-For 헤더 확인 (여러 프록시를 거친 경우)
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
// "123.123.123.123, 192.168.10.1" 같은 형식에서 첫 번째 IP 추출
$ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$clientIP = trim($ips[0]); // 첫 번째 = 원래 클라이언트
// IP 형식 검증
if (filter_var($clientIP, FILTER_VALIDATE_IP)) {
return $clientIP;
}
}
// 4. Redis 연결
$redis = new Redis();
// Docker 서비스 이름인 'redis' 사용
$success = $redis->connect('redis', 6379);
if (!$success) {
throw new Exception("Could not connect to Redis");
// 2. X-Real-IP 헤더 확인 (단일 프록시)
if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
if (filter_var($_SERVER['HTTP_X_REAL_IP'], FILTER_VALIDATE_IP)) {
return $_SERVER['HTTP_X_REAL_IP'];
}
}
$redis->select(9); // 10번 DB 사용
// 5. 큐에 넣을 데이터 포맷팅
$payload = [
'request_data' => $data,
'received_at' => date('Y-m-d H:i:s'),
'client_ip' => $_SERVER['REMOTE_ADDR']
];
// 'naver:raw_queue'라는 이름의 리스트에 저장
$redis->lPush('naver:raw_queue', json_encode($payload));
// --- [여기서부터 로그 저장 코드 추가] ---
// 들어온 원본($rawData)을 그대로 기록합니다.
writeLog("RAW_RECEIVE | " . $rawData, 'INFO');
// --------------------------------------
// 6. 네이버측에 성공 응답 (202 Accepted)
// 처리가 완료된 것은 아니지만, 접수는 완료되었음을 의미
http_response_code(200);
echo apiResponse([
'code' => 'success',
'message' => ''
]);
} catch (Exception $e) {
// 7. 장애 발생 시 로그 기록 (시스템 로그)
writeLog( 'Exception :' . apiResponse($data) , 'ERROR');
http_response_code(500);
// 3. CF-Connecting-IP (Cloudflare)
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
if (filter_var($_SERVER['HTTP_CF_CONNECTING_IP'], FILTER_VALIDATE_IP)) {
return $_SERVER['HTTP_CF_CONNECTING_IP'];
}
}
echo apiResponse([
'code' => '-1',
'message' => $e->getMessage()
]);
// 4. 기본값: REMOTE_ADDR (nginx가 set_real_ip_from으로 이미 변경함)
return $_SERVER['REMOTE_ADDR'] ?? '';
}
/**
@@ -82,36 +79,240 @@ try {
* @param string $level 로그 레벨 (INFO, ERROR, DEBUG 등)
*/
function writeLog($message, $level = 'ERROR') {
// 1. 로그 저장 경로 설정 (프로젝트 루트의 logs 폴더)
$logBaseDir = __DIR__ . '/logs';
$Dir = $logBaseDir . '/';
// 2. 폴더가 없으면 생성 (연/월 구조로 관리하면 파일이 너무 많아지는 것을 방지)
if (!is_dir($Dir)) {
@mkdir($Dir, 0777, true);
try {
// 1. 로그 저장 경로 설정 (프로젝트 루트의 logs 폴더)
$logBaseDir = __DIR__ . '/logs';
$Dir = $logBaseDir . '/';
// 2. 폴더가 없으면 생성
if (!is_dir($Dir)) {
if (!mkdir($Dir, 0777, true) && !is_dir($Dir)) {
error_log("Failed to create log directory: $Dir");
return;
}
chmod($Dir, 0777); // 권한 명시적 설정
}
// 3. 파일명 결정
$logFile = $Dir . date('Y-m-d') . '.log';
// 4. 로그 포맷팅 (시간 [레벨] 메시지)
$timestamp = date('Y-m-d H:i:s');
$singleLineMessage = str_replace(["\r", "\n", "\t"], " ", $message);
$formattedMessage = "[$timestamp] [$level] $singleLineMessage" . PHP_EOL;
// 5. 파일 기록
$result = file_put_contents($logFile, $formattedMessage, FILE_APPEND | LOCK_EX);
if ($result === false) {
error_log("Failed to write log file: $logFile");
} else {
chmod($logFile, 0666); // 로그 파일 권한 설정
}
} catch (Exception $e) {
error_log("writeLog Exception: " . $e->getMessage());
}
// 3. 파일명 결정 (예: logs/2025/12/2025-12-22.log)
$logFile = $Dir . date('Y-m-d') . '.log';
// 4. 로그 포맷팅 (시간 [레벨] 메시지)
$timestamp = date('Y-m-d H:i:s');
$singleLineMessage = str_replace(["\r", "\n", "\t"], " ", $message);
$formattedMessage = "[$timestamp] [$level] $singleLineMessage" . PHP_EOL;
// 5. 파일 기록 (FILE_APPEND로 기존 내용 뒤에 추가)
file_put_contents($logFile, $formattedMessage, FILE_APPEND);
}
// 도우미 함수 정의
function safeJsonEncode($value, $flags = 0, $depth = 512) {
try {
return json_encode($value, $flags | JSON_THROW_ON_ERROR, $depth);
} catch (JsonException $e) {
return false;
}
}
function apiResponse($error = null) {
// $base = [
// '@type' => 'response',
// '@service' => 'confirms',
// '@version' => '1.0.0'
// ];
// if ($error) $base['error'] = $error;
$base = $error;
return json_encode($base);
$encoded = safeJsonEncode($error, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($encoded === false) {
writeLog('JSON_ENCODE_FAIL | apiResponse encoding failed', 'ERROR');
return '{"code":"E999","message":"Internal server error"}';
}
return $encoded;
}
// 1. 응답 헤더 설정 (JSON)
header('Content-Type: application/json; charset=utf-8');
// ===== 최우선: 모든 호출 정보를 로그에 저장 (보안 키 체크 전) =====
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http";
$fullUrl = $protocol . "://" . ($_SERVER['HTTP_HOST'] ?? 'localhost') . ($_SERVER['REQUEST_URI'] ?? '');
$rawData = file_get_contents('php://input');
$rawDataLog = safeJsonEncode($rawData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($rawDataLog === false) {
writeLog('JSON_ENCODE_FAIL | failed to encode raw request data for logging', 'WARNING');
$rawDataLog = '"json_encode_failed"';
}
writeLog("REQUEST_INFO | " . $rawDataLog, 'INFO');
$requestInfo = [
'timestamp' => date('Y-m-d H:i:s'),
'method' => $_SERVER['REQUEST_METHOD'] ?? 'UNKNOWN',
'full_url' => $fullUrl,
'request_uri' => $_SERVER['REQUEST_URI'] ?? '',
'query_string' => $_SERVER['QUERY_STRING'] ?? '',
'get_params' => $_GET,
'post_data' => $rawData,
'client_ip' => $_SERVER['REMOTE_ADDR'] ?? '',
'real_ip' => getRealClientIP(),
'x_forwarded_for' => $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'referer' => $_SERVER['HTTP_REFERER'] ?? '',
'content_type' => $_SERVER['CONTENT_TYPE'] ?? $_SERVER['HTTP_CONTENT_TYPE'] ?? '',
'content_length' => $_SERVER['CONTENT_LENGTH'] ?? '',
'accept' => $_SERVER['HTTP_ACCEPT'] ?? '',
'host' => $_SERVER['HTTP_HOST'] ?? '',
'server_protocol' => $_SERVER['SERVER_PROTOCOL'] ?? '',
];
$requestInfoLog = safeJsonEncode($requestInfo, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($requestInfoLog === false) {
writeLog('JSON_ENCODE_FAIL | failed to encode request metadata for logging', 'WARNING');
$requestInfoLog = '"json_encode_failed"';
}
writeLog("REQUEST_INFO | " . $requestInfoLog, 'INFO');
// ================================================================
// 2. 보안 키 체크 (URL 파라미터 key=값)
$configKey = getenv('API_RECEIVER_KEY') ?: "7EE868F4B36D36B3D86736828F4729EAC4992083";
$receivedKey = $_GET['key'] ?? '';
if ($receivedKey !== $configKey) {
writeLog("SECURITY_FAIL | Invalid key: $receivedKey", 'WARNING');
http_response_code(403);
echo apiResponse([
'code' => 'E403',
'message' => 'Unregistered key'
]);
exit;
}
try {
// 3. 데이터 수신 및 검증 (POST JSON)
if (empty($rawData)) {
throw new Exception("No data received");
}
$data = null;
$contentType = $_SERVER['CONTENT_TYPE'] ?? $_SERVER['HTTP_CONTENT_TYPE'] ?? '';
// 1차 시도: JSON으로 직접 파싱
$data = json_decode($rawData, true);
// JSON 파싱 성공
if ($data !== null && json_last_error() === JSON_ERROR_NONE) {
// 성공
}
// JSON 파싱 실패 → form-urlencoded 시도
else if (strpos($contentType, 'application/x-www-form-urlencoded') !== false || empty($contentType)) {
parse_str($rawData, $postData);
// post_data 키가 있으면 그 값을 JSON으로 파싱
if (isset($postData['post_data'])) {
$data = json_decode($postData['post_data'], true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception("Invalid JSON format in post_data: " . json_last_error_msg());
}
} else {
// post_data 키가 없으면 form 데이터 자체를 사용
$data = $postData;
}
} else {
throw new Exception("Invalid JSON format: " . json_last_error_msg() . " | Data: " . substr($rawData, 0, 200));
}
if (empty($data)) {
throw new Exception("Empty data received");
}
// 4. 페이로드 준비
$payload = [
'request_data' => $data,
'received_at' => date('Y-m-d H:i:s'),
'client_ip' => getRealClientIP()
];
// 5. Redis 연결 시도 및 폴백 처리
$redisSuccess = false;
$redis = new Redis();
try {
// .env 환경변수에서 Redis 설정 읽기
$redisHost = getenv('REDIS_HOST') ?: '127.0.0.1';
$redisPort = getenv('REDIS_PORT') ?: 6379;
$redisDatabase = getenv('REDIS_DATABASE') ?: 9;
$success = $redis->connect($redisHost, (int)$redisPort, 2.5); // 2.5초 타임아웃
if (!$success) {
throw new Exception("Could not connect to Redis at {$redisHost}:{$redisPort}");
}
$redis->select((int)$redisDatabase);
// 'naver:raw_queue'라는 이름의 리스트에 저장
$encodedPayload = safeJsonEncode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($encodedPayload === false) {
throw new Exception('Failed to encode payload for Redis queue');
}
$pushResult = $redis->lPush('naver:raw_queue', $encodedPayload);
if (!$pushResult) {
throw new Exception("Failed to push data to Redis queue");
}
$redisSuccess = true;
writeLog("SUCCESS | Redis queue length: {$pushResult} | IP: " . ($payload['client_ip'] ?? 'unknown'), 'INFO');
} catch (Exception $redisError) {
// Redis 실패 시 파일 폴백
writeLog("REDIS_FAIL | " . $redisError->getMessage() . " | Using file fallback", 'WARNING');
// 폴백 디렉토리 생성 및 권한 설정
$fallbackDir = __DIR__ . '/fallback_queue';
if (!is_dir($fallbackDir)) {
if (!mkdir($fallbackDir, 0777, true) && !is_dir($fallbackDir)) {
throw new Exception("Failed to create fallback directory");
}
chmod($fallbackDir, 0777);
}
// 파일로 저장 (타임스탬프 + 유니크ID)
$filename = $fallbackDir . '/' . date('YmdHis') . '_' . uniqid() . '.json';
$encodedPayload = safeJsonEncode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($encodedPayload === false) {
throw new Exception('Failed to encode payload for fallback file');
}
$writeResult = file_put_contents($filename, $encodedPayload, LOCK_EX);
if ($writeResult === false) {
throw new Exception("Failed to write fallback file");
}
chmod($filename, 0666);
writeLog("FALLBACK_SUCCESS | File: " . basename($filename) . " | IP: " . ($payload['client_ip'] ?? 'unknown'), 'INFO');
}
// 6. 성공 응답 (Redis 또는 Fallback 성공)
http_response_code(200);
echo apiResponse([
'code' => 'success',
'message' => ''
]);
} catch (Exception $e) {
// 7. 완전 장애 발생 시 (Redis도 실패, File도 실패)
writeLog('CRITICAL_ERROR: ' . $e->getMessage() . ' | Data: ' . substr($rawData, 0, 200), 'ERROR');
http_response_code(500);
echo apiResponse([
'code' => 'E999',
'message' => 'Internal server error'
]);
}