#!/bin/bash

# ==========================================================
# 시스템스위치 내보내기 스크립트 (Ghost)
# 작성일: 2026-03-30
# 설명: 원본 서버의 데이터를 필터링하여 스위치 서버로 이관
# 변경: BASE 기준 고객(CS) 선 추출 후 해당 고객의 연관 데이터 처리
# ==========================================================

# [기본값 설정]
DEF_SRC_IP="192.168.56.202"
DEF_TGT_IP="192.168.56.203"
DEF_DB_LIST=""

# 웹(파라미터) / 터미널(대화형) 분기
if [ "$#" -gt 0 ]; then
 # 파라미터가 있을 경우 (웹 모드)
 SRC_IP=${1:-$DEF_SRC_IP}
 TGT_IP=${2:-$DEF_TGT_IP}
 DB_LIST=${3:-$DEF_DB_LIST}
 DB_LIST=$(echo "$DB_LIST" | tr '[:lower:]' '[:upper:]')
else
 # 파라미터가 없을 경우 (대화형 모드)
 echo "----------------------------------------------------"
 echo "  시스템스위치 데이터 내보내기 설정을 시작합니다."
 echo "  (입력 없이 Enter를 누르면 기본값이 사용됩니다.)"
 echo "----------------------------------------------------"

 read -p "1. 원본 서버 IP (기본: $DEF_SRC_IP): " SRC_IP
 SRC_IP=${SRC_IP:-$DEF_SRC_IP}

 read -p "2. 스위치 서버 IP (기본: $DEF_TGT_IP): " TGT_IP
 TGT_IP=${TGT_IP:-$DEF_TGT_IP}

 read -p "3. 스위치 데이터베이스 (기본: 전체 이관): " DB_LIST
 DB_LIST=${DB_LIST:-$DEF_DB_LIST}
 DB_LIST=$(echo "$DB_LIST" | tr '[:lower:]' '[:upper:]')

 echo ""
 echo "----------------------------------------------------"
 echo " [입력 정보 확인]"
 echo " > 원본 서버 IP: $SRC_IP"
 echo " > 스위치 서버 IP: $TGT_IP"
 echo " > 대상 DB: ${DB_LIST:-"전체"}"
 echo "----------------------------------------------------"
 read -p " 위 정보를 확인하셨습니까? (Enter를 누르면 시작합니다)" CONFIRM
fi

START_DATETIME=$(date "+%Y-%m-%d %H:%M:%S")
echo ""
echo "이관 시작: $START_DATETIME"
echo "----------------------------------------------------"

# PHP 내장 스크립트 실행 (파라미터 전달)
php -- "$SRC_IP" "$TGT_IP" "$DB_LIST" << 'EOF'
<?php
// --- [환경 설정 및 초기화] ---
date_default_timezone_set('Asia/Seoul'); // 시간대 설정
ini_set('display_errors', '1'); // 에러 표시 활성화
error_reporting(E_ALL); // 모든 에러 보고
ini_set('memory_limit', '2048M'); // 대용량 데이터 처리를 위한 메모리 한도 해제
set_time_limit(0); // 스크립트 실행 시간 무제한 설정

