// =============================================================================
// 1. DOM 초기화 및 마스크 설정 (시스템 초기화 관련)
// =============================================================================

// 날짜피커 한국어 설정
$.fn.datepicker.dates['kr'] = {
	    days: ["일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일"],
	    daysShort: ["일", "월", "화", "수", "목", "금", "토"],
	    daysMin: ["일", "월", "화", "수", "목", "금", "토"],
	    months: ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"],
	    monthsShort: ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"],
	    today: "오늘",
	    clear: "Clear",
	    format: "yyyy-mm-dd",
	    titleFormat: "yyyy MM",
	    weekStart: 0
};

// DOM 준비 완료 시 초기화 함수들
$(document).ready(function () {

	$("body").on("focus", ".datepicker", function () {
		$(this).inputmask('9999-99-99');
		$(this).datepicker({
			format: "yyyy-mm-dd",
			language: "kr"
		});
	});

    $("body").on("focus", ".yearpicker", function () {
        $(this).inputmask('9999');
        $(this).datepicker({
            format: "yyyy",
            viewMode: "years",      // 시작 뷰를 '연도'로 설정
            minViewMode: "years",   // 최소 뷰 단위를 '연도'로 설정
            format: "yyyy",
            language: "kr",
            autoclose: true
        });
    });

	$("body").on("focus", ".timeMask", function () {
		$(this).inputmask("(09|19|20|21|22|23):(09|19|29|39|49|59)");
	});

	$("body").on("focus", ".numericMask", function () {
		$(this).inputmask("numeric", {
			autoGroup: true, // default: false, 정수 부분 그룹화 사용 여부
			groupSeparator: ",", // default: "", 그룹 구분자 정의
			digits: 0, // default: "*", 소수 크기 정의
			allowMinus: false, // default: true, 음수 사용 여부
			repeat: 12, //
			autoUnmask: true
		});
	});

	// 중요로직: 소수점을 허용하는 숫자 마스크 (지적 등에 사용)
	$("body").on("focus", ".decimalMask", function () {
		$(this).inputmask("numeric", {
			autoGroup: true, // 그룹화 사용
			groupSeparator: ",", // 그룹 구분자
			digits: 2, // 소수점 2자리까지 허용
			allowMinus: false, // 음수 사용 안함
			repeat: 15, // 최대 15자리
			autoUnmask: true
		});
	});

    // 중요로직: - decimal(4,4) 스펙에 맞춤
    $("body").on("focus", ".decimalMask4", function () {
        $(this).inputmask("numeric", {
            autoGroup: true, // 그룹화 사용 안함
            allowMinus: false, // 음수 사용 안함
            autoUnmask: true,
            groupSeparator: ",", // 그룹 구분자 없음
            repeat: 1, // 정수부 최대 1자리 (0)
            digits: 4,  // 소수점 최대 4자리
        });
    });

	// 중요로직: PTOUT 지적 전용 마스크 - decimal(22,2) 스펙에 맞춤
	$("body").on("focus", ".ptoutMask", function () {
		$(this).inputmask("numeric", {
			autoGroup: true, // 그룹화 사용 안함
			groupSeparator: ",", // 그룹 구분자 없음
			digits: 2, // 소수점 2자리까지 허용
			allowMinus: false, // 음수 사용 안함
			repeat: 20, // 정수부 최대 20자리
			autoUnmask: true
		});
	});

	// 중요로직: OALP 공시지가 전용 마스크 - decimal(13,0) 스펙에 맞춤
	$("body").on("focus", ".oalpMask", function () {
		$(this).inputmask("numeric", {
			autoGroup: true, // 그룹화 사용 안함
			groupSeparator: ",", // 그룹 구분자 없음
			digits: 0, // 소수점 사용 안함
			allowMinus: false, // 음수 사용 안함
			repeat: 13, // 정수부 최대 13자리
			autoUnmask: true
		});
	});

	// 중요로직: Strct_Idx_Cd 전용 마스크 - 숫자 3자리
	$("body").on("focus", ".strctIdxCdMask", function () {
	    $(this).inputmask({
	        mask: "999", // 3자리 숫자 패턴 (선행 0 포함)
	        placeholder: "000", // 입력 안내용 플레이스홀더
	        definitions: {
	            "9": {
	                validator: "[0-9]", // 숫자만 허용
	                cardinality: 1
	            }
	        },
	        autoUnmask: true, // 마스크 제거 후 실제 값 반환
	        rightAlign: false // 왼쪽 정렬
	    });
	});

	// 중요로직: Strct_Idx 전용 마스크 - decimal(10,2) 스펙에 맞춤
	$("body").on("focus", ".strctIdxMask", function () {
		$(this).inputmask("numeric", {
			autoGroup: true, // 그룹화 사용 안함
			allowMinus: false, // 음수 사용 안함
			autoUnmask: true,
			rightAlign: false,
			groupSeparator: ",", // 그룹 구분자 없음
			repeat: 8, // 정수부 최대 2자리
			digits: 2  // 소수점 사용 안함
		});
	});

	// 중요로직: RDVLRT_CN_YR_CNT 잔가율 내용 연도 수 전용 마스크 - decimal(2,0) 스펙에 맞춤
	$("body").on("focus", ".rdvlrtCnYrCntMask", function () {
		$(this).inputmask("numeric", {
			autoGroup: true, // 그룹화 사용 안함
			allowMinus: false, // 음수 사용 안함
			autoUnmask: true,
			rightAlign: false,
			groupSeparator: ",", // 그룹 구분자 없음
			repeat: 2, // 정수부 최대 2자리
			digits: 0  // 소수점 사용 안함
		});
	});

	// 중요로직: LAST_YR_RDVLRT 최종 연도 잔가율 전용 마스크 - decimal(4,2) 스펙에 맞춤
	$("body").on("focus", ".lastYrRdvlrtMask", function () {
		$(this).inputmask("numeric", {
			autoGroup: true, // 그룹화 사용 안함
			allowMinus: false, // 음수 사용 안함
			autoUnmask: true,
			rightAlign: false,
			groupSeparator: ",", // 그룹 구분자 없음
			repeat: 2, // 정수부 최대 2자리
			digits: 2  // 소수점 최대 2자리
		});
	});

	// 중요로직: DPRT 감가상각률 전용 마스크 - decimal(4,4) 스펙에 맞춤
	$("body").on("focus", ".dprtMask", function () {
		$(this).inputmask("numeric", {
			autoGroup: true, // 그룹화 사용 안함
			allowMinus: false, // 음수 사용 안함
			autoUnmask: true,
			rightAlign: false,
			groupSeparator: ",", // 그룹 구분자 없음
			repeat: 1, // 정수부 최대 1자리 (0)
			digits: 4,  // 소수점 최대 4자리
			max: 0.9999
		});
	});

	// 중요로직: Usg_Idx_Cd 전용 마스크 - 숫자 5자리
	$("body").on("focus", ".usgIdxCdMask", function () {
	    $(this).inputmask({
	        mask: "99999", // 3자리 숫자 패턴 (선행 0 포함)
	        placeholder: "00000", // 입력 안내용 플레이스홀더
	        definitions: {
	            "9": {
	                validator: "[0-9]", // 숫자만 허용
	                cardinality: 1
	            }
	        },
	        autoUnmask: true, // 마스크 제거 후 실제 값 반환
	        rightAlign: false // 왼쪽 정렬
	    });
	});

	// 중요로직: Usg_Idx 전용 마스크 - decimal(10,2) 스펙에 맞춤
	$("body").on("focus", ".usgIdxMask", function () {
		$(this).inputmask("numeric", {
			autoGroup: true, // 그룹화 사용 안함
			allowMinus: false, // 음수 사용 안함
			autoUnmask: true,
			rightAlign: false,
			groupSeparator: ",", // 그룹 구분자 없음
			repeat: 8, // 정수부 최대 2자리
			digits: 2  // 소수점 사용 안함
		});
	});

	// 중요로직: Act_Type_Cd 전용 마스크 - 숫자 3자리
	$("body").on("focus", ".actTypeCdMask", function () {
	    $(this).inputmask({
	        mask: "999", // 3자리 숫자 패턴 (선행 0 포함)
	        placeholder: "000", // 입력 안내용 플레이스홀더
	        definitions: {
	            "9": {
	                validator: "[0-9]", // 숫자만 허용
	                cardinality: 1
	            }
	        },
	        autoUnmask: true, // 마스크 제거 후 실제 값 반환
	        rightAlign: false // 왼쪽 정렬
	    });
	});

	// 중요로직: Cmpttn_Rt 전용 마스크 - 숫자 3자리
	$("body").on("focus", ".cmpttnRtMask", function () {
		$(this).inputmask("numeric", {
			autoGroup: true, // 그룹화 사용 안함
			allowMinus: false, // 음수 사용 안함
			autoUnmask: true,
			rightAlign: false,
			groupSeparator: ",", // 그룹 구분자 없음
			repeat: 3, // 정수부 최대 3자리
			digits: 0, // 소수점 사용 안함
			max: 100
		});
	});

});

