From 4765f533a9acd6876a29d63d8119d2f28c24ccbf Mon Sep 17 00:00:00 2001 From: ysCha Date: Mon, 6 Jul 2026 09:29:25 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[2311]=20=EC=9D=B8=EC=A6=9D=EC=9A=A9?= =?UTF-8?q?=EB=9F=89=20=EC=BF=BC=EB=A6=AC=20=EC=9E=84=EC=8B=9C=20=EB=A1=A4?= =?UTF-8?q?=EB=B0=B1=20=E2=80=94=20selectEstimateRoofCertVolKw=20=EC=9D=B4?= =?UTF-8?q?=EC=A0=84=20=EB=B0=A9=EC=8B=9D=20=EB=B3=B5=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 동일 PCS 다중 유닛 CONN_VOL_W 기반 계산이 현장에서 값 불일치 발생, 쿼리를 c13bda5a 이전 위치기반 누적(CIRCUIT_AGG) 방식으로 되돌림. CONN_VOL_W insert/copy/update 및 getPcsCircuitList 는 유지하되 쿼리가 읽지 않으므로 무해 상태. Co-Authored-By: Claude Opus 4 --- .../mappers/estimate/estimateMapper.xml | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/src/main/resources/mappers/estimate/estimateMapper.xml b/src/main/resources/mappers/estimate/estimateMapper.xml index b41fa02f..2c3ba206 100644 --- a/src/main/resources/mappers/estimate/estimateMapper.xml +++ b/src/main/resources/mappers/estimate/estimateMapper.xml @@ -407,7 +407,6 @@ SELECT cie.CIRCUIT_NO, cie.OBJECT_NO, cie.PLAN_NO, cie.ITEM_ID AS CIRCUIT_ITEM_ID, cie.CIRCUIT_CFG, - cie.CONN_VOL_W, -- 2026-06-30: 저장 시점 유닛별 접속용량(W). 있으면 위치기반 추정보다 우선 사용(동일 PCS 모델 다중 유닛 대응) (SELECT SUM(CAST(LTRIM(RTRIM(x.X.value('.','VARCHAR(10)'))) AS INT)) FROM (SELECT CAST(''+REPLACE(cie.CIRCUIT_CFG,',','')+'' AS XML) AS d) t CROSS APPLY t.d.nodes('/X') AS x(X) @@ -418,14 +417,11 @@ ), CC AS ( SELECT cs.*, - -- 2026-06-30: 누적 윈도우를 PCS(CIRCUIT_ITEM_ID) 내부로 한정 — 회로 위치가 다른 PCS 모듈까지 끌어오던 오배분 차단 ISNULL((SELECT SUM(c2.CIRCUIT_SUM) FROM CS c2 WHERE c2.OBJECT_NO=cs.OBJECT_NO AND c2.PLAN_NO=cs.PLAN_NO - AND c2.CIRCUIT_ITEM_ID = cs.CIRCUIT_ITEM_ID AND c2.CIRCUIT_NO < cs.CIRCUIT_NO), 0) AS CUM_START, ISNULL((SELECT SUM(c2.CIRCUIT_SUM) FROM CS c2 WHERE c2.OBJECT_NO=cs.OBJECT_NO AND c2.PLAN_NO=cs.PLAN_NO - AND c2.CIRCUIT_ITEM_ID = cs.CIRCUIT_ITEM_ID AND c2.CIRCUIT_NO <= cs.CIRCUIT_NO), 0) AS CUM_END FROM CS cs ), @@ -434,14 +430,11 @@ rie.ROOF_ITEM_NO, rie.ROOF_SURFACE_ID, rie.OBJECT_NO, rie.PLAN_NO, rie.ITEM_ID AS ROOF_ITEM_ID, rie.ITEM_NAME, CAST(rie.SPECIFICATION AS FLOAT) AS SPEC, rie.AMOUNT, rie.PC_ITEM_ID, - -- 2026-06-30: 모듈 누적도 PCS(PC_ITEM_ID) 내부로 한정 — 실제 배선된 PCS의 모듈만 누적 ISNULL((SELECT SUM(r2.AMOUNT) FROM T_PART_ROOF_ITEM_ESTIMATE r2 WHERE r2.OBJECT_NO=rie.OBJECT_NO AND r2.PLAN_NO=rie.PLAN_NO - AND r2.PC_ITEM_ID = rie.PC_ITEM_ID AND r2.ROOF_ITEM_NO < rie.ROOF_ITEM_NO), 0) AS CUM_START, ISNULL((SELECT SUM(r2.AMOUNT) FROM T_PART_ROOF_ITEM_ESTIMATE r2 WHERE r2.OBJECT_NO=rie.OBJECT_NO AND r2.PLAN_NO=rie.PLAN_NO - AND r2.PC_ITEM_ID = rie.PC_ITEM_ID AND r2.ROOF_ITEM_NO <= rie.ROOF_ITEM_NO), 0) AS CUM_END FROM T_PART_ROOF_ITEM_ESTIMATE rie WHERE rie.OBJECT_NO = #{objectNo} AND rie.PLAN_NO = #{planNo} @@ -454,9 +447,7 @@ CASE WHEN cc.CUM_END < rc.CUM_END THEN cc.CUM_END ELSE rc.CUM_END END - CASE WHEN cc.CUM_START > rc.CUM_START THEN cc.CUM_START ELSE rc.CUM_START END AS ALLOCATED_QTY FROM CC cc - -- 2026-06-30: 실제 배선(PCS) 일치 조건 추가 — 회로의 PCS 와 모듈이 배선된 PCS 가 같은 경우만 겹침 INNER JOIN RC rc ON cc.OBJECT_NO = rc.OBJECT_NO AND cc.PLAN_NO = rc.PLAN_NO - AND cc.CIRCUIT_ITEM_ID = rc.PC_ITEM_ID WHERE cc.CUM_START < rc.CUM_END AND cc.CUM_END > rc.CUM_START ), DETAIL AS ( @@ -466,28 +457,18 @@ ROUND(a.SPEC, 3) * a.ALLOCATED_QTY AS SPEC_X_QTY, (SELECT mi.PNOW_W FROM M_ITEM mi WHERE mi.ITEM_ID = a.CIRCUIT_ITEM_ID) AS PNOW_W FROM ALLOC a - ), - POS AS ( - -- 위치기반(폴백) 유닛별 접속용량 W 합 — CONN_VOL_W 가 없는 기존 데이터에만 사용 - SELECT CIRCUIT_NO, CIRCUIT_ITEM_ID, SUM(SPEC_X_QTY) AS POS_W - FROM DETAIL - GROUP BY CIRCUIT_NO, CIRCUIT_ITEM_ID ), CIRCUIT_AGG AS ( - -- 2026-06-30 option B: 저장된 CONN_VOL_W 우선, 없으면(NULL=기존 데이터) 위치기반 POS_W 폴백. - -- CS(유닛 목록) 기준으로 LEFT JOIN 하므로 유닛별 1행 보장. 유닛별 MIN(접속용량, PNOW_W) 후 합산. SELECT - cs.CIRCUIT_NO, cs.CIRCUIT_ITEM_ID, - COALESCE(cs.CONN_VOL_W, pos.POS_W, 0) AS CONN_W, - (SELECT mi.PNOW_W FROM M_ITEM mi WHERE mi.ITEM_ID = cs.CIRCUIT_ITEM_ID) AS PNOW_W - FROM CS cs - LEFT JOIN POS pos - ON pos.CIRCUIT_NO = cs.CIRCUIT_NO - AND pos.CIRCUIT_ITEM_ID = cs.CIRCUIT_ITEM_ID + CIRCUIT_NO, CIRCUIT_ITEM_ID, CIRCUIT_CFG, CIRCUIT_SUM, + SUM(SPEC_X_QTY) AS SUM_SPEC_X_QTY, PNOW_W, + CASE WHEN SUM(SPEC_X_QTY) <= PNOW_W THEN SUM(SPEC_X_QTY) ELSE PNOW_W END AS MIN_VALUE + FROM DETAIL + GROUP BY CIRCUIT_NO, CIRCUIT_ITEM_ID, CIRCUIT_CFG, CIRCUIT_SUM, PNOW_W ) -- 2026-06-05: SQL 은 W 합만 DECIMAL 로 정확히 반환. kW 변환·3자리 절사(FLOOR)는 모듈용량(totVolKw)과 동일하게 Java BigDecimal 에서 수행 → FLOAT 나눗셈 잔여(5.6899999999999995 / 5.339) 원천 차단 - SELECT CAST(SUM(CASE WHEN CONN_W <= PNOW_W THEN CONN_W ELSE PNOW_W END) AS DECIMAL(18,3)) AS CERT_VOL_W + SELECT CAST(SUM(MIN_VALUE) AS DECIMAL(18,3)) AS CERT_VOL_W FROM CIRCUIT_AGG ]]> From 7f9847394506fed2d7e52734001d8e57d7dba640 Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 7 Jul 2026 13:04:29 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[2311]=20=EC=9D=B8=EC=A6=9D=EC=9A=A9?= =?UTF-8?q?=EB=9F=89=20CONN=5FVOL=5FW=20=EC=9E=AC=ED=99=9C=EC=84=B1=20+=20?= =?UTF-8?q?=EC=95=88=EC=A0=84=EC=A1=B0=EA=B1=B4=203=EC=A2=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - selectEstimateRoofCertVolKw: COALESCE(NULLIF(CONN_VOL_W,0), SUM(SPEC_X_QTY)) 비0 저장값은 유닛별 접속용량으로 우선, NULL·0이면 위치기반 합으로 폴백. 기존 데이터·거짓0 저장 건 모두 롤백판과 동일하게 동작 보장. - getPcsCircuitList: 빈 itemId 회로 아이템({}) 선제 제거. 단일 PCS+다회로를 [{PCS},{},{}]로 보낸 경우 단일 PCS 분기로 올바르게 진입. - sumModuleSpecW: 모듈 W 합이 0이면 "0" 대신 null 반환. CONN_VOL_W=0 저장(=거짓 0)을 원천 차단하여 쿼리 NULLIF 폴백이 동작하게 함. Co-Authored-By: Claude Opus 4 --- .../qcast/biz/estimate/EstimateService.java | 20 +++++++++++++++++++ .../mappers/estimate/estimateMapper.xml | 17 +++++++++------- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/interplug/qcast/biz/estimate/EstimateService.java b/src/main/java/com/interplug/qcast/biz/estimate/EstimateService.java index e7e47e6b..bb83d137 100644 --- a/src/main/java/com/interplug/qcast/biz/estimate/EstimateService.java +++ b/src/main/java/com/interplug/qcast/biz/estimate/EstimateService.java @@ -2939,6 +2939,19 @@ public class EstimateService { return pcsItemList; } + // 2026-07-07 안전조건: itemId 없는 빈 회로 아이템({}) 제거. + // 프론트가 단일 PCS 다회로를 [{PCS},{},{}] 형태(빈 placeholder 포함)로 보내면, + // 빈 항목이 ITEM_ID 없는 회로 행으로 저장되어 인증용량 쿼리의 PNOW_W 조회가 실패하고 + // 그 회로에 배선된 모듈이 통째로 탈락(과소계산)한다. 유효 itemId 만 남겨, + // 남은 개수에 따라 아래 단일/다중 PCS 분기가 올바른 PCS 로 매칭되게 한다. + pcsItemList = pcsItemList.stream() + .filter(p -> !StringUtils.isEmpty(p.getItemId())) + .collect(Collectors.toList()); + + if (pcsItemList.isEmpty()) { + return new ArrayList(); + } + // circuit 괄호 제거 (예: "(1-1)" -> "1-1") if (moduleList != null) { for (ItemRequest module : moduleList) { @@ -3063,6 +3076,13 @@ public class EstimateService { } } } + // 2026-07-07 안전조건: 합이 0(모듈 spec 이 전부 비었거나 비숫자)이면 거짓 0 대신 null 반환. + // CONN_VOL_W=0 은 인증용량 쿼리에서 MIN(0, PNOW_W)=0 으로 그 유닛 모듈을 통째로 탈락시킨다. + // null 로 남겨 쿼리 폴백(위치기반 POS_W)이 동작하게 하고, 거짓 0 저장을 원천 차단한다. + // CONN_VOL_W 는 정의상 그 유닛 모듈 W 합(=モジュール容量)과 같아야 하며 0 은 비정상값이다. + if (sum.compareTo(java.math.BigDecimal.ZERO) == 0) { + return null; + } return sum.toPlainString(); } diff --git a/src/main/resources/mappers/estimate/estimateMapper.xml b/src/main/resources/mappers/estimate/estimateMapper.xml index 2c3ba206..f84b7846 100644 --- a/src/main/resources/mappers/estimate/estimateMapper.xml +++ b/src/main/resources/mappers/estimate/estimateMapper.xml @@ -407,6 +407,7 @@ SELECT cie.CIRCUIT_NO, cie.OBJECT_NO, cie.PLAN_NO, cie.ITEM_ID AS CIRCUIT_ITEM_ID, cie.CIRCUIT_CFG, + cie.CONN_VOL_W, -- 2026-07-07: 저장 시점 유닛별 접속용량(W). 비NULL·비0 이면 위치기반 합보다 우선(동일 PCS 모델 다중 유닛 대응). NULL·0 이면 위치기반 폴백 (SELECT SUM(CAST(LTRIM(RTRIM(x.X.value('.','VARCHAR(10)'))) AS INT)) FROM (SELECT CAST(''+REPLACE(cie.CIRCUIT_CFG,',','')+'' AS XML) AS d) t CROSS APPLY t.d.nodes('/X') AS x(X) @@ -441,7 +442,7 @@ ), ALLOC AS ( SELECT - cc.CIRCUIT_NO, cc.CIRCUIT_ITEM_ID, cc.CIRCUIT_CFG, cc.CIRCUIT_SUM, + cc.CIRCUIT_NO, cc.CIRCUIT_ITEM_ID, cc.CIRCUIT_CFG, cc.CIRCUIT_SUM, cc.CONN_VOL_W, rc.ROOF_ITEM_NO, rc.ROOF_ITEM_ID, rc.SPEC, rc.PC_ITEM_ID, -- 할당량 = MIN(끝,끝) - MAX(시작,시작) CASE WHEN cc.CUM_END < rc.CUM_END THEN cc.CUM_END ELSE rc.CUM_END END @@ -452,23 +453,25 @@ ), DETAIL AS ( SELECT - a.CIRCUIT_NO, a.CIRCUIT_ITEM_ID, a.CIRCUIT_CFG, a.CIRCUIT_SUM, + a.CIRCUIT_NO, a.CIRCUIT_ITEM_ID, a.CIRCUIT_CFG, a.CIRCUIT_SUM, a.CONN_VOL_W, a.ROOF_ITEM_NO, a.ROOF_ITEM_ID, a.ALLOCATED_QTY, ROUND(a.SPEC, 3) * a.ALLOCATED_QTY AS SPEC_X_QTY, (SELECT mi.PNOW_W FROM M_ITEM mi WHERE mi.ITEM_ID = a.CIRCUIT_ITEM_ID) AS PNOW_W FROM ALLOC a ), CIRCUIT_AGG AS ( + -- 2026-07-07: 회로(유닛)별 접속용량 CONN_W 결정 — + -- NULLIF(CONN_VOL_W,0) 으로 저장값이 비0 일 때만 채택, NULL·0 이면 위치기반 합(SUM(SPEC_X_QTY))으로 폴백. + -- → 신규/재저장 건은 정확한 유닛별 접속용량, 기존·거짓0 데이터는 롤백판(위치기반)과 동일 동작. SELECT - CIRCUIT_NO, CIRCUIT_ITEM_ID, CIRCUIT_CFG, CIRCUIT_SUM, - SUM(SPEC_X_QTY) AS SUM_SPEC_X_QTY, PNOW_W, - CASE WHEN SUM(SPEC_X_QTY) <= PNOW_W THEN SUM(SPEC_X_QTY) ELSE PNOW_W END AS MIN_VALUE + CIRCUIT_NO, CIRCUIT_ITEM_ID, CIRCUIT_CFG, CIRCUIT_SUM, PNOW_W, + COALESCE(NULLIF(CONN_VOL_W, 0), SUM(SPEC_X_QTY)) AS CONN_W FROM DETAIL - GROUP BY CIRCUIT_NO, CIRCUIT_ITEM_ID, CIRCUIT_CFG, CIRCUIT_SUM, PNOW_W + GROUP BY CIRCUIT_NO, CIRCUIT_ITEM_ID, CIRCUIT_CFG, CIRCUIT_SUM, PNOW_W, CONN_VOL_W ) -- 2026-06-05: SQL 은 W 합만 DECIMAL 로 정확히 반환. kW 변환·3자리 절사(FLOOR)는 모듈용량(totVolKw)과 동일하게 Java BigDecimal 에서 수행 → FLOAT 나눗셈 잔여(5.6899999999999995 / 5.339) 원천 차단 - SELECT CAST(SUM(MIN_VALUE) AS DECIMAL(18,3)) AS CERT_VOL_W + SELECT CAST(SUM(CASE WHEN CONN_W <= PNOW_W THEN CONN_W ELSE PNOW_W END) AS DECIMAL(18,3)) AS CERT_VOL_W FROM CIRCUIT_AGG ]]>