// --- [데이터 설정] (BASE 옵션 추가) ---
$server_databases = [
 ['HM' => 'ABLYPS'  , 'BASE' => 'DS', 'MONTH' => '24', 'DS' => '내원상담,일반진료', 'RS' => '상담예약,진료예약'],
 ['HM' => 'CDSTAR'  , 'BASE' => 'DS', 'MONTH' => '24', 'DS' => '내원상담,일반진료', 'RS' => '상담예약,진료예약', 'PA' => '수납'],
 ['HM' => 'COHEN'   , 'BASE' => 'DS', 'MONTH' => '24', 'DS' => '내원상담,일반진료', 'RS' => '상담예약,진료예약'],
 ['HM' => 'COSHARP' , 'BASE' => 'DS', 'MONTH' => '24', 'DS' => '내원상담,일반진료', 'RS' => '상담예약,진료예약', 'PA' => '수납'],
 ['HM' => 'DESIGNER', 'BASE' => 'DS', 'MONTH' => '24', 'DS' => '내원상담,일반진료', 'RS' => '상담예약,진료예약', 'GX' => 'ROI'],
 ['HM' => 'DR4UIC'  , 'BASE' => 'DS', 'MONTH' => '24', 'DS' => '내원상담,일반진료', 'RS' => '상담예약,진료예약,관리예약', 'PA' => '수납'],
 ['HM' => 'DR4UJR'  , 'BASE' => 'DS', 'MONTH' => '24', 'DS' => '내원상담,일반진료', 'RS' => '상담예약,진료예약,관리예약', 'PA' => '수납'],
 ['HM' => 'DR4UMA'  , 'BASE' => 'DS', 'MONTH' => '24', 'DS' => '내원상담,일반진료', 'RS' => '상담예약,진료예약,관리예약', 'PA' => '수납'],
 ['HM' => 'GAINPS'  , 'BASE' => 'DS', 'MONTH' => '24', 'DS' => '내원상담,일반진료', 'RS' => '상담예약,치료예약'],
 ['HM' => 'LICO'    , 'BASE' => 'DS', 'MONTH' => '24', 'DS' => '내원상담,일반진료', 'RS' => '상담예약'],
 ['HM' => 'SAMPLE1' , 'BASE' => 'DS', 'MONTH' => '24', 'DS' => '내원상담,일반진료', 'RS' => '상담예약,진료예약'],
 ['HM' => 'SKYFEEL' , 'BASE' => 'DS', 'MONTH' => '24', 'DS' => '내원상담,치료정보', 'RS' => '상담예약,진료예약', 'PA' => '수납'],
 ['HM' => 'ROSEE'   , 'BASE' => 'DS', 'MONTH' => '24', 'DS' => '내원상담,일반진료', 'RS' => '상담예약,진료예약'],
 ['HM' => 'ROSECHN' , 'BASE' => 'CS', 'MONTH' => '24', 'RS' => '상담예약,진료예약', 'PA' => '수납'],
 ['HM' => 'ROSECJU' , 'BASE' => 'CS', 'MONTH' => '24', 'RS' => '상담예약,진료예약', 'PA' => '수납'],
 ['HM' => 'ROSECJ'  , 'BASE' => 'DS', 'MONTH' => '24', 'DS' => '내원상담,일반진료', 'RS' => '상담예약,진료예약,관리예약', 'PA' => '수납'],
 ['HM' => 'YELLOW'  , 'BASE' => 'DS', 'MONTH' => '24', 'DS' => '내원상담,일반진료', 'RS' => '상담예약,진료예약,관리예약', 'PA' => '수납'],
]; // 대상 병원 DB 및 필터링 설정

// --- [쉘 파라미터 적용 처리] ---
$input_src_ip = trim($argv[1] ?? ''); // 전달된 원본 IP
$input_tgt_ip = trim($argv[2] ?? ''); // 전달된 스위치 IP
$input_db = trim($argv[3] ?? '');  // 전달된 스위치 DB

$data_server = [
 'host' => $input_src_ip ? $input_src_ip : '192.168.56.202',
 'dbname' => 'mysql',
 'user' => 'root', 'pass' => '@smartserver',
]; // 소스 서버 설정

$switch_server = [
 'host' => $input_tgt_ip ? $input_tgt_ip : '192.168.56.203',
 'dbname' => 'mysql',
 'user' => 'root', 'pass' => '@smartserver',
]; // 타겟(스위치) 서버 설정

// DB 설정 필터링 (사용자 입력 DB가 있을 경우 해당 DB만 처리)
if (!empty($input_db)) {
 $db_list = array_map('strtoupper', array_map('trim', explode(',', $input_db))); // 입력 DB 대문자 변환
 $filtered_databases = [];
 
 foreach ($server_databases as $config) {
  if (in_array($config['HM'], $db_list)) {
   $filtered_databases[] = $config; // 일치하는 DB 설정 추가
  } // End of If
 } // End of Foreach
 
 // 배열에 없는 DB인 경우 기본값으로 추가
 foreach ($db_list as $db) {
  if (!empty($db) && !in_array($db, array_column($server_databases, 'HM'))) {
   $filtered_databases[] = ['HM' => $db, 'BASE' => 'DS', 'MONTH' => '24', 'DS' => '내원상담,일반진료', 'RS' => '상담예약,진료예약'];
  } // End of If
 } // End of Foreach
 
 $server_databases = $filtered_databases; // 필터링된 배열로 교체
} // End of If