// =============================================================================
// 2. URL 파라미터 관리 함수들 (검색 조건 유지 및 파라미터 관리)
// =============================================================================

/**
 * URL의 쿼리 파라미터를 객체로 변환
 *
 * @param {string} search - location.search 문자열 (기본값: 현재 페이지의 location.search)
 * @returns {object} 파라미터 객체
 */
var getUrlParams = function(search) {
    // 파라미터가 없으면 현재 페이지의 search 사용
    var searchStr = search || window.location.search;

    // 빈 객체 생성
    var params = {};

    // 파라미터가 없으면 빈 객체 반환
    if (!searchStr) {
        return params;
    }

    // 맨 앞의 '?' 제거
    if (searchStr.startsWith('?')) {
        searchStr = searchStr.substring(1);
    }

    // 파라미터 분리 및 객체로 변환
    var pairs = searchStr.split('&');
    for (var i = 0; i < pairs.length; i++) {
        var pair = pairs[i].split('=');
        // 디코딩하여 저장
        if (pair[0]) {
            params[decodeURIComponent(pair[0])] = pair.length > 1 ? decodeURIComponent(pair[1] || '') : '';
        }
    }

    return params;
};

/**
 * URL에서 파라미터 값 가져오기
 *
 * @param {string} paramName - 가져올 파라미터 이름
 * @param {string} defaultValue - 파라미터가 없을 경우 기본값
 * @param {string} url - 대상 URL (기본값: 현재 페이지 URL)
 * @returns {string} 파라미터 값 또는 기본값
 */
var getUrlParam = function(paramName, defaultValue, url) {
    // URL이 없으면 현재 페이지 URL 사용
    var targetUrl = url || window.location.href;
    var params = getUrlParams(targetUrl.split('?')[1] ? '?' + targetUrl.split('?')[1] : '');

    // 파라미터 값이 있으면 반환, 없으면 기본값 반환
    return params[paramName] !== undefined ? params[paramName] : (defaultValue || null);
};

/**
 * URL에 파라미터 추가
 *
 * @param {string|object} param - 추가할 파라미터 이름 또는 파라미터 객체
 * @param {string} value - 파라미터 값 (param이 문자열일 경우)
 * @param {string} url - 대상 URL (기본값: 현재 페이지 URL)
 * @returns {string} 파라미터가 추가된 URL
 */
var addUrlParam = function(param, value, url) {
    // URL이 없으면 현재 페이지 URL 사용
    var targetUrl = url || window.location.href;
    var params = getUrlParams(targetUrl.split('?')[1] ? '?' + targetUrl.split('?')[1] : '');
    var baseUrl = targetUrl.split('?')[0];

    // param이 객체인 경우
    if (typeof param === 'object') {
        for (var key in param) {
            if (param.hasOwnProperty(key)) {
                params[key] = param[key];
            }
        }
    } else {
        // param이 문자열인 경우
        params[param] = value;
    }

    // 객체를 쿼리 문자열로 변환
    var queryString = Object.keys(params)
        .filter(function(key) {
            return params[key] !== null && params[key] !== undefined;
        })
        .map(function(key) {
            return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
        })
        .join('&');

    // 쿼리 문자열이 있으면 '?'를 붙여서 반환
    return baseUrl + (queryString ? '?' + queryString : '');
};

/**
 * URL에서 파라미터 제거
 *
 * @param {string|array} paramName - 제거할 파라미터 이름 또는 이름 배열
 * @param {string} url - 대상 URL (기본값: 현재 페이지 URL)
 * @returns {string} 파라미터가 제거된 URL
 */
var removeUrlParam = function(paramName, url) {
    // URL이 없으면 현재 페이지 URL 사용
    var targetUrl = url || window.location.href;
    var params = getUrlParams(targetUrl.split('?')[1] ? '?' + targetUrl.split('?')[1] : '');
    var baseUrl = targetUrl.split('?')[0];

    // paramName이 배열인 경우
    if (Array.isArray(paramName)) {
        for (var i = 0; i < paramName.length; i++) {
            delete params[paramName[i]];
        }
    } else {
        // paramName이 문자열인 경우
        delete params[paramName];
    }

    // 객체를 쿼리 문자열로 변환
    var queryString = Object.keys(params)
        .filter(function(key) {
            return params[key] !== null && params[key] !== undefined;
        })
        .map(function(key) {
            return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
        })
        .join('&');

    // 쿼리 문자열이 있으면 '?'를 붙여서 반환
    return baseUrl + (queryString ? '?' + queryString : '');
};

/**
 * URL에서 파라미터 값 변경
 *
 * @param {string|object} param - 변경할 파라미터 이름 또는 파라미터 객체
 * @param {string} value - 변경할 파라미터 값 (param이 문자열일 경우)
 * @param {string} url - 대상 URL (기본값: 현재 페이지 URL)
 * @returns {string} 파라미터가 변경된 URL
 */
