m-bom 버전 이력 팝업 & 총단가 저장 안되는 오류 수정 등...

This commit is contained in:
2025-11-28 18:38:23 +09:00
parent 91edcb9fe7
commit 169fbeda16
8 changed files with 347 additions and 75 deletions

View File

@@ -0,0 +1,159 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page import="com.pms.common.utils.*"%>
<%@include file="/init_no_login.jsp" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>M-BOM 이력</title>
<style>
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow-x: hidden;
font-family: Arial, sans-serif;
}
body {
padding: 20px;
}
.header-info {
background-color: #f5f5f5;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
}
.header-info table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
.header-info th {
text-align: left;
padding: 5px 10px;
background-color: #e0e0e0;
width: 120px;
white-space: nowrap;
}
.header-info td {
padding: 5px 10px;
word-wrap: break-word;
}
.history-table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
table-layout: fixed;
}
.history-table th {
background-color: #4CAF50;
color: white;
padding: 10px;
text-align: center;
border: 1px solid #ddd;
}
.history-table td {
padding: 8px;
border: 1px solid #ddd;
text-align: center;
word-wrap: break-word;
}
.history-table tr:nth-child(even) {
background-color: #f9f9f9;
}
.history-table tr:hover {
background-color: #f5f5f5;
}
.no-data {
text-align: center;
padding: 30px;
color: #999;
}
h3 {
margin-top: 0;
color: #333;
}
</style>
</head>
<body>
<h3>M-BOM 이력</h3>
<!-- M-BOM 헤더 정보 -->
<div class="header-info">
<table>
<tr>
<th>M-BOM 품번</th>
<td>${mbomHeader.MBOM_NO}</td>
<th>품번</th>
<td>${mbomHeader.PART_NO}</td>
</tr>
<tr>
<th>품명</th>
<td colspan="3">${mbomHeader.PART_NAME}</td>
</tr>
<tr>
<th>최초 작성자</th>
<td>${mbomHeader.WRITER}</td>
<th>최초 작성일</th>
<td>${mbomHeader.REGDATE}</td>
</tr>
<tr>
<th>최종 수정자</th>
<td>${mbomHeader.EDITER}</td>
<th>최종 수정일</th>
<td>${mbomHeader.EDIT_DATE}</td>
</tr>
</table>
</div>
<!-- 이력 테이블 -->
<h3>변경 이력</h3>
<c:choose>
<c:when test="${not empty historyList}">
<table class="history-table">
<thead>
<tr>
<th style="width: 60px;">No</th>
<th style="width: 100px;">변경유형</th>
<th style="width: auto;">변경내용</th>
<th style="width: 120px;">변경자</th>
<th style="width: 180px;">변경일시</th>
</tr>
</thead>
<tbody>
<c:forEach items="${historyList}" var="history" varStatus="status">
<tr>
<td>${historyList.size() - status.index}</td>
<td>
<c:choose>
<c:when test="${history.CHANGE_TYPE == 'CREATE'}">생성</c:when>
<c:when test="${history.CHANGE_TYPE == 'UPDATE'}">수정</c:when>
<c:when test="${history.CHANGE_TYPE == 'DELETE'}">삭제</c:when>
<c:otherwise>${history.CHANGE_TYPE}</c:otherwise>
</c:choose>
</td>
<td style="text-align: left;">${history.CHANGE_DESCRIPTION}</td>
<td>${history.CHANGE_USER_NAME}</td>
<td>${history.CHANGE_DATE}</td>
</tr>
</c:forEach>
</tbody>
</table>
</c:when>
<c:otherwise>
<div class="no-data">변경 이력이 없습니다.</div>
</c:otherwise>
</c:choose>
<div style="text-align: center; margin-top: 20px;">
<button type="button" onclick="window.close();" style="padding: 8px 20px; cursor: pointer;">닫기</button>
</div>
</body>
</html>

View File

