출하지시/판매등록 통합 및 S/N 마스터 기준 조회로 변경

- 출하지시/판매등록 버튼: 항상 신규 shipment_log 생성 (기존 분할출하와 통합)
- 출하일 상세 팝업: 행 클릭 시 해당 shipment_log 수정 가능
- S/N 조회: shipment_log 스냅샷 → CONTRACT_ITEM_SERIAL 마스터 기준으로 변경
- 수정 시 최신 S/N 표시, 저장 시 마스터에 sync

Made-with: Cursor
This commit is contained in:
2026-03-09 11:35:53 +09:00
parent 30f644a2fc
commit b643c9d494
7 changed files with 566 additions and 158 deletions

View File

@@ -844,19 +844,22 @@
FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PAYMENT_TYPE,
T.PART_NO AS PRODUCT_NO,
T.PART_NAME AS PRODUCT_NAME,
-- S/N: 해당 품목의 시리얼 번호 (여러 개일 경우 "S/N 외 N건" 형식)
(SELECT
CASE
WHEN COUNT(*) = 0 THEN ''
WHEN COUNT(*) = 1 THEN MIN(CIS.SERIAL_NO)
ELSE MIN(CIS.SERIAL_NO) || ' 외 ' || (COUNT(*) - 1)::TEXT || '건'
END
FROM CONTRACT_ITEM CI
LEFT JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID AND UPPER(CIS.STATUS) = 'ACTIVE'
WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID
AND CI.PART_OBJID = T.PART_OBJID
AND CI.STATUS = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL) AS SERIAL_NO,
-- S/N: CONTRACT_ITEM_SERIAL(마스터) 우선, 없으면 판매등록 텍스트 fallback (그리드 요약용)
COALESCE(
(SELECT
CASE
WHEN COUNT(*) = 0 THEN NULL
WHEN COUNT(*) = 1 THEN MIN(CIS.SERIAL_NO)
ELSE MIN(CIS.SERIAL_NO) || ' 외 ' || (COUNT(*) - 1)::TEXT || '건'
END
FROM CONTRACT_ITEM CI
LEFT JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID AND UPPER(CIS.STATUS) = 'ACTIVE'
WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID
AND CI.PART_OBJID = T.PART_OBJID
AND CI.STATUS = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL),
SR.serial_no
) AS SERIAL_NO,
COALESCE(T.QUANTITY::numeric, 0) AS ORDER_QUANTITY,
-- 요청납기: CONTRACT_ITEM 우선, 없으면 PROJECT_MGMT, 없으면 CONTRACT_MGMT
COALESCE(
@@ -893,20 +896,10 @@
COALESCE(SR.sales_vat, 0) AS SALES_VAT,
COALESCE(SR.sales_total_amount, 0) AS SALES_TOTAL_AMOUNT,
COALESCE(SR.sales_total_amount, 0) AS SALES_TOTAL_AMOUNT_KRW,
-- 잔량 계산: 수주수량 - shipment_log의 split_quantity 합계
COALESCE(T.QUANTITY::numeric, 0) - COALESCE(
(SELECT SUM(COALESCE(split_quantity, 0))
FROM shipment_log
WHERE target_objid = T.PROJECT_NO),
0
) AS REMAINING_QUANTITY,
-- 잔량원화총액 계산: (수주수량 - shipment_log 합계) * 판매단가
(COALESCE(T.QUANTITY::numeric, 0) - COALESCE(
(SELECT SUM(COALESCE(split_quantity, 0))
FROM shipment_log
WHERE target_objid = T.PROJECT_NO),
0
)) * COALESCE(SR.sales_unit_price, 0) AS REMAINING_AMOUNT_KRW,
-- 잔량 계산: 수주수량 - sales_registration.sales_quantity
COALESCE(T.QUANTITY::numeric, 0) - COALESCE(SR.sales_quantity, 0) AS REMAINING_QUANTITY,
-- 잔량원화총액 계산: 잔량 * 판매단가
(COALESCE(T.QUANTITY::numeric, 0) - COALESCE(SR.sales_quantity, 0)) * COALESCE(SR.sales_unit_price, 0) AS REMAINING_AMOUNT_KRW,
COALESCE(SR.sales_currency, T.CONTRACT_CURRENCY) AS SALES_CURRENCY,
CODE_NAME(COALESCE(SR.sales_currency, T.CONTRACT_CURRENCY)) AS SALES_CURRENCY_NAME,
COALESCE(SR.sales_exchange_rate, T.CONTRACT_PRICE_CURRENCY::numeric, 0) AS SALES_EXCHANGE_RATE,
@@ -942,13 +935,14 @@
THEN '분할판매'
ELSE ''
END AS SALES_STATUS,
SR.sale_no AS SALES_REG_NO,
T.OBJID::VARCHAR AS SALE_NO,
'ORIGINAL' AS RECORD_TYPE,
'' AS SPLIT_LOG_ID,
-- 거래명세서 존재 여부
-- 거래명세서 존재 여부 (LinkedProjectObjids에 해당 프로젝트 OBJID 포함 여부)
CASE WHEN EXISTS(
SELECT 1 FROM NSWOS100_TBL
WHERE OdOrderNo = T.PROJECT_NO
SELECT 1 FROM NSWOS100_TBL
WHERE T.OBJID::VARCHAR = ANY(STRING_TO_ARRAY(LinkedProjectObjids, ','))
) THEN 'Y' ELSE 'N' END AS HAS_TRANSACTION_STATEMENT
FROM PROJECT_MGMT AS T
LEFT JOIN sales_registration SR ON T.PROJECT_NO = SR.project_no
@@ -1346,7 +1340,18 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
AND T.CUSTOMER_OBJID = #{customer_objid}
</if>
<if test="serialNo != null and serialNo != ''">
AND SR.serial_no LIKE '%' || #{serialNo} || '%'
AND (
EXISTS (
SELECT 1 FROM CONTRACT_ITEM CI
JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID
WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID
AND CI.PART_OBJID = T.PART_OBJID
AND CI.STATUS = 'ACTIVE'
AND UPPER(CIS.STATUS) = 'ACTIVE'
AND UPPER(CIS.SERIAL_NO) LIKE UPPER('%' || #{serialNo} || '%')
)
OR SR.serial_no LIKE '%' || #{serialNo} || '%'
)
</if>
<if test="poNo != null and poNo != ''">
AND EXISTS (SELECT 1 FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID AND CM.PO_NO LIKE '%' || #{poNo} || '%')
@@ -1550,19 +1555,17 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PAYMENT_TYPE,
T.PART_NO AS PRODUCT_NO,
T.PART_NAME AS PRODUCT_NAME,
-- S/N: 해당 품목의 시리얼 번호 (여러 개일 경우 "S/N 외 N건" 형식)
(SELECT
CASE
WHEN COUNT(*) = 0 THEN ''
WHEN COUNT(*) = 1 THEN MIN(CIS.SERIAL_NO)
ELSE MIN(CIS.SERIAL_NO) || ' 외 ' || (COUNT(*) - 1)::TEXT || '건'
END
FROM CONTRACT_ITEM CI
LEFT JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID AND UPPER(CIS.STATUS) = 'ACTIVE'
WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID
AND CI.PART_OBJID = T.PART_OBJID
AND CI.STATUS = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL) AS SERIAL_NO,
-- S/N: CONTRACT_ITEM_SERIAL(마스터) 우선, 전체 콤마 리스트 (판매등록 폼 파싱용)
COALESCE(
(SELECT STRING_AGG(CIS.SERIAL_NO, ', ' ORDER BY CIS.SERIAL_NO)
FROM CONTRACT_ITEM CI
LEFT JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID AND UPPER(CIS.STATUS) = 'ACTIVE'
WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID
AND CI.PART_OBJID = T.PART_OBJID
AND CI.STATUS = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL),
SR.serial_no
) AS SERIAL_NO,
COALESCE(T.QUANTITY::numeric, 0) AS ORDER_QUANTITY,
-- 요청납기: CONTRACT_ITEM 우선, 없으면 PROJECT_MGMT, 없으면 CONTRACT_MGMT
COALESCE(
@@ -1648,8 +1651,8 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
CODE_NAME(CM.CONTRACT_CURRENCY) AS SALES_CURRENCY_NAME,
COALESCE(CM.EXCHANGE_RATE::NUMERIC, 0) AS SALES_EXCHANGE_RATE,
-- 수주 날짜 - VARCHAR 타입이므로 그대로 사용
CM.ORDER_DATE AS SHIPPING_DATE,
-- 출하일 기본값: 오늘 날짜
TO_CHAR(CURRENT_DATE, 'YYYY-MM-DD') AS SHIPPING_DATE,
-- 담당자
CM.PM_USER_ID AS MANAGER
@@ -1662,7 +1665,6 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
CM.OBJID,
CM.CONTRACT_CURRENCY,
CM.EXCHANGE_RATE,
CM.ORDER_DATE,
CM.PM_USER_ID
</select>
@@ -1696,24 +1698,35 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
CODE_NAME(CM.CONTRACT_CURRENCY) AS SALES_CURRENCY_NAME,
COALESCE(NULLIF(CM.EXCHANGE_RATE, '')::NUMERIC, 0) AS SALES_EXCHANGE_RATE,
-- 수주 날짜 - VARCHAR 타입이므로 그대로 사용
CM.ORDER_DATE AS SHIPPING_DATE,
-- 출하일 기본값: 오늘 날짜
TO_CHAR(CURRENT_DATE, 'YYYY-MM-DD') AS SHIPPING_DATE,
-- 담당자
CM.PM_USER_ID AS MANAGER
CM.PM_USER_ID AS MANAGER,
-- 기존 S/N (CONTRACT_ITEM_SERIAL 마스터에서 조회)
COALESCE((
SELECT STRING_AGG(CIS2.SERIAL_NO, ',' ORDER BY CIS2.SEQ)
FROM CONTRACT_ITEM CI2
JOIN CONTRACT_ITEM_SERIAL CIS2 ON CI2.OBJID = CIS2.ITEM_OBJID AND CIS2.STATUS = 'ACTIVE'
WHERE CI2.CONTRACT_OBJID = PM.CONTRACT_OBJID
AND CI2.PART_OBJID = PM.PART_OBJID
AND CI2.STATUS = 'ACTIVE'
), '') AS SERIAL_NO
FROM PROJECT_MGMT PM
INNER JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID
LEFT JOIN CONTRACT_ITEM CI ON CM.OBJID::VARCHAR = CI.CONTRACT_OBJID AND UPPER(CI.STATUS) = 'ACTIVE'
WHERE PM.PROJECT_NO = #{orderNo}
GROUP BY
GROUP BY
CM.CONTRACT_NO,
CM.OBJID,
CM.CONTRACT_CURRENCY,
CM.EXCHANGE_RATE,
CM.ORDER_DATE,
CM.PM_USER_ID,
PM.QUANTITY
PM.QUANTITY,
PM.CONTRACT_OBJID,
PM.PART_OBJID
</select>
<!--
@@ -1920,7 +1933,31 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
WHERE target_objid = #{projectNo}
),
shipping_order_status = '출하지시'
WHERE sale_no = #{saleNo}
<if test="managerUserId != null and managerUserId != ''">
, manager_user_id = #{managerUserId}
</if>
<if test="incoterms != null and incoterms != ''">
, incoterms = #{incoterms}
</if>
<if test="serialNo != null and serialNo != ''">
, serial_no = #{serialNo}
</if>
<if test="shippingMethod != null and shippingMethod != ''">
, shipping_method = #{shippingMethod}
</if>
<if test="shippingDate != null and shippingDate != ''">
, shipping_date = TO_DATE(#{shippingDate}, 'YYYY-MM-DD')
</if>
<if test="salesUnitPrice != null and salesUnitPrice != ''">
, sales_unit_price = #{salesUnitPrice}::numeric
</if>
<if test="salesCurrency != null and salesCurrency != ''">
, sales_currency = #{salesCurrency}
</if>
<if test="salesExchangeRate != null and salesExchangeRate != ''">
, sales_exchange_rate = #{salesExchangeRate}::numeric
</if>
WHERE sale_no = #{saleNo}::integer
</update>
<!-- PROJECT_MGMT의 OBJID 조회 (shipment_log의 target_objid로 사용) -->
@@ -1932,19 +1969,98 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
<!-- 출하일 상세 내역 조회 (모든 분할 출하 포함) -->
<select id="getShippingDetailList" parameterType="map" resultType="map">
/* salesNcollectMgmt.getShippingDetailList - shipment_log에서 모든 분할 출하 조회 */
SELECT
SELECT
SL.log_id,
COALESCE(TO_CHAR(SL.shipping_date, 'YYYY-MM-DD'), '미등록') AS shipping_date,
COALESCE(SL.split_quantity, 0) AS shipping_quantity,
COALESCE(SL.shipping_status, '미등록') AS shipping_order_status,
COALESCE(SL.serial_no, '-') AS serial_no,
COALESCE((
SELECT STRING_AGG(CIS.SERIAL_NO, ',' ORDER BY CIS.SEQ)
FROM CONTRACT_ITEM CI
JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID AND CIS.STATUS = 'ACTIVE'
JOIN PROJECT_MGMT PM ON PM.CONTRACT_OBJID = CI.CONTRACT_OBJID AND PM.PART_OBJID = CI.PART_OBJID
WHERE PM.PROJECT_NO = SL.target_objid AND CI.STATUS = 'ACTIVE'
), '-') AS serial_no,
SL.target_objid AS project_no,
'분할 출하 ' || SL.log_id AS source,
TO_CHAR(SL.reg_date, 'YYYY-MM-DD HH24:MI:SS') AS reg_date
FROM shipment_log SL
FROM shipment_log SL
WHERE SL.target_objid = #{projectNo}
ORDER BY SL.shipping_date DESC, SL.log_id DESC
</select>
<!-- shipment_log 단건 조회 (수정 팝업용) -->
<select id="getShipmentLogById" parameterType="map" resultType="map">
/* salesNcollectMgmt.getShipmentLogById - shipment_log 단건 조회 */
SELECT
SL.log_id,
SL.target_objid,
COALESCE(TO_CHAR(SL.shipping_date, 'YYYY-MM-DD'), '') AS SHIPPING_DATE,
COALESCE(SL.split_quantity, 0) AS SALES_QUANTITY,
COALESCE(SL.shipping_status, '') AS SHIPPING_ORDER_STATUS,
COALESCE(SL.shipping_method, '') AS SHIPPING_METHOD,
COALESCE((
SELECT STRING_AGG(CIS.SERIAL_NO, ',' ORDER BY CIS.SEQ)
FROM CONTRACT_ITEM CI
JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID AND CIS.STATUS = 'ACTIVE'
JOIN PROJECT_MGMT PM ON PM.CONTRACT_OBJID = CI.CONTRACT_OBJID AND PM.PART_OBJID = CI.PART_OBJID
WHERE PM.PROJECT_NO = SL.target_objid AND CI.STATUS = 'ACTIVE'
), '') AS SERIAL_NO,
COALESCE(SL.sales_unit_price, 0) AS SALES_UNIT_PRICE,
COALESCE(SL.sales_supply_price, 0) AS SALES_SUPPLY_PRICE,
COALESCE(SL.sales_vat, 0) AS SALES_VAT,
COALESCE(SL.sales_total_amount, 0) AS SALES_TOTAL_AMOUNT,
COALESCE(SL.sales_currency, '') AS SALES_CURRENCY,
COALESCE(SL.sales_exchange_rate, 0) AS SALES_EXCHANGE_RATE,
COALESCE(SL.manager_user_id, '') AS MANAGER,
COALESCE(SL.incoterms, '') AS INCOTERMS,
COALESCE(SL.original_quantity, 0) AS ORDER_QUANTITY,
COALESCE(SL.remaining_quantity, 0) AS REMAINING_QUANTITY
FROM shipment_log SL
WHERE SL.log_id = #{logId}::integer
</select>
<!-- shipment_log 수정 -->
<update id="updateShipmentLog" parameterType="map">
/* salesNcollectMgmt.updateShipmentLog - shipment_log 수정 */
UPDATE shipment_log SET
split_quantity = #{salesQuantity}::integer,
shipping_status = #{shippingOrderStatus}
<if test="shippingDate != null and shippingDate != ''">
, shipping_date = TO_DATE(#{shippingDate}, 'YYYY-MM-DD')
</if>
<if test="shippingMethod != null and shippingMethod != ''">
, shipping_method = #{shippingMethod}
</if>
<if test="serialNo != null and serialNo != ''">
, serial_no = #{serialNo}
</if>
<if test="salesUnitPrice != null and salesUnitPrice != ''">
, sales_unit_price = #{salesUnitPrice}::numeric
</if>
<if test="salesSupplyPrice != null and salesSupplyPrice != ''">
, sales_supply_price = #{salesSupplyPrice}::numeric
</if>
<if test="salesVat != null and salesVat != ''">
, sales_vat = #{salesVat}::numeric
</if>
<if test="salesTotalAmount != null and salesTotalAmount != ''">
, sales_total_amount = #{salesTotalAmount}::numeric
</if>
<if test="salesCurrency != null and salesCurrency != ''">
, sales_currency = #{salesCurrency}
</if>
<if test="salesExchangeRate != null and salesExchangeRate != ''">
, sales_exchange_rate = #{salesExchangeRate}::numeric
</if>
<if test="managerUserId != null and managerUserId != ''">
, manager_user_id = #{managerUserId}
</if>
<if test="incoterms != null and incoterms != ''">
, incoterms = #{incoterms}
</if>
WHERE log_id = #{logId}::integer
</update>
<!-- 거래명세서 - 고객 정보 조회 -->
<select id="getCustomerInfoByProjectNo" parameterType="map" resultType="map">
/* salesNcollectMgmt.getCustomerInfoByProjectNo - 프로젝트 번호로 고객 정보 조회 */
@@ -1976,7 +2092,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
WHERE PM.PROJECT_NO = #{projectNo}
</select>
<!-- 거래명세서 저장 - NSWOS100_TBL 테이블 사용 -->
<!-- 거래명세서 저장 - NSWOS100_TBL 테이블 사용 (ON CONFLICT DO UPDATE) -->
<insert id="saveTransactionStatement" parameterType="map">
/* salesNcollectMgmt.saveTransactionStatement - 거래명세서 저장 */
INSERT INTO NSWOS100_TBL (
@@ -1995,6 +2111,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
,IsQty /* 납품수량 */
,IsPrice /* 납품단가 */
,IsAmount /* 납품금액 */
,LinkedProjectObjids /* 연결된 프로젝트 OBJID 목록 */
) VALUES (
#{suVndCd} /* 업체코드 */
,#{issueDt} /* 작성일자 */
@@ -2011,6 +2128,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
,COALESCE(#{isQty}, 0)::integer /* 납품수량 */
,COALESCE(#{isPrice}, 0)::numeric /* 납품단가 */
,COALESCE(#{isAmount}, 0)::numeric /* 납품금액 */
,#{linkedProjectObjids} /* 연결된 프로젝트 OBJID 목록 */
) ON CONFLICT (SuVndCd, IssueDt, IssueNo, IsNo) DO
UPDATE SET
ProdCd = COALESCE(#{prodCd}, '')
@@ -2024,6 +2142,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
,IsQty = COALESCE(#{isQty}, 0)::integer
,IsPrice = COALESCE(#{isPrice}, 0)::numeric
,IsAmount = COALESCE(#{isAmount}, 0)::numeric
,LinkedProjectObjids = #{linkedProjectObjids}
</insert>
<!-- 거래명세서 번호 생성 -->
@@ -2051,7 +2170,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
<select id="getSavedTransactionStatement" parameterType="map" resultType="map">
/* salesNcollectMgmt.getSavedTransactionStatement - 저장된 거래명세서 조회 */
SELECT
SELECT
SuVndCd,
IssueDt,
IssueNo,
@@ -2066,9 +2185,10 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
IsDt,
IsQty,
IsPrice,
IsAmount
IsAmount,
LinkedProjectObjids
FROM NSWOS100_TBL
WHERE OdOrderNo = #{projectNo}
WHERE #{projectObjid} = ANY(STRING_TO_ARRAY(LinkedProjectObjids, ','))
ORDER BY IsNo
</select>
@@ -2142,6 +2262,102 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
LOADING_DATE = #{loadingDate}
WHERE OBJID::VARCHAR = #{OBJID}
</update>
<!--
/**
* 매출마감 전표연동용 데이터 조회
* OBJID 리스트로 전표 생성에 필요한 모든 정보를 일괄 조회한다.
* @since 2026.02.24
* @author system
* @version 1.0
**/
-->
<select id="getErpAccountCode" parameterType="map" resultType="com.pms.common.UpperKeyMap">
/* salesNcollectMgmt.getErpAccountCode - ERP 계정과목코드 조회 */
SELECT acct_cd AS ACCT_CD, acct_nm AS ACCT_NM
FROM erp_acct_code
WHERE co_cd = #{coCd}
AND acct_nm = #{acctNm}
LIMIT 1
</select>
<select id="getSlipDataForDeadline" parameterType="map" resultType="com.pms.common.UpperKeyMap">
/* salesNcollectMgmt.getSlipDataForDeadline - 매출마감 전표연동용 데이터 조회 */
SELECT
PM.OBJID,
PM.PROJECT_NO,
PM.AREA_CD,
PM.CUSTOMER_OBJID,
PM.PART_NO,
PM.PART_NAME,
COALESCE(PM.TAX_TYPE, '') AS TAX_TYPE,
COALESCE(PM.TAX_INVOICE_DATE, '') AS TAX_INVOICE_DATE,
COALESCE(PM.EXPORT_DECL_NO, '') AS EXPORT_DECL_NO,
COALESCE(PM.LOADING_DATE, '') AS LOADING_DATE,
COALESCE(PM.SALES_STATUS, '') AS SALES_STATUS,
/* 거래처 정보: ERP 연동 거래처(C_ 접두어)에서 client_cd 조회 */
CASE WHEN PM.CUSTOMER_OBJID LIKE 'C_%' THEN
(SELECT C.CLIENT_CD FROM CLIENT_MNG C WHERE 'C_' || C.OBJID::VARCHAR = PM.CUSTOMER_OBJID)
ELSE NULL END AS ERP_CLIENT_CD,
CASE WHEN PM.CUSTOMER_OBJID LIKE 'C_%' THEN
(SELECT C.CLIENT_NM FROM CLIENT_MNG C WHERE 'C_' || C.OBJID::VARCHAR = PM.CUSTOMER_OBJID)
ELSE
(SELECT S.SUPPLY_NAME FROM SUPPLY_MNG S WHERE S.OBJID::VARCHAR = PM.CUSTOMER_OBJID::VARCHAR)
END AS CUSTOMER_NAME,
CASE WHEN PM.CUSTOMER_OBJID LIKE 'C_%' THEN
(SELECT C.BUS_REG_NO FROM CLIENT_MNG C WHERE 'C_' || C.OBJID::VARCHAR = PM.CUSTOMER_OBJID)
ELSE NULL END AS BUS_REG_NO,
/* 판매 금액 정보 */
COALESCE(SR.SALES_SUPPLY_PRICE, 0) AS SALES_SUPPLY_PRICE,
COALESCE(SR.SALES_VAT, 0) AS SALES_VAT,
COALESCE(SR.SALES_TOTAL_AMOUNT, 0) AS SALES_TOTAL_AMOUNT,
/* 환종/환율 (해외) */
COALESCE(SR.SALES_CURRENCY, PM.CONTRACT_CURRENCY) AS SALES_CURRENCY,
COALESCE(SR.SALES_EXCHANGE_RATE, 0) AS SALES_EXCHANGE_RATE,
/* 외화 총액 계산: 원화총액 / 환율 (환율이 0이 아닐 때) */
CASE WHEN COALESCE(SR.SALES_EXCHANGE_RATE, 0) > 0 THEN
ROUND(COALESCE(SR.SALES_TOTAL_AMOUNT, 0)::NUMERIC / SR.SALES_EXCHANGE_RATE::NUMERIC, 2)
ELSE 0 END AS FOREIGN_AMOUNT
FROM PROJECT_MGMT PM
LEFT JOIN SALES_REGISTRATION SR ON PM.PROJECT_NO = SR.PROJECT_NO
WHERE PM.OBJID::VARCHAR = #{OBJID}
</select>
<!--
/**
* 매출마감 전표번호 시퀀스 조회
* 동일 일자의 최대 작성번호를 조회하여 다음 번호를 부여한다.
* @since 2026.02.24
* @author system
* @version 1.0
**/
-->
<select id="getNextSlipMenuSq" parameterType="map" resultType="int">
/* salesNcollectMgmt.getNextSlipMenuSq - 다음 전표 작성번호 조회 */
SELECT COALESCE(MAX(SLIP_MENU_SQ), 0) + 1
FROM PROJECT_MGMT
WHERE SALES_SLIP_DATE = #{slipDate}
AND SALES_SLIP_MENU_SQ IS NOT NULL
</select>
<!--
/**
* 매출마감 완료 후 전표 정보 저장
* @since 2026.02.24
* @author system
* @version 1.0
**/
-->
<update id="updateSlipInfo" parameterType="map">
/* salesNcollectMgmt.updateSlipInfo - 전표 연동 정보 저장 */
UPDATE PROJECT_MGMT
SET
SALES_STATUS = '완료',
SALES_DEADLINE_DATE = #{deadlineDate},
SALES_SLIP_DATE = #{slipDate},
SALES_SLIP_MENU_SQ = #{slipMenuSq}
WHERE OBJID::VARCHAR = #{OBJID}
</update>
</mapper>

View File

@@ -620,19 +620,21 @@ function fn_openTransactionStatementPopup(projectNo, projectObjid, hasStatement)
}
function fn_openShippingDetail(projectNo) {
console.log("=== fn_openShippingDetail 호출 ===");
console.log("전달받은 projectNo:", projectNo);
console.log("projectNo 타입:", typeof projectNo);
if(!projectNo) {
alert("프로젝트 번호가 없습니다.");
return;
}
var url = "/salesMgmt/shippingDetailPopup.do?projectNo=" + encodeURIComponent(projectNo);
console.log("팝업 URL:", url);
window.open(url, "shippingDetailPopup", "width=800,height=600,scrollbars=yes,resizable=yes");
var detailPopup = window.open(url, "shippingDetailPopup", "width=800,height=600,scrollbars=yes,resizable=yes");
// 팝업 닫힐 때 목록 새로고침
var checkPopup = setInterval(function() {
if(detailPopup.closed) {
clearInterval(checkPopup);
fn_search();
}
}, 500);
}
// 분할출하 함수 - 선택한 항목을 동일 프로젝트번호로 분할출하
@@ -702,13 +704,8 @@ function fn_bulkRegister(){
}
}
if(selectedRow.SALES_REG_NO) {
// 기존 판매등록 수정 모드
fn_openSaleRegPopup(selectedRow.PROJECT_NO, selectedRow.SALES_REG_NO);
} else {
// 신규 판매등록
fn_openSaleRegPopupWithData(selectedRow);
}
// 항상 신규 출하 등록 (수정은 출하일 상세 팝업에서)
fn_openSaleRegPopupWithData(selectedRow);
}
</script>
<style>

View File

@@ -596,54 +596,46 @@ function fn_calculateSelectedItem() {
dataType : "json",
success : function(data) {
alert(data.msg);
// 저장 후 잔량 확인을 위해 서버에 다시 조회
// 수정 모드(logId)면 바로 닫기 (부모 팝업이 자동 새로고침)
var logIdVal = $("input[name='logId']").val();
if(logIdVal && logIdVal.trim() !== '') {
self.close();
return;
}
// 신규 등록 모드: 저장 후 잔량 확인
$.ajax({
url: "/salesMgmt/salesRegForm.do",
type: "GET",
data: { orderNo: "${param.orderNo}" },
dataType: "html",
success: function(response) {
// 응답에서 SALES_QUANTITY 추출 (잔량)
var match = response.match(/SALES_QUANTITY:\s*(\d+)/);
var remainingQuantity = match ? parseInt(match[1]) : 0;
console.log("서버에서 계산한 잔량:", remainingQuantity);
if(remainingQuantity > 0) {
if(confirm("잔량 " + remainingQuantity + "개가 남았습니다. 계속 등록하시겠습니까?")) {
// 부모 창 새로고침 후 팝업 새로고침
if(opener && opener.fn_search) {
console.log("분할 출하 계속 - 부모 창 fn_search 호출");
opener.fn_search();
}
// 팝업 새로고침 (잔량으로 수량 자동 설정)
location.reload();
} else {
// 목록 새로고침 후 팝업 닫기
console.log("분할 출하 중단 - 팝업 닫기");
if(opener && opener.fn_search) {
console.log("부모 창 fn_search 호출");
opener.fn_search();
} else {
console.log("부모 창 fn_search 없음!");
}
self.close();
}
} else {
// 목록 새로고침 후 팝업 닫기
console.log("잔량 없음 - 팝업 닫기");
if(opener && opener.fn_search) {
console.log("부모 창 fn_search 호출");
opener.fn_search();
} else {
console.log("부모 창 fn_search 없음!");
}
self.close();
}
},
error: function() {
// 에러 시 그냥 팝업 닫기
if(opener && opener.fn_search) {
opener.fn_search();
}
@@ -680,14 +672,19 @@ function fn_calculateSelectedItem() {
<body>
<form name="form1" id="form1" action="" method="post">
<input type="hidden" name="orderNo" value="${param.orderNo}">
<input type="hidden" name="saleNo" value="${param.saleNo}">
<input type="hidden" name="logId" value="${logId}">
<!-- serialNoList는 name 속성 없이 id만 사용 (클라이언트 전용) -->
<input type="hidden" id="serialNoList" value="${saleInfo.SERIAL_NO_LIST}" />
<section class="business_popup_min_width">
<div class="plm_menu_name">
<h2>
<span>판매 등록 (주문번호: ${param.orderNo})</span>
<span>
<c:choose>
<c:when test="${not empty logId}">출하 수정 (주문번호: ${param.orderNo})</c:when>
<c:otherwise>판매 등록 (주문번호: ${param.orderNo})</c:otherwise>
</c:choose>
</span>
</h2>
</div>

View File

@@ -13,6 +13,7 @@ body {
margin: 0;
padding: 20px;
font-family: 'Malgun Gothic', sans-serif;
width: auto !important;
}
.popup-header {
font-size: 18px;
@@ -38,6 +39,15 @@ body {
border: 1px solid #ddd;
text-align: center;
}
.shipping-date-link {
color: #2196F3;
text-decoration: underline;
cursor: pointer;
}
.shipping-date-link:hover {
color: #1565C0;
font-weight: bold;
}
.btn-close {
padding: 8px 20px;
background-color: #666;
@@ -54,6 +64,24 @@ body {
margin-top: 20px;
}
</style>
<script>
function fn_openEditPopup(logId, orderNo) {
var popup_width = 850;
var popup_height = 420;
var url = "/salesMgmt/salesRegForm.do?orderNo=" + encodeURIComponent(orderNo) + "&logId=" + logId;
var left = (screen.width - popup_width) / 2;
var top = (screen.height - popup_height) / 2;
var editPopup = window.open(url, "salesRegEdit_" + logId,
"width=" + popup_width + ",height=" + popup_height + ",left=" + left + ",top=" + top + ",scrollbars=yes,resizable=yes");
var checkPopup = setInterval(function() {
if(editPopup.closed) {
clearInterval(checkPopup);
location.reload();
}
}, 500);
}
</script>
</head>
<body>
<div class="popup-header">
@@ -79,7 +107,12 @@ body {
<c:otherwise>
<c:forEach items="${shippingList}" var="item">
<tr>
<td>${item.shipping_date}</td>
<td>
<span class="shipping-date-link"
onclick="fn_openEditPopup('${item.log_id}', '${projectNo}')">
${item.shipping_date}
</span>
</td>
<td>${item.shipping_quantity}</td>
<td>${item.shipping_order_status}</td>
<td>${item.serial_no}</td>