var updateUrlParam = function(param, value, url) {
    // addUrlParam 함수를 사용하여 파라미터 추가 또는 변경
    return addUrlParam(param, value, url);
};

/**
 * 현재 URL의 파라미터를 유지하면서 새 URL 생성
 *
 * @param {object} additionalParams - 추가할 파라미터 객체 (선택사항)
 * @param {array} excludeParams - 제외할 파라미터 이름 배열 (선택사항)
 * @param {string} newUrl - 새 URL 기본 주소
 * @returns {string} 파라미터가 포함된 새 URL
 */
var buildUrlWithCurrentParams = function(additionalParams, excludeParams, newUrl) {
    // 현재 URL의 파라미터 가져오기
    var currentParams = getUrlParams();

    // 제외할 파라미터 처리
    if (excludeParams && Array.isArray(excludeParams)) {
        for (var i = 0; i < excludeParams.length; i++) {
            delete currentParams[excludeParams[i]];
        }
    }

    // 새 URL 생성
    var resultUrl = newUrl;

    // 현재 파라미터 추가
    for (var key in currentParams) {
        if (currentParams.hasOwnProperty(key)) {
            resultUrl = addUrlParam(key, currentParams[key], resultUrl);
        }
    }

    // 추가 파라미터 처리
    if (additionalParams && typeof additionalParams === 'object') {
        resultUrl = addUrlParam(additionalParams, null, resultUrl);
    }

    return resultUrl;
};

/**
 * 파라미터를 쿼리 문자열로 변환
 *
 * @param {object} params - 파라미터 객체
 * @returns {string} 쿼리 문자열 ('?' 포함)
 */
var paramsToQueryString = function(params) {
    if (!params || typeof params !== 'object') {
        return '';
    }

    var queryString = Object.keys(params)
        .filter(function(key) {
            return params[key] !== null && params[key] !== undefined;
        })
        .map(function(key) {
            return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
        })
        .join('&');

    return queryString ? '?' + queryString : '';
};

/**
 * 파라미터 객체와 ID를 URL에 추가
 *
 * @param {object} paramCond - 파라미터 객체 (예: ALL_PARAM_COND)
 * @param {string} idName - ID 파라미터 이름
 * @param {string} idValue - ID 파라미터 값
 * @param {string} baseUrl - 기본 URL
 * @returns {string} 모든 파라미터가 추가된 URL
 */
var buildUrlWithParamCondAndId = function(paramCond, idName, idValue, baseUrl) {
    // 파라미터 객체가 정의되어 있지 않으면 기본 URL에 ID만 추가
    if (typeof paramCond === 'undefined' || !paramCond) {
        return addUrlParam(idName, idValue, baseUrl);
    }

    // 1. 기본 URL에 파라미터 객체의 모든 파라미터 추가
    var urlWithParams = addUrlParam(paramCond, null, baseUrl);

    // 2. ID 파라미터 추가
    return addUrlParam(idName, idValue, urlWithParams);
};

/**
 * 파라미터 객체와 여러 개의 key-value 쌍을 URL에 추가
 *
 * @param {object} paramCond - 파라미터 객체 (예: ALL_PARAM_COND)
 * @param {object} additionalParams - 추가할 파라미터들의 key-value 객체 (예: {key1: value1, key2: value2})
 * @param {string} baseUrl - 기본 URL
 * @returns {string} 모든 파라미터가 추가된 URL
 */
var buildUrlWithParamCondAndMultipleKeys = function(paramCond, additionalParams, baseUrl) {
    // 파라미터 객체가 정의되어 있지 않으면 기본 URL에 추가 파라미터만 추가
    if (typeof paramCond === 'undefined' || !paramCond) {
        return addUrlParam(additionalParams, null, baseUrl);
    }

    // 1. 기본 URL에 파라미터 객체의 모든 파라미터 추가
    var urlWithParams = addUrlParam(paramCond, null, baseUrl);

    // 2. 추가 파라미터들 추가
    if (additionalParams && typeof additionalParams === 'object') {
        urlWithParams = addUrlParam(additionalParams, null, urlWithParams);
    }

    return urlWithParams;
};

/**
 * URL에 컨택스트 패스를 적용하는 유틸리티 함수
 * 상대 URL을 받아서 컨택스트 패스가 적용된 절대 URL을 반환
 *
 * @param {string} url - 상대 URL (예: '/user/list.do', '/bbs/user/post/comment/register.ajax')
 * @returns {string} 컨택스트 패스가 적용된 URL
 */
var buildContextUrl = function(url) {
    // contextPath 변수가 정의되지 않은 경우 기본값 사용
    if (typeof contextPath === 'undefined') {
        console.warn('contextPath 변수가 정의되지 않았습니다. 기본값("")을 사용합니다.');
        return url;
    }

    // URL이 비어있거나 null인 경우 처리
    if (!url || url.trim() === '') {
        return url;
    }

    // 이미 절대 URL인 경우 (http:// 또는 https://로 시작) 그대로 반환
    if (url.startsWith('http://') || url.startsWith('https://')) {
        return url;
    }

    // 상대 URL인 경우 컨택스트 패스 적용
    // URL이 '/'로 시작하지 않는 경우 '/' 추가
    var normalizedUrl = url.startsWith('/') ? url : '/' + url;

    // 컨택스트 패스가 빈 문자열이거나 '/'인 경우 처리
    if (!contextPath || contextPath === '/') {
        return normalizedUrl;
    }

    // 컨택스트 패스와 URL 결합 (중복된 '/' 제거)
    var result = contextPath;
    if (!contextPath.endsWith('/') && !normalizedUrl.startsWith('/')) {
        result += '/';
    } else if (contextPath.endsWith('/') && normalizedUrl.startsWith('/')) {
        normalizedUrl = normalizedUrl.substring(1);
    }
    result += normalizedUrl;

    return result;
};

// =============================================================================
// 3. 공통 코드 및 선택박스 관리
// =============================================================================

