아주화장품 BRN 개발
Category
개발Customer
아주화장품Date
2025내용
1. 처방전 일괄 변경
동적 버전 관리
updateVersion() 함수를 통해 BOM 버전을 동적으로 생성합니다.
날짜 기반 버전 관리 시스템을 구현하여, 같은 날짜 내 여러 번 수정 시 증분 값을 사용합니다.
트랜잭션 관리
장기 실행 트랜잭션에 대한 롤백 메커니즘을 구현했습니다.
10분 이상 진행 중인 트랜잭션을 자동으로 감지하고 롤백합니다.
데이터베이스 최적화
윈도우 함수(ROW_NUMBER())를 사용하여 복잡한 쿼리를 효율적으로 처리합니다.
PARTITION BY를 활용해 각 아이템 코드와 속성 코드별로 최신 데이터만 선택합니다.
보안 강화
check_password() 함수를 사용하여 비밀번호를 안전하게 검증합니다.
사용자 인증 후에만 중요 작업을 수행할 수 있도록 설계되었습니다.
대량 데이터 처리
여러 BOM을 동시에 업데이트하는 대량 처리 로직을 구현했습니다.
각 업데이트에 대한 로그를 aju_bom_replace_log 테이블에 기록합니다.
코드 모듈화
버전 생성, 비밀번호 검증 등의 기능을 별도 함수로 분리하여 코드 재사용성을 높였습니다.
트랜잭션 상태를 별도 테이블(aju_transaction_status)에서 관리합니다.
PHP 처리부분 일부 함수
function bom_replace_sarch($item_code, $item_code_new = NULL, $cate, $bom_version = NULL, $password = NULL, $mb_name = NULL, $mb_id = NULL) { global $g5; $res = array(); // item_code가 없는 경우 if (!$item_code) { return array('error' => 'item_code가 필요합니다.'); } //step1. 원료검색 if ($cate == "search") { //대상 BOM을 화면에 뿌려주기 위한 쿼리 $sql = " SELECT item_code, bom_version, prop_code, item_name, bom_default, production_qty, prop_name, consumption_qty, manufact_input, memo, sort FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY item_code, prop_code ORDER BY rowid DESC) as rn FROM aju_bom WHERE prop_code = '{$item_code}' AND bom_default = 'Y' ) as subquery WHERE rn = 1 ORDER BY rowid DESC;"; $result = sql_query($sql); //대상 BOM에 노출 $res['bom_list'] = array(); while ($row = sql_fetch_array($result)) { $res['bom_list'][] = array('bom' => $row); } }; // 비밀번호 체크 if ($cate == "pwd_chk") { if (!empty($mb_id)) { // 데이터베이스에서 암호화된 비밀번호를 가져옵니다. $sql = " SELECT mb_password FROM aju_member WHERE mb_id = '{$mb_id}' LIMIT 1; "; $result = sql_query($sql); if ($row = sql_fetch_array($result)) { $db_password = $row['mb_password']; } else { $res['status'] = "error"; return; // 결과가 없으면 종료 } } else { $res['status'] = "error"; return; } // 비밀번호 검증 if (check_password($password, $db_password)) { // 비밀번호가 맞을 경우 $res['status'] = "success"; } else { // 비밀번호가 틀릴 경우 $res['status'] = "error"; } } // 버전 업데이트 함수 function updateVersion($version, $today) { // 버전을 '/' 기준으로 분리하여 날짜 부분과 나머지 부분을 구분 $parts = explode('/', $version); $datePart = $parts[0]; // 날짜 부분 $suffixPart = isset($parts[1]) ? '/' . $parts[1] : ''; // 내용 부분 // 날짜 부분을 '-' 기준으로 분리하여 기본 날짜와 증분 값을 구분 $dateParts = explode('-', $datePart); $date = $dateParts[0]; // 날짜 $increment = isset($dateParts[1]) ? intval($dateParts[1]) : 0; // 증분 값 (없을 경우 0) // 현재 날짜와 비교하여 업데이트 로직 결정 if ($date !== $today) { // 날짜가 다르면 오늘 날짜로 업데이트하고 내용은 그대로 유지 return $today . $suffixPart; } else { // 같은 날짜라면 증분 값을 1 증가시키고 새로운 버전 생성 return $today . '-' . ($increment + 1) . $suffixPart; } } // 새 BOM 버전 생성 함수 function generateNewBomVersion($item_code, $bom_version = NULL, $today_date) { // 이전에 구한 BOM 버전 값을 저장할 변수 static $previous_item_code = null; static $previous_bom_version = null; static $previous_returned_version = null; // 입력된 값이 이전 값과 동일한 경우, 이전 값을 반환 if ($item_code === $previous_item_code && $bom_version === $previous_bom_version) { return $previous_returned_version; // 이전에 반환된 BOM 버전 } // 주어진 item_code에 대한 기존 BOM 버전 조회 쿼리 $existing_sql = " SELECT bom_version FROM aju_bom WHERE item_code = '{$item_code}' ORDER BY rowid DESC LIMIT 1 "; $existing_result = sql_query($existing_sql); // 쿼리 실행 // 기존 BOM 버전을 저장할 배열 초기화 $all_bom_versions = array(); while ($row_version = sql_fetch_array($existing_result)) { // 기존 BOM 버전을 배열에 추가 $all_bom_versions[] = $row_version['bom_version']; } // 새로운 버전을 저장할 배열 초기화 $new_versions = array(); foreach ($all_bom_versions as $version) { // 업데이트 함수 호출하여 새로운 버전 생성 $new_version = updateVersion($version, $today_date); // 중복된 버전이 생기지 않도록 체크 및 업데이트 while (in_array($new_version, $new_versions)) { // 중복된 경우, 다시 업데이트하여 새로운 버전 생성 $new_version = updateVersion($new_version, $today_date); } // 새로운 버전을 배열에 추가 $new_versions[] = $new_version; } // 마지막으로 업데이트된 버전 $latest_new_version = end($new_versions); // 입력된 값 저장 $previous_item_code = $item_code; $previous_bom_version = $bom_version; // BOM 버전도 저장 $previous_returned_version = $latest_new_version; // 반환될 최신 버전 저장 // 새로운 BOM 버전 반환 return $latest_new_version; } // 카테고리가 "replace_new_bom"인 경우 if ($cate == "replace_new_bom") { global $g5; $mysqli = $g5['connect_db']; // DB 연결 $res['new_code'] = $item_code_new; // 새 코드 저장 $today_date = date('y.m.d'); // 현재 날짜 포맷 // 최신 기본 자재명을 가져오는 쿼리 $sql = " SELECT prop_name FROM aju_bom WHERE bom_default = 'Y' AND prop_code = '{$item_code}' ORDER BY rowid DESC LIMIT 1; "; $target_name = sql_fetch_array(sql_query($sql))['prop_name'] ?? ''; // 기본 자재명 저장 // 대체 자재명을 가져오는 쿼리 $sql_new = " SELECT rm.rm_name FROM aju_raw_material AS rm LEFT JOIN aju_raw_material_info AS mi ON rm.`rm_code` = mi.`rm_code` WHERE LOWER(rm.rm_code) = LOWER('{$item_code_new}') LIMIT 1 "; $substitute_name = sql_fetch_array(sql_query($sql_new))['rm_name'] ?? ''; // 대체 자재명 저장 // 결과 배열 초기화 $res['bom_list'] = array(); $res['update_list'] = array(); $res['update_list_new'] = array(); // 현재 prop_code에 대한 BOM 정보 가져오기 $sql = " SELECT item_code, bom_version, prop_code, sort, COUNT(*) OVER (PARTITION BY prop_code) as total_count FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY item_code, prop_code ORDER BY sort DESC) as rn FROM aju_bom WHERE prop_code = '{$item_code}' AND bom_default = 'Y' ) as subquery WHERE rn = 1 ORDER BY rowid DESC; "; $result = sql_query($sql); // 쿼리 실행 // BOM 정보를 가져온 결과를 반복 while ($row = sql_fetch_array($result)) { $res['bom_list'][] = array('bom' => $row); // BOM 리스트에 추가 // 총 카운트는 첫 번째 결과의 total_count에서 가져옴 if (!isset($update_bulk_count)) { $update_bulk_count = $row['total_count']; } //개수 구하기 $sql = " SELECT count(*) as cnt FROM aju_bom WHERE bom_default = 'Y' AND bom_version = '{$row['bom_version']}' AND item_code = '{$row['item_code']}' "; $target_cnt = sql_fetch_array(sql_query($sql))['cnt'] ?? ''; $single_count[] = $target_cnt; // BOM 업데이트 리스트 가져오기 $sql_updateList = " SELECT * FROM aju_bom WHERE bom_default = 'Y' AND item_code ='" . $row['item_code'] . "' AND bom_version ='" . $row['bom_version'] . "' "; $result_updateList = sql_query($sql_updateList); // 쿼리 실행 $i = 0; // count 값 배열로 변환 $counts = $single_count; $previous_count = 0; while ($row2 = sql_fetch_array($result_updateList)) { $res['update_list'][] = $row2; // 업데이트 리스트에 추가 //새 bom 생성 $new_bom_version = generateNewBomVersion($row2['item_code'], $row2['bom_version'], $today_date); // prop_code 변경 로직 $new_prop_code = ($item_code == $row2['prop_code']) ? $item_code_new : $row2['prop_code']; $new_prop_name = ($item_code == $row2['prop_code']) ? $substitute_name : $row2['prop_name']; // BOM 데이터 삽입 쿼리 $insert_sql = " INSERT INTO aju_bom (item_code, item_name, bom_version, bom_default, production_qty, prop_code, prop_name, consumption_qty, manufact_input, memo, sort) VALUES ('{$row2['item_code']}', '{$row2['item_name']}', '{$new_bom_version}', '{$row2['bom_default']}', '{$row2['production_qty']}', '{$new_prop_code}', '{$new_prop_name}', '{$row2['consumption_qty']}', '{$row2['manufact_input']}', '{$row2['memo']}', '{$row2['sort']}') "; //$res['sql_check'][] = $insert_sql; // 새로운 BOM 데이터 삽입 if (sql_query($insert_sql)) { // 로그 기록 쿼리 $status = $insert_sql ? '성공' : '실패'; $memo = $insert_sql ? '' : '인서트 과정에서 오류'; $log_sql = " INSERT INTO aju_bom_replace_log (item_code, bom_version, user_id, target_code, target_material, substitute_code, substitute_material, update_bulk_count, stats, memo, file_name) VALUES ('{$row2['item_code']}', '{$new_bom_version}', '{$mb_name}', '{$item_code}', '{$target_name}', '{$item_code_new}', '{$substitute_name}', '{$update_bulk_count}', '{$status}', '{$memo}', ''); "; sql_query($log_sql); // prop_code가 변경된 경우 update_list_new에 추가 if ($item_code == $row2['prop_code']) { // Add new entry to update_list_new with new bom_version $res['update_list_new'][] = [ 'item_code' => $row2['item_code'], // row2에서 가져온 item_code 'bom_version' => $new_bom_version, // 새로운 bom_version 'prop_code' => $item_code, // 기존 prop_code 'prop_new' => $item_code_new, // 새 prop_code 'prop_name' => $substitute_name, // 대체 자재명 'item_name' => $row2['item_name'] // row2에서 가져온 item_name ]; } } }//BOM 전체 데이터 구해서 insert }//원료 변경할 아이템코느나 BOM 버전 구하기 }// 카테고리 분류 정리 // 주기적으로 실행되는 스크립트에서 트랜잭션 상태를 검사하고 롤백 $sql = "SELECT item_code FROM aju_transaction_status WHERE status='IN_PROGRESS' AND last_update < NOW() - INTERVAL 10 MINUTE"; $result = sql_query($sql); while ($row = sql_fetch_array($result)) { $item_code = $row['item_code']; // 롤백 로직 실행 global $g5; $mysqli = $g5['connect_db']; $mysqli->rollback(); // 트랜잭션 상태 삭제 $sql = "DELETE FROM aju_transaction_status WHERE item_code='{$item_code}'"; sql_query($sql); } return $res; }
2. 웹과 파이썬 프로그램을 통한 동기화
Ecount ERP 시스템에서 거래처 데이터를 자동으로 동기화
멀티스레딩 및 비동기 처리
threading 모듈을 사용하여 인터넷 연결 모니터링을 별도의 스레드로 실행합니다.
schedule 라이브러리로 정기적인 작업 스케줄링을 구현합니다.
웹 스크래핑 자동화
Selenium WebDriver를 사용하여 Ecount ERP 시스템에 자동 로그인 및 데이터 추출을 수행합니다.
chrome_options를 통해 브라우저 설정을 최적화하여 안정성과 성능을 향상시킵니다.
데이터베이스 연동
MySQL 데이터베이스와 연동하여 거래처 정보를 실시간으로 갱신합니다.
트랜잭션 관리를 통해 데이터 무결성을 보장합니다.
예외 처리 및 로깅
세분화된 예외 처리로 다양한 오류 상황에 대응합니다.
텔레그램 API를 통해 실시간 로깅 및 알림 시스템을 구현합니다.
파일 시스템 활용
동기화 상태 및 트리거 관리를 위해 파일 시스템을 활용합니다.
다운로드된 엑셀 파일의 이름을 타임스탬프로 변경하여 버전 관리를 용이하게 합니다.
보안 강화
환경 변수나 별도의 설정 파일을 통해 민감한 정보(API 키, 데이터베이스 자격 증명 등)를 관리합니다.
프로세스 관리
signal 모듈을 사용하여 프로세스의 정상적인 종료를 보장합니다.
전역 변수를 통해 프로세스 상태를 관리하고, 정리(cleanup) 함수로 리소스를 안전하게 해제합니다.
데이터 검증 및 전처리
Pandas를 사용하여 엑셀 데이터를 효율적으로 처리합니다.
거래처 코드 검증 로직을 통해 데이터 품질을 관리합니다.
파이썬 동기화 부분
while is_running: schedule.run_pending() trigger_file = "C:\\Selenium_crawling\\trigger_sync.txt" if os.path.exists(trigger_file): os.remove(trigger_file) print("트리거 파일 감지됨. 즉시 동기화 작업 실행 중...") with open("C:\\Selenium_crawling\\sync_status.txt", "w") as f: f.write("in_progress") try: job() except Exception as e: print(f"동기화 작업 중 오류 발생: {e}") with open("C:\\Selenium_crawling\\sync_status.txt", "w") as f: f.write("error") time.sleep(1)
PHP 처리
$('#data_sync').click(function(){ if(confirm('현재 시점으로 Ecount거래처 데이터와 동기화 하시겠습니까?')){ // 버튼 비활성화 $(this).prop('disabled', true); $.ajax({ url: '/supplier_auto.php', type: 'GET', success: function(response) { if (response === "이미 동기화 작업이 진행 중입니다.") { alert("이미 동기화 작업이 진행 중입니다. 작업이 완료될 때까지 기다려주세요."); } else { alert(response); } // 상태 폴링 시작 pollSyncStatus(); }, error: function() { alert('동기화 요청에 실패했습니다.'); $('#data_sync').prop('disabled', false); } }); } }); function pollSyncStatus() { $.ajax({ url: '/check_sync_status.php', type: 'GET', success: function(status) { if (status === 'in_progress') { // 작업 진행 중, 5초 후 재확인 setTimeout(pollSyncStatus, 5000); } else { // 버튼 활성화 $('#data_sync').prop('disabled', false); if (status === 'completed') { alert('동기화가 완료되었습니다.'); } else if (status === 'error') { alert('동기화 중 오류가 발생했습니다.'); } else { alert('동기화 상태를 확인할 수 없습니다.'); } } }, error: function() { // 폴링 오류, 버튼 활성화 $('#data_sync').prop('disabled', false); alert('동기화 상태 확인에 실패했습니다.'); } }); }
3. datatables를 이용한 데이터 처리
동적 UI 관리
jQuery를 사용하여 동적으로 UI 요소를 생성하고 관리합니다.
모달 창을 통해 BOM 정보를 표시하고 편집합니다.
데이터 시각화
DataTables 라이브러리를 활용하여 복잡한 BOM 데이터를 효과적으로 표시합니다.
정전개, 역전개 등 다양한 BOM 구조를 트리 형태로 시각화합니다.
비동기 데이터 처리
AJAX를 사용하여 서버와 비동기적으로 통신하며 실시간으로 데이터를 갱신합니다.
JSON 형식으로 데이터를 주고받아 효율적인 데이터 교환을 구현합니다.
드래그 앤 드롭 기능
Sortable.js 라이브러리를 사용하여 BOM 항목의 순서를 드래그 앤 드롭으로 변경할 수 있게 합니다.
엑셀 데이터 처리
SheetJS 라이브러리를 활용하여 BOM 데이터를 엑셀 형식으로 내보내기 기능을 구현합니다.
사용자 경험 개선
SweetAlert2를 사용하여 사용자 친화적인 알림 및 확인 대화상자를 제공합니다.
로딩 화면을 통해 장시간 작업 중 사용자에게 피드백을 제공합니다.
반응형 디자인
Bootstrap을 활용하여 다양한 디바이스에서 일관된 사용자 경험을 제공합니다.
코드 모듈화
여러 기능을 함수로 분리하여 코드의 재사용성과 유지보수성을 높였습니다.
javascript 일부분
$(document).ready(function() { //초기 셋팅. itemcode = $("#item_code").val(); let currentPage = 1; let cachedResults = {}; let currentSearchTerm = ''; //모달창 설정 $('#bomVer_pop').modal({ backdrop: 'static', keyboard: false }); //모달창 esc로만 닫히도록 설정 $(document).on('keydown', function(e) { if (e.key === "Escape" && $('#bomVer_pop').hasClass('show')) { // ESC 키가 눌렸고 모달이 열려있을 때 e.preventDefault(); $('#bomVer_pop').modal('hide'); } }); //swal2와 모달의 충돌 방지를 위한 코드 $(document).on('keydown', function(event) { if ($('.swal2-container').length && (event.key === ' ' || event.key === 'Enter')) { event.preventDefault(); event.stopPropagation(); } }); window.onbeforeunload = function() { // 부모 창에 메시지 보내기 if (window.opener) { window.opener.postMessage('childClosed', '*'); } }; //제조투입여부 $('input[name="manufact_choice"]').on('change', function() { var value = this.value; $('input[name="manufact_input[]"]').each(function() { $(this).val(value); }); Swal.fire({ title: '변경 완료', text: '제조투입여부가 변경되었습니다.', icon: 'success', confirmButtonText: '확인' }); }); //신규일떄 상품명 + 검색 $('.searchProducts').click(function() { currentPage = 1; searchProducts(); return false; }); $(document).on('keydown', function(event) { if ($('.swal2-container').length && (event.key === ' ' || event.key === 'Enter')) { event.preventDefault(); event.stopPropagation(); } }); function searchProducts(page = 1) { currentSearchTerm = $('#searchInput').val().trim(); if (currentSearchTerm === '') { alert('검색어를 입력해주세요.'); return false; } if (page === 1) { $('#results').html(''); cachedResults = {}; } if (cachedResults[currentSearchTerm] && cachedResults[currentSearchTerm][page]) { displayResults(cachedResults[currentSearchTerm][page]); return; } $('#loading-screen').show(); $('#loadMore').hide(); $.ajax({ url: '/views/lab/bom.getcode.api.php', data: { get_prod_des: currentSearchTerm, page: page }, dataType: 'json', success: function(data) { if (!cachedResults[currentSearchTerm]) { cachedResults[currentSearchTerm] = {}; } cachedResults[currentSearchTerm][page] = data; displayResults(data); }, error: function(jqXHR, textStatus, errorThrown) { $('#results').append('
검색 중 오류가 발생했습니다: ' + textStatus + '
'); }, complete: function() { $('#loading-screen').hide(); } }); } function displayResults(data) { if (data.error) { $('#results').html('오류: ' + data.error + '
'); } else if (data.Result.length === 0) { $('#results').html('해당 품목명을 가진 품목이 없습니다. 수동 입력
'); } else { let resultsHtml = ''; let filteredResults = data.Result.filter(product => product.PROD_DES.toLowerCase().includes(currentSearchTerm.toLowerCase()) ); if (filteredResults.length === 0) { $('#results').html('해당 품목명을 가진 품목이 없습니다. 수동 입력
'); $('#loadMore').hide(); return; } filteredResults.forEach(function(product) { resultsHtml += ` `; }); $('#results').append(resultsHtml); if (data.Result.length === 20) { // Assuming 20 is the page size $('#loadMore').show(); currentPage++; } else { $('#loadMore').hide(); } } } // 이벤트 위임을 사용하여 동적으로 생성된 .input_open 요소에 클릭 이벤트 바인딩 $(document).on('click', '.input_open', function() { Swal.fire({ title: '알림!', text: '이 기능은 카운트와 데이터 꼬임이 발생할 수 있습니다. 코드 입력시 이카운트에 있는 코드인지 조회 해주세요', icon: 'question', showCancelButton: true, confirmButtonText: '확인', cancelButtonText: '취소' }).then((result) => { if (result.isConfirmed) { $('#item_name').prop('readonly', false); $('#item_code').attr('type', 'text').val(''); $('.hidden_code').css('display', 'block'); } }); }); //상단의 수동입력 부분(관리자용?) $(document).on('click', '.input_open_btn', function() { Swal.fire({ title: '알림!', text: '이 기능은 카운트와 데이터 꼬임이 발생할 수 있습니다. 코드 입력시 이카운트에 있는 코드인지 조회 해주세요', icon: 'question', showCancelButton: true, confirmButtonText: '확인', cancelButtonText: '취소' }).then((result) => { if (result.isConfirmed) { $('#item_name').prop('readonly', false); $('#item_code').attr('type', 'text').val(''); $('.hidden_code').css('display', 'block'); } }); }); $(document).on('click', '.codeNew', function() { event.preventDefault(); // 기본 클릭 동작을 막습니다. var dataCode = $(this).attr('data-id'); var dataName = $(this).attr('id'); $('#results').html(''); $("#item_code").val(dataCode); $('#searchInput').val(dataName); $('#item_name').val(dataName); // 폼 유효성 검사 if ($('#item_name').val() === '') { $('#item_name').removeClass('success').addClass('error'); } else { $('#item_name').removeClass('error').addClass('success'); } }); $('#searchInput').keypress(function(e) { if (e.which == 13) { currentPage = 1; searchProducts(); return false; } }); $('#loadMore').click(function() { searchProducts(currentPage); }); //BOM 신규 생성이 아니면 초기 함수 실행 if (itemcode != "new") { tableset(itemcode); } //테이블 내용 불러오기 function tableset(code) { // 로딩 화면 표시 $('#loading-screen').show(); $.ajax({ url: '/bom/bomdata_json', type: 'POST', data: { item_code: code }, dataType: 'json', success: function(response) { //초기화 작업 $('#item_code').val(''); $('#item_name').val(''); $('#top_bom_version').val(''); $('#production_qty').val(''); if (response.status === 'success') { //데이터 바꿔야지... $('#item_code').val(response.data.bom.item_code); $('#item_name').val(response.data.bom.item_name); $('#top_bom_version').val(response.data.bom.bom_version); $('#production_qty').val(response.data.bom.production_qty); //BOM 셀렉트 박스 생성 $.ajax({ url: '/bom/bomlistsearch?cate=select&item_code=' + code, dataType: 'json', success: function(response) { // Select 요소 선택 var selectElement = $('#bom_select'); // 기존 옵션 제거 selectElement.empty(); // 플레이스홀더 옵션 추가 selectElement.append(''); response.select_list[''].forEach(function(item) { var value = item.bom_version; var displayText = '[' + item.item_code + '] ' + item.bom_version; var newOption = $(''); $('#bomVer_pop').modal('show'); // Select 요소 선택 var selectElement = $('#copy_input'); // 기존 옵션 제거 selectElement.empty(); // 모달이 표시된 후 Select2 초기화 $('#bomVer_pop').on('shown.bs.modal', function() { // Select2 초기화 $('#copy_input').select2({ placeholder: '클릭하시면 검색 또는 선택이 가능합니다.', allowClear: true, dropdownParent: $('#bomVer_pop'), // 모달 내에서 표시되도록 설정 templateSelection: function(selected, container) { if (selected.id === '') { return '클릭하시면 검색 또는 선택이 가능합니다.'; // placeholder를 선택한 경우 } return selected.text; // 선택한 값을 텍스트로 표시 } }); // Ajax로 데이터 가져오기 $.ajax({ url: '/bom/bomlistsearch?cate=list', dataType: 'json', success: function(response) { // 기존 옵션 제거 $('#copy_input').empty(); // 플레이스홀더 옵션 추가 $('#copy_input').append(''); // 데이터 추가 response.forEach(function(item) { var displayText = "[" + item.value + "] " + item.name; var newOption = new Option(displayText, item.value, false, false); $('#copy_input').append(newOption); }); // Select2 갱신 $('#copy_input').trigger('change'); }, error: function(xhr, status, error) { console.error('Error fetching JSON data:', error); } }); // Select2 change 이벤트 핸들러 $('#copy_input').on('change', function() { var selectedValue = $(this).val(); var selectedText = $(this).find('option:selected').text(); let parts = selectedText.split('] '); let copy_code = parts[0].replace('[', ''); let copy_name = parts[1]; // 2차 선택 if (copy_name !== null && copy_name !== undefined) { $('.bom_selectTr').show(); $.ajax({ url: '/bom/bomlistsearch?cate=select&item_code=' + copy_code, dataType: 'json', success: function(response) { // Select 요소 선택 var selectElement = $('.bomver_Select'); // 기존 옵션 제거 selectElement.empty(); // 플레이스홀더 옵션 추가 selectElement.append(''); response.select_list[''].forEach(function(item) { var value = item.bom_version; var displayText = '[' + item.item_code + '] ' + item.bom_version; var newOption = $('