견적요청서관리 기능 개발

This commit is contained in:
2026-01-19 12:06:41 +09:00
parent ec94dde67a
commit d6a75db657
7 changed files with 2263 additions and 30 deletions

View File

@@ -75,6 +75,7 @@ body, html {
<div class="header">
<h3 style="margin: 0 0 10px 0; float: left;">구매리스트</h3>
<div style="float: right;">
<input type="button" value="견적요청서 생성" class="plm_btns" onclick="fn_createQuotationRequest();" style="margin-right: 5px; background-color: #4CAF50; border-color: #4CAF50;">
<input type="button" value="저장" class="plm_btns" onclick="fn_save();" style="margin-right: 5px;">
<input type="button" value="닫기" class="plm_btns" onclick="window.close();" style="margin-right: 5px;">
</div>
@@ -216,22 +217,15 @@ function fn_loadInitialData(){
var hasMbomHeader = mbomHeaderObjid && mbomHeaderObjid !== "" && mbomHeaderObjid !== "null";
var hasMaster = salesRequestMasterObjid && salesRequestMasterObjid !== "" && salesRequestMasterObjid !== "null";
// M-BOM에서 생성된 경우만 M-BOM 데이터를 로드 (MBOM_HEADER_OBJID가 있어야 함)
// M-BOM에서 데이터를 로드 (MBOM_HEADER_OBJID가 있어야 함)
if(hasMbomHeader){
logDebug("purchaseListFormPopUp :: M-BOM에서 생성된 구매리스트 - MBOM 로드");
fn_loadFromMBom(function(mbomList){
if(hasMaster){
// M-BOM 데이터가 있으면 merge, 없으면 SALES_REQUEST_PART만 로드
var hasMbomData = mbomList && mbomList.length > 0;
logDebug("purchaseListFormPopUp :: MBOM data count:", mbomList ? mbomList.length : 0);
fn_loadPurchaseList(hasMbomData); // M-BOM 있으면 merge, 없으면 단독 로드
}
// M-BOM 데이터만 사용 (SALES_REQUEST_PART 조회 안 함)
logDebug("purchaseListFormPopUp :: MBOM data count:", mbomList ? mbomList.length : 0);
});
} else if(hasMaster){
logDebug("purchaseListFormPopUp :: 수동 작성된 구매리스트 - SALES_REQUEST_PART만 로드");
fn_loadPurchaseList(false);
} else {
logDebug("purchaseListFormPopUp :: no data to load");
logDebug("purchaseListFormPopUp :: MBOM_HEADER_OBJID 없음 - 데이터 로드 안 함");
}
}
@@ -1009,6 +1003,196 @@ function fn_save() {
}
});
}
// 견적요청서 생성
function fn_createQuotationRequest() {
// 체크된 행 가져오기
var checkedRows = [];
$('.rowCheck:checked').each(function() {
var row = $(this).closest('.tabulator-row');
var rowData = _tabulGrid.getRow(row.attr('data-row')).getData();
checkedRows.push(rowData);
});
// 체크된 항목이 없으면 전체 데이터 사용
if(checkedRows.length === 0) {
checkedRows = _tabulGrid.getData();
}
if(checkedRows.length === 0) {
Swal.fire({
title: '알림',
text: '견적요청서를 생성할 데이터가 없습니다.',
icon: 'warning'
});
return;
}
// 공급업체/가공업체가 입력된 항목 분류
var supplyItems = []; // 공급업체가 입력된 항목
var processingItems = []; // 가공업체가 입력된 항목
checkedRows.forEach(function(item) {
var vendorPm = item.VENDOR_PM || '';
var processingVendor = item.PROCESSING_VENDOR || '';
if(vendorPm && vendorPm !== '') {
supplyItems.push({
objid: item.OBJID,
vendorObjid: vendorPm,
partNo: item.PART_NO,
partName: item.PART_NAME
});
}
if(processingVendor && processingVendor !== '') {
processingItems.push({
objid: item.OBJID,
vendorObjid: processingVendor,
partNo: item.PART_NO,
partName: item.PART_NAME
});
}
});
if(supplyItems.length === 0 && processingItems.length === 0) {
Swal.fire({
title: '알림',
text: '공급업체 또는 가공업체가 입력된 항목이 없습니다.\n업체를 먼저 입력해주세요.',
icon: 'warning'
});
return;
}
// 업체별로 그룹화
var supplyVendorGroups = {};
var processingVendorGroups = {};
supplyItems.forEach(function(item) {
if(!supplyVendorGroups[item.vendorObjid]) {
supplyVendorGroups[item.vendorObjid] = [];
}
supplyVendorGroups[item.vendorObjid].push(item.objid);
});
processingItems.forEach(function(item) {
if(!processingVendorGroups[item.vendorObjid]) {
processingVendorGroups[item.vendorObjid] = [];
}
processingVendorGroups[item.vendorObjid].push(item.objid);
});
// 생성할 견적요청서 목록 표시
var supplyCount = Object.keys(supplyVendorGroups).length;
var processingCount = Object.keys(processingVendorGroups).length;
var totalCount = supplyCount + processingCount;
var confirmMsg = '견적요청서를 생성하시겠습니까?\n\n';
if(supplyCount > 0) {
confirmMsg += '- 공급업체: ' + supplyCount + '개 업체\n';
}
if(processingCount > 0) {
confirmMsg += '- 가공업체: ' + processingCount + '개 업체\n';
}
confirmMsg += '\n총 ' + totalCount + '개의 견적요청서가 생성됩니다.';
Swal.fire({
title: '견적요청서 생성',
text: confirmMsg,
icon: 'question',
showCancelButton: true,
confirmButtonText: '생성',
cancelButtonText: '취소'
}).then((result) => {
if(result.isConfirmed) {
fn_executeCreateQuotationRequest(supplyVendorGroups, processingVendorGroups);
}
});
}
// 견적요청서 생성 실행
function fn_executeCreateQuotationRequest(supplyVendorGroups, processingVendorGroups) {
var createPromises = [];
// 공급업체별 견적요청서 생성
for(var vendorObjid in supplyVendorGroups) {
var partObjids = supplyVendorGroups[vendorObjid];
createPromises.push(
$.ajax({
type: "POST",
url: "/salesMng/createQuotationRequest.do",
data: {
SALES_REQUEST_MASTER_OBJID: salesRequestMasterObjid,
VENDOR_OBJID: vendorObjid,
VENDOR_TYPE: 'SUPPLY',
PART_OBJIDS: JSON.stringify(partObjids)
},
dataType: "json"
})
);
}
// 가공업체별 견적요청서 생성
for(var vendorObjid in processingVendorGroups) {
var partObjids = processingVendorGroups[vendorObjid];
createPromises.push(
$.ajax({
type: "POST",
url: "/salesMng/createQuotationRequest.do",
data: {
SALES_REQUEST_MASTER_OBJID: salesRequestMasterObjid,
VENDOR_OBJID: vendorObjid,
VENDOR_TYPE: 'PROCESSING',
PART_OBJIDS: JSON.stringify(partObjids)
},
dataType: "json"
})
);
}
Promise.all(createPromises).then(function(results) {
var successCount = 0;
var failCount = 0;
var createdNos = [];
results.forEach(function(result) {
if(result.resultFlag === 'S') {
successCount++;
if(result.QUOTATION_REQUEST_NO) {
createdNos.push(result.QUOTATION_REQUEST_NO);
}
} else {
failCount++;
}
});
if(successCount > 0) {
var msg = successCount + '개의 견적요청서가 생성되었습니다.';
if(createdNos.length > 0) {
msg += '\n\n생성된 번호:\n' + createdNos.join('\n');
}
Swal.fire({
title: '완료',
text: msg,
icon: 'success'
});
} else {
Swal.fire({
title: '실패',
text: '견적요청서 생성에 실패했습니다.',
icon: 'error'
});
}
}).catch(function(error) {
console.error("견적요청서 생성 오류:", error);
Swal.fire({
title: '오류',
text: '견적요청서 생성 중 오류가 발생했습니다.',
icon: 'error'
});
});
}
</script>
</body>
</html>

View File