/**
 * 공통코드를 조회하여 select box에 option을 추가하는 함수
 * 중요로직: 코드 그룹 ID를 기준으로 해당 코드들을 조회하여 select box를 동적으로 생성
 * 확장된 파라미터를 통해 정렬 컬럼, 정렬 방향, 추가 검색 조건 등을 설정할 수 있음
 *
 * @param {string} cdGroupId - 코드 그룹 ID (예: 'RGN_SE_CD', 'DSCL_MTHD_CD' 등)
 * @param {string} selectId - select 엘리먼트의 ID
 * @param {string} firstOptionText - 첫번째 옵션 텍스트 (예: '전체', '선택하세요' 등)
 * @param {string} selectedValue - 선택될 값 (옵션)
 * @param {object} options - 추가 옵션 객체 (옵션)
 * @param {string} options.sortColumn - 정렬 컬럼 (예: 'SORT_ORDR', 'CD_NM', 'CD_ID' 등)
 * @param {boolean} options.sortAscending - 정렬 방향 (true: 오름차순, false: 내림차순)
 * @param {string} options.searchCdId - 코드 ID 검색 조건
 * @param {string} options.searchCdNm - 코드명 검색 조건
 * @param {string} options.searchUseYn - 사용 여부 (기본값: 'Y')
 * @param {string} options.searchAttribute1 - 속성1 검색 조건
 * @param {string} options.searchAttribute2 - 속성2 검색 조건
 * @param {string} options.searchAttribute3 - 속성3 검색 조건
 * @param {string} options.searchAttribute4 - 속성4 검색 조건
 * @param {string} options.searchAttribute5 - 속성5 검색 조건
 * @param {string} options.searchRegDttmStart - 등록일시 시작일
 * @param {string} options.searchRegDttmEnd - 등록일시 종료일
 * @param {string} options.searchRgtr - 등록자 검색 조건
 * @param {string} options.searchMdfcnDttmStart - 수정일시 시작일
 * @param {string} options.searchMdfcnDttmEnd - 수정일시 종료일
 * @param {string} options.searchMdfr - 수정자 검색 조건
 */
function commonCodeSelectAjax(cdGroupId, selectId, firstOptionText, selectedValue, options) {
    if (!cdGroupId || !selectId) {
        console.error('commonCodeSelectAjax: cdGroupId와 selectId는 필수 파라미터입니다.');
        return;
    }

    // select 엘리먼트 찾기
    var $select = $('#' + selectId);
    if ($select.length === 0) {
        console.error('commonCodeSelectAjax: ID "' + selectId + '"인 select 엘리먼트를 찾을 수 없습니다.');
        return;
    }

    // 기존 option 초기화 (첫번째 옵션 제외하고 모두 삭제)
    $select.find('option:gt(0)').remove();

    // 첫번째 옵션이 제공된 경우 설정
    if (firstOptionText) {
        $select.find('option:first').text(firstOptionText).val('');
    }

    // options 객체가 없으면 빈 객체로 초기화
    options = options || {};

    // AJAX 요청 데이터 구성 - 중요로직: 백엔드 CmmnCodeSearchVO와 정확히 매핑
    var requestData = {
        searchCdGroupId: cdGroupId,  // 필수 파라미터
        searchUseYn: options.searchUseYn || 'Y'  // 기본값: 사용 중인 코드만 조회
    };

    // 추가 검색 조건들을 requestData에 추가 (값이 있는 경우만)
    if (options.searchCdId) requestData.searchCdId = options.searchCdId;
    if (options.searchCdNm) requestData.searchCdNm = options.searchCdNm;
    if (options.searchAttribute1) requestData.searchAttribute1 = options.searchAttribute1;
    if (options.searchAttribute2) requestData.searchAttribute2 = options.searchAttribute2;
    if (options.searchAttribute3) requestData.searchAttribute3 = options.searchAttribute3;
    if (options.searchAttribute4) requestData.searchAttribute4 = options.searchAttribute4;
    if (options.searchAttribute5) requestData.searchAttribute5 = options.searchAttribute5;
    if (options.searchRegDttmStart) requestData.searchRegDttmStart = options.searchRegDttmStart;
    if (options.searchRegDttmEnd) requestData.searchRegDttmEnd = options.searchRegDttmEnd;
    if (options.searchRgtr) requestData.searchRgtr = options.searchRgtr;
    if (options.searchMdfcnDttmStart) requestData.searchMdfcnDttmStart = options.searchMdfcnDttmStart;
    if (options.searchMdfcnDttmEnd) requestData.searchMdfcnDttmEnd = options.searchMdfcnDttmEnd;
    if (options.searchMdfr) requestData.searchMdfr = options.searchMdfr;

    // 정렬 관련 파라미터 추가
    if (options.sortColumn) {
        requestData.sortColumn = options.sortColumn;
        // sortAscending이 명시적으로 false인 경우에만 false로 설정 (기본값: true)
        requestData.sortAscending = options.sortAscending !== false;
    }

    // AJAX로 코드 상세 목록 조회
    $.ajax({
        url: '/common/code/detail/list.ajax',
        type: 'GET',
        data: requestData,
        success: function(response) {
            if (response && response.success) {
                // 응답 데이터 구조 확인 (그리드용 응답의 경우 response.data.data에 실제 배열이 있을 수 있음)
                var codeList = null;
                if (response.data && Array.isArray(response.data)) {
                    codeList = response.data;
                } else if (response.data && response.data.data && Array.isArray(response.data.data)) {
                    codeList = response.data.data;
                } else {
                    console.warn('commonCodeSelectAjax: 응답 데이터 구조를 찾을 수 없습니다. cdGroupId: ' + cdGroupId, response);
                    return;
                }

                // 정렬 처리 - 중요로직: 백엔드에서 정렬되지 않은 경우 클라이언트에서 기본 정렬 적용
                if (!options.sortColumn) {
                    // 정렬 컬럼이 지정되지 않은 경우 기본적으로 정렬순서(sortOrdr) 기준으로 정렬
                    codeList.sort(function(a, b) {
                        return (a.sortOrdr || 0) - (b.sortOrdr || 0);
                    });
                }

                // option 추가
                $.each(codeList, function(index, item) {
                    var option = $('<option></option>');
                    option.val(item.cdId).text(item.cdNm);
                    $select.append(option);
                });

                // 선택될 값이 있으면 설정
                if (selectedValue) {
                    $select.val(selectedValue);
                }
            } else {
                console.warn('commonCodeSelectAjax: 코드 조회 결과가 없거나 실패했습니다. cdGroupId: ' + cdGroupId);
            }
        },
        error: function(xhr, status, error) {
            console.error('commonCodeSelectAjax: 코드 조회 중 오류 발생', {
                cdGroupId: cdGroupId,
                status: status,
                error: error
            });
        }
    });
}

// =============================================================================
// 4. UI 컴포넌트 (드롭다운)
// =============================================================================

/**
 * 전역 드롭다운 인스턴스 관리
 * 중요한 로직 주석: 활성화된 드롭다운들을 추적하여 다중 오픈 제어
 */
if (typeof window.XitDropdownInstances === 'undefined') {
    window.XitDropdownInstances = [];
}

/**
 * XIT 공통 드롭다운 컴포넌트
 * 중요한 로직 주석: 재사용 가능한 드롭다운 컴포넌트로 여러 페이지에서 사용할 수 있다.
 */
