판매관리 원상복구

This commit is contained in:
Johngreen
2025-11-06 15:12:20 +09:00
parent 94bf8421d4
commit c647285e70
7 changed files with 584 additions and 2320 deletions

View File

@@ -201,7 +201,6 @@ function fn_search(){
// 그리드 초기화
_tabulGrid = new Tabulator("#mainGrid", {
layout: _tabul_layout_fitColumns,
height: "auto",
columns: columns,
data: response.RESULTLIST || []
});

View File

@@ -13,44 +13,6 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<style>
/* 분할출하 행 스타일 */
.tabulator-row .split-row {
background-color: #E3F2FD !important;
}
.tabulator-row:hover .split-row {
background-color: #BBDEFB !important;
}
/* 분할출하 부모 행 클릭 가능 표시 */
.has-children-row {
cursor: pointer !important;
}
/* Tabulator 기본 트리 아이콘 숨김 */
.tabulator-row .tabulator-data-tree-branch,
.tabulator-row .tabulator-data-tree-control {
display: none !important;
}
/* 트리 토글 아이콘 커스텀 */
.tabulator-tree-toggle {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 5px;
cursor: pointer;
}
/* 자식 행(분할출하 데이터) 들여쓰기 */
.tabulator-row.tabulator-tree-level-1 {
background-color: #F5F5F5 !important;
}
.tabulator-row.tabulator-tree-level-1:hover {
background-color: #EEEEEE !important;
}
</style>
<script>
$(document).ready(function(){
_fnc_datepick(); // 날짜 선택기 초기화
@@ -63,14 +25,12 @@
}
});
$("#btnSearch").click(function(){
$("#btnSearch").click(function(){
fn_search();
});
// 초기 데이터 조회 (DOM이 완전히 로드된 후 실행)
setTimeout(function(){
fn_search();
}, 100);
// 초기 데이터 조회
fn_search();
// 출하지시/판매등록 버튼
$("#btnBulkRegister").click(function(){
@@ -110,49 +70,8 @@
</script>
<script type="text/javascript">
// 새로운 테이블 구조에 맞게 컬럼 정의 수정 (분할출하 구분 추가)
// 새로운 테이블 구조에 맞게 컬럼 정의 수정
var columns = [
{headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '출하구분', field : 'RECORD_TYPE',
formatter: function(cell) {
var value = cell.getValue();
var data = cell.getData();
var hasSplit = data.HAS_SPLIT_SHIPMENT;
var hasChildren = data._children && data._children.length > 0;
var row = cell.getRow();
var treeLevel = row.getTreeParent() ? 1 : 0;
var html = '';
// 출하구분 뱃지
if (value === 'SPLIT') {
// 자식 행: 더 큰 들여쓰기 + 뱃지
html += '<span style="display:inline-block; width:40px;"></span>';
html += '<span style="display:inline-block; padding:3px 8px; background:#E3F2FD; color:#1976D2; border-radius:3px; font-size:11px; font-weight:bold;">분할</span>';
} else if (hasSplit === true || hasSplit === 'true' || hasSplit === 't') {
// 분할출하 부모 행: 화살표 + 뱃지
var isExpanded = row.isTreeExpanded();
var toggleIcon = isExpanded ? '▼' : '▶';
html += '<span class="tabulator-tree-toggle" style="font-size:10px; color:#666; margin-right:5px;">' + toggleIcon + '</span>';
html += '<span style="display:inline-block; padding:3px 8px; background:#FFF3E0; color:#F57C00; border-radius:3px; font-size:11px; font-weight:bold;">분할출하</span>';
} else {
// 일반 행: 들여쓰기(화살표 크기만큼) + 뱃지
html += '<span style="display:inline-block; width:21px;"></span>';
html += '<span style="display:inline-block; padding:3px 8px; background:#E8F5E9; color:#388E3C; border-radius:3px; font-size:11px; font-weight:bold;">일반</span>';
}
return html;
},
cellClick: function(e, cell) {
// 분할출하 행 클릭 시 펼치기/접기
var row = cell.getRow();
var data = cell.getData();
var hasChildren = data._children && data._children.length > 0;
if (hasChildren) {
row.treeToggle();
}
}
},
{headerHozAlign : 'center', hozAlign : 'center', width : '120', title : '프로젝트번호', field : 'PROJECT_NO', frozen : true},
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '주문유형', field : 'ORDER_TYPE'},
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '제품구분', field : 'PRODUCT_TYPE'},
@@ -162,17 +81,7 @@ var columns = [
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '유/무상', field : 'PAYMENT_TYPE'},
{headerHozAlign : 'center', hozAlign : 'center', width : '120', title : '품번', field : 'PRODUCT_NO'},
{headerHozAlign : 'center', hozAlign : 'left', width : '180', title : '품명', field : 'PRODUCT_NAME'},
{headerHozAlign : 'center', hozAlign : 'center', width : '120', title : 'S/N', field : 'SPLIT_SERIAL_NO',
formatter: function(cell) {
var splitSn = cell.getValue();
var recordType = cell.getData().RECORD_TYPE;
// 분할인 경우 분할 S/N, 원본인 경우 계약 S/N
if(recordType === 'SPLIT' && splitSn) {
return splitSn;
}
return cell.getData().SERIAL_NO_ORIGINAL || '';
}
},
{headerHozAlign : 'center', hozAlign : 'center', width : '120', title : 'S/N', field : 'SERIAL_NO'},
{headerHozAlign : 'center', hozAlign : 'right', width : '100', title : '수주수량', field : 'ORDER_QUANTITY',
formatter: "money", formatterParams: {thousand: ",", symbolAfter: "", precision: false}
},
@@ -192,7 +101,7 @@ var columns = [
},
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '생산 상태', field : 'PRODUCTION_STATUS'},
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '출하지시 상태', field : 'SHIPPING_ORDER_STATUS'},
{headerHozAlign : 'center', hozAlign : 'right', width : '100', title : '출하수량', field : 'SALES_QUANTITY',
{headerHozAlign : 'center', hozAlign : 'right', width : '100', title : '판매수량', field : 'SALES_QUANTITY',
formatter: "money", formatterParams: {thousand: ",", symbolAfter: "", precision: false}
},
{headerHozAlign : 'center', hozAlign : 'right', width : '120', title : '판매단가', field : 'SALES_UNIT_PRICE',
@@ -217,139 +126,14 @@ var columns = [
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '출하일', field : 'SHIPPING_DATE'},
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '출하방법', field : 'SHIPPING_METHOD'},
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '담당자', field : 'MANAGER'},
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '인도조건', field : 'INCOTERMS'},
{headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '비고', field : 'SPLIT_REMARK',
visible: false // 기본적으로 숨김, 필요시 표시
}
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '인도조건', field : 'INCOTERMS'}
];
// 데이터 조회 함수 (트리 구조 적용)
// 데이터 조회 함수
function fn_search(){
// 원본 데이터 조회 (페이지네이션 포함)
var originalSearch = function(){
return fnc_tabul_search(_tabul_layout_fitColumns, null, "/salesMgmt/salesMgmtGridList.do", columns, true);
};
// 초기 로드
fn_loadData();
_tabulGrid = fnc_tabul_search(_tabul_layout_fitColumns, _tabulGrid, "/salesMgmt/salesMgmtGridList.do", columns, true);
}
// 데이터 로드 함수 (페이지네이션에서도 사용)
function fn_loadData() {
console.log("fn_loadData 호출됨, PAGE_T:", $("#PAGE_T").val());
$.ajax({
url: "/salesMgmt/salesMgmtGridList.do",
type: "POST",
data: $("#form1").serialize(),
dataType: "json",
success: function(response){
console.log("서버에서 받은 데이터 개수:", response.RESULTLIST ? response.RESULTLIST.length : 0);
// TOTAL_CNT 찾기 (PAGE_HTML에서 파싱)
var totalCnt = 0;
if(response.PAGE_HTML) {
// PAGE_HTML에서 "119 건" 같은 패턴 찾기
var match = response.PAGE_HTML.match(/<b id="bTableStatus">(\d+)<\/b>/);
if(match && match[1]) {
totalCnt = parseInt(match[1]);
}
}
// 못 찾으면 다른 곳에서 찾기
if(!totalCnt) {
totalCnt = response.TOTAL_CNT || response.totalCnt ||
(response.pagingMap && response.pagingMap.TOTAL_CNT) ||
(response.RESULTLIST ? response.RESULTLIST.length : 0);
}
console.log("파싱된 TOTAL_CNT:", totalCnt);
// 트리 구조로 데이터 변환
var treeData = fn_convertToTreeData(response.RESULTLIST || []);
console.log("트리 구조 변환 후 개수:", treeData.length);
// 기존 그리드 제거 (매번 재생성)
if(_tabulGrid && _tabulGrid.element){
try {
_tabulGrid.destroy();
} catch(e) {
console.warn("그리드 destroy 중 오류:", e);
}
}
// Tabulator 그리드 생성 (dataTree 옵션 활성화)
_tabulGrid = new Tabulator("#mainGrid", {
layout: "fitColumns",
height: "auto",
data: treeData,
columns: columns,
dataTree: true,
dataTreeStartExpanded: false,
dataTreeChildField: "_children",
dataTreeChildIndent: 20,
selectable: true,
rowFormatter: function(row) {
var data = row.getData();
if (data._children && data._children.length > 0) {
row.getElement().classList.add("has-children-row");
}
}
});
// 페이지네이션 HTML 업데이트 (매출관리와 동일한 방식)
if(response.PAGE_HTML){
$(".table_paging_wrap").html(response.PAGE_HTML);
}
// 전체 개수 표시
$(".totalCntArea").html("전체 <strong>" + totalCnt + "</strong>건");
},
error: function(xhr, status, error){
alert("데이터 조회 중 오류가 발생했습니다.");
console.error("Error:", error);
}
});
}
// 플랫 데이터를 트리 구조로 변환하는 함수
function fn_convertToTreeData(flatData){
var treeData = [];
var parentMap = {}; // PROJECT_NO를 키로 하는 부모 맵
// 1단계: 모든 데이터를 순회하며 부모와 자식 구분
for(var i = 0; i < flatData.length; i++){
var item = flatData[i];
if(item.RECORD_TYPE === 'ORIGINAL'){
// 원본 데이터: 부모로 등록
item._children = []; // 자식 배열 초기화
parentMap[item.PROJECT_NO] = item;
treeData.push(item);
} else if(item.RECORD_TYPE === 'SPLIT'){
// 분할 데이터: 부모를 찾아서 자식으로 추가
var parent = parentMap[item.PROJECT_NO];
if(parent){
parent._children.push(item);
} else {
// 부모가 없는 경우 (이론적으로는 발생하지 않아야 함)
console.warn("부모를 찾을 수 없습니다:", item.PROJECT_NO);
}
}
}
// 2단계: 자식이 없는 부모의 _children 제거
for(var j = 0; j < treeData.length; j++){
if(treeData[j]._children && treeData[j]._children.length === 0){
delete treeData[j]._children;
}
}
return treeData;
}
// 페이지 이동 함수는 common.js의 기본 구현 사용
// common.js에서 이미 정의된 fnc_goPage가 PAGE_T를 설정하고 fn_search()를 호출함
// 출하지시/판매등록 함수 (1건만 선택 가능)
function fn_bulkRegister(){
if(!_tabulGrid){
@@ -374,12 +158,6 @@ function fn_bulkRegister(){
// 선택한 1건의 항목 가져오기
var selectedRow = selectedRows[0];
// 분할출하 건은 편집 불가 알림
if(selectedRow.RECORD_TYPE === 'SPLIT'){
alert("분할출하 건은 원본 데이터에서만 수정할 수 있습니다.\n프로젝트번호: " + selectedRow.PROJECT_NO + "의 원본 행을 선택해주세요.");
return;
}
// 판매등록 팝업 열기
fn_openSaleRegPopup(selectedRow.PROJECT_NO, selectedRow.SALE_NO);
}

View File

@@ -17,11 +17,6 @@
// S/N 관리 전역 변수
var snList = [];
var snCounter = 1;
var currentSnField = null; // 현재 클릭된 S/N 필드 추적
// 분할출하 관련 전역 변수
var splitCounter = 1;
var splitShipments = [];
$(function() {
$('.select2').select2();
@@ -38,9 +33,8 @@
// 판매환종 초기값 설정 (견적환종과 동기화)
initializeSalesCurrency();
// S/N 필드 클릭 이벤트 (기본 모드)
// S/N 필드 클릭 이벤트
$("#serialNo").click(function() {
currentSnField = $(this); // 현재 필드 저장
fn_openSnManagePopup();
});
@@ -69,42 +63,11 @@
self.close();
});
// 저장 버튼 (분할출하 포함)
// 저장 버튼
$("#btnSave").click(function() {
fn_saveWithSplit();
fn_save();
});
// 분할 추가 버튼
$("#btnAddSplit").click(function() {
fn_addSplitRow();
});
// 분할출하 모드 토글
$("#enableSplitShipment").change(function() {
fn_toggleSplitShipmentMode();
});
// 기존 분할출하 데이터 로드
var saleNo = "${param.saleNo}";
if(saleNo && saleNo.trim() != '') {
fn_loadSplitShipments(saleNo);
}
});
// 분할출하 모드 토글 함수
function fn_toggleSplitShipmentMode() {
var isEnabled = $("#enableSplitShipment").is(':checked');
if(isEnabled) {
// 분할 모드 ON: 기본 섹션 숨기고 분할출하 섹션만 표시
$(".single-shipment-section").hide();
$(".split-shipment-section").show();
} else {
// 분할 모드 OFF: 기본 섹션 표시하고 분할출하 섹션 숨김
$(".single-shipment-section").show();
$(".split-shipment-section").hide();
}
}
// 판매공급가액 계산 함수
function fn_calculateSupplyPrice() {
@@ -204,57 +167,29 @@
// S/N 화면 표시 업데이트
function fn_updateSnDisplay() {
var count = snList.length;
var displayValue = '';
if(count > 0) {
// 모든 S/N을 쉼표로 연결
var snValues = [];
for(var i = 0; i < snList.length; i++) {
snValues.push(snList[i].value);
}
displayValue = snValues.join(', ');
}
// 현재 클릭된 필드가 있으면 해당 필드에 값 설정
if(currentSnField && currentSnField.length > 0) {
currentSnField.val(displayValue);
$("#serialNo").val(snValues.join(', '));
} else {
// 기본 필드에 값 설정 (하위 호환성)
$("#serialNo").val(displayValue);
$("#serialNo").val('');
}
}
// S/N 관리 팝업 열기
function fn_openSnManagePopup() {
// 현재 클릭된 필드의 값 가져오기
var serialNoValue = currentSnField ? currentSnField.val() : $("#serialNo").val();
// 최신 데이터 다시 로드 (display 업데이트 없이)
var serialNoValue = $("#serialNo").val();
var serialNoListValue = $("#serialNoList").val();
console.log("팝업 열기 시작");
console.log("현재 필드:", currentSnField ? "분할 S/N" : "기본 S/N");
console.log("serialNoValue:", serialNoValue);
console.log("serialNoList 값:", serialNoListValue);
// 분할 S/N 필드인 경우 (빈 값이든 아니든 독립적으로 처리)
if(currentSnField && currentSnField.hasClass('split-sn')) {
console.log("분할 S/N 필드에서 파싱");
snList = [];
snCounter = 1; // 분할 S/N은 독립적으로 카운터 시작
if(serialNoValue && serialNoValue.trim() != '') {
var snArray = serialNoValue.split(',');
for(var i = 0; i < snArray.length; i++) {
if(snArray[i].trim() != '') {
snList.push({
id: snCounter++,
value: snArray[i].trim()
});
}
}
}
}
// 기본 모드이고 hidden 필드에 JSON 데이터가 있으면 파싱
else if(serialNoListValue && serialNoListValue.trim() != '') {
// 데이터가 있을 때만 로드
if(serialNoListValue && serialNoListValue.trim() != '') {
// JSON 형태로 저장된 데이터가 있으면 파싱
try {
snList = JSON.parse(serialNoListValue);
@@ -596,359 +531,6 @@
}
}
// ======================================
// 분할출하 관련 함수들
// ======================================
// 분할출하 카드 추가 (기본 정보 + 금액 정보 레이아웃 그대로 복제)
function fn_addSplitRow() {
// 기본값 가져오기
var serialNo = $("#serialNo").val() || '';
var salesQuantity = $("#salesQuantity").val() || 0;
var shippingDate = $("#shippingDate").val() || '';
var shippingMethod = $("#shippingMethod").val() || '';
var manager = $("#manager").val() || '';
var unitPrice = $("#salesUnitPrice").val() || 0;
var salesCurrency = $("#salesCurrency").val() || '';
var exchangeRate = $("#salesExchangeRate").val() || 0;
var incoterms = $("#incoterms").val() || '';
var html = '<div class="split-card" data-split-id="' + splitCounter + '" style="border:1px solid #ddd; border-radius:5px; padding:15px; margin-bottom:15px; background:#f9f9f9;">';
html += ' <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:10px; padding-bottom:10px; border-bottom:2px solid #4CAF50;">';
html += ' <h4 style="margin:0; color:#4CAF50;">분할 출하 #' + splitCounter + '</h4>';
html += ' <button type="button" class="plm_btns" onclick="fn_removeSplitRow(' + splitCounter + ')" style="padding:5px 10px; background:#f44336; color:white;">삭제</button>';
html += ' </div>';
// === 기본 정보 섹션 ===
html += ' <div style="background:#fff; padding:10px; border-radius:3px; margin-bottom:10px;">';
html += ' <h5 style="margin:0 0 10px 0; color:#666; font-size:13px; border-bottom:1px solid #eee; padding-bottom:5px;">기본 정보</h5>';
html += ' <table class="pmsPopuptable">';
html += ' <colgroup>';
html += ' <col width="15%" />';
html += ' <col width="35%" />';
html += ' <col width="15%" />';
html += ' <col width="35%" />';
html += ' </colgroup>';
// S/N
html += ' <tr>';
html += ' <td class="input_title"><label>S/N</label></td>';
html += ' <td colspan="3"><input type="text" class="split-sn" name="splitSn_' + splitCounter + '" placeholder="클릭하여 S/N 추가" style="width:100%; cursor:pointer; background-color:#f8f9fa; color:#333 !important;" value="' + serialNo + '" readonly /></td>';
html += ' </tr>';
// 판매수량, 출하일
html += ' <tr>';
html += ' <td class="input_title"><label>판매수량</label></td>';
html += ' <td><input type="number" class="split-qty" name="splitQty_' + splitCounter + '" value="' + salesQuantity + '" onchange="fn_calculateSplitRowAmount(this)" /></td>';
html += ' <td class="input_title"><label>출하일</label></td>';
html += ' <td><input type="text" class="date_icon split-date" name="splitDate_' + splitCounter + '" value="' + shippingDate + '" autocomplete="off" /></td>';
html += ' </tr>';
// 출하방법, 담당자
html += ' <tr>';
html += ' <td class="input_title"><label>출하방법</label></td>';
html += ' <td>';
html += ' <select class="select2 split-method" name="splitMethod_' + splitCounter + '">';
html += ' <option value="">선택</option>';
html += ' <option value="내수/직납"' + (shippingMethod == '내수/직납' ? ' selected' : '') + '>내수/직납</option>';
html += ' <option value="내수/택배"' + (shippingMethod == '내수/택배' ? ' selected' : '') + '>내수/택배</option>';
html += ' <option value="내수/기타"' + (shippingMethod == '내수/기타' ? ' selected' : '') + '>내수/기타</option>';
html += ' <option value="수출"' + (shippingMethod == '수출' ? ' selected' : '') + '>수출</option>';
html += ' </select>';
html += ' </td>';
html += ' <td class="input_title"><label>담당자</label></td>';
html += ' <td>';
html += ' <select class="select2 split-manager" name="splitManager_' + splitCounter + '">';
// 담당자 옵션 복사 (동적으로 생성, "선택" 옵션 제거 없이 그대로 복사)
$("#manager option").each(function() {
var val = $(this).val();
var text = $(this).text();
var selected = (val == manager) ? ' selected' : '';
html += ' <option value="' + val + '"' + selected + '>' + text + '</option>';
});
html += ' </select>';
html += ' </td>';
html += ' </tr>';
html += ' </table>';
html += ' </div>';
// === 금액 정보 섹션 ===
html += ' <div style="background:#fff; padding:10px; border-radius:3px;">';
html += ' <h5 style="margin:0 0 10px 0; color:#666; font-size:13px; border-bottom:1px solid #eee; padding-bottom:5px;">금액 정보</h5>';
html += ' <table class="pmsPopuptable">';
html += ' <colgroup>';
html += ' <col width="15%" />';
html += ' <col width="35%" />';
html += ' <col width="15%" />';
html += ' <col width="35%" />';
html += ' </colgroup>';
// 판매단가, 판매공급가액
html += ' <tr>';
html += ' <td class="input_title"><label>판매단가</label></td>';
html += ' <td><input type="number" class="split-unit-price" name="splitUnitPrice_' + splitCounter + '" value="' + unitPrice + '" onchange="fn_calculateSplitRowAmount(this)" /></td>';
html += ' <td class="input_title"><label>판매공급가액</label></td>';
html += ' <td><input type="number" class="split-supply-price" name="splitSupplyPrice_' + splitCounter + '" readonly style="background-color:#f5f5f5;" /></td>';
html += ' </tr>';
// 판매부가세, 판매총액
html += ' <tr>';
html += ' <td class="input_title"><label>판매부가세</label></td>';
html += ' <td><input type="number" class="split-vat" name="splitVat_' + splitCounter + '" readonly style="background-color:#f5f5f5;" /></td>';
html += ' <td class="input_title"><label>판매총액</label></td>';
html += ' <td><input type="number" class="split-total-amount" name="splitTotalAmount_' + splitCounter + '" readonly style="background-color:#f5f5f5;" /></td>';
html += ' </tr>';
// 판매환종, 판매환율
html += ' <tr>';
html += ' <td class="input_title"><label>판매환종</label></td>';
html += ' <td>';
html += ' <select class="select2 split-currency" name="splitCurrency_' + splitCounter + '" onchange="fn_calculateSplitRowAmount(this)">';
// 환종 옵션 복사 ("선택" 옵션 포함하여 그대로 복사)
$("#salesCurrency option").each(function() {
var val = $(this).val();
var text = $(this).text();
var selected = (val == salesCurrency) ? ' selected' : '';
html += ' <option value="' + val + '"' + selected + '>' + text + '</option>';
});
html += ' </select>';
html += ' </td>';
html += ' <td class="input_title"><label>판매환율</label></td>';
html += ' <td><input type="number" class="split-exchange-rate" name="splitExchangeRate_' + splitCounter + '" step="0.01" value="' + exchangeRate + '" onchange="fn_calculateSplitRowAmount(this)" /></td>';
html += ' </tr>';
// 인도조건
html += ' <tr>';
html += ' <td class="input_title"><label>인도조건</label></td>';
html += ' <td colspan="3">';
html += ' <select class="select2 split-incoterms" name="splitIncoterms_' + splitCounter + '">';
html += ' <option value="">선택</option>';
html += ' <option value="FOB"' + (incoterms == 'FOB' ? ' selected' : '') + '>FOB</option>';
html += ' <option value="EXW"' + (incoterms == 'EXW' ? ' selected' : '') + '>EXW</option>';
html += ' <option value="CIF"' + (incoterms == 'CIF' ? ' selected' : '') + '>CIF</option>';
html += ' <option value="DDP"' + (incoterms == 'DDP' ? ' selected' : '') + '>DDP</option>';
html += ' <option value="DAP"' + (incoterms == 'DAP' ? ' selected' : '') + '>DAP</option>';
html += ' </select>';
html += ' </td>';
html += ' </tr>';
html += ' </table>';
html += ' </div>';
html += '</div>';
$("#splitShipmentContainer").append(html);
// 새로 추가된 카드 선택
var newCard = $(".split-card[data-split-id='" + splitCounter + "']");
// 날짜 선택기 초기화
newCard.find(".split-date").datepicker({
changeMonth: true,
changeYear: true,
dateFormat: 'yy-mm-dd'
});
// select2 초기화 (드롭다운 스타일 적용)
newCard.find('.select2').select2();
// S/N 클릭 이벤트 추가 (분할 모드)
newCard.find(".split-sn").click(function() {
currentSnField = $(this); // 현재 필드 저장
fn_openSnManagePopup();
});
// 초기 금액 계산
fn_calculateSplitRowAmount(newCard.find(".split-qty")[0]);
splitCounter++;
}
// 분할 행의 금액 계산
function fn_calculateSplitRowAmount(input) {
// 카드 전체를 기준으로 필드 찾기
var card = $(input).closest('.split-card');
// 기본 값 가져오기
var qty = parseFloat(card.find('.split-qty').val()) || 0;
var unitPrice = parseFloat(card.find('.split-unit-price').val()) || 0;
var currency = card.find('.split-currency').val();
var exchangeRate = parseFloat(card.find('.split-exchange-rate').val()) || 1;
// 외화 기준 금액 계산
var foreignSupplyPrice = qty * unitPrice;
// KRW가 아니고 환율이 있으면 환율 적용 (기본 모드 로직과 동일)
var supplyPrice;
if(currency && currency !== 'KRW' && exchangeRate > 0) {
supplyPrice = Math.round(foreignSupplyPrice * exchangeRate);
} else {
supplyPrice = Math.round(foreignSupplyPrice);
}
// 부가세 = 공급가액 * 0.1
var vat = Math.round(supplyPrice * 0.1);
// 총액 = 공급가액 + 부가세
var totalAmount = supplyPrice + vat;
// 값 설정
card.find('.split-supply-price').val(supplyPrice);
card.find('.split-vat').val(vat);
card.find('.split-total-amount').val(totalAmount);
}
// 분할출하 카드 삭제
function fn_removeSplitRow(id) {
if (confirm("이 분할출하를 삭제하시겠습니까?")) {
$(".split-card[data-split-id='" + id + "']").remove();
fn_reorderSplitRows();
}
}
// 분할출하 카드 번호 재정렬
function fn_reorderSplitRows() {
$("#splitShipmentContainer .split-card").each(function(index) {
$(this).find("h4").text("분할 출하 #" + (index + 1));
});
}
// 분할출하 데이터 수집
function fn_collectSplitData() {
var splits = [];
var isSplitMode = $("#enableSplitShipment").is(':checked');
// 분할 모드가 아니면 빈 배열 반환
if(!isSplitMode) {
return splits;
}
// 분할 모드일 때만 수량 합계 검증 (원본 수량 없음)
var totalSplitQty = 0;
$("#splitShipmentContainer .split-card").each(function() {
var card = $(this);
var shippingDate = card.find(".split-date").val();
var qty = parseInt(card.find(".split-qty").val()) || 0;
// 필수값 체크
if(!shippingDate || qty <= 0) {
return true; // continue
}
splits.push({
shippingDate: shippingDate,
splitQuantity: qty,
serialNo: card.find(".split-sn").val() || '',
salesUnitPrice: parseFloat(card.find(".split-unit-price").val()) || 0,
salesSupplyPrice: parseFloat(card.find(".split-supply-price").val()) || 0,
salesVat: parseFloat(card.find(".split-vat").val()) || 0,
salesTotalAmount: parseFloat(card.find(".split-total-amount").val()) || 0,
salesCurrency: card.find(".split-currency").val() || '',
salesExchangeRate: parseFloat(card.find(".split-exchange-rate").val()) || 0,
shippingMethod: card.find(".split-method").val() || '',
managerUserId: card.find(".split-manager").val() || '',
incoterms: card.find(".split-incoterms").val() || ''
});
totalSplitQty += qty;
});
// 분할 모드에서는 최소 1개 이상의 분할 데이터가 있어야 함
if (isSplitMode && splits.length === 0) {
alert("분할출하 모드에서는 최소 1개 이상의 분할 데이터를 입력해야 합니다.");
return null;
}
return splits;
}
// 저장 (분할출하 포함)
function fn_saveWithSplit() {
// 체크박스 상태에 따라 출하지시 상태 값 설정
var isChecked = $("#isShippingOrder").is(":checked");
$("#shippingOrderStatus").val(isChecked ? "출하지시" : "");
// S/N 데이터 처리
var serialNoListValue = $("#serialNoList").val();
if(serialNoListValue && serialNoListValue.trim() != '') {
try {
var snArray = JSON.parse(serialNoListValue);
var snValues = [];
for(var i = 0; i < snArray.length; i++) {
if(snArray[i].value) {
snValues.push(snArray[i].value);
}
}
$("#serialNo").val(snValues.join(','));
} catch(e) {
console.error("S/N JSON 파싱 오류:", e);
}
}
// 분할출하 데이터 수집
var splits = fn_collectSplitData();
if (splits === null) return; // 검증 실패
if (confirm("저장하시겠습니까?")) {
var formData = $("#form1").serialize();
// 분할출하 데이터 추가
if(splits && splits.length > 0) {
formData += "&splitShipments=" + encodeURIComponent(JSON.stringify(splits));
}
$.ajax({
url: "/salesMgmt/saveSplitShipments.do",
type: "POST",
data: formData,
dataType: "json",
success: function(data) {
alert(data.msg);
if (data.result && opener && opener.fn_search) {
opener.fn_search();
}
if (data.result) {
self.close();
}
},
error: function() {
alert("저장 중 오류가 발생했습니다.");
}
});
}
}
// 기존 분할출하 로드
function fn_loadSplitShipments(saleNo) {
if(!saleNo) return;
$.ajax({
url: "/salesMgmt/getSplitShipments.do",
type: "POST",
data: { saleNo: saleNo },
dataType: "json",
success: function(response) {
if (response.result && response.data && response.data.length > 0) {
$.each(response.data, function(i, item) {
fn_addSplitRow();
var row = $("#splitShipmentBody tr:last");
row.find(".split-date").val(item.SHIPPING_DATE || '');
row.find(".split-qty").val(item.SPLIT_QUANTITY || '');
row.find(".split-sn").val(item.SERIAL_NO || '');
row.find(".split-method").val(item.SHIPPING_METHOD || '');
row.find(".split-remark").val(item.REMARK || '');
row.attr("data-log-id", item.LOG_ID);
});
}
},
error: function() {
console.error("분할출하 데이터 로드 실패");
}
});
}
</script>
</head>
<body>
@@ -998,13 +580,10 @@
</table>
<!-- 기본 정보 영역 -->
<div class="form_popup_title" style="margin-top:15px; display:flex; justify-content:space-between; align-items:center;">
<span class="single-shipment-section">기본 정보</span>
<label style="font-weight:normal; display:inline-flex; align-items:center; margin-left:auto; margin-right:10px;">
<input type="checkbox" id="enableSplitShipment" style="margin-right:5px;" >분할출하 모드</label>
</label>
<div class="form_popup_title" style="margin-top:15px;">
<span>기본 정보</span>
</div>
<table class="single-shipment-section">
<table class="">
<colgroup>
<col width="100%" />
</colgroup>
@@ -1018,7 +597,7 @@
<col width="35%" />
</colgroup>
<!-- 첫번째 행: S/N (항상 표시) -->
<!-- 첫번째 행: S/N -->
<tr>
<td class="input_title"><label for="serialNo">S/N</label></td>
<td colspan="3">
@@ -1030,7 +609,7 @@
</td>
</tr>
<!-- 두번째 행: 판매수량, 출하일 (항상 표시) -->
<!-- 두번째 행: 판매수량, 출하일 -->
<tr>
<td class="input_title"><label for="salesQuantity">판매수량</label></td>
<td>
@@ -1042,7 +621,7 @@
</td>
</tr>
<!-- 세번째 행: 출하방법, 담당자 (항상 표시) -->
<!-- 세번째 행: 출하방법, 담당자 -->
<tr>
<td class="input_title"><label for="shippingMethod">출하방법</label></td>
<td>
@@ -1068,11 +647,11 @@
</tr>
</table>
<!-- 금액 정보 영역 (항상 표시) -->
<div class="form_popup_title single-shipment-section" style="margin-top:15px;">
<!-- 금액 정보 영역 -->
<div class="form_popup_title" style="margin-top:15px;">
<span>금액 정보</span>
</div>
<table class="single-shipment-section">
<table class="">
<colgroup>
<col width="100%" />
</colgroup>
@@ -1145,21 +724,6 @@
</tr>
</table>
<!-- 분할출하 관리 섹션 (분할 모드 ON시만 표시) -->
<div class="split-shipment-section" style="display:none;">
<div class="form_popup_title" style="margin-top:15px;">
<span>분할출하 관리</span>
<button type="button" class="plm_btns" id="btnAddSplit" style="float:right; margin-left:5px;">
분할 추가
</button>
</div>
<!-- 분할출하 카드들이 추가될 컨테이너 -->
<div id="splitShipmentContainer" style="margin-top:10px;">
<!-- 동적으로 카드 추가 -->
</div>
</div>
</div>
<div class="btn_wrap">

View File

@@ -281,11 +281,8 @@ public class SalesNcollectMgmtController {
@RequestMapping(value = "/salesMgmt/salesMgmtGridList.do", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> getSalesMgmtGridList(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
// 기본 쿼리 사용 (분할출하 VIEW는 나중에 필요시 활성화)
String queryId = "salesNcollectMgmt.getSalesMgmtGridList";
// commonService.selectListPagingNew를 사용하여 페이지네이션 HTML 생성
commonService.selectListPagingNew(queryId, request, paramMap);
commonService.selectListPagingNew("salesNcollectMgmt.getSalesMgmtGridList", request, paramMap);
return paramMap;
}
@@ -729,49 +726,4 @@ public class SalesNcollectMgmtController {
Map resultMap = salesNcollectMgmtService.salesDeadlineConfirm(request, paramMap);
return resultMap;
}
/**
* <pre>
* 분할출하 저장
* </pre>
* @param request
* @param paramMap - 분할출하 정보
* @return Map
*/
@RequestMapping(value = "/salesMgmt/saveSplitShipments.do", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> saveSplitShipments(HttpServletRequest request,
@RequestParam Map<String, Object> paramMap) {
return salesNcollectMgmtService.saveSplitShipments(request, paramMap);
}
/**
* <pre>
* 분할출하 목록 조회
* </pre>
* @param paramMap - 조회 조건 (saleNo)
* @return Map
*/
@RequestMapping(value = "/salesMgmt/getSplitShipments.do", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> getSplitShipments(@RequestParam Map<String, Object> paramMap) {
List<Map<String, Object>> list = salesNcollectMgmtService.getSplitShipmentList(paramMap);
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("data", list);
resultMap.put("result", true);
return resultMap;
}
/**
* <pre>
* 분할출하 삭제
* </pre>
* @param paramMap - 삭제 조건 (logId)
* @return Map
*/
@RequestMapping(value = "/salesMgmt/deleteSplitShipment.do", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> deleteSplitShipment(@RequestParam Map<String, Object> paramMap) {
return salesNcollectMgmtService.deleteSplitShipment(paramMap);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -835,267 +835,4 @@ public class SalesNcollectMgmtService {
}
return resultMap;
}
/**
* <pre>
* 분할출하 저장 (여러 건)
* </pre>
* @param request
* @param paramMap - 분할출하 정보 (JSON 배열)
* @return Map
*/
public Map<String, Object> saveSplitShipments(HttpServletRequest request, Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<String, Object>();
SqlSession sqlSession = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
PersonBean person = (PersonBean) request.getSession().getAttribute(Constants.PERSON_BEAN);
String userId = person.getUserId();
// 분할출하 데이터 JSON 파싱
String splitDataJson = CommonUtils.checkNull(paramMap.get("splitShipments"));
if (StringUtils.isBlank(splitDataJson) || "[]".equals(splitDataJson)) {
// 분할출하 데이터가 없으면 일반 저장만 수행
return saveSaleRegistration(request, paramMap);
}
// JSON 파싱 (간단한 파싱 - 실제로는 Jackson 등 사용 권장)
List<Map<String, Object>> splitList = parseSplitShipments(splitDataJson);
// 원본 sales_registration 데이터 조회 또는 생성
String projectNo = CommonUtils.checkNull(paramMap.get("orderNo"));
Map<String, Object> saleInfo = sqlSession.selectOne("salesNcollectMgmt.getSaleInfo", paramMap);
Integer saleNo = null;
int originalQuantity = 0;
BigDecimal salesUnitPrice = BigDecimal.ZERO;
String salesCurrency = "";
BigDecimal salesExchangeRate = BigDecimal.ONE;
String incoterms = "";
if (saleInfo != null && saleInfo.get("SALE_NO") != null) {
// 기존 판매 정보가 있는 경우
saleNo = Integer.parseInt(saleInfo.get("SALE_NO").toString());
originalQuantity = Integer.parseInt(CommonUtils.checkNull(saleInfo.get("SALES_QUANTITY"), "0"));
salesUnitPrice = new BigDecimal(CommonUtils.checkNull(saleInfo.get("SALES_UNIT_PRICE"), "0"));
salesCurrency = CommonUtils.checkNull(saleInfo.get("SALES_CURRENCY"));
salesExchangeRate = new BigDecimal(CommonUtils.checkNull(saleInfo.get("SALES_EXCHANGE_RATE"), "1"));
incoterms = CommonUtils.checkNull(saleInfo.get("INCOTERMS"));
} else {
// 신규 판매 정보 생성
sqlSession.insert("salesNcollectMgmt.insertSaleRegistration", paramMap);
// INSERT 후 반환된 saleNo 사용 (useGeneratedKeys로 자동 설정됨)
if (paramMap.get("saleNo") == null) {
resultMap.put("result", false);
resultMap.put("msg", "판매 정보 저장 후 sale_no 생성 실패");
return resultMap;
}
saleNo = Integer.parseInt(paramMap.get("saleNo").toString());
originalQuantity = Integer.parseInt(CommonUtils.checkNull(paramMap.get("salesQuantity"), "0"));
salesUnitPrice = new BigDecimal(CommonUtils.checkNull(paramMap.get("salesUnitPrice"), "0"));
salesCurrency = CommonUtils.checkNull(paramMap.get("salesCurrency"));
salesExchangeRate = new BigDecimal(CommonUtils.checkNull(paramMap.get("salesExchangeRate"), "1"));
incoterms = CommonUtils.checkNull(paramMap.get("incoterms"));
}
// 분할 수량 합계 검증
int totalSplitQty = 0;
for (Map<String, Object> split : splitList) {
String qtyStr = CommonUtils.checkNull(split.get("splitQuantity"), "0");
totalSplitQty += Integer.parseInt(qtyStr);
}
if (totalSplitQty > originalQuantity) {
resultMap.put("result", false);
resultMap.put("msg", "분할 수량 합계(" + totalSplitQty + ")가 원본 수량(" + originalQuantity + ")을 초과했습니다.");
return resultMap;
}
// 기존 분할출하 삭제 (재저장 방식)
sqlSession.delete("salesNcollectMgmt.deleteAllSplitShipments", paramMap);
// 분할출하 데이터 저장
for (Map<String, Object> split : splitList) {
split.put("saleNo", saleNo);
split.put("projectNo", projectNo);
split.put("originalQuantity", originalQuantity);
split.put("regUserId", userId);
// 금액 계산
int splitQty = Integer.parseInt(CommonUtils.checkNull(split.get("splitQuantity"), "0"));
BigDecimal supplyPrice = salesUnitPrice.multiply(new BigDecimal(splitQty));
BigDecimal vat = supplyPrice.multiply(new BigDecimal("0.1"));
BigDecimal totalAmount = supplyPrice.add(vat);
split.put("salesUnitPrice", salesUnitPrice);
split.put("salesSupplyPrice", supplyPrice);
split.put("salesVat", vat);
split.put("salesTotalAmount", totalAmount);
split.put("salesCurrency", salesCurrency);
split.put("salesExchangeRate", salesExchangeRate);
// incoterms가 없으면 원본 값 사용
if (StringUtils.isBlank(CommonUtils.checkNull(split.get("incoterms")))) {
split.put("incoterms", incoterms);
}
// managerUserId가 없으면 현재 사용자
if (StringUtils.isBlank(CommonUtils.checkNull(split.get("managerUserId")))) {
split.put("managerUserId", userId);
}
sqlSession.insert("salesNcollectMgmt.insertSplitShipment", split);
}
// sales_registration의 has_split_shipment 플래그 업데이트
paramMap.put("saleNo", saleNo);
paramMap.put("updUserId", userId);
sqlSession.update("salesNcollectMgmt.updateSplitFlag", paramMap);
sqlSession.commit();
resultMap.put("result", true);
resultMap.put("msg", "분할출하가 저장되었습니다.");
} catch (Exception e) {
if (sqlSession != null) {
sqlSession.rollback();
}
resultMap.put("result", false);
resultMap.put("msg", "저장 중 오류가 발생했습니다: " + e.getMessage());
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
return resultMap;
}
/**
* <pre>
* 분할출하 목록 조회
* </pre>
* @param paramMap - 조회 조건
* @return List
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public List<Map<String, Object>> getSplitShipmentList(Map<String, Object> paramMap) {
List<Map<String, Object>> resultList = new ArrayList<Map<String, Object>>();
SqlSession sqlSession = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
resultList = (ArrayList) sqlSession.selectList("salesNcollectMgmt.getSplitShipmentList", paramMap);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
return CommonUtils.toUpperCaseMapKey(resultList);
}
/**
* <pre>
* 분할출하 삭제
* </pre>
* @param paramMap - 삭제 조건
* @return Map
*/
public Map<String, Object> deleteSplitShipment(Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<String, Object>();
SqlSession sqlSession = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
int cnt = sqlSession.delete("salesNcollectMgmt.deleteSplitShipment", paramMap);
if (cnt > 0) {
// has_split_shipment 플래그 업데이트
sqlSession.update("salesNcollectMgmt.updateSplitFlag", paramMap);
sqlSession.commit();
resultMap.put("result", true);
resultMap.put("msg", "삭제되었습니다.");
} else {
resultMap.put("result", false);
resultMap.put("msg", "삭제할 데이터가 없습니다.");
}
} catch (Exception e) {
if (sqlSession != null) {
sqlSession.rollback();
}
resultMap.put("result", false);
resultMap.put("msg", "삭제 중 오류가 발생했습니다.");
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
return resultMap;
}
/**
* <pre>
* 분할출하 JSON 문자열 파싱
* </pre>
* @param jsonStr - JSON 문자열
* @return List
*/
private List<Map<String, Object>> parseSplitShipments(String jsonStr) {
List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
try {
// 간단한 JSON 파싱 (실제로는 Jackson 또는 Gson 사용 권장)
// 형식: [{"shippingDate":"2024-01-01","splitQuantity":"10",...},...]
jsonStr = jsonStr.trim();
if (jsonStr.startsWith("[")) {
jsonStr = jsonStr.substring(1);
}
if (jsonStr.endsWith("]")) {
jsonStr = jsonStr.substring(0, jsonStr.length() - 1);
}
// 각 객체를 분리
String[] objects = jsonStr.split("\\},\\{");
for (String obj : objects) {
obj = obj.replace("{", "").replace("}", "").trim();
if (obj.isEmpty()) continue;
Map<String, Object> map = new HashMap<String, Object>();
// 각 속성을 파싱
String[] pairs = obj.split("\",\"");
for (String pair : pairs) {
pair = pair.replace("\"", "").trim();
String[] keyValue = pair.split(":");
if (keyValue.length == 2) {
String key = keyValue[0].trim();
String value = keyValue[1].trim();
map.put(key, value);
}
}
if (!map.isEmpty()) {
result.add(map);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
}