Files
wace_plm/WebContent/WEB-INF/view/contractMgmt/estimateAndOrderRegistFormPopup.jsp
syc0123 52145478a9 [RAPID-micro] 견적요청등록/수주통합등록/수주등록 팝업 그리드 컬럼 리사이즈 UX 개선
- 컬럼 드래그 시 양쪽 컬럼 너비 맞교환 (테이블 전체 너비 고정)
- getBoundingClientRect로 시작 너비 정확히 측정 (폭 점프 제거)
- requestAnimationFrame으로 렌더링 스로틀 (빠른 드래그 시 부드러움)
- 마지막 컬럼 핸들 제거
- tabulator_custom.js 임시 overflow 코드 정리

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 17:09:16 +09:00

1781 lines
64 KiB
Plaintext

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@include file="/init_new.jsp"%>
<%
PersonBean person = (PersonBean) session.getAttribute(Constants.PERSON_BEAN);
String userId = CommonUtils.checkNull(person.getUserId());
%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<style>
.fileListscrollTbody td {
font-size: 11px !important;
}
/* S/N 입력 필드 placeholder 색상 */
#serial_no::placeholder {
color: #999 !important;
opacity: 1;
}
#serial_no::-webkit-input-placeholder {
color: #999 !important;
}
#serial_no::-moz-placeholder {
color: #999 !important;
opacity: 1;
}
#serial_no:-ms-input-placeholder {
color: #999 !important;
}
/* ===== 팝업 폼 전체 너비 확장 ===== */
section.business_popup_min_width {
padding: 0 !important;
margin: 0 auto !important;
max-width: none !important;
width: calc(100% - 8px) !important;
border: none !important;
box-shadow: none !important;
}
#EntirePopupFormWrap {
width: 100% !important;
margin: 5px 0 0 0 !important;
padding: 0 5px 10px 5px !important;
box-sizing: border-box;
}
/* ===== 품목 그리드 컬럼 리사이즈 ===== */
#EntirePopupFormWrap > table {
width: 100% !important;
}
#itemListTable {
table-layout: fixed;
width: 100%;
}
#itemListTable th {
position: relative;
user-select: none;
}
#itemListTable th,
#itemListTable td {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
#itemListTable th .col-resizer {
position: absolute;
right: 0;
top: 0;
width: 5px;
height: 100%;
cursor: col-resize;
z-index: 10;
background: transparent;
transition: background 0.15s;
}
#itemListTable.col-resizing {
user-select: none;
}
/* ===== Select2 드래그 복사 지원 ===== */
.select2-container .select2-selection__rendered {
user-select: text !important;
-webkit-user-select: text !important;
cursor: text;
padding-right: 36px !important;
}
/* x(초기화) 버튼을 드롭다운 화살표 왼쪽에 배치 */
#itemListTable .select2-selection__clear {
position: absolute !important;
right: 20px;
top: 50%;
transform: translateY(-50%);
font-size: 14px;
color: #999;
cursor: pointer;
user-select: none;
}
#itemListTable .select2-selection--single {
position: relative;
}
</style>
<script type="text/javascript">
// 품목 관리 변수
var itemCounter = 1;
var itemList = [];
// 반납사유 공통코드 옵션 (숨겨진 div에서 읽어옴)
var returnReasonOptions = '';
// 프로젝트 존재 여부 및 Machine 여부
var hasProject = ("${hasProject}" === "Y");
var isMachine = ("${isMachine}" === "Y");
// ===== 컬럼 리사이즈 기능 =====
function fn_initColumnResize() {
var $table = $('#itemListTable');
if ($table.length === 0) return;
var $cols = $table.find('colgroup col');
var $ths = $table.find('thead th');
$ths.each(function(i) {
if (i < $ths.length - 1) {
if ($(this).find('.col-resizer').length === 0) {
$(this).append('<div class="col-resizer"></div>');
}
}
});
var dragging = false;
var startX = 0;
var startWidthL = 0;
var startWidthR = 0;
var $colL, $colR, $resizerEl;
var rafId = null;
$(document).on('mousedown', '#itemListTable .col-resizer', function(e) {
e.preventDefault();
e.stopPropagation();
dragging = true;
$resizerEl = $(this);
var colIdx = $resizerEl.parent().index();
$colL = $cols.eq(colIdx);
$colR = $cols.eq(colIdx + 1);
startWidthL = $colL[0].getBoundingClientRect().width;
startWidthR = $colR[0].getBoundingClientRect().width;
startX = e.clientX;
$resizerEl.addClass('is-resizing');
$table.addClass('col-resizing');
$('body').css('cursor', 'col-resize');
});
$(document).on('mousemove.colResize', function(e) {
if (!dragging) return;
if (rafId) return;
rafId = requestAnimationFrame(function() {
rafId = null;
var delta = e.clientX - startX;
var newLeft = startWidthL + delta;
var newRight = startWidthR - delta;
if (newLeft < 40 || newRight < 40) return;
$colL.css('width', newLeft + 'px');
$colR.css('width', newRight + 'px');
});
});
$(document).on('mouseup.colResize mouseleave.colResize', function() {
if (!dragging) return;
dragging = false;
if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
if ($resizerEl) $resizerEl.removeClass('is-resizing');
$table.removeClass('col-resizing');
$('body').css('cursor', '');
});
}
// ===== Select2 드래그 복사 지원 =====
function fn_initSelect2DragCopy() {
var mouseTarget = null;
var wasDragged = false;
var startX = 0, startY = 0;
// select2:opening 상시 감시 — mouseTarget이 설정되어 있으면 열림 차단
$(document).on('select2:opening', '#itemListTable select', function(e) {
if(mouseTarget === this) {
e.preventDefault();
}
});
// capturing phase: Select2 핸들러보다 먼저 mouseTarget 플래그 설정
document.addEventListener('mousedown', function(e) {
var $sel = $(e.target).closest('#itemListTable .select2-selection');
if($sel.length === 0) return;
if($(e.target).hasClass('select2-selection__clear')) return;
var $container = $sel.closest('.select2-container');
var $select = $container.prev('select');
mouseTarget = $select[0]; // Select2 열림 차단 활성화
wasDragged = false;
startX = e.pageX;
startY = e.pageY;
var onMove = function(ev) {
if(Math.abs(ev.pageX - startX) > 3 || Math.abs(ev.pageY - startY) > 3) {
wasDragged = true;
}
};
var onUp = function() {
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
if(!wasDragged) {
mouseTarget = null;
$select.select2('open');
}
setTimeout(function() { mouseTarget = null; }, 50);
};
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
}, true); // capturing phase — Select2보다 먼저 실행
}
$(function() {
// 반납사유 옵션을 템플릿에서 읽어옴
returnReasonOptions = $('#return_reason_template').html();
// 컬럼 리사이즈 초기화
fn_initColumnResize();
// Select2 드래그 복사 초기화
fn_initSelect2DragCopy();
// 기존 품목 데이터 로드
fn_loadExistingItems();
// 주문유형에 따른 필드 표시/숨김
if("${info.CATEGORY_CD}" == '0000170' || "${info.CATEGORY_CD}" == '0000171'){//오버홀, 개조
$(".DIRECT").show();
$(".SELECT").hide();
}else{
$(".DIRECT").hide();
$(".SELECT").show();
}
// 숫자만 입력 처리
$("input:text[numberOnly]").on("keyup", function() {
if($(this).hasClass("item-order-quantity") || $(this).attr("id") === "facility_qty") {
$(this).val(formatInteger($(this).val().replace(/[^0-9]/g, "")));
} else {
$(this).val(fnc_addComma($(this).val().replace(/[^0-9.]/g, "")));
}
});
$(document).on("blur", "input:text[numberOnly]", function() {
var val = $(this).val();
if(val && val !== '') {
if($(this).hasClass("item-order-quantity") || $(this).attr("id") === "facility_qty") {
$(this).val(formatInteger(removeComma(val)));
} else {
$(this).val(formatMoney(removeComma(val)));
}
}
});
$('.select2').select2();
// 품목 추가 버튼
$("#btnAddItem").click(function() {
// 프로젝트가 있으면 품목 추가 불가
if(hasProject) {
Swal.fire({
title: '품목 추가 불가',
text: '프로젝트가 이미 생성되어 품목을 추가할 수 없습니다.\n수량, 납기일 등만 수정 가능합니다.',
icon: 'warning'
});
return false;
}
fn_addItemRow();
});
// 날짜 선택기 초기화
_fnc_datepick();
// 저장 버튼
$("#btnSave").click(function() {
fn_save();
});
// 닫기 버튼
$("#btnClose").click(function() {
self.close();
});
// 주문유형 변경 이벤트
$("#category_cd").change(function(){
if($(this).val() == '0000170' || $(this).val() == '0000171'){//오버홀, 개조
$(".DIRECT").show();
$(".SELECT").hide();
$("#target_project_no_direct").attr('required', 'required');
$("#overhaul_order").attr('required', 'required');
}else{
$(".DIRECT").hide();
$(".SELECT").show();
$("#target_project_no_direct").removeAttr('required');
$("#overhaul_order").removeAttr('required');
}
});
});
function addComma(data) {
return formatMoney(data);
}
function addCommaInt(data) {
if(!data && data !== 0) return '';
var num = Math.round(Number(String(data).replace(/,/g, '')));
if(isNaN(num)) return '';
return num.toLocaleString();
}
function removeComma(data) {
if(!data) return '';
return data.toString().replace(/,/g, "");
}
var isSaving = false;
function fn_save() {
if(isSaving) return false;
if (fnc_valitate("form1")) {
// 품목 유효성 검사
if(!fn_validateItems()) {
return false;
}
if(confirm("등록하시겠습니까?")) {
isSaving = true;
$("#btnSave").prop("disabled", true);
// 품목 데이터 수집
var itemsData = fn_collectItemsData();
console.log("최종 전송 데이터:", JSON.stringify(itemsData, null, 2));
// 환율에서 콤마 제거
var exchangeRate = $("#exchange_rate").val();
if(exchangeRate) {
$("#exchange_rate").val(exchangeRate.replace(/,/g, ''));
}
// 기본 폼 데이터
var formData = $("#form1").serialize();
// 품목 데이터를 JSON 문자열로 추가
formData += "&items_json=" + encodeURIComponent(JSON.stringify(itemsData));
$.ajax({
url : "/contractMgmt/saveEstimateAndOrderInfo.do",
type : "POST",
data : formData,
dataType : "json",
success : function(data) {
alert(data.msg);
if(opener && opener.fn_search) {
opener.fn_search();
}
self.close();
},
error : function(jqxhr, status, error) {
alert("저장 중 오류가 발생했습니다.");
isSaving = false;
$("#btnSave").prop("disabled", false);
}
});
}
}
}
// 품목 유효성 검사
function fn_validateItems() {
var itemRows = $(".item-row");
if(itemRows.length == 0) {
alert("최소 1개 이상의 품목을 등록해주세요.");
return false;
}
for(var i = 0; i < itemRows.length; i++) {
var $row = $(itemRows[i]);
var partObjId = $row.find(".item-part-objid").val();
var orderQuantity = $row.find(".item-order-quantity").val().replace(/,/g, "").trim();
var orderUnitPrice = $row.find(".item-order-unit-price").val().replace(/,/g, "").trim();
var product = $row.find(".item-product").val();
if(!product || product == "") {
alert((i+1) + "번째 품목의 제품구분을 선택해주세요.");
$row.find(".item-product").focus();
return false;
}
if(!partObjId || partObjId == "") {
alert((i+1) + "번째 품목의 품번을 선택해주세요.");
$row.find(".item-part-no-select").focus();
return false;
}
if(orderQuantity == "" || orderQuantity == "0") {
alert((i+1) + "번째 품목의 수주수량을 입력해주세요.");
$row.find(".item-order-quantity").focus();
return false;
}
}
return true;
}
// 품목 데이터 수집
function fn_collectItemsData() {
var items = [];
var itemRows = $(".item-row");
itemRows.each(function(index) {
var $row = $(this);
var itemId = $row.attr("id");
// S/N 목록 가져오기
var snListStr = $("#" + itemId + "_sn_list").val();
var snList = [];
if(snListStr && snListStr.trim() != "") {
try {
snList = JSON.parse(snListStr);
} catch(e) {
console.error("품목 " + (index+1) + " S/N 파싱 실패:", e);
snList = [];
}
}
var item = {
objId: $row.find(".item-objid").val() || '',
product: $row.find(".item-product").val(),
partObjId: $row.find(".item-part-objid").val(),
partNo: $row.find(".item-part-no").val() ? $row.find(".item-part-no").val().trim() : "",
partName: $row.find(".item-part-name").val() ? $row.find(".item-part-name").val().trim() : "",
dueDate: $row.find(".item-due-date").val().trim(),
customerRequest: $row.find(".item-customer-request").val().trim(),
returnReason: $row.find(".item-return-reason").val().trim(),
snList: snList,
// 수주정보
orderQuantity: $row.find(".item-order-quantity").val().replace(/,/g, "").trim(),
orderUnitPrice: $row.find(".item-order-unit-price").val().replace(/,/g, "").trim(),
orderSupplyPrice: $row.find(".item-order-supply-price").val().replace(/,/g, "").trim(),
orderVat: $row.find(".item-order-vat").val().replace(/,/g, "").trim(),
orderTotalAmount: $row.find(".item-order-total-amount").val().replace(/,/g, "").trim()
};
items.push(item);
});
return items;
}
function _fnc_datepick() {
var $dateinput = $("input.date_icon");
for (var i = 0; i < $dateinput.length; i++) {
$dateinput.eq(i).attr("size", "10");
$dateinput.eq(i).datepicker({
changeMonth : true,
changeYear : true
});
}
}
// ========== 품목 관리 함수 ==========
// 품목 행 추가
function fn_addItemRow(existingData) {
var itemId = 'item_' + itemCounter++;
// "품목 추가하세요" 메시지 행 제거
$("#noItemRow").hide();
var html = '<tr id="' + itemId + '" class="item-row">';
html += '<td style="text-align:center; padding:5px; border:1px solid #ddd;">' + (itemCounter-1) + '</td>';
// 제품구분 드롭다운
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<select name="item_product[]" id="PRODUCT_' + itemId + '" class="item-product select2" style="width:100%;" required>';
html += '<option value="">선택</option>';
html += '</select>';
html += '</td>';
// 품번 셀렉트박스
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<select name="item_part_no_select[]" id="PART_NO_' + itemId + '" class="item-part-no-select" style="width:100%;" required>';
html += '<option value="">선택</option>';
html += '</select>';
html += '<input type="hidden" name="item_part_objid[]" class="item-part-objid" />';
html += '<input type="hidden" name="item_part_no[]" class="item-part-no" />';
html += '</td>';
// 품명 셀렉트박스
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<select name="item_part_name_select[]" id="PART_NAME_' + itemId + '" class="item-part-name-select" style="width:100%;" required>';
html += '<option value="">선택</option>';
html += '</select>';
html += '<input type="hidden" name="item_part_name[]" class="item-part-name" />';
html += '</td>';
// S/N
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<input type="text" name="item_serial_no[]" class="item-serial-no" placeholder="클릭하여 S/N 추가" readonly style="width:100%; padding:5px; cursor:pointer; background-color:#f8f9fa;" onclick="fn_openItemSnPopup(\'' + itemId + '\')" />';
html += '<input type="hidden" name="item_serial_no_list[]" id="' + itemId + '_sn_list" value="" />';
html += '</td>';
// 요청납기
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<input type="text" name="item_due_date[]" class="item-due-date date_icon" style="width:100%; padding:5px;" />';
html += '</td>';
// 고객요청사항
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<textarea name="item_customer_request[]" class="item-customer-request" style="width:100%; padding:5px; min-height:34px; resize:vertical; font-family:inherit; font-size:inherit;" rows="1"></textarea>';
html += '</td>';
// 반납사유
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<select name="item_return_reason[]" class="item-return-reason select2" style="width:100%;"></select>';
html += '</td>';
// 수주수량 (Machine이고 프로젝트가 있으면 readonly)
html += '<td style="padding:5px; border:1px solid #ddd;">';
if(isMachine && hasProject) {
html += '<input type="text" name="item_order_quantity[]" class="item-order-quantity" style="width:100%; padding:5px; text-align:right; background:#f5f5f5;" required numberOnly readonly title="Machine 제품은 프로젝트 생성 후 수량 변경이 불가능합니다." />';
} else {
html += '<input type="text" name="item_order_quantity[]" class="item-order-quantity" style="width:100%; padding:5px; text-align:right;" required numberOnly />';
}
html += '</td>';
// 수주단가
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<input type="text" name="item_order_unit_price[]" class="item-order-unit-price" style="width:100%; padding:5px; text-align:right;" numberOnly />';
html += '</td>';
// 수주공급가액 (자동계산 + 수정가능)
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<input type="text" name="item_order_supply_price[]" class="item-order-supply-price" style="width:100%; padding:5px; text-align:right;" numberOnly />';
html += '</td>';
// 수주부가세 (자동계산 + 수정가능)
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<input type="text" name="item_order_vat[]" class="item-order-vat" style="width:100%; padding:5px; text-align:right;" numberOnly />';
html += '</td>';
// 수주총액 (자동계산 + 수정가능)
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<input type="text" name="item_order_total_amount[]" class="item-order-total-amount" style="width:100%; padding:5px; text-align:right;" numberOnly />';
html += '</td>';
// 삭제 버튼
html += '<td style="text-align:center; padding:5px; border:1px solid #ddd;">';
html += '<button type="button" onclick="fn_deleteItemRow(\'' + itemId + '\')" class="plm_btns" style="padding:5px 10px; font-size:12px;">삭제</button>';
html += '</td>';
html += '</tr>';
$("#itemListBody").append(html);
// 제품구분 드롭다운 초기화
fnc_getCodeListAppend("0000001", "PRODUCT_" + itemId, "");
$("#PRODUCT_" + itemId).select2({ width: '100%' });
// 품번/품명 옵션 채우기
fn_fillPartOptions(itemId, null, null, null);
// 추가된 행의 날짜 필드에 datepicker 적용
$("#" + itemId + " .date_icon").datepicker({
changeMonth: true,
changeYear: true
});
// 반납사유 옵션 추가 및 select2 초기화
$("#" + itemId + " .item-return-reason").html(returnReasonOptions).select2({
width: '100%'
});
// 고객요청사항 textarea 자동 높이 조절
$("#" + itemId + " .item-customer-request").on('input', function() {
this.style.height = 'auto';
this.style.height = (this.scrollHeight) + 'px';
});
// 숫자만 입력 처리 및 금액 계산
$("#" + itemId + " .item-order-quantity, #" + itemId + " .item-order-unit-price, #" + itemId + " .item-order-supply-price, #" + itemId + " .item-order-vat, #" + itemId + " .item-order-total-amount").on("keyup", function() {
if($(this).hasClass("item-order-quantity")) {
$(this).val(formatInteger($(this).val().replace(/[^0-9]/g, "")));
} else {
$(this).val(fnc_addComma($(this).val().replace(/[^0-9.]/g, "")));
}
// 수주 금액 계산
if($(this).hasClass("item-order-quantity") || $(this).hasClass("item-order-unit-price")) {
fn_calculateItemAmount(itemId);
} else if($(this).hasClass("item-order-supply-price")) {
fn_calculateFromSupplyPrice(itemId);
} else if($(this).hasClass("item-order-vat")) {
fn_calculateTotalFromVat(itemId);
}
// 총액 직접 수정시에도 합계는 갱신
fn_calculateTotal();
});
// 품목 정보 저장
var itemData = {
id: itemId,
snList: []
};
// 기존 데이터가 있으면 채우기
if(existingData) {
itemData.objId = existingData.objId || '';
// 품번/품명 설정 (Select2에 옵션 추가 후 선택)
if(existingData.partObjId && existingData.partNo && existingData.partName) {
// hidden 필드에 값 설정
$("#" + itemId + " .item-part-objid").val(existingData.partObjId);
$("#" + itemId + " .item-part-no").val(existingData.partNo);
$("#" + itemId + " .item-part-name").val(existingData.partName);
// Select2에 옵션 추가 및 선택
var $partNoSelect = $("#PART_NO_" + itemId);
var $partNameSelect = $("#PART_NAME_" + itemId);
$partNoSelect.append(new Option(existingData.partNo, existingData.partObjId, true, true));
$partNameSelect.append(new Option(existingData.partName, existingData.partObjId, true, true));
}
// S/N 설정
if(existingData.serialNo) {
var snArray = existingData.serialNo.split(', ').map(function(sn, idx) {
return { id: 'sn_' + (idx + 1), value: sn.trim() };
});
itemData.snList = snArray;
// S/N 표시 필드 업데이트
$("#" + itemId + " .item-serial-no").val(existingData.serialNo);
$("#" + itemId + "_sn_list").val(JSON.stringify(snArray));
}
// 수주 정보 설정
if(existingData.orderQuantity) $("#" + itemId + " .item-order-quantity").val(formatInteger(existingData.orderQuantity));
if(existingData.orderUnitPrice) $("#" + itemId + " .item-order-unit-price").val(addComma(existingData.orderUnitPrice));
if(existingData.orderSupplyPrice) $("#" + itemId + " .item-order-supply-price").val(addComma(existingData.orderSupplyPrice));
if(existingData.orderVat) $("#" + itemId + " .item-order-vat").val(addComma(existingData.orderVat));
if(existingData.orderTotalAmount) $("#" + itemId + " .item-order-total-amount").val(addComma(existingData.orderTotalAmount));
// 기타 정보 설정
if(existingData.dueDate) $("#" + itemId + " .item-due-date").val(existingData.dueDate);
if(existingData.customerRequest) $("#" + itemId + " .item-customer-request").val(existingData.customerRequest);
if(existingData.returnReason) $("#" + itemId + " .item-return-reason").val(existingData.returnReason).trigger('change');
if(existingData.product) {
fnc_getCodeListAppend("0000001", "PRODUCT_" + itemId, existingData.product);
$("#PRODUCT_" + itemId).select2({ width: '100%' });
}
}
itemList.push(itemData);
}
// 기존 품목 데이터 로드 (수정 모드)
function fn_loadExistingItems() {
<%
@SuppressWarnings("unchecked")
List<Map<String, Object>> serverItemList = (List<Map<String, Object>>) request.getAttribute("itemList");
if(serverItemList != null && serverItemList.size() > 0) {
for(int i = 0; i < serverItemList.size(); i++) {
Map<String, Object> item = serverItemList.get(i);
// S/N을 SERIAL_NO 컬럼에서 가져와서 배열로 변환
String serialNos = CommonUtils.checkNull(item.get("SERIAL_NO"));
String snListJson = "[]";
if(!"".equals(serialNos)) {
String[] snArray = serialNos.split(", ");
StringBuilder sb = new StringBuilder("[");
for(int j = 0; j < snArray.length; j++) {
if(j > 0) sb.append(",");
sb.append("{");
sb.append("\"id\":\"sn_").append(j+1).append("\",");
sb.append("\"value\":\"").append(snArray[j].trim()).append("\"");
sb.append("}");
}
sb.append("]");
snListJson = sb.toString();
}
%>
// 품목 행 추가 - 품목 <%= i + 1 %>
(function() {
var itemId = 'item_' + itemCounter++;
var savedItemObjId = "<%= CommonUtils.checkNull(item.get("OBJID")) %>";
var savedPartObjId = "<%= CommonUtils.checkNull(item.get("PART_OBJID")) %>";
var savedPartNo = "<%= CommonUtils.checkNull(item.get("PART_NO")) %>";
var savedPartName = "<%= CommonUtils.checkNull(item.get("PART_NAME")) %>";
var snJsonData = <%= snListJson %>;
$("#noItemRow").hide();
var html = '<tr id="' + itemId + '" class="item-row">';
html += '<td style="text-align:center; padding:5px; border:1px solid #ddd;">' + (itemCounter-1) + '</td>';
// 제품구분 드롭다운
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<select name="item_product[]" id="PRODUCT_' + itemId + '" class="item-product select2" style="width:100%;" required>';
html += '<option value="">선택</option>';
html += '</select>';
html += '</td>';
// 품번 셀렉트박스
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<select name="item_part_no_select[]" id="PART_NO_' + itemId + '" class="item-part-no-select" style="width:100%;" required>';
html += '<option value="">선택</option>';
html += '</select>';
html += '<input type="hidden" name="item_objid[]" class="item-objid" value="' + savedItemObjId + '" />';
html += '<input type="hidden" name="item_part_objid[]" class="item-part-objid" value="' + savedPartObjId + '" />';
html += '<input type="hidden" name="item_part_no[]" class="item-part-no" value="' + savedPartNo + '" />';
html += '</td>';
// 품명 셀렉트박스
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<select name="item_part_name_select[]" id="PART_NAME_' + itemId + '" class="item-part-name-select" style="width:100%;" required>';
html += '<option value="">선택</option>';
html += '</select>';
html += '<input type="hidden" name="item_part_name[]" class="item-part-name" value="' + savedPartName + '" />';
html += '</td>';
// S/N, 수량, 납기일, 고객요청사항, 반납사유
var serialNos = "<%= CommonUtils.checkNull(item.get("SERIAL_NO")) %>";
var orderQuantity = "<%= CommonUtils.checkNull(item.get("ORDER_QUANTITY")) %>";
var orderUnitPrice = "<%= CommonUtils.checkNull(item.get("ORDER_UNIT_PRICE")) %>";
var orderSupplyPrice = "<%= CommonUtils.checkNull(item.get("ORDER_SUPPLY_PRICE")) %>";
var orderVat = "<%= CommonUtils.checkNull(item.get("ORDER_VAT")) %>";
var orderTotalAmount = "<%= CommonUtils.checkNull(item.get("ORDER_TOTAL_AMOUNT")) %>";
var dueDate = "<%= CommonUtils.checkNull(item.get("DUE_DATE")) %>";
var customerRequest = <%= new com.google.gson.Gson().toJson(CommonUtils.checkNull(item.get("CUSTOMER_REQUEST"))) %>;
var returnReason = "<%= CommonUtils.checkNull(item.get("RETURN_REASON")) %>";
var savedProduct = "<%= CommonUtils.checkNull(item.get("PRODUCT")) %>";
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<input type="text" name="item_serial_no[]" class="item-serial-no" placeholder="클릭하여 S/N 추가" readonly style="width:100%; padding:5px; cursor:pointer; background-color:#f8f9fa;" onclick="fn_openItemSnPopup(\'' + itemId + '\')" value="' + serialNos + '" />';
html += '<input type="hidden" name="item_serial_no_list[]" id="' + itemId + '_sn_list" value="" />';
html += '</td>';
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<input type="text" name="item_due_date[]" class="item-due-date date_icon" style="width:100%; padding:5px;" value="' + dueDate + '" />';
html += '</td>';
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<textarea name="item_customer_request[]" class="item-customer-request" style="width:100%; padding:5px; min-height:34px; resize:vertical; font-family:inherit; font-size:inherit;" rows="1"></textarea>';
html += '</td>';
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<select name="item_return_reason[]" class="item-return-reason select2" style="width:100%;"></select>';
html += '</td>';
// 수주 정보 (Machine이고 프로젝트가 있으면 수량 readonly)
html += '<td style="padding:5px; border:1px solid #ddd;">';
if(isMachine && hasProject) {
html += '<input type="text" name="item_order_quantity[]" class="item-order-quantity" style="width:100%; padding:5px; text-align:right; background:#f5f5f5;" required numberOnly readonly title="Machine 제품은 프로젝트 생성 후 수량 변경이 불가능합니다." value="' + (orderQuantity ? formatInteger(orderQuantity) : '') + '" />';
} else {
html += '<input type="text" name="item_order_quantity[]" class="item-order-quantity" style="width:100%; padding:5px; text-align:right;" required numberOnly value="' + (orderQuantity ? formatInteger(orderQuantity) : '') + '" />';
}
html += '</td>';
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<input type="text" name="item_order_unit_price[]" class="item-order-unit-price" style="width:100%; padding:5px; text-align:right;" numberOnly value="' + (orderUnitPrice ? addComma(orderUnitPrice) : '') + '" />';
html += '</td>';
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<input type="text" name="item_order_supply_price[]" class="item-order-supply-price" style="width:100%; padding:5px; text-align:right;" numberOnly value="' + (orderSupplyPrice ? addComma(orderSupplyPrice) : '') + '" />';
html += '</td>';
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<input type="text" name="item_order_vat[]" class="item-order-vat" style="width:100%; padding:5px; text-align:right;" numberOnly value="' + (orderVat ? addComma(orderVat) : '') + '" />';
html += '</td>';
html += '<td style="padding:5px; border:1px solid #ddd;">';
html += '<input type="text" name="item_order_total_amount[]" class="item-order-total-amount" style="width:100%; padding:5px; text-align:right;" numberOnly value="' + (orderTotalAmount ? addComma(orderTotalAmount) : '') + '" />';
html += '</td>';
html += '<td style="text-align:center; padding:5px; border:1px solid #ddd;">';
html += '<button type="button" onclick="fn_deleteItemRow(\'' + itemId + '\')" class="plm_btns" style="padding:5px 10px; font-size:12px;">삭제</button>';
html += '</td>';
html += '</tr>';
$("#itemListBody").append(html);
// 고객요청사항 값 설정
if(customerRequest) {
$("#" + itemId + " .item-customer-request").val(customerRequest);
}
// S/N 데이터 설정
var snJsonString = JSON.stringify(snJsonData);
setTimeout(function() {
$("#" + itemId + "_sn_list").val(snJsonString);
if(returnReason) {
$("#" + itemId + " .item-return-reason").val(returnReason).trigger('change');
}
}, 10);
// 품번/품명 옵션 채우기
fn_fillPartOptions(itemId, savedPartObjId, savedPartNo, savedPartName);
// 제품구분 드롭다운 초기화
fnc_getCodeListAppend("0000001", "PRODUCT_" + itemId, savedProduct);
$("#PRODUCT_" + itemId).select2({ width: '100%' });
// datepicker 적용
$("#" + itemId + " .date_icon").datepicker({
changeMonth: true,
changeYear: true
});
// 반납사유 옵션 추가 및 select2 초기화
$("#" + itemId + " .item-return-reason").html(returnReasonOptions).select2({
width: '100%'
});
// 고객요청사항 textarea 자동 높이 조절
var $textarea = $("#" + itemId + " .item-customer-request");
$textarea.on('input', function() {
this.style.height = 'auto';
this.style.height = (this.scrollHeight) + 'px';
});
setTimeout(function() {
if($textarea.val()) {
$textarea[0].style.height = 'auto';
$textarea[0].style.height = ($textarea[0].scrollHeight) + 'px';
}
}, 50);
// 숫자만 입력 처리 및 금액 계산
$("#" + itemId + " .item-order-quantity, #" + itemId + " .item-order-unit-price, #" + itemId + " .item-order-supply-price, #" + itemId + " .item-order-vat, #" + itemId + " .item-order-total-amount").on("keyup", function() {
if($(this).hasClass("item-order-quantity")) {
$(this).val(formatInteger($(this).val().replace(/[^0-9]/g, "")));
} else {
$(this).val(fnc_addComma($(this).val().replace(/[^0-9.]/g, "")));
}
if($(this).hasClass("item-order-quantity") || $(this).hasClass("item-order-unit-price")) {
fn_calculateItemAmount(itemId);
} else if($(this).hasClass("item-order-supply-price")) {
fn_calculateFromSupplyPrice(itemId);
} else if($(this).hasClass("item-order-vat")) {
fn_calculateTotalFromVat(itemId);
}
// 총액 직접 수정시에도 합계는 갱신
fn_calculateTotal();
});
// 품목 정보 저장
itemList.push({
id: itemId,
snList: snJsonData
});
})();
<%
}
}
%>
// 기존 품목 로드 완료 후 합계 계산
fn_calculateTotal();
}
// 품목별 금액 계산
function fn_calculateItemAmount(itemId) {
var quantity = parseFloat(removeComma($("#" + itemId + " .item-order-quantity").val())) || 0;
var unitPrice = parseFloat(removeComma($("#" + itemId + " .item-order-unit-price").val())) || 0;
// 공급가액 계산
var supplyPrice = quantity * unitPrice;
$("#" + itemId + " .item-order-supply-price").val(addComma(supplyPrice));
// 부가세 자동 계산 (공급가액의 10%)
var vat = Math.round(supplyPrice * 0.1);
$("#" + itemId + " .item-order-vat").val(addComma(vat));
// 총액 계산
var totalAmount = supplyPrice + vat;
$("#" + itemId + " .item-order-total-amount").val(addComma(totalAmount));
fn_calculateTotal();
}
// 부가세 직접 입력 시 총액만 재계산
function fn_calculateTotalFromVat(itemId) {
var supplyPrice = parseFloat(removeComma($("#" + itemId + " .item-order-supply-price").val())) || 0;
var vat = parseFloat(removeComma($("#" + itemId + " .item-order-vat").val())) || 0;
// 총액 계산
var totalAmount = supplyPrice + vat;
$("#" + itemId + " .item-order-total-amount").val(addComma(totalAmount));
fn_calculateTotal();
}
// 공급가액 직접 입력 시 부가세와 총액 재계산
function fn_calculateFromSupplyPrice(itemId) {
var supplyPrice = parseFloat(removeComma($("#" + itemId + " .item-order-supply-price").val())) || 0;
// 부가세 자동 계산 (공급가액의 10%)
var vat = Math.round(supplyPrice * 0.1);
$("#" + itemId + " .item-order-vat").val(addComma(vat));
// 총액 계산
var totalAmount = supplyPrice + vat;
$("#" + itemId + " .item-order-total-amount").val(addComma(totalAmount));
fn_calculateTotal();
}
// 품목 합계 계산
function fn_calculateTotal() {
var totalQty = 0, totalSupply = 0, totalVat = 0, totalAmount = 0;
$(".item-row").each(function() {
totalQty += parseFloat(removeComma($(this).find(".item-order-quantity").val())) || 0;
totalSupply += parseFloat(removeComma($(this).find(".item-order-supply-price").val())) || 0;
totalVat += parseFloat(removeComma($(this).find(".item-order-vat").val())) || 0;
totalAmount += parseFloat(removeComma($(this).find(".item-order-total-amount").val())) || 0;
});
$("#totalOrderQuantity").text(addCommaInt(totalQty));
$("#totalOrderSupplyPrice").text(addComma(totalSupply));
$("#totalOrderVat").text(addComma(totalVat));
$("#totalOrderTotalAmount").text(addComma(totalAmount));
}
// 품번/품명 셀렉트박스 옵션 채우기 (Select2 AJAX)
function fn_fillPartOptions(itemId, selectedObjId, savedPartNo, savedPartName) {
var $partNoSelect = $("#PART_NO_" + itemId);
var $partNameSelect = $("#PART_NAME_" + itemId);
// null 문자열 체크
if(selectedObjId === "null" || !selectedObjId) selectedObjId = null;
if(savedPartNo === "null" || !savedPartNo) savedPartNo = null;
if(savedPartName === "null" || !savedPartName) savedPartName = null;
// partObjId는 있는데 partNo/partName이 없는 경우 -> DB에서 조회
if(selectedObjId && (!savedPartNo || !savedPartName)) {
console.log("품번/품명 정보 없음. OBJID로 조회:", selectedObjId);
$.ajax({
url: '/contractMgmt/searchPartList.do',
type: 'POST',
data: { partObjId: selectedObjId },
async: false, // 동기 처리
success: function(data) {
if(data && data.length > 0) {
var part = data[0];
savedPartNo = part.PART_NO || part.part_no;
savedPartName = part.PART_NAME || part.part_name;
console.log("조회된 품번/품명:", savedPartNo, savedPartName);
}
},
error: function() {
console.error("품번/품명 조회 실패");
}
});
}
// 기존 선택값이 있으면 Select2 초기화 전에 옵션 추가
if(selectedObjId && savedPartNo && savedPartName) {
// Select2 초기화 전에 옵션 추가
var partNoOption = new Option(savedPartNo, selectedObjId, true, true);
$partNoSelect.append(partNoOption);
var partNameOption = new Option(savedPartName, selectedObjId, true, true);
$partNameSelect.append(partNameOption);
// hidden 필드에도 직접 값 설정 (이벤트 트리거 없이)
$("#" + itemId + " .item-part-objid").val(selectedObjId);
$("#" + itemId + " .item-part-no").val(savedPartNo);
$("#" + itemId + " .item-part-name").val(savedPartName);
}
// 기존 데이터가 있으면 Select2 초기화를 나중에 하므로 여기서는 건너뜀
var hasExistingData = (selectedObjId && savedPartNo && savedPartName);
// 품번 Select2 AJAX 설정 (기존 데이터 없을 때만)
if(!hasExistingData) {
$partNoSelect.select2({
placeholder: "품번 입력하여 검색...",
allowClear: true,
width: '100%',
minimumInputLength: 1,
language: {
inputTooShort: function() {
return "최소 1글자 이상 입력하세요";
},
searching: function() {
return "검색 중...";
},
noResults: function() {
return "검색 결과가 없습니다";
}
},
ajax: {
url: '/contractMgmt/searchPartList.do',
dataType: 'json',
type: 'POST',
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
delay: 250,
data: function(params) {
return {
searchTerm: params.term
};
},
processResults: function(data) {
var results = $.map(data, function(item) {
var objId = item.OBJID || item.objid || item.objId;
var partNo = item.PART_NO || item.part_no || item.partNo;
var partName = item.PART_NAME || item.part_name || item.partName;
return {
id: objId,
text: partNo,
partName: partName,
partNo: partNo
};
});
return {
results: results
};
},
cache: true
}
});
}
// 품명 Select2 AJAX 설정 (기존 데이터 없을 때만)
if(!hasExistingData) {
$partNameSelect.select2({
placeholder: "품명 입력하여 검색...",
allowClear: true,
width: '100%',
minimumInputLength: 1,
language: {
inputTooShort: function() {
return "최소 1글자 이상 입력하세요";
},
searching: function() {
return "검색 중...";
},
noResults: function() {
return "검색 결과가 없습니다";
}
},
ajax: {
url: '/contractMgmt/searchPartList.do',
dataType: 'json',
type: 'POST',
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
delay: 250,
data: function(params) {
return {
searchTerm: params.term
};
},
processResults: function(data) {
var results = $.map(data, function(item) {
var objId = item.OBJID || item.objid || item.objId;
var partNo = item.PART_NO || item.part_no || item.partNo;
var partName = item.PART_NAME || item.part_name || item.partName;
return {
id: objId,
text: partName,
partName: partName,
partNo: partNo
};
});
return {
results: results
};
},
cache: true
}
});
}
// 품번 선택 이벤트 (기존 데이터 없을 때만 등록)
if(!hasExistingData) {
$partNoSelect.off('select2:select').on('select2:select', function(e) {
var selectedData = e.params.data;
if(selectedData && selectedData.id) {
$partNameSelect.data('syncing', true);
if($partNameSelect.find("option[value='" + selectedData.id + "']").length === 0) {
var newOption = new Option(selectedData.partName, selectedData.id, true, true);
$(newOption).data('partNo', selectedData.partNo);
$partNameSelect.append(newOption);
}
$partNameSelect.val(selectedData.id).trigger('change.select2');
var $row = $("#" + itemId);
$row.find(".item-part-objid").val(selectedData.id);
$row.find(".item-part-no").val(selectedData.partNo);
$row.find(".item-part-name").val(selectedData.partName);
setTimeout(function() {
$partNameSelect.data('syncing', false);
}, 100);
}
});
}
// 품명 선택 이벤트 (기존 데이터 없을 때만 등록)
if(!hasExistingData) {
$partNameSelect.off('select2:select').on('select2:select', function(e) {
if($(this).data('syncing')) return;
var selectedData = e.params.data;
if(selectedData && selectedData.id) {
$partNoSelect.data('syncing', true);
if($partNoSelect.find("option[value='" + selectedData.id + "']").length === 0) {
var newOption = new Option(selectedData.partNo, selectedData.id, true, true);
$(newOption).data('partName', selectedData.partName);
$partNoSelect.append(newOption);
}
$partNoSelect.val(selectedData.id).trigger('change.select2');
var $row = $("#" + itemId);
$row.find(".item-part-objid").val(selectedData.id);
$row.find(".item-part-no").val(selectedData.partNo);
$row.find(".item-part-name").val(selectedData.partName);
setTimeout(function() {
$partNoSelect.data('syncing', false);
}, 100);
}
});
}
// 기존 데이터가 있으면 Select2 destroy 후 재초기화
if(hasExistingData) {
setTimeout(function() {
// Select2 destroy
if($partNoSelect.hasClass("select2-hidden-accessible")) {
$partNoSelect.select2('destroy');
}
if($partNameSelect.hasClass("select2-hidden-accessible")) {
$partNameSelect.select2('destroy');
}
// 값 설정
$partNoSelect.val(selectedObjId);
$partNameSelect.val(selectedObjId);
// Select2 재초기화 (AJAX 포함 - 위와 동일한 설정)
$partNoSelect.select2({
placeholder: "품번 입력하여 검색...",
allowClear: true,
width: '100%',
minimumInputLength: 1,
language: {
inputTooShort: function() { return "최소 1글자 이상 입력하세요"; },
searching: function() { return "검색 중..."; },
noResults: function() { return "검색 결과가 없습니다"; }
},
ajax: {
url: '/contractMgmt/searchPartList.do',
dataType: 'json',
type: 'POST',
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
delay: 250,
data: function(params) { return { searchTerm: params.term }; },
processResults: function(data) {
var results = $.map(data, function(item) {
var objId = item.OBJID || item.objid || item.objId;
var partNo = item.PART_NO || item.part_no || item.partNo;
var partName = item.PART_NAME || item.part_name || item.partName;
return { id: objId, text: partNo, partName: partName, partNo: partNo };
});
return { results: results };
},
cache: true
}
});
$partNameSelect.select2({
placeholder: "품명 입력하여 검색...",
allowClear: true,
width: '100%',
minimumInputLength: 1,
language: {
inputTooShort: function() { return "최소 1글자 이상 입력하세요"; },
searching: function() { return "검색 중..."; },
noResults: function() { return "검색 결과가 없습니다"; }
},
ajax: {
url: '/contractMgmt/searchPartList.do',
dataType: 'json',
type: 'POST',
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
delay: 250,
data: function(params) { return { searchTerm: params.term }; },
processResults: function(data) {
var results = $.map(data, function(item) {
var objId = item.OBJID || item.objid || item.objId;
var partNo = item.PART_NO || item.part_no || item.partNo;
var partName = item.PART_NAME || item.part_name || item.partName;
return { id: objId, text: partName, partName: partName, partNo: partNo };
});
return { results: results };
},
cache: true
}
});
// 이벤트 핸들러 재등록
$partNoSelect.off('select2:select').on('select2:select', function(e) {
var selectedData = e.params.data;
if(selectedData && selectedData.id) {
$partNameSelect.data('syncing', true);
if($partNameSelect.find("option[value='" + selectedData.id + "']").length === 0) {
var newOption = new Option(selectedData.partName, selectedData.id, true, true);
$(newOption).data('partNo', selectedData.partNo);
$partNameSelect.append(newOption);
}
$partNameSelect.val(selectedData.id).trigger('change.select2');
var $row = $("#" + itemId);
$row.find(".item-part-objid").val(selectedData.id);
$row.find(".item-part-no").val(selectedData.partNo);
$row.find(".item-part-name").val(selectedData.partName);
setTimeout(function() { $partNameSelect.data('syncing', false); }, 100);
}
});
$partNameSelect.off('select2:select').on('select2:select', function(e) {
if($(this).data('syncing')) return;
var selectedData = e.params.data;
if(selectedData && selectedData.id) {
$partNoSelect.data('syncing', true);
if($partNoSelect.find("option[value='" + selectedData.id + "']").length === 0) {
var newOption = new Option(selectedData.partNo, selectedData.id, true, true);
$(newOption).data('partName', selectedData.partName);
$partNoSelect.append(newOption);
}
$partNoSelect.val(selectedData.id).trigger('change.select2');
var $row = $("#" + itemId);
$row.find(".item-part-objid").val(selectedData.id);
$row.find(".item-part-no").val(selectedData.partNo);
$row.find(".item-part-name").val(selectedData.partName);
setTimeout(function() { $partNoSelect.data('syncing', false); }, 100);
}
});
}, 200);
}
// 프로젝트가 있으면 품번/품명 셀렉트박스 비활성화 및 삭제 버튼 비활성화
if(hasProject) {
setTimeout(function() {
$("#PART_NO_" + itemId).prop("disabled", true).css("background-color", "#f5f5f5");
$("#PART_NAME_" + itemId).prop("disabled", true).css("background-color", "#f5f5f5");
$("#" + itemId + " button[onclick*='fn_deleteItemRow']").prop("disabled", true).css({
"background-color": "#ccc",
"cursor": "not-allowed",
"opacity": "0.6"
});
}, 300);
}
}
// 품목 행 삭제
function fn_deleteItemRow(itemId) {
// 프로젝트가 있으면 품목 삭제 불가
if(hasProject) {
Swal.fire({
title: '품목 삭제 불가',
text: '프로젝트가 이미 생성되어 품목을 삭제할 수 없습니다.',
icon: 'warning'
});
return;
}
if(confirm("해당 품목을 삭제하시겠습니까?")) {
$("#" + itemId).remove();
// itemList에서 제거
for(var i = 0; i < itemList.length; i++) {
if(itemList[i].id == itemId) {
itemList.splice(i, 1);
break;
}
}
// 행 번호 재정렬
fn_reorderItemRows();
// 합계 재계산
fn_calculateTotal();
// 모든 행이 삭제되면 안내 메시지 표시
if($(".item-row").length == 0) {
$("#noItemRow").show();
}
}
}
// 품목 행 번호 재정렬
function fn_reorderItemRows() {
$(".item-row").each(function(index) {
$(this).find("td:first").text(index + 1);
});
}
// 품목별 S/N 관리 팝업
var currentItemId = '';
var currentItemSnList = [];
var itemSnCounter = 1;
function fn_openItemSnPopup(itemId) {
currentItemId = itemId;
// 해당 품목의 S/N 목록 로드
var snListValue = $("#" + itemId + "_sn_list").val();
currentItemSnList = [];
if(snListValue && snListValue.trim() != '') {
try {
currentItemSnList = JSON.parse(snListValue);
if(currentItemSnList.length > 0) {
var maxId = Math.max.apply(Math, currentItemSnList.map(function(item) { return item.id; }));
itemSnCounter = maxId + 1;
}
} catch(e) {
currentItemSnList = [];
}
}
// 팝업 HTML 생성
var popupHtml = '<div style="padding:10px; color:#333;">';
popupHtml += ' <h3 style="margin:0 0 15px 0; text-align:center; color:#333;">S/N 관리</h3>';
popupHtml += ' <div id="itemSnListContainer" style="margin-bottom:15px; max-height:300px; overflow-y:auto; color:#333;"></div>';
popupHtml += ' <div style="margin-bottom:15px; display:flex; gap:5px;">';
popupHtml += ' <input type="text" id="newItemSnInput" placeholder="S/N 입력" style="flex:1; padding:8px; border:1px solid #ddd; border-radius:4px; color:#333;">';
popupHtml += ' <button type="button" onclick="fn_addItemSn()" class="plm_btns">추가</button>';
popupHtml += ' </div>';
popupHtml += ' <div style="text-align:center; margin-top:20px; display:flex; gap:10px; justify-content:center;">';
popupHtml += ' <button type="button" onclick="fn_openItemSequentialSnPopup()" class="plm_btns">연속번호생성</button>';
popupHtml += ' <button type="button" onclick="fn_confirmItemSnList()" class="plm_btns">확인</button>';
popupHtml += ' <button type="button" onclick="fn_closeItemSnPopup()" class="plm_btns">취소</button>';
popupHtml += ' </div>';
popupHtml += '</div>';
// 팝업 오픈
Swal.fire({
html: popupHtml,
width: '700px',
showConfirmButton: false,
showCloseButton: true,
customClass: {
popup: 'sn-manage-popup'
},
onOpen: function() {
setTimeout(function() {
fn_renderItemSnList();
$(".swal2-html-container #newItemSnInput").keypress(function(e) {
if(e.which == 13) {
fn_addItemSn();
return false;
}
});
}, 50);
}
});
}
// 품목 S/N 목록 렌더링
function fn_renderItemSnList() {
var html = '<table style="width:100%; margin-bottom:10px; border-collapse:collapse; border:1px solid #ddd; color:#333;">';
html += '<colgroup><col width="15%"><col width="65%"><col width="20%"></colgroup>';
html += '<thead><tr style="background:#f5f5f5; color:#333;">';
html += '<th style="padding:10px; border:1px solid #ddd; text-align:center; color:#333;">번호</th>';
html += '<th style="padding:10px; border:1px solid #ddd; text-align:center; color:#333;">S/N</th>';
html += '<th style="padding:10px; border:1px solid #ddd; text-align:center; color:#333;">삭제</th>';
html += '</tr></thead>';
html += '<tbody>';
if(currentItemSnList.length == 0) {
html += '<tr><td colspan="3" style="text-align:center; padding:30px; color:#999; border:1px solid #ddd;">등록된 S/N이 없습니다.</td></tr>';
} else {
for(var i = 0; i < currentItemSnList.length; i++) {
html += '<tr>';
html += '<td style="text-align:center; padding:8px; border:1px solid #ddd; color:#333;">' + (i+1) + '</td>';
html += '<td style="padding:8px; border:1px solid #ddd; color:#333;">' + currentItemSnList[i].value + '</td>';
html += '<td style="text-align:center; padding:8px; border:1px solid #ddd;">';
html += '<button type="button" onclick="fn_deleteItemSn(' + currentItemSnList[i].id + ')" class="plm_btns" style="padding:5px 10px; font-size:12px;">삭제</button>';
html += '</td>';
html += '</tr>';
}
}
html += '</tbody></table>';
$(".swal2-html-container #itemSnListContainer").html(html);
}
// 품목 S/N 추가
function fn_addItemSn() {
var newSn = $(".swal2-html-container #newItemSnInput").val().trim();
if(newSn == '') {
alert('S/N을 입력해주세요.');
return;
}
// 중복 체크
for(var i = 0; i < currentItemSnList.length; i++) {
if(currentItemSnList[i].value == newSn) {
alert('이미 등록된 S/N입니다.');
return;
}
}
// 추가
currentItemSnList.push({
id: itemSnCounter++,
value: newSn
});
$(".swal2-html-container #newItemSnInput").val('');
fn_renderItemSnList();
}
// 품목 S/N 삭제
function fn_deleteItemSn(snId) {
for(var i = 0; i < currentItemSnList.length; i++) {
if(currentItemSnList[i].id == snId) {
currentItemSnList.splice(i, 1);
break;
}
}
fn_renderItemSnList();
}
// 품목 연속번호 생성 팝업
function fn_openItemSequentialSnPopup() {
Swal.fire({
title: '연속번호 생성',
html:
'<div style="text-align:left; padding:5px; color:#333;">' +
'<div style="margin-bottom:10px;">' +
'<label style="display:block; margin-bottom:3px; font-size:13px; color:#333;">시작번호 <span style="color:red;">*</span></label>' +
'<input type="text" id="itemSeqStartNo" placeholder="예: ITEM-001" style="width:100%; padding:6px; border:1px solid #ddd; border-radius:3px; font-size:13px; color:#333;">' +
'</div>' +
'<div style="margin-bottom:10px;">' +
'<label style="display:block; margin-bottom:3px; font-size:13px; color:#333;">생성개수 <span style="color:red;">*</span></label>' +
'<input type="number" id="itemSeqCount" placeholder="예: 10" min="1" max="100" style="width:100%; padding:6px; border:1px solid #ddd; border-radius:3px; font-size:13px; color:#333;">' +
'</div>' +
'<div style="background:#f8f9fa; padding:8px; border-radius:3px; color:#666; font-size:11px; line-height:1.5;">' +
'예: ITEM-001, 개수 3 → ITEM-001, ITEM-002, ITEM-003<br>' +
'※ 최대 100개까지 생성 가능' +
'</div>' +
'</div>',
width: '400px',
showCancelButton: true,
confirmButtonText: '생성',
cancelButtonText: '취소',
preConfirm: () => {
const startNo = document.getElementById('itemSeqStartNo').value.trim();
const count = parseInt(document.getElementById('itemSeqCount').value);
if(!startNo) {
Swal.showValidationMessage('시작 번호를 입력해주세요.');
return false;
}
if(!count || count < 1) {
Swal.showValidationMessage('생성 개수를 1 이상 입력해주세요.');
return false;
}
if(count > 100) {
Swal.showValidationMessage('최대 100개까지만 생성 가능합니다.');
return false;
}
return { startNo: startNo, count: count };
}
}).then((result) => {
if(result.isConfirmed) {
fn_generateItemSequentialSn(result.value.startNo, result.value.count);
}
});
}
// 품목 연속번호 생성
function fn_generateItemSequentialSn(startNo, count) {
var match = startNo.match(/^(.*?)(\d+)$/);
if(!match) {
alert('올바른 형식이 아닙니다. 마지막에 숫자가 있어야 합니다. (예: ITEM-001)');
fn_openItemSequentialSnPopup();
return;
}
var prefix = match[1];
var startNum = parseInt(match[2]);
var numLength = match[2].length;
var addedCount = 0;
for(var i = 0; i < count; i++) {
var currentNum = startNum + i;
var paddedNum = String(currentNum).padStart(numLength, '0');
var newSn = prefix + paddedNum;
// 중복 체크
var isDuplicate = false;
for(var j = 0; j < currentItemSnList.length; j++) {
if(currentItemSnList[j].value == newSn) {
isDuplicate = true;
break;
}
}
if(!isDuplicate) {
currentItemSnList.push({
id: itemSnCounter++,
value: newSn
});
addedCount++;
}
}
// 저장 후 팝업 재오픈
$("#" + currentItemId + "_sn_list").val(JSON.stringify(currentItemSnList));
fn_openItemSnPopup(currentItemId);
}
// 품목 S/N 확인
function fn_confirmItemSnList() {
// hidden 필드에 저장
var jsonString = JSON.stringify(currentItemSnList);
$("#" + currentItemId + "_sn_list").val(jsonString);
// display 필드에 표시
var snValues = [];
for(var i = 0; i < currentItemSnList.length; i++) {
snValues.push(currentItemSnList[i].value);
}
$("#" + currentItemId + " .item-serial-no").val(snValues.join(', '));
// itemList 업데이트
for(var i = 0; i < itemList.length; i++) {
if(itemList[i].id == currentItemId) {
itemList[i].snList = currentItemSnList;
break;
}
}
Swal.close();
}
// 품목 S/N 팝업 닫기
function fn_closeItemSnPopup() {
Swal.close();
}
</script>
</head>
<body>
<form name="form1" id="form1" action="" method="post">
<input type="hidden" name="objId" id="objId" value="${objId}">
<input type="hidden" name="actionType" id="actionType" value="${actionType}">
<!-- 반납사유 공통코드 템플릿 (숨김) -->
<select id="return_reason_template" style="display:none;">
<option value="">선택</option>
${code_map.return_reason_cd}
</select>
<section class="business_popup_min_width">
<div class="plm_menu_name">
<h2>
<span>영업관리_주문서관리_수주통합등록</span>
</h2>
</div>
<div id="EntirePopupFormWrap">
<div class="form_popup_title">
<span>수주통합 기본정보</span>
</div>
<table class="">
<colgroup>
<col width="100%" />
</colgroup>
<tr>
<td>
<table class="pmsPopuptable">
<colgroup>
<col width="12%" />
<col width="21%" />
<col width="12%" />
<col width="21%" />
<col width="12%" />
<col width="22%" />
</colgroup>
<!-- 첫번째 행: 주문유형, 제품구분, 국내/해외, 고객사 -->
<tr>
<td class="input_title"><label for="">주문유형 <span style="color:red;">*</span></label></td>
<td>
<select name="category_cd" id="category_cd" required reqTitle="주문유형" type="select" class="select2">
<option value="">선택</option>
${code_map.category_cd}
</select>
</td>
<%-- 제품구분: 품목정보 그리드로 이동 --%>
<%-- <td class="input_title"><label for="">제품구분 <span style="color:red;">*</span></label></td>
<td>
<select name="product" id="product" required reqTitle="제품구분" type="select" class="select2">
<option value="">선택</option>
${code_map.product_cd}
</select>
</td> --%>
<td class="input_title"><label for="">국내/해외 <span style="color:red;">*</span></label></td>
<td>
<select name="area_cd" id="area_cd" reqTitle="국내/해외" type="select" class="select2" required>
<option value="">선택</option>
${code_map.area_cd}
</select>
</td>
<td class="input_title"><label for="">고객사 <span style="color:red;">*</span></label></td>
<td>
<select name="customer_objid" id="customer_objid" required reqTitle="고객사" type="select" class="select2">
<option value="">선택</option>
${code_map.customer_cd}
</select>
</td>
</tr>
<!-- 두번째 행: 유/무상, 접수일, 견적환종 -->
<tr>
<td class="input_title"><label for="">유/무상 <span style="color:red;">*</span></label></td>
<td>
<select name="paid_type" id="paid_type" reqTitle="유/무상" type="select" class="select2">
<option value="">선택</option>
<option value="paid" ${info.PAID_TYPE == 'paid' ? 'selected' : ''}>유상</option>
<option value="free" ${info.PAID_TYPE == 'free' ? 'selected' : ''}>무상</option>
</select>
</td>
<td class="input_title"><label for="">접수일 <span style="color:red;">*</span></label></td>
<td>
<input type="text" class="date_icon" name="receipt_date" id="receipt_date" reqTitle="접수일" required value="${info.RECEIPT_DATE}">
</td>
<td class="input_title"><label for="">견적환종</label></td>
<td>
<select name="contract_currency" id="contract_currency" reqTitle="환종" type="select" class="select2">
<option value="">선택</option>
${code_map.contract_currency}
</select>
</td>
</tr>
<!-- 세번째 행: 견적환율, 발주번호, 발주일 -->
<tr>
<td class="input_title"><label for="">견적환율</label></td>
<td>
<input type="text" name="exchange_rate" id="exchange_rate" reqTitle="환율" value="${info.EXCHANGE_RATE}" />
</td>
<td class="input_title"><label for="">발주번호</label></td>
<td>
<input type="text" name="po_no" id="po_no" value="${info.PO_NO}" />
</td>
<td class="input_title"><label for="">발주일 <span style="color:red;">*</span></label></td>
<td>
<input type="text" class="date_icon" name="order_date" id="order_date" required reqTitle="발주일" value="${info.ORDER_DATE}">
</td>
</tr>
<input type="hidden" name="contract_result" id="contract_result" value="${info.CONTRACT_RESULT}" />
</table>
</td>
</tr>
</table>
<!-- ========== 품목정보 영역 ========== -->
<div class="form_popup_title">
<span>품목정보</span>
<button type="button" id="btnAddItem" class="plm_btns" style="float:right; margin-top:-5px;">품목 추가</button>
</div>
<table class="">
<colgroup>
<col width="100%" />
</colgroup>
<tr>
<td>
<div style="max-height:400px; overflow-y:auto;">
<table class="pmsPopuptable" id="itemListTable">
<colgroup>
<col width="3%" /> <!-- 번호 -->
<col width="6%" /> <!-- 제품구분 -->
<col width="7%" /> <!-- 품번 -->
<col width="10%" /> <!-- 품명 -->
<col width="9%" /> <!-- S/N -->
<col width="7%" /> <!-- 요청납기 -->
<col width="11%" /> <!-- 고객요청사항 -->
<col width="6%" /> <!-- 반납사유 -->
<col width="6%" /> <!-- 수주수량 -->
<col width="7%" /> <!-- 수주단가 -->
<col width="7%" /> <!-- 수주공급가액 -->
<col width="7%" /> <!-- 수주부가세 -->
<col width="7%" /> <!-- 수주총액 -->
<col width="4%" /> <!-- 삭제 -->
</colgroup>
<thead>
<tr style="background:#f5f5f5;">
<th style="text-align:center; padding:8px; border:1px solid #ddd;">No</th>
<th style="text-align:center; padding:8px; border:1px solid #ddd; background:#e3f2fd;">제품구분 <span style="color:red;">*</span></th>
<th style="text-align:center; padding:8px; border:1px solid #ddd; background:#e3f2fd;">품번 <span style="color:red;">*</span></th>
<th style="text-align:center; padding:8px; border:1px solid #ddd; background:#e3f2fd;">품명 <span style="color:red;">*</span></th>
<th style="text-align:center; padding:8px; border:1px solid #ddd; background:#e3f2fd;">S/N</th>
<th style="text-align:center; padding:8px; border:1px solid #ddd; background:#e3f2fd;">요청납기</th>
<th style="text-align:center; padding:8px; border:1px solid #ddd; background:#e3f2fd;">고객요청사항</th>
<th style="text-align:center; padding:8px; border:1px solid #ddd; background:#e3f2fd;">반납사유</th>
<th style="text-align:center; padding:8px; border:1px solid #ddd; background:#fff3e0;">수주수량 <span style="color:red;">*</span></th>
<th style="text-align:center; padding:8px; border:1px solid #ddd; background:#fff3e0;">수주단가 </th>
<th style="text-align:center; padding:8px; border:1px solid #ddd; background:#fff3e0;">수주공급가액</th>
<th style="text-align:center; padding:8px; border:1px solid #ddd; background:#fff3e0;">수주부가세</th>
<th style="text-align:center; padding:8px; border:1px solid #ddd; background:#fff3e0;">수주총액</th>
<th style="text-align:center; padding:8px; border:1px solid #ddd;">삭제</th>
</tr>
</thead>
<tbody id="itemListBody">
<!-- 품목 행이 동적으로 추가됩니다 -->
<tr id="noItemRow">
<td colspan="14" style="text-align:center; padding:30px; color:#999;">
품목 추가 버튼을 클릭하여 품목을 등록하세요.
</td>
</tr>
</tbody>
<tfoot>
<tr id="totalRow" style="background:#f5f5f5; font-weight:bold;">
<td colspan="8" style="text-align:center; padding:8px; border:1px solid #ddd;">Total</td>
<td style="text-align:right; padding:8px; border:1px solid #ddd;" id="totalOrderQuantity">0</td>
<td style="padding:8px; border:1px solid #ddd;"></td>
<td style="text-align:right; padding:8px; border:1px solid #ddd;" id="totalOrderSupplyPrice">0.00</td>
<td style="text-align:right; padding:8px; border:1px solid #ddd;" id="totalOrderVat">0.00</td>
<td style="text-align:right; padding:8px; border:1px solid #ddd;" id="totalOrderTotalAmount">0.00</td>
<td style="padding:8px; border:1px solid #ddd;"></td>
</tr>
</tfoot>
</table>
</div>
</td>
</tr>
</table>
</div>
<div class="btn_wrap">
<div class="plm_btn_wrap_center">
<input type="button" value="저장" id="btnSave" class="plm_btns">
<input type="button" value="닫기" id="btnClose" class="plm_btns">
</div>
</div>
</section>
</form>
</body>
</html>