function XitDropdown(options) {
    // 기본 옵션 설정
    this.options = $.extend({
        inputSelector: null,           // input 필드 셀렉터 (필수)
        hiddenSelector: null,          // hidden 필드 셀렉터 (필수)
        dataUrl: null,                 // 데이터 조회 URL (필수)
        width: 'auto',                 // 드롭다운 넓이
        maxHeight: '300px',            // 드롭다운 최대 높이
        displayFields: [],             // 표시할 필드 배열
        valueField: 'id',              // 값 필드명
        textField: 'name',             // 텍스트 필드명
        searchField: 'name',           // 검색 대상 필드명
        placeholder: '검색어를 입력하세요', // placeholder 텍스트
        noResultsText: '검색 결과가 없습니다', // 검색 결과 없음 텍스트
        cssClass: 'xit-dropdown',      // CSS 클래스명
        disabled: false,               // 비활성화 여부
        allowMultiple: false           // 다중 드롭다운 허용 여부 (기본값: false)
    }, options);

    // 필수 옵션 검증
    if (!this.options.inputSelector || !this.options.hiddenSelector || !this.options.dataUrl) {
        throw new Error('XitDropdown: inputSelector, hiddenSelector, dataUrl은 필수입니다.');
    }

    this.$input = $(this.options.inputSelector);
    this.$hidden = $(this.options.hiddenSelector);
    this.data = [];
    this.filteredData = [];
    this.selectedIndex = -1;
    this.isOpen = false;
    this.keyboardNavActive = false; // 키보드 네비게이션 상태 추적
    this.dropdownId = 'xit-dropdown-' + Date.now() + Math.random().toString(36).substr(2, 9);

    // 전역 드롭다운 인스턴스 배열에 현재 인스턴스 등록
    window.XitDropdownInstances.push(this);

    this.init();
}

/**
 * XitDropdown 프로토타입 메소드들
 */