// --- [로그 설정] ---
$base_dir = __DIR__; // 현재 디렉토리
$log_dir = $base_dir . '/data_ghost/'; // 로그 저장 폴더
if (!is_dir($log_dir)) @mkdir($log_dir, 0775, true); // 권한 오류 방지
$log_file = $log_dir . 'server_data_ghost_' . date('Ymd') . '.log'; // 오늘 날짜 로그 파일명

/**
 * 텍스트를 로그 파일에 기록하고 터미널에 출력합니다.
 */
function writeLog($msg) {
 global $log_file;
 $timestamp = date('Y-m-d H:i:s'); // 현재 시간
 $log_msg = "[{$timestamp}] {$msg}\n"; // 메시지 포맷
 @file_put_contents($log_file, $log_msg, FILE_APPEND | LOCK_EX); // 권한 오류 방지용 억제자(@) 추가
 echo $log_msg; // CLI 환경 출력
} // End of Function writeLog

/**
 * 배열 데이터를 바탕으로 다중 INSERT(IGNORE) 쿼리를 실행합니다.
 */
function insertBatch($conn, $db_name, $table, $rows) {
 if (empty($rows)) return; // End of If
 
 $cols = array_keys($rows[0]); // 컬럼명 추출
 $cols_str = "`" . implode("`, `", $cols) . "`"; // 컬럼명 포맷팅
 
 $values_arr = [];
 foreach ($rows as $row) {
  $val_str = [];
  foreach ($cols as $col) {
   if ($row[$col] === null) {
    $val_str[] = "NULL"; // null 값 처리
   } else {
    $val_str[] = "'" . $conn->real_escape_string($row[$col]) . "'"; // 이스케이프 처리
   } // End of If
  } // End of Foreach
  $values_arr[] = "(" . implode(", ", $val_str) . ")"; // 괄호로 묶기
 } // End of Foreach
 
 $sql = "INSERT IGNORE INTO `{$db_name}`.`{$table}` ({$cols_str}) VALUES " . implode(", ", $values_arr); // 최종 쿼리 생성
 if (!$conn->query($sql)) {
  writeLog("[에러] INSERT 실패 ({$db_name}.{$table}): " . $conn->error); // 에러 로깅
 } // End of If
} // End of Function insertBatch

/**
 * 조건에 맞는 데이터를 조회하여 타겟 DB로 복사합니다.
 */
function copyTableData($source_conn, $target_conn, $db_name, $table, $where_clause) {
 $sql = "SELECT * FROM `{$db_name}`.`{$table}`"; // 기본 쿼리
 if (!empty($where_clause)) $sql .= " WHERE {$where_clause}"; // 조건 추가
 
 $result = $source_conn->query($sql); // 소스 서버 조회
 if (!$result) {
  writeLog("[에러] 조회 실패 ({$db_name}.{$table}): " . $source_conn->error); // 에러 로깅
  return 0; // End of If
 } // End of If
 
 $count = 0; // 복사 건수
 $rows = []; // 배치 컨테이너
 $batch_size = 500; // 한 번에 INSERT 할 청크 사이즈
 
 while ($row = $result->fetch_assoc()) {
  // 메모 데이터 초기화
  if (isset($row['CS18'])) $row['CS18'] = '0'; // 소개고객코드
  if (isset($row['CS19'])) $row['CS19'] = '';  // 소개고객이름 삭제
  if (isset($row['CS20'])) $row['CS20'] = '';  // 고객메모 삭제
  if (isset($row['CS27'])) $row['CS27'] = '0'; // 사진개수
  if (isset($row['CS28'])) $row['CS28'] = '0'; // 누적수납금액
  if (isset($row['CS29'])) $row['CS29'] = '0'; // 미수수납금액
  if (isset($row['CS31'])) $row['CS31'] = '';  // 소개고객경로
  if (isset($row['CS32'])) $row['CS32'] = '0'; // 소개고객수
  if (isset($row['CS33'])) $row['CS33'] = '0'; // 소개고객누적수
  if (isset($row['RS11'])) $row['RS11'] = '';  // 예약메모 삭제
  if (isset($row['PA11'])) $row['PA11'] = '';  // 수납메모 삭제

  if (isset($row['DS11'])) {
   if (!isset($row['DS10']) || strpos($row['DS10'], '치료정보') === false) {
    $row['DS11'] = ''; // 치료정보가 아닐 경우 진료메모 삭제
   } // End of If
  } // End of If
  
  // 고객이름 한글/영문 외 제거 (괄호 내용 포함 특수문자/숫자 삭제)
  if (isset($row['CS08'])) {
   $row['CS08'] = preg_replace('/\([^)]*\)|\[[^\]]*\]/', '', $row['CS08']); // 괄호와 그 안의 내용 제거
   $row['CS08'] = preg_replace('/[^가-힣a-zA-Z\s]/u', '', $row['CS08']); // 한글, 영문, 공백 외 모두 제거
   $row['CS08'] = trim($row['CS08']); // 양끝 공백 제거
  } // End of If
  if (isset($row['RS21'])) {
   $row['RS21'] = preg_replace('/\([^)]*\)|\[[^\]]*\]/', '', $row['RS21']); // 괄호와 그 안의 내용 제거
   $row['RS21'] = preg_replace('/[^가-힣a-zA-Z\s]/u', '', $row['RS21']); // 한글, 영문, 공백 외 모두 제거
   $row['RS21'] = trim($row['RS21']); // 양끝 공백 제거
  } // End of If
  
  $rows[] = $row; // 행 추가
  if (count($rows) >= $batch_size) {
   insertBatch($target_conn, $db_name, $table, $rows); // 배치 실행
   $count += count($rows); // 누적
   $rows = []; // 배열 초기화
  } // End of If
 } // End of While
 
 if (count($rows) > 0) {
  insertBatch($target_conn, $db_name, $table, $rows); // 남은 데이터 실행
  $count += count($rows); // 누적
 } // End of If
 
 writeLog("[완료] {$db_name}.{$table} 복사 완료: {$count} 건"); // 성공 로깅
 return $count; // 건수 반환
} // End of Function copyTableData

