V2025121901 #121

Merged
hjjeong merged 5 commits from V2025121901 into main 2026-01-16 08:01:41 +00:00
6 changed files with 434 additions and 12 deletions
Showing only changes of commit ec94dde67a - Show all commits

View File

@@ -88,6 +88,7 @@ $(document).ready(function(){
var columns = [
// 요구사항: 품의서 No, 발주서 No, 프로젝트번호, 품번, 품명, 공급업체, 발주수량, 입고수량, 미입고수량, 검사성적서, 입고결과
{title:'STATUS' ,field:'STATUS' ,visible:false, frozen:true},
{title:'TOTAL_SUPPLY_PRICE' ,field:'TOTAL_SUPPLY_PRICE' ,visible:false, frozen:true},
{title:'TOTAL_DELIVERY_PRICE' ,field:'TOTAL_DELIVERY_PRICE' ,visible:false, frozen:true},
{title:'TOTAL_NOT_DELIVERY_PRICE',field:'TOTAL_NOT_DELIVERY_PRICE',visible:false, frozen:true},
@@ -127,15 +128,36 @@ var columns = [
}
},
{headerHozAlign : 'center', hozAlign : 'center', minWidth : 90, widthGrow : 1, title : '입고결과', field : 'DELIVERY_STATUS',
formatter:fnc_createGridAnchorTag,
cellClick:function(e, cell){
formatter: function(cell, formatterParams, onRendered){
var status = fnc_checkNull(cell.getData().STATUS);
var deliveryStatus = fnc_checkNull(cell.getValue());
// 발주취소 상태인 경우
if(status === 'orderCancel'){
return '<span style="color:red; font-weight:bold;">발주취소</span>';
}
// 일반 상태 - 링크로 표시
if(deliveryStatus != ''){
return '<a href="#none" style="color:#0000EE">' + deliveryStatus + '</a>';
}
return deliveryStatus;
},
cellClick:function(e, cell){
var status = fnc_checkNull(cell.getData().STATUS);
// 발주취소 상태인 경우 팝업 열지 않음
if(status === 'orderCancel'){
return;
}
var objId = fnc_checkNull(cell.getData().OBJID);
var DELIVERY_STATUS = fnc_checkNull(cell.getData().DELIVERY_STATUS);
var purchaseOrderNo = fnc_checkNull(cell.getData().PURCHASE_ORDER_NO);
fn_deliveryAcceptanceViewPopUp(objId,DELIVERY_STATUS);
}
},
{headerHozAlign : 'center', hozAlign : 'center', minWidth : 140, widthGrow : 1, title : '매입마감', field : 'PURCHASE_CLOSE_DATE'}
}
// {headerHozAlign : 'center', hozAlign : 'center', minWidth : 140, widthGrow : 1, title : '매입마감', field : 'PURCHASE_CLOSE_DATE'}
];
//var grid;
@@ -219,7 +241,14 @@ function fn_deliveryAcceptancePopUp(){
if(selected.length > 1){
Swal.fire("한건씩 등록 가능합니다.");
return;
}else{
}else{
// 발주취소 상태 체크
var status = fnc_checkNull(selected[0].STATUS);
if(status === 'orderCancel'){
Swal.fire("발주취소된 건은 입고등록할 수 없습니다.");
return;
}
var MULTI_MASTER_YN = fnc_checkNull(selected[0].MULTI_MASTER_YN);
var MULTI_YN = fnc_checkNull(selected[0].MULTI_YN);
@@ -472,7 +501,7 @@ function fn_purchaseClose(){
<div class="btnArea">
<input type="button" class="plm_btns" value="조회" id="btnSearch">
<input type="button" class="plm_btns" value="입고등록" id="btnAccept">
<input type="button" class="plm_btns" value="매입마감" id="btnClose">
<!-- <input type="button" class="plm_btns" value="매입마감" id="btnClose"> -->
<%-- <input type="button" class="plm_btns" value="부적합등록" id="btnInvaild"> --%>
</div>
</div>

View File

@@ -76,6 +76,11 @@ $(document).ready(function(){
fn_sendPurchaseOrder();
});
// 발주 취소 버튼 클릭
$("#btnOrderCancel").click(function(){
fn_orderCancel();
});
//수주활동 복사 팝업
$("#btnCopy").click(function(){
var checkedObj = _tabulGrid.getSelectedData();
@@ -352,7 +357,11 @@ var columns = [
{headerHozAlign:'center', hozAlign:'center', widthGrow:1, title:'메일발송', field:'MAIL_SEND_YN',
formatter: function(cell, formatterParams, onRendered){
var value = fnc_checkNull(cell.getValue());
if(value === 'Y'){
var status = fnc_checkNull(cell.getData().STATUS);
// 발주취소 상태인 경우
if(status === 'orderCancel'){
return '<span style="color:red; font-weight:bold;">발주취소</span>';
} else if(value === 'Y'){
return '<span style="color:green;">발송완료</span>';
} else {
return '';
@@ -771,7 +780,7 @@ function fn_sendPurchaseOrder(){
var mailSendYn = fnc_checkNull(selectedData[0].MAIL_SEND_YN);
// 취소 상태 확인
if(status === "cancel"){
if(status === "cancel" || status === "orderCancel"){
Swal.fire("취소된 발주서는 발송할 수 없습니다.");
return false;
}
@@ -817,6 +826,119 @@ function fn_openMailFormPopup(purchaseOrderObjId){
window.open(url, "purchaseOrderMailForm", "width="+popup_width+",height="+popup_height+",menubar=no,scrollbars=yes,resizable=yes");
}
// 발주 취소
function fn_orderCancel(){
var selectedData = _tabulGrid.getSelectedData();
if(selectedData.length < 1){
Swal.fire("발주 취소할 행을 선택해주세요.");
return false;
} else if(selectedData.length > 1){
Swal.fire("한번에 한 개의 발주서만 취소 가능합니다.");
return false;
}
var objId = fnc_checkNull(selectedData[0].OBJID);
var status = fnc_checkNull(selectedData[0].STATUS);
var MULTI_YN = fnc_checkNull(selectedData[0].MULTI_YN);
var MULTI_MASTER_YN = fnc_checkNull(selectedData[0].MULTI_MASTER_YN);
var purchaseOrderNo = fnc_checkNull(selectedData[0].PURCHASE_ORDER_NO);
// 이미 취소된 상태 확인
if(status === 'cancel'){
Swal.fire("이미 취소된 발주서입니다.");
return false;
}
// 이미 발주취소된 상태 확인
if(status === 'orderCancel'){
Swal.fire("이미 발주취소된 발주서입니다.");
return false;
}
// 동시발주 하위건 확인
if(MULTI_YN === 'Y' && MULTI_MASTER_YN !== 'Y'){
Swal.fire("동시발주 하위건은 마스터건으로 취소해주세요.");
return false;
}
// 입고 여부 확인 후 취소 진행
$.ajax({
type: "POST",
url: "/purchaseOrder/checkReceiptForCancel.do",
data: { PURCHASE_ORDER_MASTER_OBJID: objId },
dataType: "json",
success: function(data){
if(data.hasReceipt){
// 입고된 항목이 있는 경우
Swal.fire({
title: '취소 불가',
html: '입고된 항목이 있어 발주 취소가 불가합니다.<br/><br/>' +
'<span style="color:#666;">입고수량: ' + data.totalReceiptQty + '개</span>',
icon: 'error'
});
} else {
// 입고된 항목이 없는 경우 - 취소 확인
Swal.fire({
title: '발주 취소',
html: '발주서 <strong>' + purchaseOrderNo + '</strong>을(를) 취소하시겠습니까?<br/><br/>' +
'<span style="color:#666; font-size:12px;">취소 후에도 목록에서 확인 가능하며, 메일발송 컬럼에 "발주취소"로 표시됩니다.</span>',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
confirmButtonText: '취소하기',
cancelButtonText: '닫기'
}).then((result) => {
if(result.isConfirmed){
fn_executeOrderCancel(objId);
}
});
}
},
error: function(jqxhr, status, error){
console.error("입고 확인 오류:", error);
Swal.fire("입고 확인 중 오류가 발생했습니다.");
}
});
}
// 발주 취소 실행
function fn_executeOrderCancel(objId){
$.ajax({
type: "POST",
url: "/purchaseOrder/executeOrderCancel.do",
data: { PURCHASE_ORDER_MASTER_OBJID: objId },
dataType: "json",
beforeSend: function(){
_startLoading("처리중입니다.");
},
complete: function(){
_endLoading();
},
success: function(data){
if(data.result){
Swal.fire({
title: '완료',
text: '발주가 취소되었습니다.',
icon: 'success'
}).then(() => {
fn_search();
});
} else {
Swal.fire({
title: '오류',
text: data.message || '발주 취소 중 오류가 발생했습니다.',
icon: 'error'
});
}
},
error: function(jqxhr, status, error){
console.error("발주 취소 오류:", error);
Swal.fire("발주 취소 중 오류가 발생했습니다.");
}
});
}
</script>
<body class="<%--bodyNoScrollX--%>">
<form name="hiddenForm" id="hiddenForm" method="post">
@@ -842,6 +964,7 @@ function fn_openMailFormPopup(purchaseOrderObjId){
<input type="button" class="plm_btns" value="삭제" id="btnDelete">
<input type="button" class="plm_btns" value="저장" id="btnSave"> -->
<input type="button" class="plm_btns" value="발주서 송부" id="btnSend">
<input type="button" class="plm_btns" value="발주 취소" id="btnOrderCancel" style="background-color:#dc3545; border-color:#dc3545;">
<input type="button" class="plm_btns" value="조회" id="btnSearch">
</div>

View File

@@ -191,7 +191,14 @@ String purchaseOrderObjId = request.getParameter("purchaseOrderObjId");
<div class="attachment-status">
<i class="fa fa-file-pdf-o"></i>
<strong>첨부파일:</strong> 발주서(PDF) 및 도면 파일이 자동으로 첨부됩니다.
<strong>첨부파일:</strong> 발주서(PDF) 자동으로 첨부됩니다.
<div style="margin-top: 8px;">
<label style="display: inline-flex; align-items: center; cursor: pointer; font-weight: normal;">
<input type="checkbox" id="includeDrawingFiles" name="includeDrawingFiles" checked style="width: 16px; height: 16px; margin-right: 6px;">
<span>도면 파일 첨부</span>
</label>
<span id="drawingFileCount" style="margin-left: 10px; color: #666; font-size: 12px;"></span>
</div>
</div>
<form id="mailForm">
@@ -291,6 +298,9 @@ function fn_loadPurchaseOrderInfo(){
} else {
$("#managerListContainer").html('<div class="no-managers">공급업체 정보가 없습니다.</div>');
}
// 도면 파일 개수 조회
fn_loadDrawingFileCount(purchaseOrderObjId);
} else {
Swal.fire({
title: '오류',
@@ -313,6 +323,31 @@ function fn_loadPurchaseOrderInfo(){
});
}
// 도면 파일 개수 조회
function fn_loadDrawingFileCount(purchaseOrderObjId){
$.ajax({
url: "/purchaseOrder/getDrawingFileCount.do",
type: "POST",
data: { objId: purchaseOrderObjId },
dataType: "json",
success: function(data){
if(data.result === "success"){
var count = parseInt(data.count) || 0;
if(count > 0){
$("#drawingFileCount").text("(" + count + "개 파일)");
} else {
$("#drawingFileCount").text("(파일 없음)");
$("#includeDrawingFiles").prop("checked", false);
$("#includeDrawingFiles").prop("disabled", true);
}
}
},
error: function(){
$("#drawingFileCount").text("(조회 실패)");
}
});
}
// 공급업체 담당자 목록 로드
function fn_loadPartnerManagers(partnerObjId){
$.ajax({
@@ -669,7 +704,8 @@ function fn_submitMailForm(){
toEmails: $("#toEmails").val(),
ccEmails: $("#ccEmails").val(),
subject: $("#subject").val(),
contents: $("#contents").val()
contents: $("#contents").val(),
includeDrawingFiles: $("#includeDrawingFiles").is(":checked") ? "Y" : "N"
};
$.ajax({

View File

@@ -2112,6 +2112,44 @@ public class PurchaseOrderController {
return resultMap;
}
/**
* 도면 파일 개수 조회 (AJAX)
* @param request
* @param paramMap - objId (PURCHASE_ORDER_MASTER_OBJID)
* @return
*/
@ResponseBody
@RequestMapping("/purchaseOrder/getDrawingFileCount.do")
public Map getDrawingFileCount(HttpServletRequest request, @RequestParam Map paramMap){
Map resultMap = new HashMap();
try {
String objId = CommonUtils.checkNull(paramMap.get("objId"));
if("".equals(objId)){
resultMap.put("result", "error");
resultMap.put("message", "잘못된 요청입니다.");
return resultMap;
}
// 도면 파일 개수 조회
paramMap.put("PURCHASE_ORDER_MASTER_OBJID", objId);
paramMap.put("MULTI_MASTER_OBJID", objId);
List fileList = commonService.selectList("purchaseOrder.purchaseOrderPartFileListForMail", null, paramMap);
int count = (fileList != null) ? fileList.size() : 0;
resultMap.put("result", "success");
resultMap.put("count", count);
} catch (Exception e) {
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", "도면 파일 조회 중 오류가 발생했습니다.");
}
return resultMap;
}
/**
* PDF 청크 업로드 (AJAX)
* @param request
@@ -2234,4 +2272,48 @@ public class PurchaseOrderController {
return resultMap;
}
/**
* 발주 취소 전 입고 여부 확인
* @param request
* @param paramMap
* @return
*/
@ResponseBody
@RequestMapping("/purchaseOrder/checkReceiptForCancel.do")
public Map checkReceiptForCancel(HttpServletRequest request, @RequestParam Map paramMap){
Map resultMap = new HashMap();
try {
resultMap = purchaseOrderService.checkReceiptForCancel(request, paramMap);
} catch (Exception e) {
e.printStackTrace();
resultMap.put("hasReceipt", true);
resultMap.put("message", "입고 확인 중 오류가 발생했습니다.");
}
return resultMap;
}
/**
* 발주 취소 실행 (입고가 없는 경우만)
* @param request
* @param paramMap
* @return
*/
@ResponseBody
@RequestMapping("/purchaseOrder/executeOrderCancel.do")
public Map executeOrderCancel(HttpServletRequest request, @RequestParam Map paramMap){
Map resultMap = new HashMap();
try {
resultMap = purchaseOrderService.executeOrderCancel(request, paramMap);
} catch (Exception e) {
e.printStackTrace();
resultMap.put("result", false);
resultMap.put("message", "발주 취소 중 오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
}

View File

@@ -4246,6 +4246,7 @@ SELECT T.*
<select id="deliveryMngList_new" parameterType="map" resultType="map">
SELECT POM.OBJID
,POM.STATUS
<!-- ,TO_CHAR(CM.REGDATE, 'YYYY') AS CM_YEAR -->
,TO_CHAR(POM.REGDATE,'YYYY') AS POM_YEAR
,(SELECT SUPPLY_NAME FROM SUPPLY_MNG AS O WHERE O.OBJID::VARCHAR = CM.CUSTOMER_OBJID) AS CUSTOMER_NAME
@@ -6123,4 +6124,31 @@ FROM(
ORDER BY PART_NO
</select>
<!-- 발주 취소를 위한 입고 수량 조회 (ARRIVAL_PLAN 테이블) -->
<select id="getTotalReceiptQtyForCancel" parameterType="map" resultType="map">
SELECT
COALESCE(SUM(
CASE
WHEN AP.RECEIPT_QTY IS NULL OR AP.RECEIPT_QTY = '' THEN 0
ELSE AP.RECEIPT_QTY::NUMERIC
END
), 0) AS TOTAL_RECEIPT_QTY
FROM ARRIVAL_PLAN AP
WHERE AP.PARENT_OBJID = #{PURCHASE_ORDER_MASTER_OBJID}
</select>
<!-- 발주 취소 상태 업데이트 -->
<update id="updateOrderCancelStatus" parameterType="map">
UPDATE PURCHASE_ORDER_MASTER
SET STATUS = #{STATUS}
WHERE OBJID = #{OBJID}
</update>
<!-- 동시발주 하위건 발주 취소 상태 업데이트 -->
<update id="updateOrderCancelStatusMulti" parameterType="map">
UPDATE PURCHASE_ORDER_MASTER
SET STATUS = #{STATUS}
WHERE MULTI_MASTER_OBJID = #{OBJID}
</update>
</mapper>

View File

@@ -2961,6 +2961,8 @@ public class PurchaseOrderService {
String contents = CommonUtils.checkNull(paramMap.get("contents"));
String writerEmail = CommonUtils.checkNull(paramMap.get("WRITER_EMAIL"));
String writer = CommonUtils.checkNull(paramMap.get("WRITER"));
// 도면 파일 첨부 여부 (기본값: Y)
String includeDrawingFiles = CommonUtils.checkNull(paramMap.get("includeDrawingFiles"), "Y");
// 발주서 정보 조회
Map infoParam = new HashMap();
@@ -3035,8 +3037,8 @@ public class PurchaseOrderService {
attachFileList.add(excelFile);
}
// 2. 도면 파일 압축 첨부
if(partFileList != null && partFileList.size() > 0) {
// 2. 도면 파일 압축 첨부 (includeDrawingFiles가 Y인 경우에만)
if("Y".equals(includeDrawingFiles) && partFileList != null && partFileList.size() > 0) {
File zf = MailUtil.zipFileListMail(zipName, partFileList);
if(zf != null && zf.exists()) {
HashMap hm = new HashMap();
@@ -3177,4 +3179,126 @@ public class PurchaseOrderService {
}
return resultMap;
}
/**
* 발주 취소 전 입고 여부 확인
* @param request
* @param paramMap
* @return hasReceipt: 입고 여부, totalReceiptQty: 총 입고수량
*/
public Map checkReceiptForCancel(HttpServletRequest request, Map paramMap) throws Exception {
Map resultMap = new HashMap();
SqlSession sqlSession = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
String purchaseOrderMasterObjid = CommonUtils.checkNull(paramMap.get("PURCHASE_ORDER_MASTER_OBJID"));
if(purchaseOrderMasterObjid.isEmpty()) {
resultMap.put("hasReceipt", true);
resultMap.put("message", "발주서 정보가 없습니다.");
return resultMap;
}
// 입고 수량 조회 (ARRIVAL_PLAN 테이블에서 RECEIPT_QTY 합계)
Map queryParam = new HashMap();
queryParam.put("PURCHASE_ORDER_MASTER_OBJID", purchaseOrderMasterObjid);
Map receiptInfo = (Map)sqlSession.selectOne("purchaseOrder.getTotalReceiptQtyForCancel", queryParam);
int totalReceiptQty = 0;
if(receiptInfo != null) {
// 대소문자 구분 없이 키 찾기
Object qtyValue = receiptInfo.get("TOTAL_RECEIPT_QTY");
if(qtyValue == null) {
qtyValue = receiptInfo.get("total_receipt_qty");
}
if(qtyValue != null) {
totalReceiptQty = Integer.parseInt(qtyValue.toString());
}
System.out.println("=== 발주취소 입고확인(check) === OBJID: " + purchaseOrderMasterObjid + ", receiptInfo: " + receiptInfo + ", totalReceiptQty: " + totalReceiptQty);
}
resultMap.put("hasReceipt", totalReceiptQty > 0);
resultMap.put("totalReceiptQty", totalReceiptQty);
} finally {
if(sqlSession != null) sqlSession.close();
}
return resultMap;
}
/**
* 발주 취소 실행 (입고가 없는 경우만)
* STATUS를 'orderCancel'로 변경
* @param request
* @param paramMap
* @return
*/
public Map executeOrderCancel(HttpServletRequest request, Map paramMap) throws Exception {
Map resultMap = new HashMap();
SqlSession sqlSession = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
String purchaseOrderMasterObjid = CommonUtils.checkNull(paramMap.get("PURCHASE_ORDER_MASTER_OBJID"));
if(purchaseOrderMasterObjid.isEmpty()) {
resultMap.put("result", false);
resultMap.put("message", "발주서 정보가 없습니다.");
return resultMap;
}
// 입고 여부 재확인
Map queryParam = new HashMap();
queryParam.put("PURCHASE_ORDER_MASTER_OBJID", purchaseOrderMasterObjid);
Map receiptInfo = (Map)sqlSession.selectOne("purchaseOrder.getTotalReceiptQtyForCancel", queryParam);
int totalReceiptQty = 0;
if(receiptInfo != null) {
// 대소문자 구분 없이 키 찾기
Object qtyValue = receiptInfo.get("TOTAL_RECEIPT_QTY");
if(qtyValue == null) {
qtyValue = receiptInfo.get("total_receipt_qty");
}
if(qtyValue != null) {
totalReceiptQty = Integer.parseInt(qtyValue.toString());
}
System.out.println("=== 발주취소 입고확인 === OBJID: " + purchaseOrderMasterObjid + ", receiptInfo: " + receiptInfo + ", totalReceiptQty: " + totalReceiptQty);
}
if(totalReceiptQty > 0) {
resultMap.put("result", false);
resultMap.put("message", "입고된 항목이 있어 취소할 수 없습니다. (입고수량: " + totalReceiptQty + ")");
return resultMap;
}
// 발주 취소 처리 (STATUS = 'orderCancel')
Map updateParam = new HashMap();
updateParam.put("OBJID", purchaseOrderMasterObjid);
updateParam.put("STATUS", "orderCancel");
sqlSession.update("purchaseOrder.updateOrderCancelStatus", updateParam);
// 동시발주 하위건도 함께 취소
sqlSession.update("purchaseOrder.updateOrderCancelStatusMulti", updateParam);
sqlSession.commit();
resultMap.put("result", true);
resultMap.put("message", "발주가 취소되었습니다.");
} catch(Exception e) {
if(sqlSession != null) sqlSession.rollback();
throw e;
} finally {
if(sqlSession != null) sqlSession.close();
}
return resultMap;
}
}