견적요청서 생성 시 동일 품번 그룹핑 적용

- getPurchaseListForQuotationFromMBom: CTE로 동일 품번 GROUP BY 적용
- getGroupedSalesRequestPartInfo 신규: ORIGINAL_OBJIDS 기반 합산 조회
- createQuotationRequest: 그룹핑된 데이터로 견적요청서 상세 생성
- purchaseListFormPopUp.jsp, salesRequestMngRegList.jsp: ORIGINAL_OBJIDS 전달
- insertProposalPartFromMBom/Processing: MATERIAL_YN 컬럼 추가
- SalesMngService: RAW_MATERIAL_NO 기반 MATERIAL_YN 판별 로직

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-24 09:50:26 +09:00
parent ea0dde8501
commit 89d4d1d4e7
4 changed files with 134 additions and 90 deletions

View File

@@ -1061,10 +1061,11 @@ function fn_createQuotationRequest() {
checkedRows.forEach(function(item) {
var vendorPm = item.VENDOR_PM || '';
var processingVendor = item.PROCESSING_VENDOR || '';
var originalObjids = item.ORIGINAL_OBJIDS || item.OBJID;
if(vendorPm && vendorPm !== '') {
supplyItems.push({
objid: item.OBJID,
originalObjids: originalObjids,
vendorObjid: vendorPm,
partNo: item.PART_NO,
partName: item.PART_NAME
@@ -1073,7 +1074,7 @@ function fn_createQuotationRequest() {
if(processingVendor && processingVendor !== '') {
processingItems.push({
objid: item.OBJID,
originalObjids: originalObjids,
vendorObjid: processingVendor,
partNo: item.PART_NO,
partName: item.PART_NAME
@@ -1098,14 +1099,14 @@ function fn_createQuotationRequest() {
if(!supplyVendorGroups[item.vendorObjid]) {
supplyVendorGroups[item.vendorObjid] = [];
}
supplyVendorGroups[item.vendorObjid].push(item.objid);
supplyVendorGroups[item.vendorObjid].push(item.originalObjids);
});
processingItems.forEach(function(item) {
if(!processingVendorGroups[item.vendorObjid]) {
processingVendorGroups[item.vendorObjid] = [];
}
processingVendorGroups[item.vendorObjid].push(item.objid);
processingVendorGroups[item.vendorObjid].push(item.originalObjids);
});
// 생성할 견적요청서 목록 표시

View File

@@ -893,14 +893,12 @@ function fn_processQuotationRequestCreation(salesRequestObjid, purchaseList) {
// 대소문자 모두 처리 (서버에서 소문자로 반환될 수 있음)
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 originalObjids = fnc_checkNull(item.ORIGINAL_OBJIDS || item.original_objids || 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] = {
@@ -908,10 +906,9 @@ function fn_processQuotationRequestCreation(salesRequestObjid, purchaseList) {
parts: []
};
}
supplyVendorGroups[vendorPm].parts.push(objid);
supplyVendorGroups[vendorPm].parts.push(originalObjids);
}
// 가공업체 견적요청서 생성 가능한 경우
if(processingVendor !== '' && canCreateProcessing === 'Y') {
if(!processingVendorGroups[processingVendor]) {
processingVendorGroups[processingVendor] = {
@@ -919,7 +916,7 @@ function fn_processQuotationRequestCreation(salesRequestObjid, purchaseList) {
parts: []
};
}
processingVendorGroups[processingVendor].parts.push(objid);
processingVendorGroups[processingVendor].parts.push(originalObjids);
}
});

View File

@@ -4956,6 +4956,35 @@ ORDER BY V.PATH2
WHERE MD.OBJID = #{OBJID}
</select>
<!-- 구매리스트 품목 정보 조회 (그룹핑된 OBJID 목록 합산) - 견적요청서 생성용 -->
<select id="getGroupedSalesRequestPartInfo" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
MIN(MD.OBJID) AS OBJID,
MIN(MD.MBOM_HEADER_OBJID) AS MBOM_HEADER_OBJID,
MD.PART_OBJID,
MIN(PM.PART_NO) AS PART_NO,
MIN(PM.PART_NAME) AS PART_NAME,
COALESCE(MIN(MD.RAW_MATERIAL), '') AS RAW_MATERIAL,
MIN(MD.RAW_MATERIAL_PART_NO) AS RAW_MATERIAL_NO,
COALESCE(MIN(MD.RAW_MATERIAL_SIZE), '') AS SIZE,
SUM(COALESCE(MD.PO_QTY, 0)) AS PO_QTY,
SUM(COALESCE(MD.PRODUCTION_QTY, 0)) AS PRODUCTION_QTY,
MIN(MD.UNIT_PRICE) AS UNIT_PRICE,
MIN(MD.PROCESSING_UNIT_PRICE) AS PROCESSING_UNIT_PRICE,
MIN(MD.VENDOR) AS VENDOR_PM,
(SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = MIN(MD.VENDOR)) AS VENDOR_NAME,
MIN(MD.PROCESSING_VENDOR) AS PROCESSING_VENDOR,
(SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = MIN(MD.PROCESSING_VENDOR)) AS PROCESSING_VENDOR_NAME,
STRING_AGG(MD.OBJID::VARCHAR, ',' ORDER BY MD.OBJID) AS ORIGINAL_OBJIDS
FROM MBOM_DETAIL MD
LEFT JOIN PART_MNG PM ON MD.PART_OBJID::VARCHAR = PM.OBJID::VARCHAR
WHERE MD.OBJID::VARCHAR IN
<foreach item="objid" collection="OBJID_LIST" open="(" separator="," close=")">
#{objid}
</foreach>
GROUP BY MD.PART_OBJID
</select>
<!-- =====================================================
견적요청서 관리 쿼리
===================================================== -->
@@ -5301,71 +5330,89 @@ ORDER BY V.PATH2
WHERE MBOM_HEADER_OBJID = #{MBOM_HEADER_OBJID}
</update>
<!-- 구매리스트에서 견적요청서 생성 대상 조회 - M-BOM 기반 (MBOM_DETAIL에서 조회) -->
<!-- 구매리스트에서 견적요청서 생성 대상 조회 - M-BOM 기반 (동일 품번 그룹핑) -->
<!-- 공급업체/가공업체별로 견적요청서 생성 가능 여부 플래그 포함 -->
<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 != '')
WITH GROUPED_MBOM AS (
SELECT
MIN(MD.OBJID) AS OBJID,
STRING_AGG(MD.OBJID::VARCHAR, ',' ORDER BY MD.OBJID) AS ORIGINAL_OBJIDS,
MD.PART_OBJID,
MIN(PM.PART_NO) AS PART_NO,
MIN(PM.PART_NAME) AS PART_NAME,
COALESCE(MIN(MD.RAW_MATERIAL), '') AS RAW_MATERIAL,
COALESCE(MIN(MD.RAW_MATERIAL_SIZE), '') AS SIZE,
SUM(COALESCE(MD.PO_QTY, 0)) AS PO_QTY,
SUM(COALESCE(MD.PRODUCTION_QTY, 0)) AS PRODUCTION_QTY,
MIN(MD.UNIT_PRICE) AS UNIT_PRICE,
MIN(MD.PROCESSING_UNIT_PRICE) AS PROCESSING_UNIT_PRICE,
MIN(MD.VENDOR) AS VENDOR_PM,
MIN(MD.PROCESSING_VENDOR) AS PROCESSING_VENDOR,
MIN(MD.RAW_MATERIAL_PART_NO) AS RAW_MATERIAL_NO
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 != '')
)
GROUP BY MD.PART_OBJID,
COALESCE(MD.SUPPLY_TYPE, ''),
COALESCE(MD.RAW_MATERIAL, ''),
COALESCE(MD.RAW_MATERIAL_SIZE, '')
)
-- 공급업체 또는 가공업체 중 하나라도 견적요청서 생성 가능해야 함
AND (
-- 공급업체 견적요청서 생성 가능
(MD.VENDOR IS NOT NULL AND MD.VENDOR != '' AND NOT EXISTS (
SELECT
G.OBJID,
G.ORIGINAL_OBJIDS,
#{SALES_REQUEST_MASTER_OBJID} AS SALES_REQUEST_MASTER_OBJID,
G.PART_OBJID,
G.PART_NO,
G.PART_NAME,
G.RAW_MATERIAL,
G.SIZE,
G.PO_QTY,
G.PRODUCTION_QTY,
G.UNIT_PRICE,
G.PROCESSING_UNIT_PRICE,
G.VENDOR_PM,
(SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = G.VENDOR_PM) AS VENDOR_NAME,
G.PROCESSING_VENDOR,
(SELECT CLIENT_NM FROM CLIENT_MNG WHERE OBJID::VARCHAR = G.PROCESSING_VENDOR) AS PROCESSING_VENDOR_NAME,
G.RAW_MATERIAL_NO,
'MBOM' AS DATA_SOURCE,
CASE WHEN G.VENDOR_PM IS NOT NULL AND G.VENDOR_PM != '' 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
WHERE QRD.SALES_REQUEST_PART_OBJID = G.OBJID
AND QRM.VENDOR_TYPE = 'SUPPLY'
AND QRM.VENDOR_OBJID::VARCHAR = MD.VENDOR
AND QRM.VENDOR_OBJID::VARCHAR = G.VENDOR_PM
) THEN 'Y' ELSE 'N' END AS CAN_CREATE_SUPPLY,
CASE WHEN G.PROCESSING_VENDOR IS NOT NULL AND G.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 = G.OBJID
AND QRM.VENDOR_TYPE = 'PROCESSING'
AND QRM.VENDOR_OBJID::VARCHAR = G.PROCESSING_VENDOR
) THEN 'Y' ELSE 'N' END AS CAN_CREATE_PROCESSING
FROM GROUPED_MBOM G
WHERE (
(G.VENDOR_PM IS NOT NULL AND G.VENDOR_PM != '' 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 = G.OBJID
AND QRM.VENDOR_TYPE = 'SUPPLY'
AND QRM.VENDOR_OBJID::VARCHAR = G.VENDOR_PM
))
OR
-- 가공업체 견적요청서 생성 가능
(MD.PROCESSING_VENDOR IS NOT NULL AND MD.PROCESSING_VENDOR != '' AND NOT EXISTS (
(G.PROCESSING_VENDOR IS NOT NULL AND G.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
WHERE QRD.SALES_REQUEST_PART_OBJID = G.OBJID
AND QRM.VENDOR_TYPE = 'PROCESSING'
AND QRM.VENDOR_OBJID::VARCHAR = MD.PROCESSING_VENDOR
AND QRM.VENDOR_OBJID::VARCHAR = G.PROCESSING_VENDOR
))
)
ORDER BY MD.REGDATE
ORDER BY G.PART_NO
</select>
<!-- 구매리스트에서 견적요청서 생성 대상 조회 - 수동 작성 (SALES_REQUEST_PART에서 조회) -->

View File

@@ -11,6 +11,7 @@ package com.pms.salesmgmt.service;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
@@ -2382,54 +2383,52 @@ public class SalesMngService {
sqlSession.insert("salesMng.insertQuotationRequestMaster", quotationMaster);
// 4. 선택된 품목들로 견적요청서 상세 생성
List<String> partObjids = new ArrayList<String>();
// PART_OBJIDS: 각 항목이 ORIGINAL_OBJIDS (콤마구분 MBOM_DETAIL.OBJID) 형태
List<String> partOriginalObjidsList = 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);
partOriginalObjidsList = mapper.readValue(partObjidsJson, List.class);
}
for(String partObjid : partObjids) {
// 구매리스트 품목 정보 조회
for(String originalObjids : partOriginalObjidsList) {
// ORIGINAL_OBJIDS를 개별 OBJID 리스트로 분리하여 그룹핑 조회
List<String> objidList = Arrays.asList(originalObjids.split(","));
Map partParam = new HashMap();
partParam.put("OBJID", partObjid);
Map partInfo = (Map)sqlSession.selectOne("salesMng.getSalesRequestPartInfo", partParam);
partParam.put("OBJID_LIST", objidList);
Map partInfo = (Map)sqlSession.selectOne("salesMng.getGroupedSalesRequestPartInfo", 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("SALES_REQUEST_PART_OBJID", partInfo.get("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 {
// 공급업체: 소재품번 유무에 따라 분기
String rawMaterialNo = CommonUtils.checkNull(partInfo.get("RAW_MATERIAL_NO"));
if(!rawMaterialNo.isEmpty()) {
// 소재품번이 있는 경우: 소재품번, 소재재질, 규격, 발주수량
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"));
} else {
// 소재품번이 없는 경우: 부품품번, 부품명, 제작수량
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"));
String rawMaterialNo = CommonUtils.checkNull(partInfo.get("RAW_MATERIAL_NO"));
if(!rawMaterialNo.isEmpty()) {
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"));
} else {
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"));
}
}
}
detailParam.put("UNIT_PRICE", 0); // 단가는 견적 수신 후 입력
detailParam.put("UNIT_PRICE", 0);
detailParam.put("REMARK", "");
sqlSession.insert("salesMng.insertQuotationRequestDetail", detailParam);