XitDropdown.prototype = {
    /**
     * 드롭다운 초기화
     * 중요한 로직 주석: DOM 구조를 생성하고 데이터를 로드한 후 이벤트를 바인딩한다.
     */
    init: function() {
        this.createDropdownElement();
        this.loadData();
        this.bindEvents();

        // 비활성화 상태 설정
        if (this.options.disabled) {
            this.$input.prop('disabled', true);
        }
    },

    /**
     * 드롭다운 DOM 엘리먼트 생성
     * 중요한 로직 주석: input 필드 다음에 드롭다운 컨테이너를 추가한다.
     */
    createDropdownElement: function() {
        var containerClass = this.options.cssClass + '-container';
        var dropdownClass = this.options.cssClass;

        // 이미 컨테이너가 있다면 제거
        this.$input.closest('.' + containerClass).find('.' + dropdownClass).remove();

        // 컨테이너가 없다면 생성
        if (!this.$input.parent().hasClass(containerClass)) {
            this.$input.wrap('<div class="' + containerClass + '" style="position: relative; display: inline-block;"></div>');
        }

        // 드롭다운 엘리먼트 생성
        var $dropdown = $('<div>')
            .attr('id', this.dropdownId)
            .addClass(dropdownClass)
            .css({
                'position': 'absolute',
                'top': '100%',
                'left': '0',
                'right': '0',
                'background': 'white',
                'border': '1px solid #ccc',
                'border-top': 'none',
                'border-radius': '0 0 4px 4px',
                'box-shadow': '0 2px 8px rgba(0, 0, 0, 0.1)',
                'z-index': '1000',
                'display': 'none',
                'max-height': this.options.maxHeight,
                'overflow-y': 'auto',
                'width': this.options.width
            });

        this.$input.parent().append($dropdown);
        this.$dropdown = $dropdown;
    },

    /**
     * 데이터 로드
     * 중요한 로직 주석: AJAX로 서버에서 데이터를 가져온다.
     */
    loadData: function() {
        var self = this;
        $.ajax({
            url: this.options.dataUrl,
            type: 'POST',
            success: function(response) {
                if (response && response.success) {
                    self.data = response.data || [];
                    self.filteredData = self.data;
                } else {
                    console.error('XitDropdown: 데이터 로드 실패', response);
                }
            },
            error: function() {
                console.error('XitDropdown: 데이터 로드 중 오류 발생');
            }
        });
    },

    /**
     * 이벤트 바인딩
     * 중요한 로직 주석: input 필드와 드롭다운의 각종 이벤트를 처리한다.
     */
    bindEvents: function() {
        var self = this;

        // input 클릭 시 드롭다운 열기
        this.$input.on('click.xitdropdown', function(e) {
            e.stopPropagation();
            if (!self.options.disabled) {
                self.showDropdown();
            }
        });

        // input 포커스 시 드롭다운 열기
        this.$input.on('focus.xitdropdown', function() {
            if (!self.options.disabled) {
                self.showDropdown();
            }
        });

        // input 키보드 입력 시 실시간 필터링
        this.$input.on('input.xitdropdown', function() {
            if (!self.options.disabled) {
                self.filterData($(this).val());
                self.showDropdown();
                self.selectedIndex = -1;
            }
        });

        // 키보드 네비게이션
        this.$input.on('keydown.xitdropdown', function(e) {
            if (!self.isOpen || self.options.disabled) return;

            switch (e.keyCode) {
                case 38: // 위 화살표
                    e.preventDefault();
                    self.navigateDropdown(-1);
                    break;
                case 40: // 아래 화살표
                    e.preventDefault();
                    self.navigateDropdown(1);
                    break;
                case 13: // 엔터
                    e.preventDefault();
                    self.selectCurrentItem();
                    break;
                case 27: // ESC
                    self.hideDropdown();
                    break;
            }
        });

        // 문서 클릭 시 드롭다운 닫기
        $(document).on('click.xitdropdown-' + this.dropdownId, function(e) {
            if (!$(e.target).closest('.' + self.options.cssClass + '-container').length) {
                self.hideDropdown();
            }
        });
    },

    /**
     * 데이터 필터링
     * 중요한 로직 주석: 검색어에 따라 데이터를 필터링한다. searchField가 배열인 경우 여러 필드에서 검색한다.
     */
    filterData: function(searchTerm) {
        var term = searchTerm.trim().toLowerCase();
        var searchFields = this.options.searchField;
        var self = this;

        if (term === '') {
            this.filteredData = this.data;
        } else {
            this.filteredData = this.data.filter(function(item) {
                // searchField가 배열인지 확인
                if (Array.isArray(searchFields)) {
                    // 배열인 경우: 여러 필드 중 하나라도 검색어가 포함되면 true (OR 조건)
                    return searchFields.some(function(field) {
                        return item[field] && item[field].toString().toLowerCase().indexOf(term) > -1;
                    });
                } else {
                    // 단일 필드인 경우: 기존 로직 유지 (하위 호환성)
                    return item[searchFields] && item[searchFields].toString().toLowerCase().indexOf(term) > -1;
                }
            });
        }
    },

    /**
     * 드롭다운 표시
     * 중요한 로직 주석: 필터링된 데이터를 기반으로 드롭다운을 생성하고 표시한다.
     */
    showDropdown: function() {
        var html = '';
        var self = this;

        // 다중 드롭다운 허용하지 않는 경우 다른 드롭다운들 모두 닫기
        if (!this.options.allowMultiple) {
            window.XitDropdownInstances.forEach(function(instance) {
                if (instance !== self && instance.isOpen) {
                    instance.hideDropdown();
                }
            });
        }

        // 중요한 로직 주석: textField에 이미 값이 있다면 해당 값으로 먼저 필터링 처리
        var currentInputValue = this.$input.val();
        if (currentInputValue && currentInputValue.trim() !== '') {
            this.filterData(currentInputValue);
        } else {
            // textField에 값이 없으면 전체 데이터 표시
            this.filteredData = this.data;
        }

        if (this.filteredData.length === 0) {
            html = '<div class="' + this.options.cssClass + '-item no-results" style="padding: 10px 12px; color: #666; font-style: italic; text-align: center; cursor: default;">' + this.options.noResultsText + '</div>';
        } else {
            this.filteredData.forEach(function(item, index) {
                html += '<div class="' + self.options.cssClass + '-item" data-index="' + index + '" data-value="' + item[self.options.valueField] + '" style="padding: 10px 12px; cursor: pointer; border-bottom: 1px solid #f5f5f5; transition: background-color 0.2s;">';
                html += '  <div class="' + self.options.cssClass + '-main" style="font-size: 14px; font-weight: 600; color: #333; margin-bottom: 4px;">' + item[self.options.textField] + '</div>';

                if (self.options.displayFields.length > 0) {
                    html += '  <div class="' + self.options.cssClass + '-details" style="display: flex; flex-wrap: wrap; gap: 8px; font-size: 12px;">';
                    self.options.displayFields.forEach(function(field) {
                        if (item[field.name] !== undefined) {
                            var value = field.formatter ? field.formatter(item[field.name]) : item[field.name];
                            html += '    <span class="' + self.options.cssClass + '-value" style="color: #666; background-color: #f8f9fa; padding: 2px 6px; border-radius: 3px; font-size: 11px;">' + field.label + ': ' + value + '</span>';
                        }
                    });
                    html += '  </div>';
                }

                html += '</div>';
            });
        }

        this.$dropdown.html(html);
        this.$dropdown.show();
        this.isOpen = true;

        // 클릭 이벤트 바인딩
        this.$dropdown.find('.' + this.options.cssClass + '-item:not(.no-results)').on('click', function() {
            var index = parseInt($(this).attr('data-index'));
            self.selectItem(index);
        });

        // hover 효과 추가 - 키보드 네비게이션과 상호 배타적으로 동작
        this.$dropdown.find('.' + this.options.cssClass + '-item:not(.no-results)').hover(
            function() {
                // 키보드 네비게이션이 활성화된 상태가 아닐 때만 hover 효과 적용
                if (!self.keyboardNavActive) {
                    $(this).css('background-color', '#e3f2fd');
                }
            },
            function() {
                // 키보드 네비게이션이 활성화된 상태가 아니고 선택되지 않은 항목일 때만 스타일 초기화
                if (!self.keyboardNavActive && !$(this).hasClass('selected')) {
                    $(this).css('background-color', '');
                }
            }
        );

        // 마우스가 드롭다운 영역에 들어올 때 키보드 네비게이션 상태 초기화
        this.$dropdown.on('mouseenter', function() {
            if (self.keyboardNavActive) {
                self.keyboardNavActive = false;
                self.selectedIndex = -1;
                self.$dropdown.find('.' + self.options.cssClass + '-item').removeClass('selected').css('background-color', '');
            }
        });

        // 중요한 로직 주석: hidden 필드에 값이 있으면 해당 항목을 찾아 자동 하이라이트 및 위치 이동
        this.highlightPreselectedItem();
    },

    /**
     * 미리 선택된 항목 하이라이트
     * 중요한 로직 주석: hidden 필드에 값이 있으면 해당 항목을 찾아 자동으로 하이라이트하고 스크롤 위치 조정
     */
    highlightPreselectedItem: function() {
        var self = this;
        var hiddenValue = this.$hidden.val();

        // hidden 필드에 값이 없으면 처리하지 않음
        if (!hiddenValue || hiddenValue.trim() === '') {
            return;
        }

        var $items = this.$dropdown.find('.' + this.options.cssClass + '-item:not(.no-results)');
        var targetIndex = -1;

        // valueField와 일치하는 항목 찾기
        this.filteredData.forEach(function(item, index) {
            if (item[self.options.valueField] === hiddenValue) {
                targetIndex = index;
            }
        });

        // 일치하는 항목이 있으면 하이라이트 및 스크롤 조정
        if (targetIndex >= 0 && targetIndex < $items.length) {
            this.selectedIndex = targetIndex;
            var $targetItem = $items.eq(targetIndex);

            // 기존 하이라이트 제거
            $items.removeClass('selected').css('background-color', '');

            // 대상 항목 하이라이트
            $targetItem.addClass('selected').css('background-color', '#e3f2fd');

            // 스크롤 위치 조정
            var dropdownHeight = this.$dropdown.height();
            var itemHeight = $targetItem.outerHeight();
            var scrollTop = this.$dropdown.scrollTop();
            var itemTop = $targetItem.position().top + scrollTop;

            if (itemTop < scrollTop) {
                this.$dropdown.scrollTop(itemTop);
            } else if (itemTop + itemHeight > scrollTop + dropdownHeight) {
                this.$dropdown.scrollTop(itemTop + itemHeight - dropdownHeight);
            }
        }
    },

    /**
     * 드롭다운 숨기기
     * 중요한 로직 주석: 드롭다운을 닫을 때 키값 존재 여부에 따라 textField 값을 조정한다.
     */
    hideDropdown: function() {
        // 중요한 로직 주석: 드롭다운이 닫힐 때 키값이 없으면 텍스트 제거, 키값이 있으면 해당 텍스트로 설정
        this.validateAndSetFieldValues();

        this.$dropdown.hide();
        this.isOpen = false;
        this.selectedIndex = -1;
        this.keyboardNavActive = false; // 키보드 네비게이션 상태 초기화
    },

    /**
     * 필드 값 검증 및 설정
     * 중요한 로직 주석: hidden 필드의 키값에 따라 textField 값을 적절히 조정한다.
     */
    validateAndSetFieldValues: function() {
        var hiddenValue = this.$hidden.val();

        if (!hiddenValue || hiddenValue.trim() === '') {
            // 키값이 없으면 textField 값 제거
            this.$input.val('');
        } else {
            // 키값이 있으면 해당 키값에 맞는 텍스트 값을 찾아서 설정
            var textValue = this.getTextValueByKey(hiddenValue);
            if (textValue) {
                this.$input.val(textValue);
            }
        }
    },

    /**
     * 키값으로 텍스트 값 찾기
     * 중요한 로직 주석: 전체 데이터에서 키값에 해당하는 텍스트 값을 찾는다.
     */
    getTextValueByKey: function(keyValue) {
        var self = this;
        var textValue = '';

        // 전체 데이터에서 키값과 일치하는 항목 찾기
        this.data.forEach(function(item) {
            if (item[self.options.valueField] === keyValue) {
                textValue = item[self.options.textField];
                return false; // 찾았으면 반복 종료
            }
        });

        return textValue;
    },

    /**
     * 드롭다운 네비게이션
     * 중요한 로직 주석: 화살표 키로 드롭다운 항목들을 네비게이션한다.
     */
    navigateDropdown: function(direction) {
        var $items = this.$dropdown.find('.' + this.options.cssClass + '-item:not(.no-results)');
        if ($items.length === 0) return;

        // 키보드 네비게이션 활성화 상태로 설정
        this.keyboardNavActive = true;

        // 이전 선택 항목 하이라이트 제거
        $items.removeClass('selected').css('background-color', '');

        // 새로운 인덱스 계산
        this.selectedIndex += direction;
        if (this.selectedIndex < 0) this.selectedIndex = $items.length - 1;
        if (this.selectedIndex >= $items.length) this.selectedIndex = 0;

        // 새로운 항목 하이라이트
        var $selectedItem = $items.eq(this.selectedIndex);
        $selectedItem.addClass('selected').css('background-color', '#e3f2fd');

        // 스크롤 조정
        var dropdownHeight = this.$dropdown.height();
        var itemHeight = $selectedItem.outerHeight();
        var scrollTop = this.$dropdown.scrollTop();
        var itemTop = $selectedItem.position().top + scrollTop;

        if (itemTop < scrollTop) {
            this.$dropdown.scrollTop(itemTop);
        } else if (itemTop + itemHeight > scrollTop + dropdownHeight) {
            this.$dropdown.scrollTop(itemTop + itemHeight - dropdownHeight);
        }
    },

    /**
     * 현재 선택된 항목 선택
     */
    selectCurrentItem: function() {
        if (this.selectedIndex >= 0 && this.selectedIndex < this.filteredData.length) {
            this.selectItem(this.selectedIndex);
        }
    },

    /**
     * 항목 선택
     * 중요한 로직 주석: 선택된 항목의 값을 input과 hidden 필드에 설정한다.
     */
    selectItem: function(index) {
        if (index >= 0 && index < this.filteredData.length) {
            var selectedItem = this.filteredData[index];
            this.$input.val(selectedItem[this.options.textField]);
            this.$hidden.val(selectedItem[this.options.valueField]);
            this.hideDropdown();

            // 선택 이벤트 발생
            if (this.options.onSelect && typeof this.options.onSelect === 'function') {
                this.options.onSelect(selectedItem);
            }
        }
    },

    /**
     * 드롭다운 비활성화/활성화
     */
    setDisabled: function(disabled) {
        this.options.disabled = disabled;
        this.$input.prop('disabled', disabled);
        if (disabled) {
            this.hideDropdown();
        }
    },

    /**
     * 드롭다운 파괴
     */
    destroy: function() {
        // 전역 인스턴스 배열에서 현재 인스턴스 제거
        var index = window.XitDropdownInstances.indexOf(this);
        if (index > -1) {
            window.XitDropdownInstances.splice(index, 1);
        }

        // 이벤트 제거
        this.$input.off('.xitdropdown');
        $(document).off('.xitdropdown-' + this.dropdownId);

        // DOM 제거
        this.$dropdown.remove();

        // 컨테이너가 빈 경우 원래 상태로 복원
        var $container = this.$input.parent();
        if ($container.hasClass(this.options.cssClass + '-container') && $container.children().length === 1) {
            this.$input.unwrap();
        }
    }
};