@@ -0,0 +1,447 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*"%>
<%@include file="/init.jsp"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<script type="text/javascript" src="/js/tabulator/tabulator_custom.js"></script>
<style>
body, html {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
}
.container {
display: flex;
flex-direction: column;
height: 100%;
}
.header {
padding: 15px 20px;
background: #f5f5f5;
border-bottom: 2px solid #ddd;
}
.header h3 {
margin: 0 0 10px 0;
color: #333;
}
.info-table {
width: 100%;
border-collapse: collapse;
background: #fff;
border: 1px solid #ddd;
}
.info-table td {
padding: 8px 15px;
border: 1px solid #eee;
}
.info-table label {
font-weight: bold;
color: #555;
margin-right: 8px;
}
.info-table span {
color: #333;
}
.content {
flex: 1;
padding: 20px;
overflow: auto;
}
.status-badge {
display: inline-block;
padding: 3px 10px;
border-radius: 3px;
font-size: 12px;
font-weight: bold;
}
.status-create { background: #e3f2fd; color: #1976d2; }
.status-sent { background: #fff3e0; color: #f57c00; }
.status-received { background: #e8f5e9; color: #388e3c; }
.status-completed { background: #f3e5f5; color: #7b1fa2; }
.vendor-type-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 3px;
font-size: 11px;
font-weight: bold;
}
.vendor-supply { background: #e3f2fd; color: #1976d2; }
.vendor-processing { background: #e8f5e9; color: #388e3c; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<div style="float: left;">
<h3 style="margin: 0;">
견적요청서
<c:if test="${not empty resultMap.QUOTATION_REQUEST_NO}">
- ${resultMap.QUOTATION_REQUEST_NO}
</c:if>
<c:choose>
<c:when test="${resultMap.STATUS eq 'create'}">
<span class="status-badge status-create">작성중</span>
</c:when>
<c:when test="${resultMap.STATUS eq 'sent'}">
<span class="status-badge status-sent">발송완료</span>
</c:when>
<c:when test="${resultMap.STATUS eq 'received'}">
<span class="status-badge status-received">견적수신</span>
</c:when>
<c:when test="${resultMap.STATUS eq 'completed'}">
<span class="status-badge status-completed">완료</span>
</c:when>
</c:choose>
</h3>
</div>
<div style="float: right;">
<input type="button" value="저장" class="plm_btns" onclick="fn_save();" style="margin-right: 5px;">
<input type="button" value="닫기" class="plm_btns" onclick="window.close();">
</div>
<div style="clear: both;"></div>
<!-- 프로젝트/업체 정보 -->
<table class="info-table" style="margin-top: 15px;">
<tr>
<td style="width:25%;">
<label>프로젝트번호:</label>
<span id="infoProjectNo">${resultMap.PROJECT_NO}</span>
</td>
<td style="width:25%;">
<label>구매유형:</label>
<span id="infoPurchaseType">${resultMap.PURCHASE_TYPE_NAME}</span>
</td>
<td style="width:25%;">
<label>주문유형:</label>
<span id="infoOrderType">${resultMap.ORDER_TYPE_NAME}</span>
</td>
<td style="width:25%;">
<label>제품구분:</label>
<span id="infoProductName">${resultMap.PRODUCT_NAME_TITLE}</span>
</td>
</tr>
<tr>
<td>
<label>요청번호:</label>
<span id="infoRequestNo">${resultMap.REQUEST_MNG_NO}</span>
</td>
<td colspan="2">
<label>업체명:</label>
<span id="infoVendorName" style="font-weight: bold; color: #1976d2;">${resultMap.VENDOR_NAME}</span>
<c:choose>
<c:when test="${resultMap.VENDOR_TYPE eq 'SUPPLY'}">
<span class="vendor-type-badge vendor-supply">공급업체</span>
</c:when>
<c:when test="${resultMap.VENDOR_TYPE eq 'PROCESSING'}">
<span class="vendor-type-badge vendor-processing">가공업체</span>
</c:when>
</c:choose>
</td>
<td>
<label>업체 이메일:</label>
<span id="infoVendorEmail">${resultMap.VENDOR_EMAIL}</span>
</td>
</tr>
<c:if test="${not empty resultMap.MAIL_SEND_DATE_TITLE}">
<tr>
<td colspan="4">
<label>메일발송일:</label>
<span id="infoMailSendDate" style="color: green;">${resultMap.MAIL_SEND_DATE_TITLE}</span>
</td>
</tr>
</c:if>
</table>
</div>
<div class="content">
<div style="margin-bottom: 20px;">
<%-- 업체유형 설명 주석처리
<c:choose>
<c:when test="${resultMap.VENDOR_TYPE eq 'SUPPLY'}">
<span style="font-weight: bold; color: #1976d2;">공급업체 견적 - 품번/품명/제작수량 기준</span>
</c:when>
<c:when test="${resultMap.VENDOR_TYPE eq 'PROCESSING'}">
<span style="font-weight: bold; color: #388e3c;">가공업체 견적 - 소재품번/소재재질/규격/발주수량 기준</span>
</c:when>
</c:choose>
--%>
<span style="float: right; color: #666; font-size: 12px;">
* 단가 입력 후 저장하면 구매리스트에 자동 반영됩니다.
</span>
</div>
<div id="quotationDetailTable"></div>
</div>
</div>
<script>
var _tabulGrid;
var quotationRequestMasterObjid = "${resultMap.OBJID}";
var vendorType = "${resultMap.VENDOR_TYPE}";
var currentStatus = "${resultMap.STATUS}";
$(document).ready(function(){
fn_initGrid();
fn_loadDetailList();
});
// 그리드 초기화
function fn_initGrid() {
var columns = [];
// 공통 컬럼
columns.push({title:'OBJID', field:'OBJID', visible:false});
columns.push({title:'SALES_REQUEST_PART_OBJID', field:'SALES_REQUEST_PART_OBJID', visible:false});
columns.push({title:'PART_OBJID', field:'PART_OBJID', visible:false});
// No
columns.push({
headerHozAlign: 'center',
hozAlign: 'center',
width: 50,
title: 'No',
field: 'ROW_NUM',
formatter: "rownum"
});
// 업체유형에 따라 다른 컬럼 표시
if(vendorType === 'PROCESSING') {
// 가공업체: 품번, 품명, 규격(없음), 제작수량
columns.push({
headerHozAlign: 'center',
hozAlign: 'left',
widthGrow: 2,
title: '품번',
field: 'PART_NO'
});
columns.push({
headerHozAlign: 'center',
hozAlign: 'left',
widthGrow: 3,
title: '품명',
field: 'PART_NAME'
});
columns.push({
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '규격',
field: 'SIZE',
formatter: function(cell) {
return '-';
}
});
columns.push({
headerHozAlign: 'center',
hozAlign: 'right',
width: 100,
title: '제작수량',
field: 'QTY',
formatter: function(cell) {
var value = cell.getValue();
return value ? Number(value).toLocaleString() : '0';
}
});
} else {
// 공급업체: 소재품번, 소재재질, 규격, 발주수량
columns.push({
headerHozAlign: 'center',
hozAlign: 'left',
widthGrow: 2,
title: '소재품번',
field: 'PART_NO'
});
columns.push({
headerHozAlign: 'center',
hozAlign: 'left',
widthGrow: 2,
title: '소재재질',
field: 'RAW_MATERIAL'
});
columns.push({
headerHozAlign: 'center',
hozAlign: 'left',
width: 120,
title: '규격',
field: 'SIZE'
});
columns.push({
headerHozAlign: 'center',
hozAlign: 'right',
width: 100,
title: '발주수량',
field: 'QTY',
formatter: function(cell) {
var value = cell.getValue();
return value ? Number(value).toLocaleString() : '0';
}
});
}
// 업체명
columns.push({
headerHozAlign: 'center',
hozAlign: 'left',
widthGrow: 1.5,
title: vendorType === 'PROCESSING' ? '가공업체' : '공급업체',
field: 'VENDOR_NAME'
});
// 단가 (수정가능)
columns.push({
headerHozAlign: 'center',
hozAlign: 'right',
width: 120,
title: '<span style="background-color: #FFFF00; padding: 2px 5px;">단가</span>',
field: 'UNIT_PRICE',
editor: 'number',
editable: true,
formatter: function(cell) {
var value = cell.getValue();
return value ? Number(value).toLocaleString() : '0';
}
});
// // 총단가 (자동계산) - 주석처리
// columns.push({
// headerHozAlign: 'center',
// hozAlign: 'right',
// width: 120,
// title: '총단가',
// field: 'TOTAL_PRICE',
// formatter: function(cell) {
// var data = cell.getRow().getData();
// var qty = parseFloat(data.QTY) || 0;
// var unitPrice = parseFloat(data.UNIT_PRICE) || 0;
// var totalPrice = qty * unitPrice;
// return totalPrice > 0 ? totalPrice.toLocaleString() : '0';
// }
// });
// // 비고 - 주석처리
// columns.push({
// headerHozAlign: 'center',
// hozAlign: 'left',
// widthGrow: 2,
// title: '비고',
// field: 'REMARK',
// editor: 'input',
// editable: true
// });
_tabulGrid = new Tabulator("#quotationDetailTable", {
layout: "fitColumns",
height: "calc(100vh - 300px)",
columns: columns,
data: [],
placeholder: "데이터가 없습니다."
});
// 셀 편집 이벤트 (총단가 자동 계산)
_tabulGrid.on("cellEdited", function(cell) {
var field = cell.getField();
var row = cell.getRow();
if(field === 'UNIT_PRICE') {
row.reformat();
}
});
}
// 상세 목록 조회
function fn_loadDetailList() {
if(!quotationRequestMasterObjid || quotationRequestMasterObjid === '') {
return;
}
$.ajax({
url: "/salesMng/getQuotationRequestDetailList.do",
method: 'post',
data: {
QUOTATION_REQUEST_MASTER_OBJID: quotationRequestMasterObjid
},
dataType: 'json',
success: function(data) {
if(data && data.list) {
_tabulGrid.setData(data.list);
}
},
error: function(jqxhr, status, error){
console.error("상세 목록 조회 오류:", error);
Swal.fire({
title: '오류',
text: '상세 목록 조회 중 오류가 발생했습니다.',
icon: 'error'
});
}
});
}
// 저장
function fn_save() {
var gridData = _tabulGrid.getData();
if(!gridData || gridData.length === 0) {
Swal.fire({
title: '알림',
text: '저장할 데이터가 없습니다.',
icon: 'warning'
});
return;
}
Swal.fire({
title: '확인',
text: '저장하시겠습니까?\n단가가 구매리스트에 자동 반영됩니다.',
icon: 'question',
showCancelButton: true,
confirmButtonText: '확인',
cancelButtonText: '취소'
}).then((result) => {
if (result.isConfirmed) {
$.ajax({
url: "/salesMng/saveQuotationRequestPrice.do",
method: 'post',
data: {
QUOTATION_REQUEST_MASTER_OBJID: quotationRequestMasterObjid,
DETAIL_LIST: JSON.stringify(gridData)
},
dataType: 'json',
success: function(data) {
if(data && (data.resultFlag === 'S' || data.RESULTFLAG === 'S')) {
Swal.fire({
title: '성공',
text: data.message || '저장되었습니다.',
icon: 'success'
}).then(() => {
if(opener && opener.fn_search) {
opener.fn_search();
}
});
} else {
Swal.fire({
title: '실패',
text: data.message || data.MESSAGE || '저장에 실패했습니다.',
icon: 'error'
});
}
},
error: function(jqxhr, status, error){
console.error("저장 오류:", error);
Swal.fire({
title: '오류',
text: '저장 중 오류가 발생했습니다.',
icon: 'error'
});
}
});
}
});
}
</script>
</body>
</html>

View File

@@ -0,0 +1,322 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*" %>
<%@include file="/init.jsp"%>
<c:set var="now" value="<%=new java.util.Date() %>"/>
<c:set var="sysYear"><fmt:formatDate value="${now}" pattern="yyyy" /></c:set>
<%
// DB에서 메뉴명 조회 (공통 유틸 사용)
String menuObjId = request.getParameter("menuObjId");
String menuName = CommonUtils.getMenuName(menuObjId, "견적요청서관리");
%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<style>
body, html {
overflow-x: hidden;
width: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<link rel="stylesheet" href="/css/selectMulti.css">
<script type="text/javascript">
$(document).ready(function(){
$("input").keyup(function(e){
if(e.keyCode == 13){
$("#page").val("1");
fn_search();
}
});
$('.select2').select2();
fnc_datepick();
// 조회 버튼
$("#btnSearch").click(function(){
$("#page").val("1");
fn_search();
});
// 삭제 버튼
$("#btnDelete").click(function(){
fn_delete();
});
// 메일발송 버튼
$("#btnSend").click(function(){
fn_sendMail();
});
fn_search();
setTimeout(() => fnc_calculateContentHeight("gridDiv", 15), 50);
$("#gridDiv").off("fnc_calculateContentHeight");
$(window).resize(function() {
fnc_calculateContentHeight("gridDiv", 15);
});
});
// 그리드 컬럼 정의
var columns = [
{title:'OBJID', field:'OBJID', visible:false},
{title:'SALES_REQUEST_MASTER_OBJID', field:'SALES_REQUEST_MASTER_OBJID', visible:false},
{title:'PROJECT_MGMT_OBJID', field:'PROJECT_MGMT_OBJID', visible:false},
{title:'VENDOR_OBJID', field:'VENDOR_OBJID', visible:false},
{title:'VENDOR_TYPE', field:'VENDOR_TYPE', visible:false},
{headerHozAlign:'center', hozAlign:'center', width:140, title:'견적번호', field:'QUOTATION_REQUEST_NO', frozen:true,
formatter: function(cell, formatterParams, onRendered){
var value = fnc_checkNull(cell.getValue());
return "<a href='#none' style='color:#0000EE'>" + value + "</a>";
},
cellClick: function(e, cell){
var objId = fnc_checkNull(cell.getData().OBJID);
fn_formPopUp(objId);
}
},
{headerHozAlign:'center', hozAlign:'center', widthGrow:1, title:'요청번호', field:'REQUEST_MNG_NO'},
{headerHozAlign:'center', hozAlign:'center', widthGrow:0.8, title:'구매유형', field:'PURCHASE_TYPE_NAME'},
{headerHozAlign:'center', hozAlign:'center', widthGrow:1.2, title:'프로젝트번호', field:'PROJECT_NUMBER'},
{headerHozAlign:'center', hozAlign:'center', widthGrow:0.7, title:'주문유형', field:'ORDER_TYPE_NAME'},
{headerHozAlign:'center', hozAlign:'center', widthGrow:0.7, title:'제품구분', field:'PRODUCT_NAME_TITLE'},
{headerHozAlign:'center', hozAlign:'left', widthGrow:1.2, title:'품번', field:'PART_NO'},
{headerHozAlign:'center', hozAlign:'left', widthGrow:1.5, title:'품명', field:'PART_NAME'},
{headerHozAlign:'center', hozAlign:'left', widthGrow:1.2, title:'업체명', field:'VENDOR_NAME',
formatter: function(cell, formatterParams, onRendered){
var value = fnc_checkNull(cell.getValue());
var vendorType = fnc_checkNull(cell.getData().VENDOR_TYPE);
var badge = '';
if(vendorType === 'SUPPLY') {
badge = ' <span style="font-size:10px; color:#2196F3;">[공급]</span>';
} else if(vendorType === 'PROCESSING') {
badge = ' <span style="font-size:10px; color:#4CAF50;">[가공]</span>';
}
return value + badge;
}
},
{headerHozAlign:'center', hozAlign:'center', widthGrow:0.6, title:'견적요청서', field:'QUOTATION_REQUEST_NO',
formatter: function(cell, formatterParams, onRendered){
return '<a href="#" class="File file_icon" style="width:20px; height:20px; display:inline-block;"></a>';
},
cellClick: function(e, cell){
var objId = fnc_checkNull(cell.getData().OBJID);
fn_formPopUp(objId);
}
},
{headerHozAlign:'center', hozAlign:'center', widthGrow:0.9, title:'메일발송', field:'MAIL_SEND_DATE_TITLE',
formatter: function(cell, formatterParams, onRendered){
var sendYn = fnc_checkNull(cell.getData().MAIL_SEND_YN);
var sendDate = fnc_checkNull(cell.getValue());
if(sendYn === 'Y' && sendDate !== ''){
return '<span style="color:green;">' + sendDate + '</span>';
} else {
return '<span style="color:#999;">미발송</span>';
}
}
},
{headerHozAlign:'center', hozAlign:'center', widthGrow:0.6, title:'수신견적서', field:'ATTACH_FILE_CNT',
formatter: function(cell, formatterParams, onRendered){
var cnt = cell.getValue() || 0;
var iconClass = cnt > 0 ? 'file_icon' : 'file_empty_icon';
return '<a href="#" class="File ' + iconClass + '" style="width:20px; height:20px; display:inline-block;"></a>';
},
cellClick: function(e, cell){
var objId = fnc_checkNull(cell.getData().OBJID);
fn_openAttachFile(objId);
}
}
];
// 조회
function fn_search(){
_tabulGrid = fnc_tabul_search(_tabul_layout_fitColumns, _tabulGrid, "/salesMng/quotationRequestListPaging.do", columns, true);
}
// 상세 팝업
function fn_formPopUp(objId){
var popup_width = 1200;
var popup_height = 800;
var url = "/salesMng/quotationRequestFormPopup.do?QUOTATION_REQUEST_MASTER_OBJID=" + objId;
window.open(url, "quotationRequestFormPopup", "width="+popup_width+",height="+popup_height+",menubar=no,scrollbars=yes,resizable=yes");
}
// 수신견적서 첨부파일 팝업
function fn_openAttachFile(objId){
var popup_width = 800;
var popup_height = 600;
var url = "/common/attachFilePopup.do?TARGET_OBJID=" + objId + "&DOC_TYPE=QUOTATION_RECEIVED&TITLE=수신견적서";
window.open(url, "attachFilePopup", "width="+popup_width+",height="+popup_height+",menubar=no,scrollbars=yes,resizable=yes");
}
// 삭제
function fn_delete(){
var selectedData = _tabulGrid.getSelectedData();
if(selectedData.length < 1){
Swal.fire("삭제할 행을 선택해주세요.");
return false;
}
// 발송완료 상태 확인
for(var i = 0; i < selectedData.length; i++){
var status = fnc_checkNull(selectedData[i].STATUS);
if(status === 'sent' || status === 'received' || status === 'completed'){
Swal.fire("발송완료/견적수신/완료 상태는 삭제할 수 없습니다.");
return false;
}
}
Swal.fire({
title: '확인',
text: '선택한 ' + selectedData.length + '건을 삭제하시겠습니까?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: '삭제',
cancelButtonText: '취소'
}).then((result) => {
if(result.isConfirmed){
var deletePromises = [];
for(var i = 0; i < selectedData.length; i++){
var objId = fnc_checkNull(selectedData[i].OBJID);
deletePromises.push(
$.ajax({
type: "POST",
url: "/salesMng/deleteQuotationRequest.do",
data: { QUOTATION_REQUEST_MASTER_OBJID: objId },
dataType: "json"
})
);
}
Promise.all(deletePromises).then(function(results){
Swal.fire({
title: '완료',
text: '삭제되었습니다.',
icon: 'success'
}).then(() => {
fn_search();
});
}).catch(function(error){
Swal.fire({
title: '오류',
text: '삭제 중 오류가 발생했습니다.',
icon: 'error'
});
});
}
});
}
// 메일 발송
function fn_sendMail(){
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 mailSendYn = fnc_checkNull(selectedData[0].MAIL_SEND_YN);
// 이미 발송된 경우 재발송 확인
if(mailSendYn === 'Y'){
Swal.fire({
title: '이미 발송된 견적요청서입니다.',
text: '다시 발송하시겠습니까?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: '재발송',
cancelButtonText: '취소'
}).then((result) => {
if(result.isConfirmed){
fn_openMailFormPopup(objId);
}
});
return false;
}
fn_openMailFormPopup(objId);
}
// 메일 발송 팝업
function fn_openMailFormPopup(quotationRequestObjId){
var popup_width = 900;
var popup_height = 750;
var url = "/salesMng/quotationRequestMailFormPopup.do?QUOTATION_REQUEST_MASTER_OBJID=" + quotationRequestObjId;
window.open(url, "quotationRequestMailForm", "width="+popup_width+",height="+popup_height+",menubar=no,scrollbars=yes,resizable=yes");
}
</script>
<body>
<form name="form1" id="form1" action="" method="post">
<input type="hidden" name="actionType" id="actionType">
<input type="hidden" name="SALES_REQUEST_MASTER_OBJID" id="SALES_REQUEST_MASTER_OBJID" value="${param.SALES_REQUEST_MASTER_OBJID}">
<div class="content-box">
<div class="content-box-s">
<div class="plm_menu_name_gdnsi">
<h2>
<span><%=menuName%></span>
</h2>
<div class="btnArea">
<input type="button" class="plm_btns" value="메일발송" id="btnSend">
<input type="button" class="plm_btns" value="삭제" id="btnDelete" style="background-color:#dc3545; border-color:#dc3545;">
<input type="button" class="plm_btns" value="조회" id="btnSearch">
</div>
</div>
<div id="plmSearchZon">
<table class="">
<tr>
<td><label for="Year">년도</label></td>
<td>
<select name="Year" id="Year" class="select2" autocomplete="off">
<option value="">선택</option>
<c:forEach begin="${sysYear-4}" end="${sysYear}" var="req_year">
<option value="${req_year}"${param.Year eq req_year ? 'selected':'' }>${req_year}</option>
</c:forEach>
</select>
</td>
<td><label for="">프로젝트번호</label></td>
<td><select name="PROJECT_NO" id="PROJECT_NO" class="select2" autocomplete="off" style="width:190px;"><option value="">선택</option>${code_map.project_no}</select></td>
<td><label for="">견적요청서No.</label></td>
<td><input type="text" name="QUOTATION_REQUEST_NO" id="QUOTATION_REQUEST_NO" autocomplete="off" value="${param.QUOTATION_REQUEST_NO}" style=""/></td>
<td><label for="">업체</label></td>
<td><select name="VENDOR_OBJID" id="VENDOR_OBJID" class="select2" autocomplete="off" style=""><option value="">선택</option>${code_map.vendor_objid}</select></td>
<td><label for="">상태</label></td>
<td><select name="STATUS" id="STATUS" class="select2" autocomplete="off" style="">${code_map.status}</select></td>
</tr>
</table>
</div>
<%@include file= "/WEB-INF/view/common/common_gridArea.jsp" %>
</div>
</div>
</form>
</body>
</html>

View File

@@ -116,6 +116,11 @@ $(document).ready(function(){
fn_createProposal();
});
// 견적요청서 생성
$("#btnQuotationRequest").click(function(){
fn_openQuotationRequestPopup();
});
$("#btnOrderBOMReg").click(function(){
fn_salesRequestTargetBOMListPopUp();
});
@@ -196,21 +201,25 @@ var columns = [
,{headerHozAlign : 'center', hozAlign : 'center', title : "유/무상", field :"PAID_TYPE_NAME" , widthGrow:0.9 }
,{headerHozAlign : 'center', hozAlign : 'left', title : "품번", field :"PART_NO" , widthGrow:1.4}
,{headerHozAlign : 'center', hozAlign : 'left' , title : "품명", field :"PART_NAME" , widthGrow:1.8 }
,{headerHozAlign : 'center', hozAlign : 'center', title : "견적요청서", field :"HAS_PURCHASE_REQUEST" , widthGrow:1.1,
,{headerHozAlign : 'center', hozAlign : 'center', title : "견적요청서", field :"HAS_QUOTATION_REQUEST" , widthGrow:1.1,
formatter: function(cell, formatterParams, onRendered){
// 구매요청서 작성 여부: HAS_PURCHASE_REQUEST가 'Y'이면 구매요청서 작성됨
// 견적요청서 존재 여부: HAS_QUOTATION_REQUEST가 'Y'이면 견적요청서 있음
var data = cell.getData();
var hasPurchaseRequest = fnc_checkNull(data.HAS_PURCHASE_REQUEST);
var iconClass = (hasPurchaseRequest == 'Y') ? 'file_icon' : 'file_empty_icon';
return '<a href="#" class="File ' + iconClass + '" style="width:20px; height:20px; display:inline-block;"></a>';
var hasQuotationRequest = fnc_checkNull(data.HAS_QUOTATION_REQUEST);
var quotationCount = parseInt(data.QUOTATION_REQUEST_COUNT) || 0;
var iconClass = (hasQuotationRequest == 'Y') ? 'file_icon' : 'file_empty_icon';
var countText = quotationCount > 0 ? ' (' + quotationCount + ')' : '';
return '<a href="#" class="File ' + iconClass + '" style="width:20px; height:20px; display:inline-block;"></a>' + countText;
},
cellClick : function(e, cell) {
var data = cell.getData();
var hasPurchaseRequest = fnc_checkNull(data.HAS_PURCHASE_REQUEST);
var hasQuotationRequest = fnc_checkNull(data.HAS_QUOTATION_REQUEST);
// 구매요청서가 작성된 경우(파란색 아이콘)만 팝업 열기
if(hasPurchaseRequest == 'Y') {
fn_openSalesRequestFormPopUp(data.OBJID);
// 견적요청서가 있는 경우 견적요청서관리 페이지로 이동 (해당 요청번호로 필터링)
if(hasQuotationRequest == 'Y') {
// 견적요청서관리 페이지 팝업으로 열기
var salesRequestObjid = fnc_checkNull(data.OBJID);
window.open("/salesMng/quotationRequestList.do?SALES_REQUEST_MASTER_OBJID=" + salesRequestObjid, "quotationRequestList", "width=1400,height=800,menubar=no,scrollbars=yes,resizable=yes");
}
}
}
@@ -698,6 +707,250 @@ function fn_executeCreateProposal(salesRequestObjid, targetParts) {
}
});
}
/**
* 견적요청서 자동 생성
* - 선택된 구매요청서의 구매리스트에서 공급업체/가공업체가 입력된 항목으로 견적요청서 자동 생성
*/
function fn_openQuotationRequestPopup() {
var selectedData = _tabulGrid.getSelectedData();
if(selectedData.length < 1) {
Swal.fire({
title: '알림',
text: '견적요청서를 생성할 행을 선택해주세요.',
icon: 'warning'
});
return false;
}
if(selectedData.length > 1) {
Swal.fire({
title: '알림',
text: '한번에 한 건만 선택해주세요.',
icon: 'warning'
});
return false;
}
var salesRequestObjid = fnc_checkNull(selectedData[0].OBJID);
var hasPurchaseRequest = fnc_checkNull(selectedData[0].HAS_PURCHASE_REQUEST);
// 구매리스트가 작성되지 않은 경우
if(hasPurchaseRequest !== 'Y') {
Swal.fire({
title: '알림',
text: '먼저 구매리스트를 작성해주세요.\n(견적요청서 아이콘 클릭하여 구매리스트 작성)',
icon: 'warning'
});
return false;
}
// 구매리스트에서 견적요청서 생성 대상 조회
$.ajax({
url: "/salesMng/getPurchaseListForQuotation.do",
type: "POST",
data: {
SALES_REQUEST_MASTER_OBJID: salesRequestObjid
},
dataType: "json",
success: function(response) {
if(response.resultFlag === "S" && response.list && response.list.length > 0) {
fn_processQuotationRequestCreation(salesRequestObjid, response.list);
} else {
Swal.fire({
title: '알림',
text: '견적요청서를 생성할 대상이 없습니다.\n(공급업체 또는 가공업체가 입력된 항목이 필요합니다)',
icon: 'warning'
});
}
},
error: function(xhr, status, error) {
console.error("구매리스트 조회 오류:", error);
Swal.fire({
title: '오류',
text: '구매리스트 조회 중 오류가 발생했습니다.',
icon: 'error'
});
}
});
}
/**
* 견적요청서 생성 처리
*/
function fn_processQuotationRequestCreation(salesRequestObjid, purchaseList) {
// 공급업체/가공업체별로 그룹화
var supplyVendorGroups = {}; // 공급업체별 그룹
var processingVendorGroups = {}; // 가공업체별 그룹
purchaseList.forEach(function(item) {
// 대소문자 모두 처리 (서버에서 소문자로 반환될 수 있음)
var vendorPm = fnc_checkNull(item.VENDOR_PM || item.vendor_pm);
var processingVendor = fnc_checkNull(item.PROCESSING_VENDOR || item.processing_vendor);
var objid = fnc_checkNull(item.OBJID || item.objid);
var vendorName = fnc_checkNull(item.VENDOR_NAME || item.vendor_name);
var processingVendorName = fnc_checkNull(item.PROCESSING_VENDOR_NAME || item.processing_vendor_name);
// 견적요청서 생성 가능 여부 플래그
var canCreateSupply = fnc_checkNull(item.CAN_CREATE_SUPPLY || item.can_create_supply);
var canCreateProcessing = fnc_checkNull(item.CAN_CREATE_PROCESSING || item.can_create_processing);
// 공급업체 견적요청서 생성 가능한 경우
if(vendorPm !== '' && canCreateSupply === 'Y') {
if(!supplyVendorGroups[vendorPm]) {
supplyVendorGroups[vendorPm] = {
vendorName: vendorName,
parts: []
};
}
supplyVendorGroups[vendorPm].parts.push(objid);
}
// 가공업체 견적요청서 생성 가능한 경우
if(processingVendor !== '' && canCreateProcessing === 'Y') {
if(!processingVendorGroups[processingVendor]) {
processingVendorGroups[processingVendor] = {
vendorName: processingVendorName,
parts: []
};
}
processingVendorGroups[processingVendor].parts.push(objid);
}
});
var supplyCount = Object.keys(supplyVendorGroups).length;
var processingCount = Object.keys(processingVendorGroups).length;
var totalCount = supplyCount + processingCount;
if(totalCount === 0) {
Swal.fire({
title: '알림',
text: '견적요청서를 생성할 대상이 없습니다.\n(공급업체 또는 가공업체가 입력된 항목이 필요합니다)',
icon: 'warning'
});
return;
}
// 생성 확인 메시지
var confirmHtml = '<div style="text-align:left; margin-top:10px;">';
if(supplyCount > 0) {
confirmHtml += '<p><strong>공급업체:</strong> ' + supplyCount + '개 업체</p>';
for(var vendorId in supplyVendorGroups) {
confirmHtml += '<span style="margin-left:20px; color:#1976d2;">- ' + supplyVendorGroups[vendorId].vendorName + ' (' + supplyVendorGroups[vendorId].parts.length + '건)</span><br/>';
}
}
if(processingCount > 0) {
confirmHtml += '<p><strong>가공업체:</strong> ' + processingCount + '개 업체</p>';
for(var vendorId in processingVendorGroups) {
confirmHtml += '<span style="margin-left:20px; color:#388e3c;">- ' + processingVendorGroups[vendorId].vendorName + ' (' + processingVendorGroups[vendorId].parts.length + '건)</span><br/>';
}
}
confirmHtml += '</div>';
Swal.fire({
title: '견적요청서 생성',
html: '<p>총 <strong>' + totalCount + '개</strong>의 견적요청서가 생성됩니다.</p>' + confirmHtml,
icon: 'question',
showCancelButton: true,
confirmButtonText: '생성',
cancelButtonText: '취소',
width: '500px'
}).then((result) => {
if(result.isConfirmed) {
fn_executeCreateQuotationRequests(salesRequestObjid, supplyVendorGroups, processingVendorGroups);
}
});
}
/**
* 견적요청서 생성 실행
*/
function fn_executeCreateQuotationRequests(salesRequestObjid, supplyVendorGroups, processingVendorGroups) {
var createPromises = [];
// 공급업체별 견적요청서 생성
for(var vendorObjid in supplyVendorGroups) {
var partObjids = supplyVendorGroups[vendorObjid].parts;
createPromises.push(
$.ajax({
type: "POST",
url: "/salesMng/createQuotationRequest.do",
data: {
SALES_REQUEST_MASTER_OBJID: salesRequestObjid,
VENDOR_OBJID: vendorObjid,
VENDOR_TYPE: 'SUPPLY',
PART_OBJIDS: JSON.stringify(partObjids)
},
dataType: "json"
})
);
}
// 가공업체별 견적요청서 생성
for(var vendorObjid in processingVendorGroups) {
var partObjids = processingVendorGroups[vendorObjid].parts;
createPromises.push(
$.ajax({
type: "POST",
url: "/salesMng/createQuotationRequest.do",
data: {
SALES_REQUEST_MASTER_OBJID: salesRequestObjid,
VENDOR_OBJID: vendorObjid,
VENDOR_TYPE: 'PROCESSING',
PART_OBJIDS: JSON.stringify(partObjids)
},
dataType: "json"
})
);
}
Promise.all(createPromises).then(function(results) {
var successCount = 0;
var failCount = 0;
var createdNos = [];
results.forEach(function(result) {
if(result.resultFlag === 'S') {
successCount++;
if(result.QUOTATION_REQUEST_NO) {
createdNos.push(result.QUOTATION_REQUEST_NO);
}
} else {
failCount++;
}
});
if(successCount > 0) {
var msgHtml = '<p>' + successCount + '개의 견적요청서가 생성되었습니다.</p>';
if(createdNos.length > 0) {
msgHtml += '<div style="margin-top:10px; text-align:left;"><strong>생성된 번호:</strong><br/>';
msgHtml += createdNos.map(function(no) { return '- ' + no; }).join('<br/>');
msgHtml += '</div>';
}
Swal.fire({
title: '완료',
html: msgHtml,
icon: 'success'
}).then(() => {
fn_search();
});
} else {
Swal.fire({
title: '실패',
text: '견적요청서 생성에 실패했습니다.',
icon: 'error'
});
}
}).catch(function(error) {
console.error("견적요청서 생성 오류:", error);
Swal.fire({
title: '오류',
text: '견적요청서 생성 중 오류가 발생했습니다.',
icon: 'error'
});
});
}
</script>
</head>
<body class="backcolor">
@@ -716,6 +969,7 @@ function fn_executeCreateProposal(salesRequestObjid, targetParts) {
<div class="btnArea">
<input type="button" value="조회" class="plm_btns" id="btnSearch">
<!-- <input type="button" value="구매요청서작성" class="plm_btns" id="btnOrderReg"> -->
<input type="button" value="견적요청서생성" class="plm_btns" id="btnQuotationRequest" style="background-color: #4CAF50; border-color: #4CAF50;">
<input type="button" value="품의서생성" class="plm_btns" id="btnReg">
</div>
</div>

View File

@@ -989,11 +989,24 @@ VALUES
-- 문서유형 (PURCHASE_REQUEST: 구매요청서, PROPOSAL: 품의서)
SRM.DOC_TYPE,
-- 구매요청서 작성 여부 (SALES_REQUEST_PART에 DOC_TYPE이 PURCHASE_REQUEST인 데이터가 있으면 'Y')
(SELECT CASE WHEN COUNT(*) > 0 THEN 'Y' ELSE 'N' END
FROM SALES_REQUEST_PART
WHERE SALES_REQUEST_MASTER_OBJID = SRM.OBJID
AND (DOC_TYPE = 'PURCHASE_REQUEST' OR DOC_TYPE IS NULL)) AS HAS_PURCHASE_REQUEST,
-- 구매요청서 작성 여부
-- M-BOM 기반이면 MBOM_HEADER_OBJID가 있으므로 'Y'
-- 수동 작성이면 SALES_REQUEST_PART에 데이터가 있으면 'Y'
CASE
WHEN SRM.MBOM_HEADER_OBJID IS NOT NULL AND SRM.MBOM_HEADER_OBJID::VARCHAR != '' THEN 'Y'
WHEN (SELECT COUNT(*) FROM SALES_REQUEST_PART WHERE SALES_REQUEST_MASTER_OBJID = SRM.OBJID AND (DOC_TYPE = 'PURCHASE_REQUEST' OR DOC_TYPE IS NULL)) > 0 THEN 'Y'
ELSE 'N'
END AS HAS_PURCHASE_REQUEST,
-- 견적요청서 존재 여부 (QUOTATION_REQUEST_MASTER에 데이터가 있으면 'Y')
COALESCE((SELECT CASE WHEN COUNT(*) > 0 THEN 'Y' ELSE 'N' END
FROM QUOTATION_REQUEST_MASTER
WHERE SALES_REQUEST_MASTER_OBJID::VARCHAR = SRM.OBJID::VARCHAR), 'N') AS HAS_QUOTATION_REQUEST,
-- 견적요청서 개수
COALESCE((SELECT COUNT(*)
FROM QUOTATION_REQUEST_MASTER
WHERE SALES_REQUEST_MASTER_OBJID::VARCHAR = SRM.OBJID::VARCHAR), 0) AS QUOTATION_REQUEST_COUNT,
-- M-BOM 관련 컬럼
SRM.MBOM_HEADER_OBJID,
@@ -3075,7 +3088,7 @@ UPDATE SET
TOTAL_PRICE = #{TOTAL_PRICE}
</update>
<!-- 구매리스트 상세 조회 (기존 저장된 데이터) -->
<!-- 구매리스트 상세 조회 (기존 저장된 데이터) - 이 쿼리는 사용하지 않음, M-BOM 기반으로만 동작 -->
<select id="getPurchaseListDetail" parameterType="map" resultType="com.pms.common.UpperKeyMap">
<!-- SALES_REQUEST_PART에서 저장된 데이터 조회, PART_MNG에서 품목정보 가져옴 -->
SELECT
@@ -3114,11 +3127,11 @@ UPDATE SET
COALESCE(SRP.VENDOR_PM, SRP.PARTNER_OBJID) AS VENDOR_PM,
COALESCE(SRP.UNIT_PRICE, NULLIF(SRP.PARTNER_PRICE, '')::numeric, 0) AS UNIT_PRICE,
COALESCE(SRP.TOTAL_PRICE, 0) AS TOTAL_PRICE,
COALESCE(SRP.PROCESSING_UNIT_PRICE, 0) AS PROCESSING_UNIT_PRICE,
COALESCE(SRP.PROCESSING_TOTAL_PRICE, 0) AS PROCESSING_TOTAL_PRICE,
COALESCE(SRP.GRAND_TOTAL_PRICE, 0) AS GRAND_TOTAL_PRICE,
0 AS PROCESSING_UNIT_PRICE,
0 AS PROCESSING_TOTAL_PRICE,
0 AS GRAND_TOTAL_PRICE,
SRP.PROPOSAL_DATE,
SRP.PROCESSING_PROPOSAL_DATE,
NULL AS PROCESSING_PROPOSAL_DATE,
'SRP' AS DATA_SOURCE -- 데이터 소스 구분용
FROM
SALES_REQUEST_PART SRP
@@ -4264,4 +4277,412 @@ ORDER BY V.PATH2
WHERE OBJID = #{PART_OBJID}
</update>
<!-- 구매리스트 품목 단일 조회 (MBOM_DETAIL에서 조회) -->
<select id="getSalesRequestPartInfo" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
MD.OBJID,
MD.MBOM_HEADER_OBJID,
MD.PART_OBJID,
PM.PART_NO,
PM.PART_NAME,
MD.RAW_MATERIAL,
MD.RAW_MATERIAL_PART_NO AS RAW_MATERIAL_NO,
MD.RAW_MATERIAL_SIZE AS SIZE,
MD.PO_QTY,
MD.PRODUCTION_QTY,
MD.UNIT_PRICE,
MD.PROCESSING_UNIT_PRICE,
MD.VENDOR AS VENDOR_PM,
(SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = MD.VENDOR) AS VENDOR_NAME,
MD.PROCESSING_VENDOR,
(SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = MD.PROCESSING_VENDOR) AS PROCESSING_VENDOR_NAME
FROM MBOM_DETAIL MD
LEFT JOIN PART_MNG PM ON MD.PART_OBJID::VARCHAR = PM.OBJID::VARCHAR
WHERE MD.OBJID = #{OBJID}
</select>
<!-- =====================================================
견적요청서 관리 쿼리
===================================================== -->
<!-- 견적요청서 목록 조회 (페이징) -->
<select id="getQuotationRequestListCount" parameterType="map" resultType="int">
SELECT COUNT(*)
FROM QUOTATION_REQUEST_MASTER QRM
LEFT JOIN SALES_REQUEST_MASTER SRM ON QRM.SALES_REQUEST_MASTER_OBJID::VARCHAR = SRM.OBJID::VARCHAR
LEFT JOIN PROJECT_MGMT PM ON PM.OBJID = SRM.PROJECT_NO
LEFT JOIN CLIENT_MNG CM ON QRM.VENDOR_OBJID::VARCHAR = CM.OBJID::VARCHAR
WHERE 1=1
<if test="SALES_REQUEST_MASTER_OBJID != null and SALES_REQUEST_MASTER_OBJID != ''">
AND QRM.SALES_REQUEST_MASTER_OBJID::VARCHAR = #{SALES_REQUEST_MASTER_OBJID}
</if>
<if test="Year != null and Year != ''">
AND TO_CHAR(QRM.REG_DATE, 'YYYY') = #{Year}
</if>
<if test="QUOTATION_REQUEST_NO != null and QUOTATION_REQUEST_NO != ''">
AND QRM.QUOTATION_REQUEST_NO LIKE '%' || #{QUOTATION_REQUEST_NO} || '%'
</if>
<if test="PROJECT_NO != null and PROJECT_NO != ''">
AND PM.PROJECT_NO LIKE '%' || #{PROJECT_NO} || '%'
</if>
<if test="VENDOR_OBJID != null and VENDOR_OBJID != ''">
AND QRM.VENDOR_OBJID::VARCHAR = #{VENDOR_OBJID}
</if>
<if test="STATUS != null and STATUS != ''">
AND QRM.STATUS = #{STATUS}
</if>
</select>
<select id="getQuotationRequestList" parameterType="map" resultType="map">
SELECT
QRM.OBJID,
QRM.QUOTATION_REQUEST_NO,
QRM.SALES_REQUEST_MASTER_OBJID,
QRM.PROJECT_MGMT_OBJID,
QRM.VENDOR_OBJID,
QRM.VENDOR_TYPE,
QRM.STATUS,
CASE
WHEN QRM.STATUS = 'create' THEN '작성중'
WHEN QRM.STATUS = 'sent' THEN '발송완료'
WHEN QRM.STATUS = 'received' THEN '견적수신'
WHEN QRM.STATUS = 'completed' THEN '완료'
ELSE QRM.STATUS
END AS STATUS_NAME,
QRM.MAIL_SEND_DATE,
TO_CHAR(QRM.MAIL_SEND_DATE, 'YYYY-MM-DD') AS MAIL_SEND_DATE_TITLE,
QRM.MAIL_SEND_YN,
QRM.DUE_DATE,
TO_CHAR(QRM.DUE_DATE, 'YYYY-MM-DD') AS DUE_DATE_TITLE,
QRM.REMARK,
QRM.WRITER,
QRM.REG_DATE,
TO_CHAR(QRM.REG_DATE, 'YYYY-MM-DD') AS REG_DATE_TITLE,
-- 구매요청서 정보
SRM.REQUEST_MNG_NO,
SRM.PURCHASE_TYPE,
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = SRM.PURCHASE_TYPE) AS PURCHASE_TYPE_NAME,
-- 주문유형
COALESCE(PM.CATEGORY_CD, SRM.ORDER_TYPE) AS ORDER_TYPE,
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = COALESCE(PM.CATEGORY_CD, SRM.ORDER_TYPE)) AS ORDER_TYPE_NAME,
-- 제품구분
COALESCE(
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CTM.PRODUCT),
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = SRM.PRODUCT_NAME)
) AS PRODUCT_NAME_TITLE,
-- 프로젝트번호
PM.PROJECT_NO AS PROJECT_NUMBER,
-- 업체 정보
CM.CLIENT_NM AS VENDOR_NAME,
-- 품번/품명 (프로젝트 정보)
PM.PART_NO,
PM.PART_NAME,
-- 수신견적서 첨부파일 개수
(SELECT COUNT(*) FROM ATTACH_FILE_INFO WHERE TARGET_OBJID = QRM.OBJID::VARCHAR AND DOC_TYPE = 'QUOTATION_RECEIVED' AND STATUS = 'Active') AS ATTACH_FILE_CNT
FROM QUOTATION_REQUEST_MASTER QRM
LEFT JOIN SALES_REQUEST_MASTER SRM ON QRM.SALES_REQUEST_MASTER_OBJID::VARCHAR = SRM.OBJID::VARCHAR
LEFT JOIN PROJECT_MGMT PM ON PM.OBJID = SRM.PROJECT_NO
LEFT JOIN CONTRACT_MGMT CTM ON CTM.OBJID = PM.CONTRACT_OBJID
LEFT JOIN CLIENT_MNG CM ON QRM.VENDOR_OBJID::VARCHAR = CM.OBJID::VARCHAR
WHERE 1=1
<if test="SALES_REQUEST_MASTER_OBJID != null and SALES_REQUEST_MASTER_OBJID != ''">
AND QRM.SALES_REQUEST_MASTER_OBJID::VARCHAR = #{SALES_REQUEST_MASTER_OBJID}
</if>
<if test="Year != null and Year != ''">
AND TO_CHAR(QRM.REG_DATE, 'YYYY') = #{Year}
</if>
<if test="QUOTATION_REQUEST_NO != null and QUOTATION_REQUEST_NO != ''">
AND QRM.QUOTATION_REQUEST_NO LIKE '%' || #{QUOTATION_REQUEST_NO} || '%'
</if>
<if test="PROJECT_NO != null and PROJECT_NO != ''">
AND PM.PROJECT_NO LIKE '%' || #{PROJECT_NO} || '%'
</if>
<if test="VENDOR_OBJID != null and VENDOR_OBJID != ''">
AND QRM.VENDOR_OBJID::VARCHAR = #{VENDOR_OBJID}
</if>
<if test="STATUS != null and STATUS != ''">
AND QRM.STATUS = #{STATUS}
</if>
ORDER BY QRM.REG_DATE DESC
<if test="startRow != null and countPerPage != null">
OFFSET #{startRow} LIMIT #{countPerPage}
</if>
</select>
<!-- 견적요청서 마스터 정보 조회 -->
<select id="getQuotationRequestMasterInfo" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
QRM.OBJID,
QRM.QUOTATION_REQUEST_NO,
QRM.SALES_REQUEST_MASTER_OBJID,
QRM.PROJECT_MGMT_OBJID,
QRM.VENDOR_OBJID,
QRM.VENDOR_TYPE,
QRM.STATUS,
QRM.MAIL_SEND_DATE,
TO_CHAR(QRM.MAIL_SEND_DATE, 'YYYY-MM-DD') AS MAIL_SEND_DATE_TITLE,
QRM.MAIL_SEND_YN,
QRM.DUE_DATE,
TO_CHAR(QRM.DUE_DATE, 'YYYY-MM-DD') AS DUE_DATE_TITLE,
QRM.REMARK,
QRM.WRITER,
QRM.REG_DATE,
TO_CHAR(QRM.REG_DATE, 'YYYY-MM-DD') AS REG_DATE_TITLE,
-- 구매요청서 정보
SRM.REQUEST_MNG_NO,
SRM.PURCHASE_TYPE,
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = SRM.PURCHASE_TYPE) AS PURCHASE_TYPE_NAME,
-- 주문유형
COALESCE(PM.CATEGORY_CD, SRM.ORDER_TYPE) AS ORDER_TYPE,
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = COALESCE(PM.CATEGORY_CD, SRM.ORDER_TYPE)) AS ORDER_TYPE_NAME,
-- 제품구분
COALESCE(
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CTM.PRODUCT),
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = SRM.PRODUCT_NAME)
) AS PRODUCT_NAME_TITLE,
-- 프로젝트번호
PM.PROJECT_NO,
-- 업체 정보
CM.CLIENT_NM AS VENDOR_NAME,
CM.EMAIL AS VENDOR_EMAIL
FROM QUOTATION_REQUEST_MASTER QRM
LEFT JOIN SALES_REQUEST_MASTER SRM ON QRM.SALES_REQUEST_MASTER_OBJID::VARCHAR = SRM.OBJID::VARCHAR
LEFT JOIN PROJECT_MGMT PM ON PM.OBJID = SRM.PROJECT_NO
LEFT JOIN CONTRACT_MGMT CTM ON CTM.OBJID = PM.CONTRACT_OBJID
LEFT JOIN CLIENT_MNG CM ON QRM.VENDOR_OBJID::VARCHAR = CM.OBJID::VARCHAR
WHERE QRM.OBJID = #{QUOTATION_REQUEST_MASTER_OBJID}::NUMERIC
</select>
<!-- 견적요청서 상세 목록 조회 -->
<select id="getQuotationRequestDetailList" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
QRD.OBJID,
QRD.QUOTATION_REQUEST_MASTER_OBJID,
QRD.SALES_REQUEST_PART_OBJID,
QRD.PART_OBJID,
QRD.PART_NO,
QRD.PART_NAME,
QRD.RAW_MATERIAL,
QRD.SIZE,
QRD.QTY,
QRD.UNIT_PRICE,
QRD.REMARK,
QRD.REG_DATE,
-- 마스터 정보
QRM.VENDOR_TYPE,
QRM.VENDOR_OBJID,
(SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = QRM.VENDOR_OBJID::VARCHAR) AS VENDOR_NAME
FROM QUOTATION_REQUEST_DETAIL QRD
LEFT JOIN QUOTATION_REQUEST_MASTER QRM ON QRD.QUOTATION_REQUEST_MASTER_OBJID = QRM.OBJID
WHERE QRD.QUOTATION_REQUEST_MASTER_OBJID = #{QUOTATION_REQUEST_MASTER_OBJID}::NUMERIC
ORDER BY QRD.OBJID
</select>
<!-- 견적요청서 번호 생성 -->
<select id="getNextQuotationRequestNo" parameterType="map" resultType="string">
SELECT 'Q' || TO_CHAR(NOW(), 'YYYYMMDD') || '-' || LPAD(NEXTVAL('SEQ_QUOTATION_REQUEST_NO')::VARCHAR, 3, '0')
</select>
<!-- 견적요청서 마스터 생성 -->
<insert id="insertQuotationRequestMaster" parameterType="map">
INSERT INTO QUOTATION_REQUEST_MASTER (
OBJID,
QUOTATION_REQUEST_NO,
SALES_REQUEST_MASTER_OBJID,
PROJECT_MGMT_OBJID,
VENDOR_OBJID,
VENDOR_TYPE,
STATUS,
DUE_DATE,
REMARK,
WRITER,
REG_DATE
) VALUES (
#{OBJID}::NUMERIC,
#{QUOTATION_REQUEST_NO},
#{SALES_REQUEST_MASTER_OBJID}::NUMERIC,
#{PROJECT_MGMT_OBJID}::NUMERIC,
#{VENDOR_OBJID}::NUMERIC,
#{VENDOR_TYPE},
'create',
<if test="DUE_DATE != null and DUE_DATE != ''">#{DUE_DATE}::DATE</if>
<if test="DUE_DATE == null or DUE_DATE == ''">NULL</if>,
#{REMARK},
#{WRITER},
NOW()
)
</insert>
<!-- 견적요청서 상세 생성 -->
<insert id="insertQuotationRequestDetail" parameterType="map">
INSERT INTO QUOTATION_REQUEST_DETAIL (
OBJID,
QUOTATION_REQUEST_MASTER_OBJID,
SALES_REQUEST_PART_OBJID,
PART_OBJID,
PART_NO,
PART_NAME,
RAW_MATERIAL,
SIZE,
QTY,
UNIT_PRICE,
REMARK,
REG_DATE
) VALUES (
#{OBJID}::NUMERIC,
#{QUOTATION_REQUEST_MASTER_OBJID}::NUMERIC,
#{SALES_REQUEST_PART_OBJID}::NUMERIC,
#{PART_OBJID}::NUMERIC,
#{PART_NO},
#{PART_NAME},
#{RAW_MATERIAL},
#{SIZE},
COALESCE(#{QTY}::NUMERIC, 0),
COALESCE(#{UNIT_PRICE}::NUMERIC, 0),
#{REMARK},
NOW()
)
</insert>
<!-- 견적요청서 상세 단가 업데이트 -->
<update id="updateQuotationRequestDetailPrice" parameterType="map">
UPDATE QUOTATION_REQUEST_DETAIL SET
UNIT_PRICE = #{UNIT_PRICE}::NUMERIC,
TOTAL_PRICE = #{QTY}::NUMERIC * #{UNIT_PRICE}::NUMERIC,
EDIT_DATE = NOW()
WHERE OBJID = #{OBJID}::NUMERIC
</update>
<!-- 견적요청서 마스터 상태 업데이트 -->
<update id="updateQuotationRequestMasterStatus" parameterType="map">
UPDATE QUOTATION_REQUEST_MASTER SET
STATUS = #{STATUS},
EDIT_DATE = NOW()
<if test="MAIL_SEND_YN != null and MAIL_SEND_YN != ''">
, MAIL_SEND_YN = #{MAIL_SEND_YN}
, MAIL_SEND_DATE = NOW()
</if>
WHERE OBJID = #{QUOTATION_REQUEST_MASTER_OBJID}::NUMERIC
</update>
<!-- MBOM_DETAIL 단가 업데이트 (견적 수신 후) -->
<update id="updatePurchaseListPriceFromQuotation" parameterType="map">
UPDATE MBOM_DETAIL SET
<if test="VENDOR_TYPE == 'SUPPLY'">
UNIT_PRICE = #{UNIT_PRICE}::NUMERIC
</if>
<if test="VENDOR_TYPE == 'PROCESSING'">
PROCESSING_UNIT_PRICE = #{UNIT_PRICE}::NUMERIC
</if>
, EDIT_DATE = NOW()
WHERE OBJID = #{SALES_REQUEST_PART_OBJID}
</update>
<!-- 구매리스트에서 견적요청서 생성 대상 조회 - M-BOM 기반 (MBOM_DETAIL에서 조회) -->
<!-- 공급업체/가공업체별로 견적요청서 생성 가능 여부 플래그 포함 -->
<select id="getPurchaseListForQuotationFromMBom" parameterType="map" resultType="map">
SELECT
MD.OBJID,
#{SALES_REQUEST_MASTER_OBJID} AS SALES_REQUEST_MASTER_OBJID,
MD.PART_OBJID,
PM.PART_NO,
PM.PART_NAME,
MD.RAW_MATERIAL,
MD.RAW_MATERIAL_SIZE AS SIZE,
MD.PO_QTY,
MD.PRODUCTION_QTY,
MD.UNIT_PRICE,
MD.PROCESSING_UNIT_PRICE,
MD.VENDOR AS VENDOR_PM,
(SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = MD.VENDOR) AS VENDOR_NAME,
MD.PROCESSING_VENDOR,
(SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = MD.PROCESSING_VENDOR) AS PROCESSING_VENDOR_NAME,
MD.RAW_MATERIAL_PART_NO AS RAW_MATERIAL_NO,
'MBOM' AS DATA_SOURCE,
-- 공급업체 견적요청서 생성 가능 여부
CASE WHEN MD.VENDOR IS NOT NULL AND MD.VENDOR != '' AND NOT EXISTS (
SELECT 1 FROM QUOTATION_REQUEST_DETAIL QRD
INNER JOIN QUOTATION_REQUEST_MASTER QRM ON QRD.QUOTATION_REQUEST_MASTER_OBJID = QRM.OBJID
WHERE QRD.SALES_REQUEST_PART_OBJID::VARCHAR = MD.OBJID::VARCHAR
AND QRM.VENDOR_TYPE = 'SUPPLY'
AND QRM.VENDOR_OBJID::VARCHAR = MD.VENDOR
) THEN 'Y' ELSE 'N' END AS CAN_CREATE_SUPPLY,
-- 가공업체 견적요청서 생성 가능 여부
CASE WHEN MD.PROCESSING_VENDOR IS NOT NULL AND MD.PROCESSING_VENDOR != '' AND NOT EXISTS (
SELECT 1 FROM QUOTATION_REQUEST_DETAIL QRD
INNER JOIN QUOTATION_REQUEST_MASTER QRM ON QRD.QUOTATION_REQUEST_MASTER_OBJID = QRM.OBJID
WHERE QRD.SALES_REQUEST_PART_OBJID::VARCHAR = MD.OBJID::VARCHAR
AND QRM.VENDOR_TYPE = 'PROCESSING'
AND QRM.VENDOR_OBJID::VARCHAR = MD.PROCESSING_VENDOR
) THEN 'Y' ELSE 'N' END AS CAN_CREATE_PROCESSING
FROM MBOM_DETAIL MD
LEFT JOIN PART_MNG PM ON MD.PART_OBJID::VARCHAR = PM.OBJID::VARCHAR
WHERE MD.MBOM_HEADER_OBJID = #{MBOM_HEADER_OBJID}
AND (
(MD.VENDOR IS NOT NULL AND MD.VENDOR != '')
OR (MD.PROCESSING_VENDOR IS NOT NULL AND MD.PROCESSING_VENDOR != '')
)
-- 공급업체 또는 가공업체 중 하나라도 견적요청서 생성 가능해야 함
AND (
-- 공급업체 견적요청서 생성 가능
(MD.VENDOR IS NOT NULL AND MD.VENDOR != '' AND NOT EXISTS (
SELECT 1 FROM QUOTATION_REQUEST_DETAIL QRD
INNER JOIN QUOTATION_REQUEST_MASTER QRM ON QRD.QUOTATION_REQUEST_MASTER_OBJID = QRM.OBJID
WHERE QRD.SALES_REQUEST_PART_OBJID::VARCHAR = MD.OBJID::VARCHAR
AND QRM.VENDOR_TYPE = 'SUPPLY'
AND QRM.VENDOR_OBJID::VARCHAR = MD.VENDOR
))
OR
-- 가공업체 견적요청서 생성 가능
(MD.PROCESSING_VENDOR IS NOT NULL AND MD.PROCESSING_VENDOR != '' AND NOT EXISTS (
SELECT 1 FROM QUOTATION_REQUEST_DETAIL QRD
INNER JOIN QUOTATION_REQUEST_MASTER QRM ON QRD.QUOTATION_REQUEST_MASTER_OBJID = QRM.OBJID
WHERE QRD.SALES_REQUEST_PART_OBJID::VARCHAR = MD.OBJID::VARCHAR
AND QRM.VENDOR_TYPE = 'PROCESSING'
AND QRM.VENDOR_OBJID::VARCHAR = MD.PROCESSING_VENDOR
))
)
ORDER BY MD.REGDATE
</select>
<!-- 구매리스트에서 견적요청서 생성 대상 조회 - 수동 작성 (SALES_REQUEST_PART에서 조회) -->
<select id="getPurchaseListForQuotationFromManual" parameterType="map" resultType="map">
SELECT
SRP.OBJID,
SRP.SALES_REQUEST_MASTER_OBJID,
SRP.PART_OBJID,
PM.PART_NO,
PM.PART_NAME,
SRP.RAW_MATERIAL,
SRP.SIZE,
SRP.PO_QTY,
SRP.PRODUCTION_QTY,
SRP.UNIT_PRICE,
SRP.PROCESSING_UNIT_PRICE,
SRP.VENDOR_PM,
(SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = SRP.VENDOR_PM) AS VENDOR_NAME,
SRP.PROCESSING_VENDOR,
(SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = SRP.PROCESSING_VENDOR) AS PROCESSING_VENDOR_NAME,
SRP.RAW_MATERIAL_NO,
'MANUAL' AS DATA_SOURCE
FROM SALES_REQUEST_PART SRP
LEFT JOIN PART_MNG PM ON SRP.PART_OBJID::VARCHAR = PM.OBJID::VARCHAR
WHERE SRP.SALES_REQUEST_MASTER_OBJID = #{SALES_REQUEST_MASTER_OBJID}::NUMERIC
AND (
(SRP.VENDOR_PM IS NOT NULL AND SRP.VENDOR_PM != '')
OR (SRP.PROCESSING_VENDOR IS NOT NULL AND SRP.PROCESSING_VENDOR != '')
)
ORDER BY SRP.OBJID
</select>
<!-- 견적요청서 삭제 (마스터) -->
<delete id="deleteQuotationRequestMaster" parameterType="map">
DELETE FROM QUOTATION_REQUEST_MASTER WHERE OBJID = #{QUOTATION_REQUEST_MASTER_OBJID}::NUMERIC
</delete>
<!-- 견적요청서 삭제 (상세) -->
<delete id="deleteQuotationRequestDetail" parameterType="map">
DELETE FROM QUOTATION_REQUEST_DETAIL WHERE QUOTATION_REQUEST_MASTER_OBJID = #{QUOTATION_REQUEST_MASTER_OBJID}::NUMERIC
</delete>
</mapper>

View File

@@ -1538,4 +1538,222 @@ public class SalesMngController {
return resultMap;
}
// =====================================================
// 견적요청서 관리 컨트롤러
// =====================================================
/**
* 견적요청서 목록 페이지
* @param request
* @param paramMap
* @return
*/
@RequestMapping("/salesMng/quotationRequestList.do")
public String quotationRequestList(HttpServletRequest request, @RequestParam Map paramMap){
String returnUrl = "/salesMng/quotationRequestList";
Map code_map = new HashMap();
try {
// 프로젝트번호
code_map.put("project_no", commonService.bizMakeOptionList("", (String)paramMap.get("project_no"), "common.getProjectNameList"));
// 업체 목록
code_map.put("vendor_objid", commonService.bizMakeOptionList("", (String)paramMap.get("vendor_objid"), "common.getsupplyselect"));
// 상태
String statusOptions = "";
statusOptions += "<option value=''>전체</option>";
statusOptions += "<option value='create'" + ("create".equals(paramMap.get("status")) ? " selected" : "") + ">작성중</option>";
statusOptions += "<option value='sent'" + ("sent".equals(paramMap.get("status")) ? " selected" : "") + ">발송완료</option>";
statusOptions += "<option value='received'" + ("received".equals(paramMap.get("status")) ? " selected" : "") + ">견적수신</option>";
statusOptions += "<option value='completed'" + ("completed".equals(paramMap.get("status")) ? " selected" : "") + ">완료</option>";
code_map.put("status", statusOptions);
} catch (Exception e) {
e.printStackTrace();
}
request.setAttribute("code_map", code_map);
return returnUrl;
}
/**
* 견적요청서 목록 조회 (페이징)
* @param request
* @param paramMap
* @return
*/
@ResponseBody
@RequestMapping("/salesMng/quotationRequestListPaging.do")
public Map getQuotationRequestListPaging(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
commonService.selectListPagingNew("salesMng.getQuotationRequestList", request, paramMap);
return paramMap;
}
/**
* 견적요청서 상세 팝업
* @param request
* @param paramMap
* @return
*/
@RequestMapping("/salesMng/quotationRequestFormPopup.do")
public String quotationRequestFormPopup(HttpServletRequest request, @RequestParam Map paramMap){
Map resultMap = new HashMap();
Map code_map = new HashMap();
try {
String quotationRequestMasterObjid = CommonUtils.checkNull(paramMap.get("QUOTATION_REQUEST_MASTER_OBJID"));
if(!"".equals(quotationRequestMasterObjid)){
// 기존 견적요청서 조회
resultMap = salesMngService.getQuotationRequestMasterInfo(request, paramMap);
} else {
// 신규 생성 (구매리스트에서 호출 시)
resultMap.put("OBJID", CommonUtils.createObjId());
resultMap.put("STATUS", "create");
}
} catch (Exception e) {
e.printStackTrace();
}
request.setAttribute("code_map", code_map);
request.setAttribute("resultMap", resultMap);
return "/salesMng/quotationRequestFormPopup";
}
/**
* 견적요청서 상세 목록 조회
* @param request
* @param paramMap
* @return
*/
@ResponseBody
@RequestMapping("/salesMng/getQuotationRequestDetailList.do")
public Map getQuotationRequestDetailList(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map resultMap = new HashMap();
try {
List<Map> detailList = salesMngService.getQuotationRequestDetailList(request, paramMap);
resultMap.put("list", detailList);
resultMap.put("resultFlag", "S");
} catch (Exception e) {
e.printStackTrace();
resultMap.put("resultFlag", "F");
resultMap.put("message", "조회 중 오류가 발생했습니다.");
}
return resultMap;
}
/**
* 견적요청서 생성 (구매리스트에서)
* @param request
* @param paramMap
* @return
*/
@ResponseBody
@RequestMapping("/salesMng/createQuotationRequest.do")
public Map createQuotationRequest(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map resultMap = new HashMap();
try {
resultMap = salesMngService.createQuotationRequest(request, paramMap);
} catch (Exception e) {
e.printStackTrace();
resultMap.put("resultFlag", "F");
resultMap.put("message", "견적요청서 생성 중 오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
/**
* 견적요청서 단가 저장
* @param request
* @param paramMap
* @return
*/
@ResponseBody
@RequestMapping("/salesMng/saveQuotationRequestPrice.do")
public Map saveQuotationRequestPrice(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map resultMap = new HashMap();
try {
resultMap = salesMngService.saveQuotationRequestPrice(request, paramMap);
} catch (Exception e) {
e.printStackTrace();
resultMap.put("resultFlag", "F");
resultMap.put("message", "저장 중 오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
/**
* 견적요청서 메일 발송 후 상태 업데이트
* @param request
* @param paramMap
* @return
*/
@ResponseBody
@RequestMapping("/salesMng/updateQuotationRequestMailSent.do")
public Map updateQuotationRequestMailSent(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map resultMap = new HashMap();
try {
resultMap = salesMngService.updateQuotationRequestMailSent(request, paramMap);
} catch (Exception e) {
e.printStackTrace();
resultMap.put("resultFlag", "F");
resultMap.put("message", "상태 업데이트 중 오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
/**
* 견적요청서 삭제
* @param request
* @param paramMap
* @return
*/
@ResponseBody
@RequestMapping("/salesMng/deleteQuotationRequest.do")
public Map deleteQuotationRequest(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map resultMap = new HashMap();
try {
resultMap = salesMngService.deleteQuotationRequest(request, paramMap);
} catch (Exception e) {
e.printStackTrace();
resultMap.put("resultFlag", "F");
resultMap.put("message", "삭제 중 오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
/**
* 구매리스트에서 견적요청서 생성 대상 조회
* @param request
* @param paramMap
* @return
*/
@ResponseBody
@RequestMapping("/salesMng/getPurchaseListForQuotation.do")
public Map getPurchaseListForQuotation(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map resultMap = new HashMap();
try {
List<Map> list = salesMngService.getPurchaseListForQuotation(request, paramMap);
resultMap.put("list", list);
resultMap.put("resultFlag", "S");
} catch (Exception e) {
e.printStackTrace();
resultMap.put("resultFlag", "F");
resultMap.put("message", "조회 중 오류가 발생했습니다.");
}
return resultMap;
}
}

View File

@@ -2194,4 +2194,391 @@ public class SalesMngService {
return proposalNo;
}
// =====================================================
// 견적요청서 관리 메서드
// =====================================================
/**
* 견적요청서 목록 조회
* @param request
* @param paramMap
* @return
*/
public List<Map> getQuotationRequestList(HttpServletRequest request, Map paramMap) {
SqlSession sqlSession = null;
List<Map> resultList = new ArrayList<Map>();
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
resultList = sqlSession.selectList("salesMng.getQuotationRequestList", paramMap);
} catch(Exception e) {
e.printStackTrace();
} finally {
if(sqlSession != null) sqlSession.close();
}
return resultList;
}
/**
* 견적요청서 마스터 정보 조회
* @param request
* @param paramMap
* @return
*/
public Map getQuotationRequestMasterInfo(HttpServletRequest request, Map paramMap) {
SqlSession sqlSession = null;
Map resultMap = new HashMap();
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
resultMap = (Map)sqlSession.selectOne("salesMng.getQuotationRequestMasterInfo", paramMap);
} catch(Exception e) {
e.printStackTrace();
} finally {
if(sqlSession != null) sqlSession.close();
}
return resultMap;
}
/**
* 견적요청서 상세 목록 조회
* @param request
* @param paramMap
* @return
*/
public List<Map> getQuotationRequestDetailList(HttpServletRequest request, Map paramMap) {
SqlSession sqlSession = null;
List<Map> resultList = new ArrayList<Map>();
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
resultList = sqlSession.selectList("salesMng.getQuotationRequestDetailList", paramMap);
} catch(Exception e) {
e.printStackTrace();
} finally {
if(sqlSession != null) sqlSession.close();
}
return resultList;
}
/**
* 견적요청서 생성 (구매리스트에서)
* @param request
* @param paramMap
* @return
*/
public Map createQuotationRequest(HttpServletRequest request, Map paramMap) {
SqlSession sqlSession = null;
Map resultMap = new HashMap();
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
String salesRequestMasterObjid = CommonUtils.checkNull(paramMap.get("SALES_REQUEST_MASTER_OBJID"));
String vendorObjid = CommonUtils.checkNull(paramMap.get("VENDOR_OBJID"));
String vendorType = CommonUtils.checkNull(paramMap.get("VENDOR_TYPE")); // SUPPLY 또는 PROCESSING
String partObjidsJson = CommonUtils.checkNull(paramMap.get("PART_OBJIDS")); // 선택된 품목 OBJID 목록
HttpSession session = request.getSession();
PersonBean personBean = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
String userId = personBean.getUserId();
// 1. 견적요청서 번호 생성
String quotationRequestNo = (String)sqlSession.selectOne("salesMng.getNextQuotationRequestNo");
// 2. 구매요청서 마스터 정보 조회 (프로젝트 정보 등)
Map masterParam = new HashMap();
masterParam.put("SALES_REQUEST_MASTER_OBJID", salesRequestMasterObjid);
Map purchaseRequestInfo = (Map)sqlSession.selectOne("salesMng.getSalesRequestMasterInfo", masterParam);
// 3. 견적요청서 마스터 생성
String quotationMasterObjid = CommonUtils.createObjId();
Map quotationMaster = new HashMap();
quotationMaster.put("OBJID", quotationMasterObjid);
quotationMaster.put("QUOTATION_REQUEST_NO", quotationRequestNo);
quotationMaster.put("SALES_REQUEST_MASTER_OBJID", salesRequestMasterObjid);
// 대소문자 모두 처리 (MyBatis에서 소문자로 반환될 수 있음)
Object projectMgmtObjid = null;
Object dueDate = null;
if(purchaseRequestInfo != null) {
projectMgmtObjid = purchaseRequestInfo.get("PROJECT_MGMT_OBJID") != null ?
purchaseRequestInfo.get("PROJECT_MGMT_OBJID") : purchaseRequestInfo.get("project_mgmt_objid");
dueDate = purchaseRequestInfo.get("DUE_DATE") != null ?
purchaseRequestInfo.get("DUE_DATE") : purchaseRequestInfo.get("due_date");
}
quotationMaster.put("PROJECT_MGMT_OBJID", projectMgmtObjid);
quotationMaster.put("VENDOR_OBJID", vendorObjid);
quotationMaster.put("VENDOR_TYPE", vendorType);
quotationMaster.put("DUE_DATE", dueDate);
quotationMaster.put("WRITER", userId);
sqlSession.insert("salesMng.insertQuotationRequestMaster", quotationMaster);
// 4. 선택된 품목들로 견적요청서 상세 생성
List<String> partObjids = new ArrayList<String>();
if(partObjidsJson != null && !partObjidsJson.isEmpty()) {
org.codehaus.jackson.map.ObjectMapper mapper = new org.codehaus.jackson.map.ObjectMapper();
partObjids = mapper.readValue(partObjidsJson, List.class);
}
for(String partObjid : partObjids) {
// 구매리스트 품목 정보 조회
Map partParam = new HashMap();
partParam.put("OBJID", partObjid);
Map partInfo = (Map)sqlSession.selectOne("salesMng.getSalesRequestPartInfo", partParam);
if(partInfo != null) {
Map detailParam = new HashMap();
detailParam.put("OBJID", CommonUtils.createObjId());
detailParam.put("QUOTATION_REQUEST_MASTER_OBJID", quotationMasterObjid);
detailParam.put("SALES_REQUEST_PART_OBJID", partObjid); // MBOM_DETAIL.OBJID
detailParam.put("PART_OBJID", partInfo.get("PART_OBJID"));
// 업체유형에 따라 다른 정보 저장 (단가는 0으로, 견적 수신 후 입력)
if("PROCESSING".equals(vendorType)) {
// 가공업체: 품번, 품명, 제작수량
detailParam.put("PART_NO", partInfo.get("PART_NO"));
detailParam.put("PART_NAME", partInfo.get("PART_NAME"));
detailParam.put("RAW_MATERIAL", "");
detailParam.put("SIZE", "");
detailParam.put("QTY", partInfo.get("PRODUCTION_QTY"));
} else {
// 공급업체: 소재품번, 소재재질, 규격, 발주수량
detailParam.put("PART_NO", partInfo.get("RAW_MATERIAL_NO"));
detailParam.put("PART_NAME", partInfo.get("RAW_MATERIAL"));
detailParam.put("RAW_MATERIAL", partInfo.get("RAW_MATERIAL"));
detailParam.put("SIZE", partInfo.get("SIZE"));
detailParam.put("QTY", partInfo.get("PO_QTY"));
}
detailParam.put("UNIT_PRICE", 0); // 단가는 견적 수신 후 입력
detailParam.put("REMARK", "");
sqlSession.insert("salesMng.insertQuotationRequestDetail", detailParam);
}
}
sqlSession.commit();
resultMap.put("resultFlag", "S");
resultMap.put("QUOTATION_REQUEST_MASTER_OBJID", quotationMasterObjid);
resultMap.put("QUOTATION_REQUEST_NO", quotationRequestNo);
resultMap.put("message", "견적요청서가 생성되었습니다.");
} catch(Exception e) {
if(sqlSession != null) sqlSession.rollback();
e.printStackTrace();
resultMap.put("resultFlag", "F");
resultMap.put("message", "견적요청서 생성 중 오류가 발생했습니다: " + e.getMessage());
} finally {
if(sqlSession != null) sqlSession.close();
}
return resultMap;
}
/**
* 견적요청서 단가 저장 및 구매리스트 업데이트
* @param request
* @param paramMap
* @return
*/
public Map saveQuotationRequestPrice(HttpServletRequest request, Map paramMap) {
SqlSession sqlSession = null;
Map resultMap = new HashMap();
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
String quotationMasterObjid = CommonUtils.checkNull(paramMap.get("QUOTATION_REQUEST_MASTER_OBJID"));
String detailListJson = CommonUtils.checkNull(paramMap.get("DETAIL_LIST"));
// JSON 파싱
List<Map> detailList = new ArrayList<Map>();
if(detailListJson != null && !detailListJson.isEmpty()) {
org.codehaus.jackson.map.ObjectMapper mapper = new org.codehaus.jackson.map.ObjectMapper();
detailList = mapper.readValue(detailListJson, List.class);
}
// 견적요청서 마스터 정보 조회 (VENDOR_TYPE 확인)
Map masterParam = new HashMap();
masterParam.put("QUOTATION_REQUEST_MASTER_OBJID", quotationMasterObjid);
Map masterInfo = (Map)sqlSession.selectOne("salesMng.getQuotationRequestMasterInfo", masterParam);
String vendorType = CommonUtils.checkNull(masterInfo.get("VENDOR_TYPE"));
// 각 상세 항목 업데이트
for(Map detail : detailList) {
String detailObjid = CommonUtils.checkNull(detail.get("OBJID"));
String salesRequestPartObjid = CommonUtils.checkNull(detail.get("SALES_REQUEST_PART_OBJID"));
String unitPrice = CommonUtils.checkNull(detail.get("UNIT_PRICE"));
String qty = CommonUtils.checkNull(detail.get("QTY"));
// 1. 견적요청서 상세 단가 업데이트
Map updateParam = new HashMap();
updateParam.put("OBJID", detailObjid);
updateParam.put("UNIT_PRICE", unitPrice);
updateParam.put("QTY", qty);
sqlSession.update("salesMng.updateQuotationRequestDetailPrice", updateParam);
// 2. 구매리스트 단가 업데이트
Map purchaseUpdateParam = new HashMap();
purchaseUpdateParam.put("SALES_REQUEST_PART_OBJID", salesRequestPartObjid);
purchaseUpdateParam.put("UNIT_PRICE", unitPrice);
purchaseUpdateParam.put("VENDOR_TYPE", vendorType);
sqlSession.update("salesMng.updatePurchaseListPriceFromQuotation", purchaseUpdateParam);
}
// 3. 견적요청서 마스터 상태 업데이트 (received: 견적수신)
Map statusParam = new HashMap();
statusParam.put("QUOTATION_REQUEST_MASTER_OBJID", quotationMasterObjid);
statusParam.put("STATUS", "received");
sqlSession.update("salesMng.updateQuotationRequestMasterStatus", statusParam);
sqlSession.commit();
resultMap.put("resultFlag", "S");
resultMap.put("message", "저장되었습니다. 구매리스트 단가가 업데이트되었습니다.");
} catch(Exception e) {
if(sqlSession != null) sqlSession.rollback();
e.printStackTrace();
resultMap.put("resultFlag", "F");
resultMap.put("message", "저장 중 오류가 발생했습니다: " + e.getMessage());
} finally {
if(sqlSession != null) sqlSession.close();
}
return resultMap;
}
/**
* 견적요청서 메일 발송 후 상태 업데이트
* @param request
* @param paramMap
* @return
*/
public Map updateQuotationRequestMailSent(HttpServletRequest request, Map paramMap) {
SqlSession sqlSession = null;
Map resultMap = new HashMap();
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
String quotationMasterObjid = CommonUtils.checkNull(paramMap.get("QUOTATION_REQUEST_MASTER_OBJID"));
Map statusParam = new HashMap();
statusParam.put("QUOTATION_REQUEST_MASTER_OBJID", quotationMasterObjid);
statusParam.put("STATUS", "sent");
statusParam.put("MAIL_SEND_YN", "Y");
sqlSession.update("salesMng.updateQuotationRequestMasterStatus", statusParam);
sqlSession.commit();
resultMap.put("resultFlag", "S");
resultMap.put("message", "메일 발송이 완료되었습니다.");
} catch(Exception e) {
if(sqlSession != null) sqlSession.rollback();
e.printStackTrace();
resultMap.put("resultFlag", "F");
resultMap.put("message", "상태 업데이트 중 오류가 발생했습니다: " + e.getMessage());
} finally {
if(sqlSession != null) sqlSession.close();
}
return resultMap;
}
/**
* 견적요청서 삭제
* @param request
* @param paramMap
* @return
*/
public Map deleteQuotationRequest(HttpServletRequest request, Map paramMap) {
SqlSession sqlSession = null;
Map resultMap = new HashMap();
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
String quotationMasterObjid = CommonUtils.checkNull(paramMap.get("QUOTATION_REQUEST_MASTER_OBJID"));
Map deleteParam = new HashMap();
deleteParam.put("QUOTATION_REQUEST_MASTER_OBJID", quotationMasterObjid);
// 상세 먼저 삭제
sqlSession.delete("salesMng.deleteQuotationRequestDetail", deleteParam);
// 마스터 삭제
sqlSession.delete("salesMng.deleteQuotationRequestMaster", deleteParam);
sqlSession.commit();
resultMap.put("resultFlag", "S");
resultMap.put("message", "삭제되었습니다.");
} catch(Exception e) {
if(sqlSession != null) sqlSession.rollback();
e.printStackTrace();
resultMap.put("resultFlag", "F");
resultMap.put("message", "삭제 중 오류가 발생했습니다: " + e.getMessage());
} finally {
if(sqlSession != null) sqlSession.close();
}
return resultMap;
}
/**
* 구매리스트에서 견적요청서 생성 대상 조회
* - MBOM_DETAIL에서만 조회 (구매리스트관리는 M-BOM 기반으로만 동작)
* @param request
* @param paramMap
* @return
*/
public List<Map> getPurchaseListForQuotation(HttpServletRequest request, Map paramMap) {
SqlSession sqlSession = null;
List<Map> resultList = new ArrayList<Map>();
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
String salesRequestMasterObjid = CommonUtils.checkNull(paramMap.get("SALES_REQUEST_MASTER_OBJID"));
System.out.println("========== getPurchaseListForQuotation ==========");
System.out.println("SALES_REQUEST_MASTER_OBJID: " + salesRequestMasterObjid);
// SALES_REQUEST_MASTER에서 MBOM_HEADER_OBJID 조회
Map masterParam = new HashMap();
masterParam.put("SALES_REQUEST_MASTER_OBJID", salesRequestMasterObjid);
Map masterInfo = (Map)sqlSession.selectOne("salesMng.getSalesRequestMasterInfo", masterParam);
System.out.println("masterInfo: " + masterInfo);
if(masterInfo != null) {
// 대소문자 구분 없이 MBOM_HEADER_OBJID 조회
String mbomHeaderObjid = "";
if(masterInfo.get("MBOM_HEADER_OBJID") != null) {
mbomHeaderObjid = CommonUtils.checkNull(masterInfo.get("MBOM_HEADER_OBJID"));
} else if(masterInfo.get("mbom_header_objid") != null) {
mbomHeaderObjid = CommonUtils.checkNull(masterInfo.get("mbom_header_objid"));
}
System.out.println("MBOM_HEADER_OBJID: " + mbomHeaderObjid);
if(!mbomHeaderObjid.isEmpty()) {
// MBOM_DETAIL에서 조회
Map queryParam = new HashMap();
queryParam.put("MBOM_HEADER_OBJID", mbomHeaderObjid);
queryParam.put("SALES_REQUEST_MASTER_OBJID", salesRequestMasterObjid);
resultList = sqlSession.selectList("salesMng.getPurchaseListForQuotationFromMBom", queryParam);
System.out.println("조회 결과 건수: " + resultList.size());
} else {
System.out.println("MBOM_HEADER_OBJID가 비어있음");
}
} else {
System.out.println("masterInfo가 null");
}
} catch(Exception e) {
e.printStackTrace();
} finally {
if(sqlSession != null) sqlSession.close();
}
return resultList;
}
}