@@ -140,23 +140,24 @@ var columns = [
field: 'PRODUCT_NAME'
},
// 부대표님이 컬럼 삭제하라고하심!(251128 국내/해외, 접수일, 고객사)
// 5. 국내/해외
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 90,
title: '국내/해외',
field: 'AREA_NAME'
},
// {
// headerHozAlign: 'center',
// hozAlign: 'center',
// width: 90,
// title: '국내/해외',
// field: 'AREA_NAME'
// },
// 6. 접수일
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 80,
title: '접수일',
field: 'RECEIPT_DATE'
},
// {
// headerHozAlign: 'center',
// hozAlign: 'center',
// width: 80,
// title: '접수일',
// field: 'RECEIPT_DATE'
// },
// 7. 고객사
{
@@ -168,19 +169,19 @@ var columns = [
},
// 8. 유/무상
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 80,
title: '유/무상',
field: 'PAID_TYPE_NAME'
},
// {
// headerHozAlign: 'center',
// hozAlign: 'center',
// width: 80,
// title: '유/무상',
// field: 'PAID_TYPE_NAME'
// },
// 9. 품번
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 120,
width: 150,
title: '품번',
field: 'PART_NO'
},
@@ -189,7 +190,7 @@ var columns = [
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
width: 180,
title: '품명',
field: 'PART_NAME'
},
@@ -225,7 +226,7 @@ var columns = [
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
// width: 150,
title: '고객사요청사항',
field: 'CUSTOMER_REQUEST'
},
@@ -271,7 +272,22 @@ var columns = [
hozAlign: 'center',
width: 70,
title: '버전',
field: 'MBOM_VERSION'
field: 'MBOM_VERSION',
cellClick: function(e, cell) {
var data = cell.getRow().getData();
if(data.MBOM_HEADER_OBJID) {
fn_showMbomHistory(data.MBOM_HEADER_OBJID);
} else {
alert('M-BOM 이력이 없습니다.');
}
},
formatter: function(cell) {
var value = cell.getValue();
if(value) {
return '<span style="color: #0066cc; cursor: pointer; text-decoration: underline;">' + value + '</span>';
}
return '-';
}
},
// 18. 구매리스트 생성일
@@ -537,6 +553,12 @@ function fn_checkAssignmentAndOpenMbom(projectObjId) {
});
}
// M-BOM 이력 팝업
function fn_showMbomHistory(mbomHeaderObjid) {
var url = "/productionplanning/mBomHistoryPopup.do?mbomHeaderObjid=" + mbomHeaderObjid;
window.open(url, "mbomHistoryPopup", "width=1000,height=700,scrollbars=yes,resizable=yes");
}
// 구매리스트 생성
function fn_openPurchaseListPopup() {
// 체크된 행 가져오기
@@ -632,6 +654,9 @@ function fn_openPurchaseListPopup() {
title: '생성 완료',
text: '구매리스트가 생성되었습니다.\n구매리스트관리 화면에서 확인하세요.',
icon: 'success'
}).then(() => {
// 그리드 새로고침
fn_search();
});
} else {
Swal.fire({

View File

@@ -913,7 +913,12 @@ function getMbomTreeData() {
// 구매 정보
vendor: row.VENDOR || row.VENDOR_PM, // 공급업체 코드/OBJID (기존 값 유지)
unitPrice: row.UNIT_PRICE, // 원본 값 그대로
totalPrice: row.TOTAL_PRICE, // 원본 값 그대로
// totalPrice 계산: 항목수량 × 단가
totalPrice: (function() {
var itemQty = parseFloat(row.ITEM_QTY) || 0;
var unitPrice = parseFloat(row.UNIT_PRICE) || 0;
return itemQty * unitPrice;
})(),
currency: row.CURRENCY,
leadTime: row.LEAD_TIME, // 원본 값 그대로
minOrderQty: row.MIN_ORDER_QTY, // 원본 값 그대로

View File

@@ -111,21 +111,23 @@ $(document).ready(function(){
,"품명"
,"품번"
//,"품번2"
,"규격","Maker","단위","설계수량","수량","실발주수량"
,"공급단가","레이져단가","용접단가","가공단가","후처리단가","공급가","부가세","부가세포함공급가"
,"규격","메이커","단위","설계수량","수량","실발주수량"
,"공급단가"
//,"레이져단가","용접단가","가공단가","후처리단가"
,"공급가","부가세","부가세포함공급가"
,"총발주수량","재고수량","재고수량(org)","총실발주수량","실공급가" //sum
]
,colModel: [
{name:"OBJID" , index:"", width: 0, align:"center", hidden: true, sortable:false, editable:false}
,{name:"PART_OBJID" , index:"", width: 0, align:"center", hidden: true, sortable:false, editable:false}
,{name:"LD_PART_OBJID" , index:"", width: 0, align:"center", hidden: true, sortable:false, editable:false}
,{name:"PART_NAME" , index:"", width:140, align:"left", hidden:false, sortable:false, editable:false
,{name:"PART_NAME" , index:"", width:200, align:"left", hidden:false, sortable:false, editable:false
,editoptions:{
dataInit: function(e){ e.style.textAlign = "left"; e.style.fontSize = 13; }
,dataEvents: [ {type:"change", fn:function(e) { }} ]
}
}
,{name:"PART_NO" , index:"", width:130, align:"left", hidden:false, sortable:false, editable:false
,{name:"PART_NO" , index:"", width:200, align:"left", hidden:false, sortable:false, editable:false
<%--
,formatter:'showlink'
,formatoptions:{baseLinkUrl:'/partMng/partMngDetailPopUp.do', //?OBJID=
@@ -177,7 +179,7 @@ $(document).ready(function(){
,dataEvents: [ {type:"change", fn:function(e) { }} ]
}
}
,{name:"MAKER" , index:"", width:90, align:"left", hidden:false, sortable:false, editable:false
,{name:"MAKER" , index:"", width:110, align:"left", hidden:false, sortable:false, editable:false
,editoptions:{
dataInit: function(e){ e.style.textAlign = "left"; e.style.fontSize = 13; }
,dataEvents: [ {type:"change", fn:function(e) { }} ]
@@ -208,7 +210,7 @@ $(document).ready(function(){
,dataEvents: [ {type:"change", fn:function(e) { gridFn.calcRowAll(e); }} ]
}
}
,{name:"ORDER_QTY" , index:"", width:53, align: "right", hidden:true, sortable:false, editable: false
,{name:"ORDER_QTY" , index:"", width:53, align: "right", hidden:false, sortable:false, editable: false
//,formatter: "integer", formatoptions:{thousandsSeparator:","}
,editoptions:{
dataInit: function(e){ e.style.textAlign = "right"; e.style.fontSize = 13; }
@@ -233,11 +235,11 @@ $(document).ready(function(){
]
}
}
,{name:"PRICE1" , index:"", width:70, align: "right", hidden:false, sortable:false, editable: true
,formatter: "integer", formatoptions:{thousandsSeparator:","}
,editoptions:{
dataInit: function(e){ e.style.textAlign = "right"; e.style.fontSize = 13; }
,dataEvents: [ {type:"change", fn:function(e) { gridFn.calcRowAll(e); }}
// ,{name:"PRICE1" , index:"", width:70, align: "right", hidden:false, sortable:false, editable: true
// ,formatter: "integer", formatoptions:{thousandsSeparator:","}
// ,editoptions:{
// dataInit: function(e){ e.style.textAlign = "right"; e.style.fontSize = 13; }
// ,dataEvents: [ {type:"change", fn:function(e) { gridFn.calcRowAll(e); }}
/*
,
{type:"focus", fn:function(e) {
@@ -247,32 +249,32 @@ $(document).ready(function(){
}
}
*/
]
}
}
,{name:"PRICE2" , index:"", width:60, align: "right", hidden:false, sortable:false, editable: true
,formatter: "integer", formatoptions:{thousandsSeparator:","}
,editoptions:{
dataInit: function(e){ e.style.textAlign = "right"; e.style.fontSize = 13; }
,dataEvents: [ {type:"change", fn:function(e) { gridFn.calcRowAll(e); }} ]
}
}
,{name:"PRICE3" , index:"", width:60, align: "right", hidden:false, sortable:false, editable: true
,formatter: "integer", formatoptions:{thousandsSeparator:","}
,editoptions:{
dataInit: function(e){ e.style.textAlign = "right"; e.style.fontSize = 13; }
,dataEvents: [ {type:"change", fn:function(e) {
gridFn.calcRowAll(e); }} ]
}
}
,{name:"PRICE4" , index:"", width:70, align: "right", hidden:false, sortable:false, editable: true
,formatter: "integer", formatoptions:{thousandsSeparator:","}
,editoptions:{
dataInit: function(e){ e.style.textAlign = "right"; e.style.fontSize = 13; }
,dataEvents: [ {type:"change", fn:function(e) {
gridFn.calcRowAll(e); }} ]
}
}
// ]
// }
// }
// ,{name:"PRICE2" , index:"", width:60, align: "right", hidden:false, sortable:false, editable: true
// ,formatter: "integer", formatoptions:{thousandsSeparator:","}
// ,editoptions:{
// dataInit: function(e){ e.style.textAlign = "right"; e.style.fontSize = 13; }
// ,dataEvents: [ {type:"change", fn:function(e) { gridFn.calcRowAll(e); }} ]
// }
// }
// ,{name:"PRICE3" , index:"", width:60, align: "right", hidden:false, sortable:false, editable: true
// ,formatter: "integer", formatoptions:{thousandsSeparator:","}
// ,editoptions:{
// dataInit: function(e){ e.style.textAlign = "right"; e.style.fontSize = 13; }
// ,dataEvents: [ {type:"change", fn:function(e) {
// gridFn.calcRowAll(e); }} ]
// }
// }
// ,{name:"PRICE4" , index:"", width:70, align: "right", hidden:false, sortable:false, editable: true
// ,formatter: "integer", formatoptions:{thousandsSeparator:","}
// ,editoptions:{
// dataInit: function(e){ e.style.textAlign = "right"; e.style.fontSize = 13; }
// ,dataEvents: [ {type:"change", fn:function(e) {
// gridFn.calcRowAll(e); }} ]
// }
// }
,{name:"SUPPLY_UNIT_PRICE", index:"", width:100, align: "right", hidden:false, sortable:false, editable: true
,formatter: "integer", formatoptions:{thousandsSeparator:","}
,editoptions:{

View File

@@ -1606,4 +1606,28 @@ public class ProductionPlanningController extends BaseService {
}
return resultMap;
}
/**
* M-BOM 이력 팝업
*/
@RequestMapping("/productionplanning/mBomHistoryPopup.do")
public String mBomHistoryPopup(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
try {
String mbomHeaderObjid = CommonUtils.checkNull(paramMap.get("mbomHeaderObjid"));
if(!"".equals(mbomHeaderObjid)) {
// M-BOM 헤더 정보 조회
paramMap.put("mbomHeaderObjid", mbomHeaderObjid);
Map<String, Object> mbomHeader = commonService.selectOne("productionplanning.getMbomHeaderByObjid", request, paramMap);
request.setAttribute("mbomHeader", mbomHeader);
// M-BOM 이력 조회
List<Map<String, Object>> historyList = commonService.selectList("productionplanning.getMbomHistoryList", request, paramMap);
request.setAttribute("historyList", historyList);
}
} catch(Exception e) {
e.printStackTrace();
}
return "/productionplanning/mBomHistoryPopup";
}
}

View File

@@ -2922,8 +2922,8 @@
WHERE CIS.ITEM_OBJID = CI.OBJID
AND UPPER(CIS.STATUS) = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL) AS SERIAL_NO,
-- 수량: CONTRACT_ITEM의 수량
COALESCE(CI.ORDER_QUANTITY::numeric, PM.QUANTITY::numeric, 0) AS QUANTITY,
-- 수량: PROJECT_MGMT 우선
COALESCE(PM.QUANTITY::numeric, CI.ORDER_QUANTITY::numeric, 0) AS QUANTITY,
-- 요청납기: CONTRACT_ITEM 우선
COALESCE(CI.DUE_DATE, PM.DUE_DATE, CM.req_del_date) AS REQ_DEL_DATE,
-- 고객요청사항: CONTRACT_ITEM에서 가져옴
@@ -3805,7 +3805,7 @@
<if test="proposalDate != null">
PROPOSAL_DATE,
</if>
STATUS, WRITER, REGDATE, REMARK
STATUS, WRITER, REGDATE, EDITER, EDIT_DATE, REMARK
) VALUES (
#{objid}, #{mbomHeaderObjid}, #{parentObjid}, #{childObjid}, #{seq}, #{level},
#{partObjid}, #{partNo}, #{partName}, #{qty}, #{unit},
@@ -3818,7 +3818,7 @@
<if test="proposalDate != null">
#{proposalDate},
</if>
'ACTIVE', #{sessionUserId}, NOW(), #{remark}
'ACTIVE', #{sessionUserId}, NOW(), #{sessionUserId}, NOW(), #{remark}
)
</insert>
@@ -3884,6 +3884,41 @@
ORDER BY REGDATE DESC, MBOM_NO
</select>
<!-- M-BOM 헤더 정보 조회 (OBJID로) -->
<select id="getMbomHeaderByObjid" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
OBJID,
MBOM_NO,
PART_NO,
PART_NAME,
SOURCE_BOM_TYPE,
TO_CHAR(REGDATE, 'YYYY-MM-DD HH24:MI:SS') AS REGDATE,
WRITER,
TO_CHAR(EDIT_DATE, 'YYYY-MM-DD HH24:MI:SS') AS EDIT_DATE,
EDITER,
STATUS
FROM MBOM_HEADER
WHERE OBJID::VARCHAR = #{mbomHeaderObjid}
</select>
<!-- M-BOM 이력 조회 -->
<select id="getMbomHistoryList" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
MH.OBJID,
MH.MBOM_HEADER_OBJID,
MH.CHANGE_TYPE,
MH.CHANGE_DESCRIPTION,
TO_CHAR(MH.CHANGE_DATE, 'YYYY-MM-DD HH24:MI:SS') AS CHANGE_DATE,
MH.CHANGE_USER,
COALESCE(
(SELECT USER_NAME FROM USER_INFO WHERE USER_ID = MH.CHANGE_USER LIMIT 1),
MH.CHANGE_USER
) AS CHANGE_USER_NAME
FROM MBOM_HISTORY MH
WHERE MH.MBOM_HEADER_OBJID::VARCHAR = #{mbomHeaderObjid}
ORDER BY MH.CHANGE_DATE DESC
</select>
<!-- M-BOM 상세 리스트 조회 (이력용) -->
<select id="getMbomDetailList" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT

View File

@@ -753,8 +753,8 @@ VALUES
#{SALES_BOM_QTY_OBJID },
#{PART_OBJID },
#{SALES_REQUEST_MASTER_OBJID},
#{QTY },
#{ORG_QTY },
COALESCE(NULLIF(TRIM(#{QTY}::TEXT), '')::NUMERIC, 0),
COALESCE(NULLIF(TRIM(#{ORG_QTY}::TEXT), '')::NUMERIC, 0),
#{PARTNER_OBJID },
#{PARTNER_PRICE },
#{DELIVERY_REQUEST_DATE },
@@ -768,8 +768,8 @@ VALUES
SALES_BOM_QTY_OBJID = #{SALES_BOM_QTY_OBJID },
PART_OBJID = #{PART_OBJID },
SALES_REQUEST_MASTER_OBJID = #{SALES_REQUEST_MASTER_OBJID},
QTY = #{QTY },
ORG_QTY = #{ORG_QTY },
QTY = COALESCE(NULLIF(TRIM(#{QTY}::TEXT), '')::NUMERIC, 0),
ORG_QTY = COALESCE(NULLIF(TRIM(#{ORG_QTY}::TEXT), '')::NUMERIC, 0),
PARTNER_OBJID = #{PARTNER_OBJID },
PARTNER_PRICE = #{PARTNER_PRICE },
DELIVERY_REQUEST_DATE = #{DELIVERY_REQUEST_DATE },

View File

@@ -1867,10 +1867,32 @@ public class PurchaseOrderService {
partParam.put("OBJID", CommonUtils.createObjId());
partParam.put("PURCHASE_ORDER_MASTER_OBJID", purchaseOrderMasterObjid);
partParam.put("PART_OBJID", CommonUtils.checkNull((String) partMap.get("part_objid")));
partParam.put("QTY", CommonUtils.checkNull(partMap.get("qty"), "0"));
// ORDER_QTY는 쿼리에서 PO_QTY 우선으로 계산된 값 사용
partParam.put("ORDER_QTY", CommonUtils.checkNull(partMap.get("order_qty"), "0"));
partParam.put("PARTNER_PRICE", CommonUtils.checkNull(partMap.get("partner_price"), "0"));
// 수량 처리
String qtyStr = CommonUtils.checkNull(partMap.get("qty"), "0");
String orderQtyStr = CommonUtils.checkNull(partMap.get("order_qty"), "0");
partParam.put("QTY", qtyStr);
partParam.put("ORDER_QTY", orderQtyStr);
// 단가 정보
String partnerPrice = CommonUtils.checkNull(partMap.get("partner_price"), "0");
String totalPrice = CommonUtils.checkNull(partMap.get("total_price"), "0");
partParam.put("PARTNER_PRICE", partnerPrice);
// 총액 계산 (품의서에서 가져온 TOTAL_PRICE 사용, 없으면 계산)
if ("0".equals(totalPrice) || "".equals(totalPrice)) {
try {
double orderQty = Double.parseDouble(orderQtyStr);
double price = Double.parseDouble(partnerPrice);
totalPrice = String.valueOf((long)(orderQty * price));
} catch (NumberFormatException e) {
totalPrice = "0";
}
}
partParam.put("SUPPLY_UNIT_PRICE", totalPrice);
partParam.put("PART_NO", CommonUtils.checkNull((String) partMap.get("part_no")));
partParam.put("PART_NAME", CommonUtils.checkNull((String) partMap.get("part_name")));
partParam.put("SPEC", CommonUtils.checkNull((String) partMap.get("spec")));