/**
 * 쉼표로 구분된 문자열을 SQL IN 절 형태의 문자열로 변환합니다.
 */
function getInClause($comma_str) {
 if (empty($comma_str)) return "('')"; // End of If
 $items = explode(',', $comma_str); // 배열로 분리
 $quoted = array_map(function($item) { return "'" . trim($item) . "'"; }, $items); // 작은따옴표 감싸기
 return "(" . implode(',', $quoted) . ")"; // 괄호로 묶기
} // End of Function getInClause

// --- [메인 로직 실행] ---
writeLog("===== 데이터 이관(Ghost) 스크립트 시작 =====");
$start_time = microtime(true); // 시작 시간 측정

// --- 1. DB 연결 ---
$source_conn = new mysqli($data_server['host'], $data_server['user'], $data_server['pass']); // 소스 연결
if ($source_conn->connect_error) die("소스 서버 연결 실패: " . $source_conn->connect_error); // 연결 검증
$source_conn->set_charset("utf8mb4"); // 캐릭터셋 설정

$target_conn = new mysqli($switch_server['host'], $switch_server['user'], $switch_server['pass']); // 타겟 연결
if ($target_conn->connect_error) die("타겟 서버 연결 실패: " . $target_conn->connect_error); // 연결 검증
$target_conn->set_charset("utf8mb4"); // 캐릭터셋 설정

