dev #539

Merged
ysCha merged 2 commits from dev into dev-deploy 2026-07-07 13:43:02 +09:00
2 changed files with 30 additions and 26 deletions

View File

@ -2939,6 +2939,19 @@ public class EstimateService {
return pcsItemList; 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<ItemRequest>();
}
// circuit 괄호 제거 (: "(1-1)" -> "1-1") // circuit 괄호 제거 (: "(1-1)" -> "1-1")
if (moduleList != null) { if (moduleList != null) {
for (ItemRequest module : moduleList) { 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(); return sum.toPlainString();
} }

View File

@ -407,7 +407,7 @@
SELECT SELECT
cie.CIRCUIT_NO, cie.OBJECT_NO, cie.PLAN_NO, cie.CIRCUIT_NO, cie.OBJECT_NO, cie.PLAN_NO,
cie.ITEM_ID AS CIRCUIT_ITEM_ID, cie.CIRCUIT_CFG, cie.ITEM_ID AS CIRCUIT_ITEM_ID, cie.CIRCUIT_CFG,
cie.CONN_VOL_W, -- 2026-06-30: 저장 시점 유닛별 접속용량(W). 있으면 위치기반 추정보다 우선 사용(동일 PCS 모델 다중 유닛 대응) 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)) (SELECT SUM(CAST(LTRIM(RTRIM(x.X.value('.','VARCHAR(10)'))) AS INT))
FROM (SELECT CAST('<X>'+REPLACE(cie.CIRCUIT_CFG,',','</X><X>')+'</X>' AS XML) AS d) t FROM (SELECT CAST('<X>'+REPLACE(cie.CIRCUIT_CFG,',','</X><X>')+'</X>' AS XML) AS d) t
CROSS APPLY t.d.nodes('/X') AS x(X) CROSS APPLY t.d.nodes('/X') AS x(X)
@ -418,14 +418,11 @@
), ),
CC AS ( CC AS (
SELECT cs.*, SELECT cs.*,
-- 2026-06-30: 누적 윈도우를 PCS(CIRCUIT_ITEM_ID) 내부로 한정 — 회로 위치가 다른 PCS 모듈까지 끌어오던 오배분 차단
ISNULL((SELECT SUM(c2.CIRCUIT_SUM) FROM CS c2 ISNULL((SELECT SUM(c2.CIRCUIT_SUM) FROM CS c2
WHERE c2.OBJECT_NO=cs.OBJECT_NO AND c2.PLAN_NO=cs.PLAN_NO 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, AND c2.CIRCUIT_NO < cs.CIRCUIT_NO), 0) AS CUM_START,
ISNULL((SELECT SUM(c2.CIRCUIT_SUM) FROM CS c2 ISNULL((SELECT SUM(c2.CIRCUIT_SUM) FROM CS c2
WHERE c2.OBJECT_NO=cs.OBJECT_NO AND c2.PLAN_NO=cs.PLAN_NO 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 AND c2.CIRCUIT_NO <= cs.CIRCUIT_NO), 0) AS CUM_END
FROM CS cs FROM CS cs
), ),
@ -434,56 +431,43 @@
rie.ROOF_ITEM_NO, rie.ROOF_SURFACE_ID, rie.OBJECT_NO, rie.PLAN_NO, rie.ROOF_ITEM_NO, rie.ROOF_SURFACE_ID, rie.OBJECT_NO, rie.PLAN_NO,
rie.ITEM_ID AS ROOF_ITEM_ID, rie.ITEM_NAME, rie.ITEM_ID AS ROOF_ITEM_ID, rie.ITEM_NAME,
CAST(rie.SPECIFICATION AS FLOAT) AS SPEC, rie.AMOUNT, rie.PC_ITEM_ID, 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 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 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, 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 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 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 AND r2.ROOF_ITEM_NO <= rie.ROOF_ITEM_NO), 0) AS CUM_END
FROM T_PART_ROOF_ITEM_ESTIMATE rie FROM T_PART_ROOF_ITEM_ESTIMATE rie
WHERE rie.OBJECT_NO = #{objectNo} AND rie.PLAN_NO = #{planNo} WHERE rie.OBJECT_NO = #{objectNo} AND rie.PLAN_NO = #{planNo}
), ),
ALLOC AS ( ALLOC AS (
SELECT 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, rc.ROOF_ITEM_NO, rc.ROOF_ITEM_ID, rc.SPEC, rc.PC_ITEM_ID,
-- 할당량 = MIN(끝,끝) - MAX(시작,시작) -- 할당량 = MIN(끝,끝) - MAX(시작,시작)
CASE WHEN cc.CUM_END < rc.CUM_END THEN cc.CUM_END ELSE rc.CUM_END END 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 - CASE WHEN cc.CUM_START > rc.CUM_START THEN cc.CUM_START ELSE rc.CUM_START END AS ALLOCATED_QTY
FROM CC cc 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 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 WHERE cc.CUM_START < rc.CUM_END AND cc.CUM_END > rc.CUM_START
), ),
DETAIL AS ( DETAIL AS (
SELECT 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, a.ROOF_ITEM_NO, a.ROOF_ITEM_ID, a.ALLOCATED_QTY,
ROUND(a.SPEC, 3) * a.ALLOCATED_QTY AS SPEC_X_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 (SELECT mi.PNOW_W FROM M_ITEM mi WHERE mi.ITEM_ID = a.CIRCUIT_ITEM_ID) AS PNOW_W
FROM ALLOC a 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 ( CIRCUIT_AGG AS (
-- 2026-06-30 option B: 저장된 CONN_VOL_W 우선, 없으면(NULL=기존 데이터) 위치기반 POS_W 폴백. -- 2026-07-07: 회로(유닛)별 접속용량 CONN_W 결정 —
-- CS(유닛 목록) 기준으로 LEFT JOIN 하므로 유닛별 1행 보장. 유닛별 MIN(접속용량, PNOW_W) 후 합산. -- NULLIF(CONN_VOL_W,0) 으로 저장값이 비0 일 때만 채택, NULL·0 이면 위치기반 합(SUM(SPEC_X_QTY))으로 폴백.
-- → 신규/재저장 건은 정확한 유닛별 접속용량, 기존·거짓0 데이터는 롤백판(위치기반)과 동일 동작.
SELECT SELECT
cs.CIRCUIT_NO, cs.CIRCUIT_ITEM_ID, CIRCUIT_NO, CIRCUIT_ITEM_ID, CIRCUIT_CFG, CIRCUIT_SUM, PNOW_W,
COALESCE(cs.CONN_VOL_W, pos.POS_W, 0) AS CONN_W, COALESCE(NULLIF(CONN_VOL_W, 0), SUM(SPEC_X_QTY)) AS CONN_W
(SELECT mi.PNOW_W FROM M_ITEM mi WHERE mi.ITEM_ID = cs.CIRCUIT_ITEM_ID) AS PNOW_W FROM DETAIL
FROM CS cs GROUP BY CIRCUIT_NO, CIRCUIT_ITEM_ID, CIRCUIT_CFG, CIRCUIT_SUM, PNOW_W, CONN_VOL_W
LEFT JOIN POS pos
ON pos.CIRCUIT_NO = cs.CIRCUIT_NO
AND pos.CIRCUIT_ITEM_ID = cs.CIRCUIT_ITEM_ID
) )
-- 2026-06-05: SQL 은 W 합만 DECIMAL 로 정확히 반환. kW 변환·3자리 절사(FLOOR)는 모듈용량(totVolKw)과 동일하게 Java BigDecimal 에서 수행 → FLOAT 나눗셈 잔여(5.6899999999999995 / 5.339) 원천 차단 -- 2026-06-05: SQL 은 W 합만 DECIMAL 로 정확히 반환. kW 변환·3자리 절사(FLOOR)는 모듈용량(totVolKw)과 동일하게 Java BigDecimal 에서 수행 → FLOAT 나눗셈 잔여(5.6899999999999995 / 5.339) 원천 차단