// =============================================================================
// 5. 파일 다운로드 및 기타 유틸리티 함수들
// =============================================================================

/**
 * 팝업 창 열기 공통함수
 * 중요로직: 팝업창을 화면 중앙에 위치시켜 열기, 위치 조정 가능
 * @param {string} url - 팝업에서 열 URL
 * @param {number} width - 팝업 너비 (기본값: 600)
 * @param {number} height - 팝업 높이 (기본값: 600)
 * @param {string} windowName - 윈도우 이름 (기본값: 'popup')
 * @param {number} leftOffset - 중앙 위치에서 좌우 조정값 (기본값: 0, 음수면 왼쪽, 양수면 오른쪽)
 * @param {number} topOffset - 중앙 위치에서 상하 조정값 (기본값: 0, 음수면 위쪽, 양수면 아래쪽)
 * @returns {Window} 열린 팝업 창 객체
 */
function openPopup(url, width, height, windowName, leftOffset, topOffset) {
    // 기본값 설정
    width = width || 600;
    height = height || 600;
    windowName = windowName || 'popup';
    leftOffset = leftOffset || 0;
    topOffset = topOffset || 0;

    // 화면 중앙 위치 계산
    var left = Math.max(0, (window.screen.availLeft + (window.screen.availWidth - width) / 2));
    var top = Math.max(0, (window.screen.availTop + (window.screen.availHeight - height) / 2));

    // 중요로직: 최종 계산된 중앙 위치에 조정값 적용
    left = Math.max(0, left + leftOffset);
    top = Math.max(0, top + topOffset);

    // 팝업 옵션 설정
    var popupOptions = 'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top + ',resizable=yes,scrollbars=yes';

    // 팝업 창 열기
    return window.open(url, windowName, popupOptions);
}

/**
 * 자식 팝업창들을 모두 닫고 현재 창을 닫는 공통 함수
 *
 * @param {Array} childPopups - 자식 팝업창 참조 배열
 * @returns {Array} 초기화된 빈 배열
 */
function closeChildPopupsAndSelf(childPopups) {
    // 자식 팝업창들이 배열이 아닌 경우 빈 배열로 초기화
    if (!Array.isArray(childPopups)) {
        childPopups = [];
    }

    // 열려있는 자식 팝업창들을 먼저 닫기
    for (var i = 0; i < childPopups.length; i++) {
        try {
            if (childPopups[i] && !childPopups[i].closed) {
                childPopups[i].close();
            }
        } catch (e) {
            // 팝업창 접근 오류 무시 (이미 닫혔거나 접근 권한 없음)
            console.warn('팝업창 닫기 중 오류 발생:', e.message);
        }
    }

    // 자식 팝업 배열 초기화
    childPopups.length = 0;

    // 자신의 창 닫기
    window.close();

    // 초기화된 배열 반환
    return childPopups;
}