// --- 2. DB별 처리 루프 ---
foreach ($server_databases as $config) {
 $db_name = $config['HM']; // 처리할 DB명
 $base_type = isset($config['BASE']) ? $config['BASE'] : 'RS'; // BASE 옵션 기준 추출
 writeLog(">>> 처리 시작 DB: {$db_name} (추출기준: {$base_type}) <<<");
 
 // 소스 DB 접근 가능 여부 사전 확인
 try {
  if (!$source_conn->select_db($db_name)) {
   throw new Exception("데이터베이스가 존재하지 않거나 권한이 없습니다.");
  } // End of If
 } catch (Exception $e) {
  writeLog("[에러] 소스 서버에 {$db_name} 데이터베이스가 없거나 접근할 수 없습니다. (건너뜀) - " . $e->getMessage());
  writeLog(">>> 처리 건너뜀 DB: {$db_name} <<<\n");
  continue; // 다음 설정으로 진행
 } // End of TryCatch
 
 // --- 2-1. 타겟 DB 새로 생성 및 선택 ---
 try {
  $target_conn->query("DROP DATABASE IF EXISTS `{$db_name}`"); // 기존 DB 삭제
  $target_conn->query("CREATE DATABASE `{$db_name}` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); // 새 DB 생성
  if (!$target_conn->select_db($db_name)) {
   throw new Exception("타겟 데이터베이스 선택 실패");
  } // End of If
  writeLog("[완료] {$db_name} 데이터베이스 초기화 및 생성 확인");
 } catch (Exception $e) {
  writeLog("[에러] 타겟 서버에 {$db_name} 데이터베이스 생성 또는 접근에 실패했습니다. (건너뜀) - " . $e->getMessage());
  writeLog(">>> 처리 건너뜀 DB: {$db_name} <<<\n");
  continue; // 다음 설정으로 진행
 } // End of TryCatch
  
 // --- 2-2. 테이블 구조 복사 ---
 $tables_res = $source_conn->query("SHOW TABLES"); // 소스 테이블 목록 조회
 while ($row = $tables_res->fetch_row()) {
  $table = $row[0]; // 테이블명
  $create_res = $source_conn->query("SHOW CREATE TABLE `{$table}`"); // 스키마 조회
  $create_row = $create_res->fetch_row(); // 결과 획득
  $create_sql = str_replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS', $create_row[1]); // 에러 방지용 치환
  $target_conn->query($create_sql); // 타겟에 테이블 생성
 } // End of While
 writeLog("[완료] {$db_name} 테이블 구조 복사 완료");
 
 // --- 2-3. 공통 테이블 전체 복사 (HM, EM, GX) ---
 copyTableData($source_conn, $target_conn, $db_name, 'HM', ''); // HM: 조건 없이 전체 복사
 copyTableData($source_conn, $target_conn, $db_name, 'EM', "EM03 = 1 AND EM14 = ''"); // EM: 재직중인 사원만 복사
 copyTableData($source_conn, $target_conn, $db_name, 'GX', "GX03 = 1"); // GX: 정상 코드만 복사
 
 // --- 2-4. BASE 옵션 기준 대상 고객(CS) 코드 추출 ---
 $months_ago = (int)($config['MONTH'] ?? 24); // 설정된 기준 개월
 $start_date = date('Y-m-01', strtotime("-{$months_ago} months")); // 시작일자 계산 (해당 월의 1일 기준)
 writeLog("- 필터링 시작일자: {$start_date}");
 
 $collected_cs_list = []; // 관련 고객 코드 수집용 배열 초기화
 $extract_sql = ""; // 고객 코드 추출 쿼리
 
 if ($base_type === 'RS') {
  // 예약(RS) 기준 추출
  $rs_where_parts = ["RS03 = 1", "RS08 >= '{$start_date}'", "RS20 = '0'"]; // 공지사항 제외
  if (!empty($config['RS'])) {
   $rs_types = array_filter(array_map('trim', explode(',', $config['RS'])), function($v) { return $v !== ''; });
   if (!empty($rs_types)) {
    $rs_in = getInClause(implode(',', $rs_types));
    $rs_where_parts[] = "RS10 IN {$rs_in}";
   } // End of If
  } // End of If
  $rs_where = implode(' AND ', $rs_where_parts); // 조건 결합
  $extract_sql = "SELECT DISTINCT RS07 AS CS01 FROM `{$db_name}`.`RS` WHERE {$rs_where}";
  
 } else if ($base_type === 'DS') {
  // 진료(DS) 기준 추출
  $ds_where_parts = ["DS03 = 1", "DS08 >= '{$start_date}'"];
  if (!empty($config['DS'])) {
   $ds_types = array_filter(array_map('trim', explode(',', $config['DS'])), function($v) { return $v !== ''; });
   if (!empty($ds_types)) {
    $ds_in = getInClause(implode(',', $ds_types));
    $ds_where_parts[] = "DS10 IN {$ds_in}";
   } // End of If
  } // End of If
  if (!empty($config['GX'])) {
   $gx_types = array_filter(array_map('trim', explode(',', $config['GX'])), function($v) { return $v !== ''; });
   $gx_likes = [];
   foreach ($gx_types as $gx_val) {
    $gx_likes[] = "DS16 LIKE '%{$gx_val}%'";
   } // End of Foreach
   if (!empty($gx_likes)) {
    $gx_or_cond = implode(' OR ', $gx_likes);
    $ds_where_parts[] = "DS07 IN (SELECT DS07 FROM `{$db_name}`.`DS` WHERE DS03 = 1 AND ({$gx_or_cond}))";
   } // End of If
  } // End of If
  if ($db_name === 'COHEN') {
   $ds_where_parts[] = "DS07 NOT IN (SELECT CS01 FROM `{$db_name}`.`CS` WHERE CS03 = 1 AND CS20 LIKE '%$$%')";
  } // End of If
  $ds_where = implode(' AND ', $ds_where_parts); // 조건 결합
  $extract_sql = "SELECT DISTINCT DS07 AS CS01 FROM `{$db_name}`.`DS` WHERE {$ds_where}";
  
 } else if ($base_type === 'PA') {
  // 수납(PA) 기준 추출
  $pa_where_parts = ["PA03 = 1", "PA08 >= '{$start_date}'"];
  $pa_where = implode(' AND ', $pa_where_parts); // 조건 결합
  $extract_sql = "SELECT DISTINCT PA07 AS CS01 FROM `{$db_name}`.`PA` WHERE {$pa_where}";
  
 } else if ($base_type === 'CS') {
  // 고객(CS) 전체 기준 추출
  $cs_where_parts = ["CS03 = 1", "CS21 >= '{$start_date}'"];
  $cs_where = implode(' AND ', $cs_where_parts); // 조건 결합
  $extract_sql = "SELECT DISTINCT CS01 FROM `{$db_name}`.`CS` WHERE {$cs_where}";
 } // End of If
 
 // 고객 추출 쿼리 실행
 if (!empty($extract_sql)) {
  $res = $source_conn->query($extract_sql); // 추출 쿼리 조회
  if ($res) {
   while ($row = $res->fetch_assoc()) {
    if (!empty($row['CS01'])) {
     $collected_cs_list[] = $row['CS01']; // 고객 코드 수집
    } // End of If
   } // End of While
  } else {
   writeLog("[에러] 고객 목록 추출 실패: " . $source_conn->error); // 에러 처리
  } // End of If
 } // End of If

 // --- 2-5. 수집된 고객 코드 기준 연관 데이터(CS, DS, RS, PA) 청크 단위 복사 ---
 $unique_cs_list = array_unique($collected_cs_list); // 고객 코드 중복 제거
 $cs_total_count = count($unique_cs_list); // 대상 고객 수
 writeLog("- 추출된 유효 고객(CS) 수: {$cs_total_count} 명");
 
 if ($cs_total_count > 0) {
  $cs_chunks = array_chunk($unique_cs_list, 1000); // 1000명 단위로 청크 분할 (SQL 길이 제한 방지)
  $cs_copied = 0; // CS 복사 건수 누적
  $ds_copied = 0; // DS 복사 건수 누적
  $rs_copied = 0; // RS 복사 건수 누적
  $pa_copied = 0; // PA 복사 건수 누적
  
  foreach ($cs_chunks as $chunk) {
   $in_str = implode(",", array_map('intval', $chunk)); // 정수형 포맷팅으로 안전하게 결합
   
   // 1. 고객(CS) 복사
   $cs_where = "CS01 IN ({$in_str})"; // 고객 쿼리 조건
   $cs_copied += copyTableData($source_conn, $target_conn, $db_name, 'CS', $cs_where); // 청크 단위 복사
   
   // 2. 진료(DS) 복사 (설정된 필터 조건 유지)
   $ds_where_parts = ["DS03 = 1", "DS07 IN ({$in_str})"]; // 기본 쿼리 조건
   if (!empty($config['DS'])) {
    $ds_types = array_filter(array_map('trim', explode(',', $config['DS'])), function($v) { return $v !== ''; });
    if (!empty($ds_types)) {
     $ds_in = getInClause(implode(',', $ds_types)); // DS 진료구분 조건 추가
     $ds_where_parts[] = "DS10 IN {$ds_in}";
    } // End of If
   } // End of If
   if (!empty($config['GX'])) {
    $gx_types = array_filter(array_map('trim', explode(',', $config['GX'])), function($v) { return $v !== ''; });
    $gx_likes = [];
    foreach ($gx_types as $gx_val) {
     $gx_likes[] = "DS16 LIKE '%{$gx_val}%'"; // 개별 LIKE 조건 추가
    } // End of Foreach
    if (!empty($gx_likes)) {
     $gx_or_cond = implode(' OR ', $gx_likes); // OR 조건 결합
     $ds_where_parts[] = "DS07 IN (SELECT DS07 FROM `{$db_name}`.`DS` WHERE DS03 = 1 AND ({$gx_or_cond}))";
    } // End of If
   } // End of If
   if ($db_name === 'COHEN') {
    $ds_where_parts[] = "DS07 NOT IN (SELECT CS01 FROM `{$db_name}`.`CS` WHERE CS03 = 1 AND CS20 LIKE '%$$%')"; // 제외 조건 서브쿼리 추가
   } // End of If
   
   $ds_where = implode(' AND ', $ds_where_parts); // 쿼리 조건 결합
   $ds_copied += copyTableData($source_conn, $target_conn, $db_name, 'DS', $ds_where); // 청크 단위 복사
   
   $ds1_where = "DS101 IN (SELECT DS01 FROM `{$db_name}`.`DS` WHERE {$ds_where})"; // DS1 상세 조건 지정
   copyTableData($source_conn, $target_conn, $db_name, 'DS1', $ds1_where); // DS1 복사 실행

   // 3. 예약(RS) 복사 (설정된 필터 조건 유지)
   $rs_where_parts = ["RS03 = 1", "RS07 IN ({$in_str})", "RS20 = '0'"]; // 기본 쿼리 조건 (공지사항 제외)
   if (!empty($config['RS'])) {
    $rs_types = array_filter(array_map('trim', explode(',', $config['RS'])), function($v) { return $v !== ''; });
    if (!empty($rs_types)) {
     $rs_in = getInClause(implode(',', $rs_types)); // RS 예약분류 조건 추가
     $rs_where_parts[] = "RS10 IN {$rs_in}";
    } // End of If
   } // End of If
   
   $rs_where = implode(' AND ', $rs_where_parts); // 쿼리 조건 결합
   $rs_copied += copyTableData($source_conn, $target_conn, $db_name, 'RS', $rs_where); // 청크 단위 복사
   
   $rs1_where = "RS101 IN (SELECT RS01 FROM `{$db_name}`.`RS` WHERE {$rs_where})"; // RS1 상세 조건 지정
   copyTableData($source_conn, $target_conn, $db_name, 'RS1', $rs1_where); // RS1 복사 실행
   
   // 4. 수납(PA) 복사 (추가조건 반영)
   $pa_extra_cond = "(PA17 = '현금영수증' OR PA18 = '부가세' OR PA19 = 'SG' OR PA23 <= 100000 OR PA24 > 0)"; // 수납 필터링 조건
   $pa_where = "PA03 = 1 AND PA07 IN ({$in_str}) AND {$pa_extra_cond}"; // 쿼리 조건 결합
   $pa_copied += copyTableData($source_conn, $target_conn, $db_name, 'PA', $pa_where); // 청크 단위 복사
  } // End of Foreach
  
  writeLog("[완료] {$db_name}.CS 총 병합 복사 완료: {$cs_copied} 건");
  writeLog("[완료] {$db_name}.DS (및 DS1) 총 병합 복사 완료: {$ds_copied} 건");
  writeLog("[완료] {$db_name}.RS (및 RS1) 총 병합 복사 완료: {$rs_copied} 건");
  writeLog("[완료] {$db_name}.PA 총 병합 복사 완료: {$pa_copied} 건");
 } else {
  writeLog("[알림] 복사할 대상 고객 데이터(CS)가 없습니다."); // 예외 로깅
 } // End of If
 
 writeLog(">>> 처리 완료 DB: {$db_name} <<<\n");
} // End of Foreach

// --- 3. 연결 종료 및 마무리 ---
$source_conn->close(); // 소스 연결 종료
$target_conn->close(); // 타겟 연결 종료

$end_time = microtime(true); // 종료 시간 측정
$duration = round($end_time - $start_time, 2); // 소요 시간 계산
writeLog("===== 데이터 이관(Ghost) 스크립트 종료 (소요시간: {$duration}초) =====");
?>
EOF

END_DATETIME=$(date "+%Y-%m-%d %H:%M:%S")
echo "----------------------------------------------------"
echo "이관 완료: $END_DATETIME"
echo "----------------------------------------------------"