var downloadAjax = function(data, jqXhr) {
	if (!data) {
		alert("다운로드 파일이 없습니다.");
		return;
	}

	try {
		var blob = new Blob([data], { type: jqXhr.getResponseHeader('content-type') });
	    var fileName = getFileName(jqXhr.getResponseHeader('content-disposition'));
	    fileName = decodeURI(fileName);

	    if (window.navigator.msSaveOrOpenBlob) { // IE 10+
	    	window.navigator.msSaveOrOpenBlob(blob, fileName);
	    } else { // not IE
	    	var link = document.createElement('a');
	        var url = window.URL.createObjectURL(blob);
	        link.href = url;
	        link.target = '_self';
	        if (fileName) link.download = fileName;
	        document.body.append(link);
	        link.click();
	        link.remove();
	        window.URL.revokeObjectURL(url);
	    }
	} catch (e) {
		alert("다운로드 중 에러입니다.");
		console.error(e)
	}
}


// =============================================================================
// 6. 핵심 유틸리티 함수들 ( 전체적으로 사용되는 기본 함수들)
// =============================================================================

/**
 * Checks if value is empty. Deep-checks arrays and objects Note: isEmpty([]) ==
 * true, isEmpty({}) == true, isEmpty([{0:false},"",0]) == true, isEmpty({0:1}) ==
 * false
 *
 * @param value
 * @returns {boolean}
 */
function isEmpty(value) {
    if (value === null || value === undefined) {
        return true;
    }

    if (typeof value === 'string' && value.trim() === '') {
        return true;
    }

    if (Array.isArray(value) && value.length === 0) {
        return true;
    }

    if (typeof value === 'object' && Object.keys(value).length === 0) {
        return true;
    }
    return false;

    /*
    기존소스 주석. 0 true 가 이닌 false 처리
    var isEmptyObject = function(a) {
		if(typeof a.length === 'undefined') { // it's an Object, not an Array
			var hasNonempty = Object.keys(a).some(function nonEmpty(element) {
				return !isEmpty(a[element]);
			});
			return hasNonempty ? false : isEmptyObject(Object.keys(a));
		}
		return !a.some(function nonEmpty(element) { // check if array is really
			// not empty as JS thinks
			return !isEmpty(element); // at least one element should be
			// non-empty
		});
	};
	return(value == false || typeof value === 'undefined' || value == null || (typeof value === 'object' && isEmptyObject(value)));
	*/
}

/**
 * null 이나 빈값을 기본값으로 변경
 *
 * @param str
 *            입력값
 * @param defaultVal
 *            기본값(옵션)
 * @returns {String} 체크 결과값
 */
function nvl(str, defaultVal) {
    var defaultValue = "";
    if(typeof defaultVal != 'undefined') {
        defaultValue = defaultVal;
    }
    if(typeof str == "undefined" || str == null || str == '' || str == "undefined") {
        return defaultValue;
    }
    return str;
}

/***********************************************************************************
 * 함 수 명 : isNull
 * 기     능 : null 체크 함수
 * 인     자 : value
 * 리 턴 값 : boolean
 **********************************************************************************/
var isNull = function(value) {
    if (new String(value).valueOf() == "undefined") return true;
    if (value == null) return true;

    value = trimAll(value);
    value = new String(value);
    if (value == null) return true;
    if (value.toString().length == 0) return true;
    return false;
};

/***********************************************************************************
 * 함 수 명 : utils.trimAll
 * 기     능 : 문자열 전체의 공백제거 함수
 * 인     자 : value
 * 리 턴 값 : string
 **********************************************************************************/
var trimAll = function(value) {
    if (value == null) return "";
    if (new String(value).valueOf() == "undefined") return "";

    var rtnValue = "";
    value = new String(value);
    if (value != null) {
        for(var i=0; i<value.length; i++) {
            if (value.charAt(i) != " ") {
                rtnValue = rtnValue + value.charAt(i);
            }
        }
    } else {
        return -1;
    }
    return rtnValue;
};

/**
 * 길이체크
 *
 * @param str
 * @returns {Number}
 */
function checkLength(str) {
    var stringLength = str.length;
    var stringByteLength = 0;
    for(var i = 0; i < stringLength; i++) {
        if(escape(str.charAt(i)).length >= 4) {
            stringByteLength += 3;
        } else if(escape(str.charAt(i)) == "%A7") {
            stringByteLength += 3;
        } else {
            if(escape(str.charAt(i)) != "%0D") {
                stringByteLength++;
            }
        }
    }
    return stringByteLength;
}

/**
 * Left 빈자리 만큼 str 을 붙인다.
 * @param src : Right에 붙을 원본 데이터
 * @param len : str붙힐 데이터 길이
 * @param str : 대상 데이터
 * @returns : str과 src가 붙은 데이터
 * @example : lpad("123123", 10, " ");
 */
function lpad(src, len, str) {
    var retStr = "";
    var padCnt = Number(len) - String(src).length;

    for(var i=0;i<padCnt;i++) {
        retStr += String(str);
    }

    return retStr+src;
}

/**
 * Right 빈자리 만큼 str 을 붙인다.
 * @param src : Left에 붙을 원본 데이터
 * @param len : str붙힐 데이터 길이
 * @param str : 대상 데이터
 * @returns : str과 src가 붙은 데이터
 * @example : rpad("123123", 10, " ");
 */
function rpad(src, len, str) {
    var retStr = "";
    var padCnt = Number(len) - String(src).length;

    for(var i=0;i<padCnt;i++) {
        retStr += String(str);
    }

    return src+retStr;
}

/**
 * 절상, 절하, 반올림 처리
 * @param strMode  - 수식
 * @param nCalcVal - 처리할 값(소수점 이하 데이터 포함)
 * @param nDigit   - 연산 기준 자릿수(오라클의 ROUND함수 자릿수 기준)
 *                   -2:십단위, -1:원단위, 0:소수점 1자리
 *                   1:소수점 2자리, 2:소수점 3자리, 3:소수점 4자리, 4:소수점 5자리 처리
 * @return String nCalcVal
 */
function fnCalcMath(strMode, nCalcVal, nDigit) {
    if(strMode == "CEIL") {  //절상
        if(nDigit < 0) {
            nDigit = -(nDigit);
            nCalcVal = Math.ceil(nCalcVal / Math.pow(10, nDigit)) * Math.pow(10, nDigit);
        } else {
            nCalcVal = Math.ceil(nCalcVal * Math.pow(10, nDigit)) / Math.pow(10, nDigit);
        }
    } else if(strMode == "FLOOR") { //절하
        if(nDigit < 0) {
            nDigit = -(nDigit);
            nCalcVal = Math.floor(nCalcVal / Math.pow(10, nDigit)) * Math.pow(10, nDigit);
        } else {
            nCalcVal = Math.floor(nCalcVal * Math.pow(10, nDigit)) / Math.pow(10, nDigit);
        }
    } else {        //반올림
        if(nDigit < 0) {
            nDigit = -(nDigit);
            nCalcVal = Math.round(nCalcVal / Math.pow(10, nDigit)) * Math.pow(10, nDigit);
        } else {
            nCalcVal = Math.round(nCalcVal * Math.pow(10, nDigit)) / Math.pow(10, nDigit);
        }
    }
    return nCalcVal;
}
