Compare commits

...

16 Commits

Author SHA1 Message Date
leeheejin
c394900a83 auto commit 2025-11-12 18:56:43 +09:00
leeheejin
479025bb79 저장 2025-11-12 18:33:33 +09:00
leeheejin
606172fada 일단 브랜치에 커밋 2025-11-12 18:29:20 +09:00
leeheejin
fdd5346c99 일단 브랜치에 커밋만 2025-11-12 18:29:11 +09:00
leeheejin
6b3a2c1cf1 분할 출하 기능 완료 - 잔량 계산 수정 2025-11-12 15:40:51 +09:00
c366b71174 Merge pull request '수주등록 견적서 내용 기본입력되도록 수정' (#60) from feature/estimate-template-improvements into main
Reviewed-on: #60
2025-11-11 07:51:46 +00:00
da883f0007 수주등록 견적서 내용 기본입력되도록 수정 2025-11-11 16:47:29 +09:00
leeheejin
2bcc39ff65 Merge branch 'V2025111104' 2025-11-11 14:49:26 +09:00
leeheejin
0b291c4ea2 오류수정 2025-11-11 14:48:22 +09:00
0817253285 Merge pull request 'feature/estimate-template-improvements' (#59) from feature/estimate-template-improvements into main
Reviewed-on: #59
2025-11-11 05:14:16 +00:00
7ae57f9719 장비견적서 템플릿 수정, pdf 변환 메일 발송 2025-11-11 14:09:11 +09:00
a9836b599b 메일작성 변경 2025-11-11 14:09:11 +09:00
9aafeb3def 견적서 기본 템플릿 수정 2025-11-11 14:09:11 +09:00
c89266ec0f Merge pull request 'V2025111101' (#58) from V2025111101 into main
Reviewed-on: #58
2025-11-11 05:00:17 +00:00
Johngreen
1c0e673068 Merge branch 'main' into V2025111101 2025-11-11 11:45:49 +09:00
Johngreen
ebc27216f4 영업관리_판매관리페이지 체크박스추가 2025-11-11 11:45:46 +09:00
24 changed files with 6054 additions and 1101 deletions

View File

@@ -3205,4 +3205,32 @@ SELECT option_objid::VARCHAR AS CODE
WHERE USER_ID = #{userId}
AND MASTER_OBJID::varchar = #{masterObjid}
</select>
<!-- 고객사 정보 조회 -->
<select id="getSupplyInfo" parameterType="map" resultType="map">
SELECT
OBJID,
SUPPLY_CODE,
SUPPLY_NAME,
REG_NO,
SUPPLY_ADDRESS,
SUPPLY_BUSNAME,
SUPPLY_STOCKNAME,
SUPPLY_TEL_NO,
SUPPLY_FAX_NO,
CHARGE_USER_NAME,
PAYMENT_METHOD,
MANAGER1_NAME,
MANAGER1_EMAIL,
MANAGER2_NAME,
MANAGER2_EMAIL,
MANAGER3_NAME,
MANAGER3_EMAIL,
MANAGER4_NAME,
MANAGER4_EMAIL,
MANAGER5_NAME,
MANAGER5_EMAIL
FROM SUPPLY_MNG
WHERE OBJID = #{objId}::numeric
</select>
</mapper>

View File

@@ -790,6 +790,102 @@
ORDER BY REGDATE DESC
</select>
<!-- 주문서관리 Total 합계 조회 (조회된 데이터 전체 합계) -->
<select id="getContractGridTotalAmount" parameterType="map" resultType="map">
/* contractMgmt.getContractGridTotalAmount */
SELECT
COALESCE(SUM(COALESCE(T.ORDER_TOTAL_AMOUNT_KRW, 0)), 0) AS "totalAmountKRW"
FROM
<include refid="contractBase"/> T
WHERE 1=1
<if test="Year !=null and Year != '' ">
AND SUBSTR(CONTRACT_DATE,0,5) = #{Year}
</if>
<if test="category_cd !=null and category_cd != '' ">
AND category_cd = #{category_cd}
</if>
<if test="customer_objid !=null and customer_objid != '' ">
AND customer_objid = #{customer_objid}
</if>
<if test="product != null and product !='' ">
AND product = #{product}
</if>
<if test="status_cd !=null and status_cd !=''">
AND status_cd = #{status_cd}
</if>
<if test="result_cd !=null and result_cd !=''">
AND result_cd = #{result_cd}
</if>
<if test="contract_result !=null and contract_result !=''">
AND contract_result = #{contract_result}
</if>
<if test="contract_start_date != null and !''.equals(contract_start_date)">
AND TO_DATE(CONTRACT_DATE,'YYYY-MM-DD') <![CDATA[ >= ]]> TO_DATE(#{contract_start_date}, 'YYYY-MM-DD')
</if>
<if test="contract_end_date != null and !''.equals(contract_end_date)">
AND TO_DATE(CONTRACT_DATE,'YYYY-MM-DD') <![CDATA[ <= ]]> TO_DATE(#{contract_end_date}, 'YYYY-MM-DD')
</if>
<if test="pm_user_id !=null and pm_user_id !=''">
AND pm_user_id = #{pm_user_id}
</if>
<if test="contract_month != null and !''.equals(contract_month)">
AND TO_DATE(TO_CHAR(TO_DATE(CONTRACT_DATE,'YYYY-MM-DD'),'YYYY-MM-DD'),'YYYY-MM') <![CDATA[ = ]]> TO_DATE(SUBSTRING(#{contract_month} FROM 1 FOR 4) || '-' || SUBSTRING(#{contract_month} FROM 5 FOR 2), 'YYYY-MM')
</if>
<!-- 견적관리 추가 검색조건 -->
<if test="appr_status !=null and appr_status != '' ">
AND APPR_STATUS = #{appr_status}
</if>
<if test="area_cd != null and area_cd !='' ">
AND AREA_CD = #{area_cd}
</if>
<if test="paid_type != null and paid_type !='' ">
AND PAID_TYPE = #{paid_type}
</if>
<!-- 품번/품명 검색: PART_OBJID로 정확하게 검색 -->
<if test="search_partObjId != null and search_partObjId != ''">
AND EXISTS (
SELECT 1
FROM CONTRACT_ITEM CI
WHERE CI.CONTRACT_OBJID = T.OBJID
AND CI.STATUS = 'ACTIVE'
AND CI.PART_OBJID = #{search_partObjId}
)
</if>
<if test="search_serialNo != null and search_serialNo != ''">
AND UPPER(SERIAL_NO) LIKE UPPER('%${search_serialNo}%')
</if>
<if test="search_poNo != null and search_poNo != ''">
AND UPPER(PO_NO) LIKE UPPER('%${search_poNo}%')
</if>
<if test="order_start_date != null and !''.equals(order_start_date)">
AND TO_DATE(ORDER_DATE,'YYYY-MM-DD') <![CDATA[ >= ]]> TO_DATE(#{order_start_date}, 'YYYY-MM-DD')
</if>
<if test="order_end_date != null and !''.equals(order_end_date)">
AND TO_DATE(ORDER_DATE,'YYYY-MM-DD') <![CDATA[ <= ]]> TO_DATE(#{order_end_date}, 'YYYY-MM-DD')
</if>
<if test="due_start_date != null and !''.equals(due_start_date)">
AND TO_DATE(DUE_DATE,'YYYY-MM-DD') <![CDATA[ >= ]]> TO_DATE(#{due_start_date}, 'YYYY-MM-DD')
</if>
<if test="due_end_date != null and !''.equals(due_end_date)">
AND TO_DATE(DUE_DATE,'YYYY-MM-DD') <![CDATA[ <= ]]> TO_DATE(#{due_end_date}, 'YYYY-MM-DD')
</if>
</select>
<select id="contractList_bak" parameterType="map" resultType="map">
SELECT *
FROM (
@@ -2356,6 +2452,16 @@ SELECT
,BUS_REG_NO
,OFFICE_NO
,EMAIL
,MANAGER1_NAME
,MANAGER1_EMAIL
,MANAGER2_NAME
,MANAGER2_EMAIL
,MANAGER3_NAME
,MANAGER3_EMAIL
,MANAGER4_NAME
,MANAGER4_EMAIL
,MANAGER5_NAME
,MANAGER5_EMAIL
FROM SUPPLY_MNG
WHERE OBJID = #{objid}::numeric
</select>
@@ -3860,7 +3966,14 @@ ORDER BY ASM.SUPPLY_NAME
FROM
CONTRACT_MGMT AS T
WHERE
OBJID::VARCHAR = #{objId}
<choose>
<when test="templateObjId != null and templateObjId != '' and templateObjId != '-1' and (objId == null or objId == '' or objId == '-1')">
OBJID::VARCHAR = (SELECT CONTRACT_OBJID FROM ESTIMATE_TEMPLATE WHERE OBJID = #{templateObjId})
</when>
<otherwise>
OBJID::VARCHAR = #{objId}
</otherwise>
</choose>
</select>
<!-- 견적서 템플릿 목록 조회 (CONTRACT_OBJID 기준) -->
@@ -3925,6 +4038,12 @@ ORDER BY ASM.SUPPLY_NAME
ET.TOTAL_AMOUNT_KRW,
ET.MANAGER_NAME,
ET.MANAGER_CONTACT,
ET.PART_NAME,
ET.PART_OBJID,
ET.NOTES_CONTENT,
ET.VALIDITY_PERIOD,
ET.CATEGORIES_JSON,
ET.GROUP1_SUBTOTAL,
ET.WRITER,
ET.REGDATE,
ET.CHG_USER_ID,
@@ -3950,12 +4069,19 @@ ORDER BY ASM.SUPPLY_NAME
ESTIMATE_TEMPLATE ET
LEFT JOIN CONTRACT_MGMT CM ON ET.CONTRACT_OBJID = CM.OBJID
WHERE
ET.CONTRACT_OBJID = #{objId}
<if test="template_type != null and template_type != ''">
AND ET.TEMPLATE_TYPE = #{template_type}
</if>
ORDER BY ET.REGDATE DESC
LIMIT 1
<choose>
<when test="templateObjId != null and templateObjId != '' and templateObjId != '-1'">
ET.OBJID = #{templateObjId}
</when>
<otherwise>
ET.CONTRACT_OBJID = #{objId}
<if test="template_type != null and template_type != ''">
AND ET.TEMPLATE_TYPE = #{template_type}
</if>
ORDER BY ET.REGDATE DESC
LIMIT 1
</otherwise>
</choose>
</select>
<!-- 견적서 템플릿 데이터 조회 (OBJID 기준) -->
@@ -3981,6 +4107,13 @@ ORDER BY ASM.SUPPLY_NAME
ET.TOTAL_AMOUNT_KRW,
ET.MANAGER_NAME,
ET.MANAGER_CONTACT,
ET.SHOW_TOTAL_ROW,
ET.PART_NAME,
ET.PART_OBJID,
ET.NOTES_CONTENT,
ET.VALIDITY_PERIOD,
ET.CATEGORIES_JSON,
ET.GROUP1_SUBTOTAL,
ET.WRITER,
TO_CHAR(ET.REGDATE, 'YYYY-MM-DD HH24:MI') AS REGDATE,
ET.CHG_USER_ID,
@@ -4059,6 +4192,30 @@ ORDER BY ASM.SUPPLY_NAME
ORDER BY SEQ
</select>
<!-- 견적서 템플릿 품목 조회 (PART_OBJID로) -->
<select id="getEstimateTemplateItemByPartObjId" parameterType="map" resultType="map">
SELECT
OBJID,
TEMPLATE_OBJID,
SEQ,
CATEGORY,
PART_OBJID,
DESCRIPTION,
SPECIFICATION,
QUANTITY,
UNIT,
UNIT_PRICE,
AMOUNT,
NOTE,
REMARK
FROM
ESTIMATE_TEMPLATE_ITEM
WHERE
TEMPLATE_OBJID = #{templateObjId}
AND PART_OBJID = #{partObjId}
LIMIT 1
</select>
<!-- 견적서 템플릿 저장 -->
<insert id="insertEstimateTemplate" parameterType="map">
INSERT INTO ESTIMATE_TEMPLATE (
@@ -4082,6 +4239,7 @@ ORDER BY ASM.SUPPLY_NAME
TOTAL_AMOUNT_KRW,
MANAGER_NAME,
MANAGER_CONTACT,
SHOW_TOTAL_ROW,
WRITER,
REGDATE,
CHG_USER_ID,
@@ -4107,6 +4265,7 @@ ORDER BY ASM.SUPPLY_NAME
#{total_amount_krw},
#{manager_name},
#{manager_contact},
#{show_total_row},
#{writer},
NOW(),
#{chg_user_id},
@@ -4135,6 +4294,7 @@ ORDER BY ASM.SUPPLY_NAME
TOTAL_AMOUNT_KRW = #{total_amount_krw},
MANAGER_NAME = #{manager_name},
MANAGER_CONTACT = #{manager_contact},
SHOW_TOTAL_ROW = #{show_total_row},
CHG_USER_ID = #{chg_user_id},
CHGDATE = NOW()
WHERE
@@ -4192,6 +4352,67 @@ WHERE
OBJID = #{template_objid}
</update>
<!-- 장비 견적서 템플릿 신규 저장 (Template 2) -->
<insert id="insertEstimateTemplate2" parameterType="map">
INSERT INTO ESTIMATE_TEMPLATE (
OBJID,
CONTRACT_OBJID,
TEMPLATE_TYPE,
EXECUTOR_DATE,
RECIPIENT,
PART_NAME,
PART_OBJID,
NOTES_CONTENT,
VALIDITY_PERIOD,
CATEGORIES_JSON,
GROUP1_SUBTOTAL,
TOTAL_AMOUNT,
TOTAL_AMOUNT_KRW,
WRITER,
REGDATE,
CHG_USER_ID,
CHGDATE
) VALUES (
#{template_objid},
#{contract_objid},
'2',
#{executor_date},
#{recipient},
#{part_name},
#{part_objid},
#{notes_content},
#{validity_period},
#{categories_json},
#{group1_subtotal},
#{total_amount},
#{total_amount_krw},
#{writer},
NOW(),
#{chg_user_id},
NOW()
)
</insert>
<!-- 장비 견적서 템플릿 수정 (Template 2) -->
<update id="updateEstimateTemplate2" parameterType="map">
UPDATE ESTIMATE_TEMPLATE
SET
EXECUTOR_DATE = #{executor_date},
RECIPIENT = #{recipient},
PART_NAME = #{part_name},
PART_OBJID = #{part_objid},
NOTES_CONTENT = #{notes_content},
VALIDITY_PERIOD = #{validity_period},
CATEGORIES_JSON = #{categories_json},
GROUP1_SUBTOTAL = #{group1_subtotal},
TOTAL_AMOUNT = #{total_amount},
TOTAL_AMOUNT_KRW = #{total_amount_krw},
CHG_USER_ID = #{chg_user_id},
CHGDATE = NOW()
WHERE
OBJID = #{template_objid}
</update>
<!-- 최종 차수 견적서 조회 (메일 발송용) -->
<select id="getLatestEstimateTemplate" parameterType="map" resultType="map">
SELECT
@@ -4349,14 +4570,112 @@ WHERE
</if>
ORDER BY REGDATE DESC
</select>
<!-- 주문서관리 Total 합계 조회 -->
<select id="getOrderTotalAmount" parameterType="map" resultType="map">
SELECT
COALESCE(SUM(COALESCE(T.ORDER_SUPPLY_PRICE_SUM, 0)), 0) AS TOTAL_SUPPLY_PRICE,
COALESCE(SUM(COALESCE(T.ORDER_VAT_SUM, 0)), 0) AS TOTAL_VAT,
COALESCE(SUM(COALESCE(T.ORDER_TOTAL_AMOUNT_SUM, 0)), 0) AS TOTAL_AMOUNT
FROM
<include refid="contractBase"/> T
WHERE 1=1
<if test="Year !=null and Year != '' ">
AND SUBSTR(CONTRACT_DATE,0,5) = #{Year}
</if>
<if test="category_cd !=null and category_cd != '' ">
AND category_cd = #{category_cd}
</if>
<if test="customer_objid !=null and customer_objid != '' ">
AND customer_objid = #{customer_objid}
</if>
<if test="product != null and product !='' ">
AND product = #{product}
</if>
<if test="status_cd !=null and status_cd !=''">
AND status_cd = #{status_cd}
</if>
<if test="result_cd !=null and result_cd !=''">
AND result_cd = #{result_cd}
</if>
<if test="contract_result !=null and contract_result !=''">
AND contract_result = #{contract_result}
</if>
<if test="contract_start_date != null and !''.equals(contract_start_date)">
AND TO_DATE(CONTRACT_DATE,'YYYY-MM-DD') <![CDATA[ >= ]]> TO_DATE(#{contract_start_date}, 'YYYY-MM-DD')
</if>
<if test="contract_end_date != null and !''.equals(contract_end_date)">
AND TO_DATE(CONTRACT_DATE,'YYYY-MM-DD') <![CDATA[ <= ]]> TO_DATE(#{contract_end_date}, 'YYYY-MM-DD')
</if>
<if test="pm_user_id !=null and pm_user_id !=''">
AND pm_user_id = #{pm_user_id}
</if>
<if test="contract_month != null and !''.equals(contract_month)">
AND TO_DATE(TO_CHAR(TO_DATE(CONTRACT_DATE,'YYYY-MM-DD'),'YYYY-MM-DD'),'YYYY-MM') <![CDATA[ = ]]> TO_DATE(SUBSTRING(#{contract_month} FROM 1 FOR 4) || '-' || SUBSTRING(#{contract_month} FROM 5 FOR 2), 'YYYY-MM')
</if>
<!-- 견적관리 추가 검색조건 -->
<if test="appr_status !=null and appr_status != '' ">
AND APPR_STATUS = #{appr_status}
</if>
<if test="area_cd != null and area_cd !='' ">
AND AREA_CD = #{area_cd}
</if>
<if test="paid_type != null and paid_type !='' ">
AND PAID_TYPE = #{paid_type}
</if>
<!-- 품번/품명 검색: PART_OBJID로 정확하게 검색 -->
<if test="search_partObjId != null and search_partObjId != ''">
AND EXISTS (
SELECT 1
FROM CONTRACT_ITEM CI
WHERE CI.CONTRACT_OBJID = T.OBJID
AND CI.STATUS = 'ACTIVE'
AND CI.PART_OBJID = #{search_partObjId}
)
</if>
<if test="search_serialNo != null and search_serialNo != ''">
AND UPPER(SERIAL_NO) LIKE UPPER('%${search_serialNo}%')
</if>
<if test="search_poNo != null and search_poNo != ''">
AND UPPER(PO_NO) LIKE UPPER('%${search_poNo}%')
</if>
<if test="order_start_date != null and !''.equals(order_start_date)">
AND TO_DATE(ORDER_DATE,'YYYY-MM-DD') <![CDATA[ >= ]]> TO_DATE(#{order_start_date}, 'YYYY-MM-DD')
</if>
<if test="order_end_date != null and !''.equals(order_end_date)">
AND TO_DATE(ORDER_DATE,'YYYY-MM-DD') <![CDATA[ <= ]]> TO_DATE(#{order_end_date}, 'YYYY-MM-DD')
</if>
<if test="due_start_date != null and !''.equals(due_start_date)">
AND TO_DATE(DUE_DATE,'YYYY-MM-DD') <![CDATA[ >= ]]> TO_DATE(#{due_start_date}, 'YYYY-MM-DD')
</if>
<if test="due_end_date != null and !''.equals(due_end_date)">
AND TO_DATE(DUE_DATE,'YYYY-MM-DD') <![CDATA[ <= ]]> TO_DATE(#{due_end_date}, 'YYYY-MM-DD')
</if>
</select>
<!-- 영업정보 조회 (수주등록용) -->
<select id="getContractInfo" parameterType="map" resultType="map">
SELECT
OBJID,
CONTRACT_NO,
CUSTOMER_OBJID,
CONTRACT_CURRENCY,
(SELECT CODE_NAME FROM TB_CODE WHERE CODE_ID = CONTRACT_CURRENCY) AS CONTRACT_CURRENCY_NAME,
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CONTRACT_CURRENCY) AS CONTRACT_CURRENCY_NAME,
EXCHANGE_RATE,
QUANTITY,
PART_NO,
@@ -4582,8 +4901,8 @@ WHERE
CI.CONTRACT_OBJID,
CI.SEQ,
CI.PART_OBJID,
CI.PART_NO,
CI.PART_NAME,
COALESCE(NULLIF(CI.PART_NO, ''), PM.PART_NO) AS PART_NO,
COALESCE(NULLIF(CI.PART_NAME, ''), PM.PART_NAME) AS PART_NAME,
CI.QUANTITY,
CI.DUE_DATE,
CI.CUSTOMER_REQUEST,
@@ -4597,6 +4916,8 @@ WHERE
LEFT JOIN CONTRACT_ITEM_SERIAL CIS
ON CI.OBJID = CIS.ITEM_OBJID
AND CIS.STATUS = 'ACTIVE'
LEFT JOIN PART_MNG PM
ON CI.PART_OBJID = PM.OBJID
WHERE
CI.CONTRACT_OBJID = #{contractObjId}
AND CI.STATUS = 'ACTIVE'
@@ -4606,7 +4927,9 @@ WHERE
CI.SEQ,
CI.PART_OBJID,
CI.PART_NO,
CI.PART_NAME,
CI.PART_NAME,
PM.PART_NO,
PM.PART_NAME,
CI.QUANTITY,
CI.DUE_DATE,
CI.CUSTOMER_REQUEST,

View File

@@ -875,16 +875,32 @@
(SELECT CM.PRODUCTION_STATUS FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PRODUCTION_STATUS,
-- 판매 관련 필드들 (sales_registration 테이블에서 한 번에 가져오기)
COALESCE(SR.shipping_order_status, '') AS SHIPPING_ORDER_STATUS,
COALESCE(SR.sales_quantity, 0) AS SALES_QUANTITY,
-- 판매수량: 모든 분할 출하의 합계
COALESCE(
(SELECT SUM(sales_quantity)
FROM sales_registration
WHERE project_no LIKE T.PROJECT_NO || '%'),
0
) AS SALES_QUANTITY,
COALESCE(SR.sales_unit_price, 0) AS SALES_UNIT_PRICE,
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_total_amount, 0) AS SALES_TOTAL_AMOUNT_KRW,
-- 잔량 계산: 수주수량 - 판매수량
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(T.QUANTITY::numeric, 0) - COALESCE(
(SELECT SUM(sales_quantity)
FROM sales_registration
WHERE project_no LIKE T.PROJECT_NO || '%'),
0
) AS REMAINING_QUANTITY,
-- 잔량원화총액 계산: (수주수량 - 모든 분할 출하 합계) * 판매단가
(COALESCE(T.QUANTITY::numeric, 0) - COALESCE(
(SELECT SUM(sales_quantity)
FROM sales_registration
WHERE project_no LIKE T.PROJECT_NO || '%'),
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,
@@ -1397,29 +1413,12 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
NULL,
</otherwise>
</choose>
#{shippingMethod},
#{manager},
#{incoterms},
#{cretEmpNo}
)
ON CONFLICT (project_no)
DO UPDATE SET
shipping_order_status = EXCLUDED.shipping_order_status,
serial_no = EXCLUDED.serial_no,
sales_quantity = EXCLUDED.sales_quantity,
sales_unit_price = EXCLUDED.sales_unit_price,
sales_supply_price = EXCLUDED.sales_supply_price,
sales_vat = EXCLUDED.sales_vat,
sales_total_amount = EXCLUDED.sales_total_amount,
sales_currency = EXCLUDED.sales_currency,
sales_exchange_rate = EXCLUDED.sales_exchange_rate,
shipping_date = EXCLUDED.shipping_date,
shipping_method = EXCLUDED.shipping_method,
manager_user_id = EXCLUDED.manager_user_id,
incoterms = EXCLUDED.incoterms,
upd_date = NOW(),
upd_user_id = EXCLUDED.reg_user_id
</insert>
#{shippingMethod},
#{manager},
#{incoterms},
#{cretEmpNo}
)
</insert>
<!--
/**
@@ -1488,9 +1487,15 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
AND UPPER(STATUS) = 'ACTIVE'
) THEN 'Y' ELSE 'N' END AS ORDER_ATTACH,
(SELECT CM.PRODUCTION_STATUS FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PRODUCTION_STATUS,
-- 판매 관련 필드들 (sales_registration 테이블에서 가져오기)
COALESCE(SR.shipping_order_status, '') AS SHIPPING_ORDER_STATUS,
COALESCE(SR.sales_quantity, 0) AS SALES_QUANTITY,
-- 판매 관련 필드들 (sales_registration 테이블에서 가져오기)
SR.sale_no AS SALE_NO,
COALESCE(SR.shipping_order_status, '') AS SHIPPING_ORDER_STATUS,
-- 주문수량 (PROJECT_MGMT에서 가져오기) - 잔량 계산용
COALESCE(T.QUANTITY::NUMERIC, 0) AS ORDER_QUANTITY,
-- 판매수량 (sales_registration에서 가져오기) - 이미 판매한 수량
COALESCE(SR.sales_quantity, 0) AS SALES_QUANTITY,
COALESCE(SR.sales_unit_price, 0) AS SALES_UNIT_PRICE,
COALESCE(SR.sales_supply_price, 0) AS SALES_SUPPLY_PRICE,
COALESCE(SR.sales_vat, 0) AS SALES_VAT,
@@ -1498,7 +1503,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
COALESCE(SR.sales_total_amount, 0) AS SALES_TOTAL_AMOUNT_KRW,
COALESCE(NULLIF(SR.sales_currency, ''), CM.CONTRACT_CURRENCY) AS SALES_CURRENCY,
CODE_NAME(COALESCE(NULLIF(SR.sales_currency, ''), CM.CONTRACT_CURRENCY)) AS SALES_CURRENCY_NAME,
COALESCE(NULLIF(SR.sales_exchange_rate, 0), CM.EXCHANGE_RATE::numeric, 0) AS SALES_EXCHANGE_RATE,
COALESCE(NULLIF(SR.sales_exchange_rate, 0), NULLIF(CM.EXCHANGE_RATE, '')::numeric, 0) AS SALES_EXCHANGE_RATE,
COALESCE(TO_CHAR(SR.shipping_date, 'YYYY-MM-DD'), '') AS SHIPPING_DATE,
COALESCE(SR.shipping_method, '') AS SHIPPING_METHOD,
COALESCE(SR.manager_user_id, T.PM_USER_ID) AS MANAGER,
@@ -1566,40 +1571,43 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
-->
<select id="getOrderDataByOrderNo" parameterType="map" resultType="map">
/* salesNcollectMgmt.getOrderDataByOrderNo - orderNo로 판매등록용 수주 데이터 조회 */
SELECT
-- 기본 정보
CM.CONTRACT_NO AS ORDER_NO,
CM.OBJID AS CONTRACT_OBJID,
-- 수주 금액 정보 (CONTRACT_ITEM 테이블에서 합산) - VARCHAR 타입이므로 NUMERIC으로 캐스팅
COALESCE(SUM(CI.ORDER_QUANTITY::NUMERIC), 0) AS SALES_QUANTITY,
COALESCE(ROUND(AVG(CI.ORDER_UNIT_PRICE::NUMERIC), 2), 0) AS SALES_UNIT_PRICE,
COALESCE(SUM(CI.ORDER_SUPPLY_PRICE::NUMERIC), 0) AS SALES_SUPPLY_PRICE,
COALESCE(SUM(CI.ORDER_VAT::NUMERIC), 0) AS SALES_VAT,
COALESCE(SUM(CI.ORDER_TOTAL_AMOUNT::NUMERIC), 0) AS SALES_TOTAL_AMOUNT,
-- 환종 정보
CM.CONTRACT_CURRENCY AS SALES_CURRENCY,
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,
-- 담당자
CM.PM_USER_ID AS MANAGER
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
CM.CONTRACT_NO,
CM.OBJID,
CM.CONTRACT_CURRENCY,
CM.EXCHANGE_RATE,
CM.ORDER_DATE,
CM.PM_USER_ID
SELECT
-- 기본 정보
CM.CONTRACT_NO AS ORDER_NO,
CM.OBJID AS CONTRACT_OBJID,
-- 수주 수량 정보 (PROJECT_MGMT에서 직접 가져오기)
COALESCE(PM.QUANTITY::NUMERIC, 0) AS SALES_QUANTITY,
-- 수주 금액 정보 (CONTRACT_ITEM 테이블에서 합산) - VARCHAR 타입이므로 NULLIF로 빈 문자열 제거 후 NUMERIC 캐스팅
COALESCE(ROUND(AVG(NULLIF(CI.ORDER_UNIT_PRICE, '')::NUMERIC), 2), 0) AS SALES_UNIT_PRICE,
COALESCE(SUM(NULLIF(CI.ORDER_SUPPLY_PRICE, '')::NUMERIC), 0) AS SALES_SUPPLY_PRICE,
COALESCE(SUM(NULLIF(CI.ORDER_VAT, '')::NUMERIC), 0) AS SALES_VAT,
COALESCE(SUM(NULLIF(CI.ORDER_TOTAL_AMOUNT, '')::NUMERIC), 0) AS SALES_TOTAL_AMOUNT,
-- 환종 정보
CM.CONTRACT_CURRENCY AS SALES_CURRENCY,
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,
-- 담당자
CM.PM_USER_ID AS MANAGER
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
CM.CONTRACT_NO,
CM.OBJID,
CM.CONTRACT_CURRENCY,
CM.EXCHANGE_RATE,
CM.ORDER_DATE,
CM.PM_USER_ID,
PM.QUANTITY
</select>
<!--
@@ -1619,5 +1627,159 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
WHERE OBJID::VARCHAR = #{OBJID}
</update>
<!-- 모든 분할 출하의 총 판매 수량 조회 -->
<select id="getTotalSalesQuantity" parameterType="map" resultType="map">
/* salesNcollectMgmt.getTotalSalesQuantity - project_no로 시작하는 모든 레코드의 판매 수량 합계 */
SELECT COALESCE(SUM(NULLIF(sales_quantity, '')::NUMERIC), 0) as total
FROM sales_registration
WHERE project_no LIKE #{orderNo} || '%'
</select>
<!-- sales_registration 개수 조회 (분할 출하 순번용) -->
<select id="getSaleRegistrationCount" parameterType="map" resultType="map">
/* salesNcollectMgmt.getSaleRegistrationCount - project_no로 시작하는 레코드 개수 */
SELECT COUNT(*) as count
FROM sales_registration
WHERE project_no LIKE #{orderNo} || '%'
</select>
<!-- sales_registration DELETE (기존 데이터 삭제) -->
<delete id="deleteSaleRegistration" parameterType="map">
/* salesNcollectMgmt.deleteSaleRegistration - sales_registration 삭제 */
DELETE FROM sales_registration
WHERE project_no = #{orderNo}
</delete>
<!-- sales_registration UPDATE (기존 데이터 수정) -->
<update id="updateSaleRegistration" parameterType="map">
/* salesNcollectMgmt.updateSaleRegistration - sales_registration 업데이트 */
UPDATE sales_registration
SET
shipping_order_status = <choose>
<when test="shippingOrderStatus != null and shippingOrderStatus != ''">
#{shippingOrderStatus},
</when>
<otherwise>
'',
</otherwise>
</choose>
serial_no = #{serialNo},
sales_quantity = #{salesQuantity}::integer,
sales_unit_price = #{salesUnitPrice}::numeric,
sales_supply_price = #{salesSupplyPrice}::numeric,
sales_vat = #{salesVat}::numeric,
sales_total_amount = #{salesTotalAmount}::numeric,
sales_currency = #{salesCurrency},
sales_exchange_rate = #{salesExchangeRate}::numeric,
shipping_date = <choose>
<when test="shippingDate != null and shippingDate != ''">
TO_DATE(#{shippingDate}, 'YYYY-MM-DD'),
</when>
<otherwise>
NULL,
</otherwise>
</choose>
shipping_method = #{shippingMethod},
manager_user_id = #{manager},
incoterms = #{incoterms},
upd_date = NOW(),
upd_user_id = #{cretEmpNo}
WHERE project_no = #{orderNo}
</update>
<!-- ========================================
분할 출하 관련 쿼리 (shipment_log 테이블 활용)
======================================== -->
<!-- shipment_log의 split_quantity 합산 조회 -->
<select id="getShipmentLogTotal" parameterType="map" resultType="map">
/* salesNcollectMgmt.getShipmentLogTotal - shipment_log의 split_quantity 합산 */
SELECT COALESCE(SUM(split_quantity), 0) as total
FROM shipment_log SL
INNER JOIN project_mgmt PM ON SL.target_objid = PM.OBJID::VARCHAR
WHERE PM.PROJECT_NO = #{orderNo}
</select>
<!-- shipment_log에 분할 출하 기록 저장 -->
<insert id="insertShipmentLog" parameterType="map">
/* salesNcollectMgmt.insertShipmentLog - 분할 출하 기록 저장 */
INSERT INTO shipment_log (
target_objid, log_type, log_message, split_quantity, original_quantity,
remaining_quantity, shipping_status, shipping_date, shipping_method,
sales_unit_price, sales_supply_price, sales_vat, sales_total_amount,
sales_currency, sales_exchange_rate, manager_user_id, incoterms,
serial_no, parent_sale_no, reg_user_id
) VALUES (
#{targetObjid}, 'SPLIT_SHIPMENT', '분할 출하',
#{salesQuantity}::integer, #{originalQuantity}::integer, #{remainingQuantity}::integer,
#{shippingOrderStatus},
<choose>
<when test="shippingDate != null and shippingDate != ''">
TO_DATE(#{shippingDate}, 'YYYY-MM-DD'),
</when>
<otherwise>NULL,</otherwise>
</choose>
#{shippingMethod}, #{salesUnitPrice}::numeric, #{salesSupplyPrice}::numeric,
#{salesVat}::numeric, #{salesTotalAmount}::numeric, #{salesCurrency},
#{salesExchangeRate}::numeric, #{manager}, #{incoterms}, #{serialNo},
#{parentSaleNo}::integer, #{cretEmpNo}
)
</insert>
<!-- PROJECT_MGMT의 OBJID 조회 (shipment_log의 target_objid로 사용) -->
<select id="getProjectObjid" parameterType="map" resultType="map">
/* salesNcollectMgmt.getProjectObjid - PROJECT_MGMT의 OBJID 조회 */
SELECT OBJID FROM PROJECT_MGMT WHERE PROJECT_NO = #{orderNo}
</select>
<!-- 출하일 상세 내역 조회 (모든 분할 출하 포함) -->
<select id="getShippingDetailList" parameterType="map" resultType="map">
/* salesNcollectMgmt.getShippingDetailList - 모든 분할 출하 포함 */
SELECT
COALESCE(TO_CHAR(SR.shipping_date, 'YYYY-MM-DD'), '미등록') AS shipping_date,
COALESCE(SR.sales_quantity, 0) AS shipping_quantity,
COALESCE(SR.shipping_order_status, '미등록') AS shipping_order_status,
COALESCE(SR.serial_no, '-') AS serial_no,
SR.project_no,
CASE
WHEN SR.project_no = #{projectNo} THEN '최초 출하'
ELSE '분할 출하 ' || SUBSTRING(SR.project_no FROM LENGTH(#{projectNo}) + 2)
END AS source
FROM sales_registration SR
WHERE SR.project_no LIKE #{projectNo} || '%'
ORDER BY shipping_date DESC, project_no
</select>
<!-- 거래명세서 - 고객 정보 조회 -->
<select id="getCustomerInfoByProjectNo" parameterType="map" resultType="map">
/* salesNcollectMgmt.getCustomerInfoByProjectNo - 프로젝트 번호로 고객 정보 조회 */
SELECT
SM.SUPPLY_NAME AS CUSTOMER_NAME,
SM.BUSINESS_NO AS CUSTOMER_REG_NO,
SM.ADDRESS AS CUSTOMER_ADDRESS,
SM.BUSINESS_TYPE AS CUSTOMER_BUSINESS,
SM.BUSINESS_ITEM AS CUSTOMER_TYPE,
SM.TEL_NO AS CUSTOMER_CONTACT
FROM PROJECT_MGMT PM
INNER JOIN SUPPLY_MNG SM ON PM.CUSTOMER_OBJID::NUMERIC = SM.OBJID
WHERE PM.PROJECT_NO = #{projectNo}
</select>
<!-- 거래명세서 - 품목 정보 조회 -->
<select id="getTransactionStatementItem" parameterType="map" resultType="map">
/* salesNcollectMgmt.getTransactionStatementItem - 프로젝트 번호로 품목 정보 조회 */
SELECT
PM.PART_NAME AS productName,
PM.PART_NO AS spec,
COALESCE(SR.sales_quantity, PM.QUANTITY) AS quantity,
COALESCE(SR.sales_unit_price, 0) AS unitPrice,
COALESCE(SR.sales_supply_price, 0) AS supplyPrice,
COALESCE(SR.sales_vat, 0) AS vat
FROM PROJECT_MGMT PM
LEFT JOIN sales_registration SR ON PM.PROJECT_NO = SR.project_no
WHERE PM.PROJECT_NO = #{projectNo}
</select>
</mapper>

View File

@@ -190,19 +190,8 @@ $(document).ready(function(){
return false;
}
// 메일 발송 확인
Swal.fire({
title: '견적서 메일 발송',
text: "최종 차수의 견적서를 발송하시겠습니까?",
icon: 'question',
showCancelButton: true,
confirmButtonText: '발송',
cancelButtonText: '취소'
}).then((result) => {
if(result.isConfirmed){
fn_sendEstimateMail(objId);
}
});
// 메일 작성 팝업 열기
fn_openMailFormPopup(objId);
}
});
@@ -683,309 +672,42 @@ function fn_showSerialNoPopup(serialNoString){
});
}
// 견적서 메일 발송
function fn_sendEstimateMail(contractObjId){
// 메일 작성 팝업 열기
function fn_openMailFormPopup(contractObjId){
if(!contractObjId || contractObjId === ''){
Swal.fire("잘못된 요청입니다.");
return;
}
// 1단계: 견적서 템플릿 정보 조회
Swal.fire({
title: '견적서 조회 중...',
text: '잠시만 기다려주세요.',
allowOutsideClick: false,
onOpen: () => {
Swal.showLoading();
}
});
var popup_width = 950;
var popup_height = 800;
var url = "/contractMgmt/estimateMailFormPopup.do?contractObjId=" + contractObjId;
$.ajax({
url: "/contractMgmt/getEstimateTemplateList.do",
type: "POST",
data: { objId: contractObjId },
dataType: "json",
success: function(data){
if(data.result === "success" && data.list && data.list.length > 0){
// 최종 차수 견적서 찾기
var latestEstimate = data.list[0]; // 이미 차수 내림차순으로 정렬되어 있음
var templateObjId = latestEstimate.OBJID || latestEstimate.objid;
var templateType = latestEstimate.TEMPLATE_TYPE || latestEstimate.template_type || latestEstimate.templateType;
// 2단계: 견적서 페이지를 새 창으로 열고 PDF 생성
fn_generatePdfAndSendMail(contractObjId, templateObjId, templateType);
} else {
Swal.close();
Swal.fire({
title: '오류',
text: '견적서를 찾을 수 없습니다.',
icon: 'error'
});
}
},
error: function(xhr, status, error){
Swal.close();
console.error("견적서 조회 오류:", xhr, status, error);
Swal.fire({
title: '오류',
text: '견적서 조회 중 오류가 발생했습니다.',
icon: 'error'
});
}
});
window.open(url, "estimateMailForm", "width="+popup_width+",height="+popup_height+",menubar=no,scrollbars=yes,resizable=yes");
}
// PDF 생성 및 메일 발송
/*
* 아래 함수들은 자동 메일 발송 기능을 위한 것입니다.
* 현재는 메일 작성 팝업을 사용하므로 주석 처리합니다.
* 필요시 참고용으로 남겨둡니다.
*/
/*
// PDF 생성 및 메일 발송 (자동)
function fn_generatePdfAndSendMail(contractObjId, templateObjId, templateType){
Swal.fire({
title: 'PDF 생성 중...',
text: '견적서를 PDF로 변환하고 있습니다.',
allowOutsideClick: false,
onOpen: () => {
Swal.showLoading();
}
});
// 견적서 페이지 URL 생성
var url = "";
if(templateType === "1"){
url = "/contractMgmt/estimateTemplate1.do?templateObjId=" + templateObjId;
} else if(templateType === "2"){
url = "/contractMgmt/estimateTemplate2.do?templateObjId=" + templateObjId;
}
// 숨겨진 iframe으로 페이지 로드
var iframe = $('<iframe>', {
id: 'pdfGeneratorFrame',
src: url,
style: 'position:absolute;width:0;height:0;border:none;'
}).appendTo('body');
// iframe 로드 완료 대기
iframe.on('load', function(){
try {
var iframeWindow = this.contentWindow;
// 데이터 로딩 완료를 기다림 (최대 10초)
var checkDataLoaded = function(attempts) {
if(attempts > 100) { // 10초 (100ms * 100)
$('#pdfGeneratorFrame').remove();
Swal.close();
Swal.fire({
title: '타임아웃',
text: '견적서 데이터 로딩 시간이 초과되었습니다.',
icon: 'error'
});
return;
}
if(iframeWindow.dataLoaded === true) {
console.log('데이터 로딩 완료 확인, PDF 생성 시작');
// iframe 내의 PDF 생성 함수 호출
if(typeof iframeWindow.fn_generateAndUploadPdf === 'function'){
iframeWindow.fn_generateAndUploadPdf(function(pdfBase64){
// iframe 제거
$('#pdfGeneratorFrame').remove();
if(pdfBase64){
// PDF Base64와 함께 메일 발송 요청
fn_sendMailWithPdf(contractObjId, pdfBase64);
} else {
Swal.close();
Swal.fire({
title: '오류',
text: 'PDF 생성에 실패했습니다.',
icon: 'error'
});
}
});
} else {
// iframe 제거
$('#pdfGeneratorFrame').remove();
Swal.close();
Swal.fire({
title: '오류',
text: '견적서 페이지 로드에 실패했습니다.',
icon: 'error'
});
}
} else {
// 아직 로딩 중이면 100ms 후 다시 확인
setTimeout(function() {
checkDataLoaded(attempts + 1);
}, 100);
}
};
// 데이터 로딩 체크 시작
checkDataLoaded(0);
} catch(e) {
console.error('PDF 생성 오류:', e);
$('#pdfGeneratorFrame').remove();
Swal.close();
Swal.fire({
title: '오류',
text: 'PDF 생성 중 오류가 발생했습니다.',
icon: 'error'
});
}
});
// 타임아웃 설정 (30초)
setTimeout(function(){
if($('#pdfGeneratorFrame').length > 0){
$('#pdfGeneratorFrame').remove();
Swal.close();
Swal.fire({
title: '타임아웃',
text: 'PDF 생성 시간이 초과되었습니다.',
icon: 'error'
});
}
}, 30000);
// ... (생략)
}
// PDF Base64와 함께 메일 발송 (청크 방식)
function fn_sendMailWithPdf(contractObjId, pdfBase64){
console.log('===== 메일 발송 시작 =====');
console.log('contractObjId:', contractObjId);
console.log('PDF Base64 길이:', pdfBase64 ? pdfBase64.length : 0);
console.log('========================');
Swal.fire({
title: '메일 발송 중...',
text: 'PDF 업로드 중...',
allowOutsideClick: false,
onOpen: () => {
Swal.showLoading();
}
});
// 청크 크기: 100KB (Base64) - POST 크기 제한 고려
var chunkSize = 100 * 1024;
var totalChunks = Math.ceil(pdfBase64.length / chunkSize);
var uploadedChunks = 0;
var sessionId = 'pdf_' + contractObjId + '_' + new Date().getTime();
console.log('PDF Base64 전체 길이:', pdfBase64.length);
console.log('청크 크기:', chunkSize);
console.log('총 청크 수:', totalChunks);
// 청크 업로드 함수
function uploadChunk(chunkIndex) {
var start = chunkIndex * chunkSize;
var end = Math.min(start + chunkSize, pdfBase64.length);
var chunk = pdfBase64.substring(start, end);
console.log('청크 ' + (chunkIndex + 1) + '/' + totalChunks + ' 업로드 중...');
$.ajax({
url: "/contractMgmt/uploadPdfChunk.do",
type: "POST",
data: {
sessionId: sessionId,
chunkIndex: chunkIndex,
totalChunks: totalChunks,
chunk: chunk
},
dataType: "json",
timeout: 30000,
success: function(data){
if(data.result === "success"){
uploadedChunks++;
// 진행률 업데이트
var progress = Math.round((uploadedChunks / totalChunks) * 100);
Swal.update({
text: 'PDF 업로드 중... ' + progress + '%'
});
// 다음 청크 업로드
if(chunkIndex + 1 < totalChunks){
uploadChunk(chunkIndex + 1);
} else {
// 모든 청크 업로드 완료, 메일 발송 요청
console.log('모든 청크 업로드 완료, 메일 발송 시작');
sendMailWithUploadedPdf(contractObjId, sessionId);
}
} else {
Swal.close();
Swal.fire({
title: '업로드 실패',
text: 'PDF 업로드 중 오류가 발생했습니다.',
icon: 'error',
confirmButtonText: '확인'
});
}
},
error: function(xhr, status, error){
Swal.close();
console.error("청크 업로드 오류:", xhr, status, error);
Swal.fire({
title: '오류',
text: 'PDF 업로드 중 시스템 오류가 발생했습니다.',
icon: 'error',
confirmButtonText: '확인'
});
}
});
}
// 첫 번째 청크부터 시작
uploadChunk(0);
// ... (생략)
}
// 업로드된 PDF로 메일 발송
function sendMailWithUploadedPdf(contractObjId, sessionId){
Swal.update({
text: '메일 발송 중...'
});
$.ajax({
url: "/contractMgmt/sendEstimateMail.do",
type: "POST",
data: {
objId: contractObjId,
pdfSessionId: sessionId
},
dataType: "json",
timeout: 60000,
success: function(data){
console.log('메일 발송 응답:', data);
Swal.close();
if(data.result === "success"){
Swal.fire({
title: '발송 완료',
text: '견적서가 성공적으로 발송되었습니다.',
icon: 'success',
confirmButtonText: '확인'
}).then(() => {
fn_search();
});
} else {
Swal.fire({
title: '발송 실패',
text: data.message || '견적서 발송 중 오류가 발생했습니다.',
icon: 'error',
confirmButtonText: '확인'
});
}
},
error: function(xhr, status, error){
Swal.close();
console.error("메일 발송 오류:", xhr, status, error);
Swal.fire({
title: '오류',
text: '메일 발송 중 시스템 오류가 발생했습니다.',
icon: 'error',
confirmButtonText: '확인'
});
}
});
// ... (생략)
}
*/
//코드값을 받아와서 동적으로 selectbox 생성
function optionJobGroup(code){

View File

@@ -0,0 +1,652 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page import="java.util.*" %>
<%@include file= "/init.jsp" %>
<%
PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
String connector = person.getUserId();
String contractObjId = request.getParameter("contractObjId");
%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>견적서 메일 발송</title>
<style>
body {
font-family: 'Malgun Gothic', sans-serif;
margin: 20px;
background-color: #f5f5f5;
}
.mail-form-container {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
max-width: 900px;
margin: 0 auto;
}
.form-title {
font-size: 24px;
font-weight: bold;
margin-bottom: 30px;
color: #333;
border-bottom: 2px solid #3085d6;
padding-bottom: 10px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
font-weight: bold;
margin-bottom: 8px;
color: #555;
}
.form-group label.required:after {
content: " *";
color: red;
}
.form-group input[type="text"],
.form-group input[type="email"],
.form-group textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
}
.form-group textarea {
min-height: 200px;
resize: vertical;
}
.manager-list {
border: 1px solid #ddd;
border-radius: 4px;
padding: 15px;
background-color: #fafafa;
max-height: 200px;
overflow-y: auto;
}
.manager-item {
padding: 8px;
margin-bottom: 5px;
background: white;
border-radius: 4px;
display: flex;
align-items: center;
}
.manager-item input[type="checkbox"] {
margin-right: 10px;
width: 18px;
height: 18px;
cursor: pointer;
}
.manager-item label {
cursor: pointer;
margin: 0;
flex: 1;
}
.no-managers {
color: #999;
text-align: center;
padding: 20px;
}
.button-group {
text-align: center;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eee;
}
.btn {
padding: 12px 30px;
margin: 0 5px;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background-color: #3085d6;
color: white;
}
.btn-primary:hover {
background-color: #2874c5;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #5a6268;
}
.info-text {
font-size: 12px;
color: #666;
margin-top: 5px;
}
.pdf-status {
padding: 10px;
background-color: #e7f3ff;
border-left: 4px solid #3085d6;
border-radius: 4px;
margin-bottom: 20px;
}
.pdf-status i {
color: #3085d6;
margin-right: 5px;
}
</style>
</head>
<body>
<div class="mail-form-container">
<div class="form-title">견적서 메일 발송</div>
<div class="pdf-status">
<i class="fa fa-file-pdf-o"></i>
<strong>PDF 첨부:</strong> 최종 차수 견적서가 자동으로 첨부됩니다.
</div>
<form id="mailForm">
<input type="hidden" id="contractObjId" name="contractObjId" value="<%=contractObjId%>"/>
<input type="hidden" id="pdfSessionId" name="pdfSessionId" value=""/>
<!-- 고객사 담당자 선택 -->
<div class="form-group">
<label>고객사 담당자 선택</label>
<div id="managerListContainer" class="manager-list">
<div class="no-managers">담당자 정보를 불러오는 중...</div>
</div>
</div>
<!-- 수신인 이메일 -->
<div class="form-group">
<label for="toEmails" class="required">수신인 (To)</label>
<input type="text" id="toEmails" name="toEmails" placeholder="이메일 주소를 입력하세요 (여러 개는 쉼표로 구분)"/>
<div class="info-text">예: email1@example.com, email2@example.com</div>
</div>
<!-- 참조 이메일 -->
<div class="form-group">
<label for="ccEmails">참조 (CC)</label>
<input type="text" id="ccEmails" name="ccEmails" placeholder="참조 이메일 주소 (선택사항)"/>
<div class="info-text">작성자 이메일이 자동으로 참조에 추가됩니다.</div>
</div>
<!-- 메일 제목 -->
<div class="form-group">
<label for="subject" class="required">제목</label>
<input type="text" id="subject" name="subject" placeholder="메일 제목을 입력하세요"/>
</div>
<!-- 메일 내용 -->
<div class="form-group">
<label for="contents" class="required">내용</label>
<textarea id="contents" name="contents" placeholder="메일 내용을 입력하세요"></textarea>
</div>
<!-- 버튼 -->
<div class="button-group">
<button type="button" class="btn btn-primary" onclick="fn_sendMail()">발송</button>
<button type="button" class="btn btn-secondary" onclick="window.close()">취소</button>
</div>
</form>
</div>
<script>
var contractInfo = null;
var managerList = [];
$(document).ready(function(){
// 계약 정보 및 담당자 목록 로드
fn_loadContractInfo();
});
// 계약 정보 로드
function fn_loadContractInfo(){
var contractObjId = $("#contractObjId").val();
$.ajax({
url: "/contractMgmt/getContractInfoForMail.do",
type: "POST",
data: { objId: contractObjId },
dataType: "json",
success: function(data){
if(data.result === "success" && data.contractInfo){
contractInfo = data.contractInfo;
// 메일 제목 자동 생성
var contractNo = fnc_checkNull(contractInfo.CONTRACT_NO);
var customerName = fnc_checkNull(contractInfo.CUSTOMER_NAME);
$("#subject").val("[" + customerName + "] " + contractNo + " 견적서");
// 메일 내용 템플릿 생성
fn_generateMailTemplate();
// 고객사 담당자 목록 로드
var customerObjId = fnc_checkNull(contractInfo.CUSTOMER_OBJID);
if(customerObjId !== ""){
fn_loadCustomerManagers(customerObjId);
} else {
$("#managerListContainer").html('<div class="no-managers">고객사 정보가 없습니다.</div>');
}
} else {
Swal.fire({
title: '오류',
text: '계약 정보를 불러올 수 없습니다.',
icon: 'error'
}).then(() => {
window.close();
});
}
},
error: function(){
Swal.fire({
title: '오류',
text: '계약 정보 조회 중 오류가 발생했습니다.',
icon: 'error'
}).then(() => {
window.close();
});
}
});
}
// 고객사 담당자 목록 로드
function fn_loadCustomerManagers(customerObjId){
$.ajax({
url: "/contractMgmt/getCustomerManagerList.do",
type: "POST",
data: { customerObjId: customerObjId },
dataType: "json",
success: function(data){
if(data.result === "success" && data.managers && data.managers.length > 0){
managerList = data.managers;
fn_renderManagerList();
} else {
$("#managerListContainer").html('<div class="no-managers">등록된 담당자가 없습니다. 수신인을 직접 입력해주세요.</div>');
}
},
error: function(){
$("#managerListContainer").html('<div class="no-managers">담당자 정보를 불러올 수 없습니다. 수신인을 직접 입력해주세요.</div>');
}
});
}
// 담당자 목록 렌더링
function fn_renderManagerList(){
var html = '';
for(var i = 0; i < managerList.length; i++){
var manager = managerList[i];
var name = fnc_checkNull(manager.name);
var email = fnc_checkNull(manager.email);
if(name !== ""){
html += '<div class="manager-item">';
html += '<input type="checkbox" id="manager_' + i + '" data-email="' + email + '" onchange="fn_updateRecipients()">';
html += '<label for="manager_' + i + '">' + name;
if(email !== ""){
html += ' (' + email + ')';
}
html += '</label>';
html += '</div>';
}
}
if(html === ''){
html = '<div class="no-managers">등록된 담당자가 없습니다. 수신인을 직접 입력해주세요.</div>';
}
$("#managerListContainer").html(html);
}
// 담당자 선택 시 수신인 필드 업데이트
function fn_updateRecipients(){
var selectedEmails = [];
$("input[type='checkbox'][id^='manager_']:checked").each(function(){
var email = $(this).attr("data-email");
if(email && email !== ""){
selectedEmails.push(email);
}
});
$("#toEmails").val(selectedEmails.join(", "));
}
// 메일 내용 템플릿 생성
function fn_generateMailTemplate(){
var customerName = fnc_checkNull(contractInfo.CUSTOMER_NAME);
var contractNo = fnc_checkNull(contractInfo.CONTRACT_NO);
var template = "안녕하세요.\n\n";
template += customerName + " 귀하께서 요청하신 견적서를 첨부파일로 송부드립니다.\n\n";
template += "영업번호: " + contractNo + "\n\n";
template += "첨부된 견적서를 검토하신 후 문의사항이 있으시면 연락 주시기 바랍니다.\n\n";
template += "감사합니다.\n";
$("#contents").val(template);
}
// 메일 발송
function fn_sendMail(){
// 입력값 검증
var toEmails = $("#toEmails").val().trim();
var subject = $("#subject").val().trim();
var contents = $("#contents").val().trim();
if(toEmails === ""){
Swal.fire("수신인을 입력해주세요.");
$("#toEmails").focus();
return;
}
// 이메일 형식 검증
var emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
var emails = toEmails.split(",").map(function(e){ return e.trim(); });
for(var i = 0; i < emails.length; i++){
if(!emailPattern.test(emails[i])){
Swal.fire("올바른 이메일 형식이 아닙니다: " + emails[i]);
$("#toEmails").focus();
return;
}
}
if(subject === ""){
Swal.fire("제목을 입력해주세요.");
$("#subject").focus();
return;
}
if(contents === ""){
Swal.fire("내용을 입력해주세요.");
$("#contents").focus();
return;
}
// 발송 확인
Swal.fire({
title: '메일 발송',
text: "견적서를 발송하시겠습니까?",
icon: 'question',
showCancelButton: true,
confirmButtonText: '발송',
cancelButtonText: '취소'
}).then((result) => {
if(result.isConfirmed){
// PDF 생성 및 발송 시작
fn_generatePdfAndSend();
}
});
}
// PDF 생성 및 발송
function fn_generatePdfAndSend(){
var contractObjId = $("#contractObjId").val();
Swal.fire({
title: 'PDF 생성 중...',
text: '견적서를 PDF로 변환하고 있습니다.',
allowOutsideClick: false,
onOpen: () => {
Swal.showLoading();
}
});
// 1. 최종 차수 견적서 정보 조회
$.ajax({
url: "/contractMgmt/getEstimateTemplateList.do",
type: "POST",
data: { objId: contractObjId },
dataType: "json",
success: function(data){
if(data.result === "success" && data.list && data.list.length > 0){
var latestEstimate = data.list[0];
var templateObjId = latestEstimate.OBJID || latestEstimate.objid;
var templateType = latestEstimate.TEMPLATE_TYPE || latestEstimate.template_type || latestEstimate.templateType;
// 2. PDF 생성
fn_generatePdf(contractObjId, templateObjId, templateType);
} else {
Swal.close();
Swal.fire({
title: '오류',
text: '견적서를 찾을 수 없습니다.',
icon: 'error'
});
}
},
error: function(){
Swal.close();
Swal.fire({
title: '오류',
text: '견적서 조회 중 오류가 발생했습니다.',
icon: 'error'
});
}
});
}
// PDF 생성
function fn_generatePdf(contractObjId, templateObjId, templateType){
var url = "";
if(templateType === "1"){
url = "/contractMgmt/estimateTemplate1.do?templateObjId=" + templateObjId;
} else if(templateType === "2"){
url = "/contractMgmt/estimateTemplate2.do?templateObjId=" + templateObjId;
}
// 숨겨진 iframe으로 페이지 로드
var iframe = $('<iframe>', {
id: 'pdfGeneratorFrame',
src: url,
style: 'position:absolute;width:0;height:0;border:none;'
}).appendTo('body');
// iframe 로드 완료 대기
iframe.on('load', function(){
try {
var iframeWindow = this.contentWindow;
// 데이터 로딩 완료 대기 (최대 120초 = 1200번 시도)
var checkDataLoaded = function(attempts) {
if(attempts > 1200) {
$('#pdfGeneratorFrame').remove();
Swal.close();
Swal.fire({
title: '타임아웃',
text: '견적서 데이터 로딩 시간이 초과되었습니다. (120초)',
icon: 'error'
});
return;
}
if(iframeWindow.dataLoaded === true) {
// PDF 생성 함수 호출
if(typeof iframeWindow.fn_generateAndUploadPdf === 'function'){
iframeWindow.fn_generateAndUploadPdf(function(pdfBase64){
$('#pdfGeneratorFrame').remove();
if(pdfBase64){
// PDF 업로드 후 메일 발송
fn_uploadPdfAndSendMail(contractObjId, pdfBase64);
} else {
Swal.close();
Swal.fire({
title: '오류',
text: 'PDF 생성에 실패했습니다.',
icon: 'error'
});
}
});
} else {
$('#pdfGeneratorFrame').remove();
Swal.close();
Swal.fire({
title: '오류',
text: '견적서 페이지 로드에 실패했습니다.',
icon: 'error'
});
}
} else {
setTimeout(function() {
checkDataLoaded(attempts + 1);
}, 100);
}
};
checkDataLoaded(0);
} catch(e) {
console.error('PDF 생성 오류:', e);
$('#pdfGeneratorFrame').remove();
Swal.close();
Swal.fire({
title: '오류',
text: 'PDF 생성 중 오류가 발생했습니다.',
icon: 'error'
});
}
});
// 타임아웃 설정 (180초 = 3분 - 장비 견적서는 데이터가 많아서 시간이 더 필요)
setTimeout(function(){
if($('#pdfGeneratorFrame').length > 0){
$('#pdfGeneratorFrame').remove();
Swal.close();
Swal.fire({
title: '타임아웃',
text: 'PDF 생성 시간이 초과되었습니다. (180초)\n견적서 데이터가 많은 경우 시간이 오래 걸릴 수 있습니다.',
icon: 'error'
});
}
}, 180000);
}
// PDF 업로드 및 메일 발송
function fn_uploadPdfAndSendMail(contractObjId, pdfBase64){
Swal.update({
text: 'PDF 업로드 중...'
});
// 청크 업로드 (기존 로직 활용)
var chunkSize = 100 * 1024;
var totalChunks = Math.ceil(pdfBase64.length / chunkSize);
var uploadedChunks = 0;
var sessionId = 'pdf_' + contractObjId + '_' + new Date().getTime();
function uploadChunk(chunkIndex) {
var start = chunkIndex * chunkSize;
var end = Math.min(start + chunkSize, pdfBase64.length);
var chunk = pdfBase64.substring(start, end);
$.ajax({
url: "/contractMgmt/uploadPdfChunk.do",
type: "POST",
data: {
sessionId: sessionId,
chunkIndex: chunkIndex,
totalChunks: totalChunks,
chunk: chunk
},
dataType: "json",
timeout: 30000,
success: function(data){
if(data.result === "success"){
uploadedChunks++;
var progress = Math.round((uploadedChunks / totalChunks) * 100);
Swal.update({
text: 'PDF 업로드 중... ' + progress + '%'
});
if(chunkIndex + 1 < totalChunks){
uploadChunk(chunkIndex + 1);
} else {
// 모든 청크 업로드 완료, 메일 발송
$("#pdfSessionId").val(sessionId);
fn_submitMailForm();
}
} else {
Swal.close();
Swal.fire({
title: '업로드 실패',
text: 'PDF 업로드 중 오류가 발생했습니다.',
icon: 'error'
});
}
},
error: function(){
Swal.close();
Swal.fire({
title: '오류',
text: 'PDF 업로드 중 시스템 오류가 발생했습니다.',
icon: 'error'
});
}
});
}
uploadChunk(0);
}
// 메일 발송 요청
function fn_submitMailForm(){
Swal.update({
text: '메일 발송 중...'
});
var formData = {
objId: $("#contractObjId").val(),
pdfSessionId: $("#pdfSessionId").val(),
toEmails: $("#toEmails").val(),
ccEmails: $("#ccEmails").val(),
subject: $("#subject").val(),
contents: $("#contents").val()
};
$.ajax({
url: "/contractMgmt/sendEstimateMailCustom.do",
type: "POST",
data: formData,
dataType: "json",
timeout: 60000,
success: function(data){
Swal.close();
if(data.result === "success"){
Swal.fire({
title: '발송 완료',
text: '견적서가 성공적으로 발송되었습니다.',
icon: 'success'
}).then(() => {
// 부모 창 새로고침
if(window.opener && typeof window.opener.fn_search === 'function'){
window.opener.fn_search();
}
window.close();
});
} else {
Swal.fire({
title: '발송 실패',
text: data.message || '메일 발송 중 오류가 발생했습니다.',
icon: 'error'
});
}
},
error: function(){
Swal.close();
Swal.fire({
title: '오류',
text: '메일 발송 중 시스템 오류가 발생했습니다.',
icon: 'error'
});
}
});
}
</script>
</body>
</html>

View File

@@ -264,6 +264,10 @@ textarea {
font-size: inherit;
}
.item-amount {
pointer-events: none;
}
textarea {
resize: vertical;
min-height: 25px;
@@ -323,11 +327,12 @@ $(function(){
}
});
// 초기 로드는 $(document).ready에서 처리하므로 주석 처리
// templateObjId가 있으면 기존 데이터 로드
var templateObjId = "<%=templateObjId%>";
if(templateObjId && templateObjId !== ""){
fn_loadTemplateData(templateObjId);
}
// var templateObjId = "<%=templateObjId%>";
// if(templateObjId && templateObjId !== ""){
// fn_loadTemplateData(templateObjId);
// }
// 인쇄 버튼
$("#btnPrint").click(function(){
@@ -351,55 +356,62 @@ $(function(){
fn_addItemRow();
});
// 금액 자동 계산
// 계 행 삭제 버튼
$(document).on("click", ".btn-delete-total-row", function(){
if(confirm("계 행을 삭제하시겠습니까?")) {
$(".total-row").hide();
}
});
// 금액 자동 계산 (수량, 단가 변경 시)
$(document).on("change keyup", ".item-qty, .item-price", function(){
fn_calculateAmount($(this).closest("tr"));
fn_calculateTotal(); // 합계 재계산
});
// 금액 필드 직접 수정 시에도 합계 재계산
$(document).on("change keyup", ".item-amount", function(){
fn_calculateTotal(); // 합계 재계산
});
// 콤마 자동 추가 (동적 요소 포함)
$(document).on("blur", ".item-price, .item-amount", function(){
var val = $(this).val().replace(/,/g, "").replace(/₩/g, "");
// 단가 입력 완료 시 콤마 자동 추가
$(document).on("blur", ".item-price", function(){
var val = $(this).val().replace(/,/g, "");
if(!isNaN(val) && val !== "") {
$(this).val(addComma(val));
}
fn_calculateTotal(); // blur 시에도 합계 재계산
});
// 단가 입력 시 실시간 콤마 처리
// 단가 입력 시 숫자만 입력 가능하도록 제한 및 실시간 콤마 처리
$(document).on("input", ".item-price", function(){
var val = $(this).val().replace(/,/g, "");
var cursorPos = this.selectionStart;
var commasBefore = ($(this).val().substring(0, cursorPos).match(/,/g) || []).length;
var originalVal = $(this).val();
var commasBefore = (originalVal.substring(0, cursorPos).match(/,/g) || []).length;
if(!isNaN(val) && val !== "") {
// 콤마 제거 후 숫자가 아닌 문자 제거
var val = originalVal.replace(/,/g, "").replace(/[^0-9]/g, "");
// 값 설정 (빈 문자열 포함)
if(val !== "") {
$(this).val(addComma(val));
// 커서 위치 조정
var commasAfter = ($(this).val().substring(0, cursorPos).match(/,/g) || []).length;
var newPos = cursorPos + (commasAfter - commasBefore);
this.setSelectionRange(newPos, newPos);
} else {
$(this).val("");
}
});
// 수량 입력 시 숫자만 입력 가능하도록 제한
$(document).on("input", ".item-qty", function(){
var val = $(this).val().replace(/[^0-9]/g, "");
$(this).val(val);
});
// 데이터 로드
if("<%=objId%>" !== "" && "<%=objId%>" !== "-1") {
fn_loadData();
} else {
// 새 견적서 작성 시 기본 행의 셀렉트박스 초기화
fn_initItemDescSelect('default_item_1');
fn_initItemDescSelect('default_item_2');
// 초기 로드 시 합계 계산
fn_calculateTotal();
// 새로 등록 시 명시적으로 작성중 상태 설정
g_apprStatus = "작성중";
fn_controlButtons();
if("<%=templateObjId%>" !== "" && "<%=templateObjId%>" !== "-1") {
// 저장된 견적서 수정: 견적서 데이터 로드
fn_loadTemplateData("<%=templateObjId%>");
} else if("<%=objId%>" !== "" && "<%=objId%>" !== "-1") {
// 처음 견적서 작성: 영업정보의 품목 데이터 로드
fn_loadContractItems("<%=objId%>");
}
});
@@ -427,6 +439,7 @@ function fn_controlButtons() {
// 삭제 버튼 숨김
$(".btn-delete-row").hide();
$(".btn-delete-total-row").hide(); // 계 삭제 버튼도 숨김
} else {
console.log("결재완료 아님 - 입력 필드 활성화");
// 결재완료가 아닌 경우 버튼 표시
@@ -446,6 +459,7 @@ function fn_controlButtons() {
// 삭제 버튼 표시
$(".btn-delete-row").show();
$(".btn-delete-total-row").show(); // 계 삭제 버튼도 표시
}
}
@@ -456,7 +470,8 @@ function fn_calculateAmount(row) {
var amount = parseInt(qty) * parseInt(price);
if(!isNaN(amount)) {
row.find(".item-amount").val(addComma(amount));
var currencySymbol = getCurrencySymbol();
row.find(".item-amount").val(currencySymbol + addComma(amount));
}
}
@@ -467,8 +482,8 @@ function fn_calculateTotal() {
// 품목 행만 순회 (계 행, 원화환산 행, 비고 행, 참조사항 행, 회사명 행 제외)
$("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row, .notes-row, .footer-row").each(function(){
var amount = $(this).find(".item-amount").val() || "0";
// 콤마와 통화 기호 제거 후 숫자로 변환
amount = amount.replace(/,/g, "").replace(/₩/g, "").replace(/\$/g, "").replace(/€/g, "").replace(/¥/g, "");
// 모든 비숫자 문자 제거 (통화 기호, 콤마 등)
amount = amount.replace(/[^0-9]/g, "");
var numAmount = parseInt(amount) || 0;
total += numAmount;
});
@@ -613,6 +628,159 @@ function fn_addItemRow() {
fn_calculateTotal();
}
// 영업정보의 품목 데이터 로드
function fn_loadContractItems(contractObjId) {
$.ajax({
url: "/contractMgmt/getContractItemList.do",
type: "POST",
data: {
contractObjId: contractObjId
},
dataType: "json",
success: function(data) {
console.log("품목 데이터:", data); // 디버깅용
if(data && data.result === "success" && data.items && data.items.length > 0) {
// 환율 정보 설정
if(data.exchangeRate) {
g_exchangeRate = parseFloat(data.exchangeRate);
}
if(data.currencyName) {
g_currencyName = data.currencyName;
}
console.log("환율 정보:", g_exchangeRate, g_currencyName);
// 고객사 정보 설정
if(data.customerObjId && data.customerObjId !== "") {
// 데이터 로드 중 플래그 설정
window.isLoadingData = true;
$("#recipient").val(data.customerObjId).trigger('change');
// 담당자 목록 로드
fn_loadCustomerContact(data.customerObjId);
// 플래그 해제
setTimeout(function() {
window.isLoadingData = false;
}, 100);
}
// 기존 테이블 초기화
$("#itemsTableBody").empty();
// 품목 HTML 생성
var itemsHtml = "";
for(var i = 0; i < data.items.length; i++) {
var item = data.items[i];
console.log("품목 " + (i+1) + ":", item); // 각 품목 디버깅
var itemId = 'contract_item_' + i;
var partNo = item.PART_NO || item.part_no || '';
var partName = item.PART_NAME || item.part_name || '';
var quantity = item.QUANTITY || item.quantity || '';
var partObjId = item.PART_OBJID || item.part_objid || '';
console.log("추출된 값 - partName:", partName, ", partObjId:", partObjId, ", quantity:", quantity);
itemsHtml += '<tr id="' + itemId + '">';
itemsHtml += '<td>' + (i + 1) + '</td>';
itemsHtml += '<td class="text-left editable">';
itemsHtml += '<select class="item-desc-select" style="width:100%;">';
if(partName) {
itemsHtml += '<option value="' + partObjId + '" selected>' + partName + '</option>';
}
itemsHtml += '</select>';
itemsHtml += '<input type="hidden" class="item-desc" value="' + partName + '">';
itemsHtml += '<input type="hidden" class="item-part-objid" value="' + partObjId + '">';
itemsHtml += '</td>';
itemsHtml += '<td class="text-left editable"><textarea class="item-spec"></textarea></td>';
itemsHtml += '<td class="editable"><input type="text" class="item-qty" value="' + quantity + '"></td>';
itemsHtml += '<td class="editable"><input type="text" class="item-unit" value="EA"></td>';
itemsHtml += '<td class="text-right editable"><input type="text" class="item-price" value=""></td>';
itemsHtml += '<td class="text-right editable"><input type="text" class="item-amount" value="" readonly></td>';
itemsHtml += '<td class="editable"><input type="text" class="item-note" value=""></td>';
itemsHtml += '</tr>';
}
// 계 행 추가
itemsHtml += '<tr class="total-row">';
itemsHtml += '<td colspan="6" style="text-align: center; font-weight: bold; background-color: #f0f0f0;">계</td>';
itemsHtml += '<td class="text-right" style="font-weight: bold; background-color: #f0f0f0;"><span id="totalAmount">0</span></td>';
itemsHtml += '<td style="background-color: #f0f0f0; text-align: center;"><button type="button" class="btn-delete-total-row" style="padding: 2px 8px; font-size: 9pt; cursor: pointer;">삭제</button></td>';
itemsHtml += '</tr>';
// 원화환산 공급가액 행 추가 (숨김)
itemsHtml += '<tr class="total-krw-row" style="display: none;">';
itemsHtml += '<td colspan="6" style="text-align: center; font-weight: bold; background-color: #e8f4f8;">원화환산 공급가액 (KRW)</td>';
itemsHtml += '<td class="text-right" style="font-weight: bold; background-color: #e8f4f8;"><span id="totalAmountKRW">0</span></td>';
itemsHtml += '<td style="background-color: #e8f4f8;"></td>';
itemsHtml += '</tr>';
// 비고 행 추가
itemsHtml += '<tr class="remarks-row">';
itemsHtml += '<td colspan="8" style="height: 100px; vertical-align: top; padding: 10px; text-align: left;">';
itemsHtml += '<div style="font-weight: bold; margin-bottom: 10px; text-align: left;">&lt;비고&gt;</div>';
itemsHtml += '<textarea id="note_remarks" style="width: 100%; height: 70px; border: none; resize: none; font-family: inherit; font-size: 10pt; text-align: left;"></textarea>';
itemsHtml += '</td>';
itemsHtml += '</tr>';
// 참조사항 행 추가
itemsHtml += '<tr class="notes-row">';
itemsHtml += '<td colspan="8" style="vertical-align: top; padding: 10px; text-align: left; border: 1px solid #000;">';
itemsHtml += '<div style="font-weight: bold; margin-bottom: 10px; text-align: left;">&lt;참조사항&gt;</div>';
itemsHtml += '<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note1" value="1. 견적유효기간: 일" style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>';
itemsHtml += '<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note2" value="2. 납품기간: 발주 후 1주 이내" style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>';
itemsHtml += '<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note3" value="3. VAT 별도" style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>';
itemsHtml += '<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note4" value="4. 결제 조건 : 기존 결제조건에 따름." style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>';
itemsHtml += '</td>';
itemsHtml += '</tr>';
// 하단 회사명 행 추가
itemsHtml += '<tr class="footer-row">';
itemsHtml += '<td colspan="8" style="text-align: right; padding: 15px; font-size: 10pt; font-weight: bold; border: none;">';
itemsHtml += '㈜알피에스';
itemsHtml += '</td>';
itemsHtml += '</tr>';
// HTML 삽입
$("#itemsTableBody").html(itemsHtml);
// 셀렉트박스 초기화
for(var i = 0; i < data.items.length; i++) {
var itemId = 'contract_item_' + i;
fn_initItemDescSelect(itemId);
}
// 합계 계산
fn_calculateTotal();
// 결재상태에 따라 버튼 제어
g_apprStatus = "작성중";
fn_controlButtons();
} else {
// 품목이 없으면 기본 행 표시
fn_initItemDescSelect('default_item_1');
fn_initItemDescSelect('default_item_2');
fn_calculateTotal();
g_apprStatus = "작성중";
fn_controlButtons();
}
},
error: function(xhr, status, error) {
console.error("품목 데이터 로드 오류:", xhr, status, error);
Swal.fire("품목 데이터를 불러오는데 실패했습니다.");
// 오류 시 기본 행 표시
fn_initItemDescSelect('default_item_1');
fn_initItemDescSelect('default_item_2');
fn_calculateTotal();
g_apprStatus = "작성중";
fn_controlButtons();
}
});
}
// 데이터 로드
function fn_loadData() {
$.ajax({
@@ -692,12 +860,12 @@ function fn_loadData() {
itemsHtml += '</tr>';
}
// 계 행 추가
itemsHtml += '<tr class="total-row">';
itemsHtml += '<td colspan="6" style="text-align: center; font-weight: bold; background-color: #f0f0f0;">계</td>';
itemsHtml += '<td class="text-right" style="font-weight: bold; background-color: #f0f0f0;"><span id="totalAmount">0</span></td>';
itemsHtml += '<td style="background-color: #f0f0f0;"></td>';
itemsHtml += '</tr>';
// 계 행 추가
itemsHtml += '<tr class="total-row">';
itemsHtml += '<td colspan="6" style="text-align: center; font-weight: bold; background-color: #f0f0f0;">계</td>';
itemsHtml += '<td class="text-right" style="font-weight: bold; background-color: #f0f0f0;"><span id="totalAmount">0</span></td>';
itemsHtml += '<td style="background-color: #f0f0f0; text-align: center;"><button type="button" class="btn-delete-total-row" style="padding: 2px 8px; font-size: 9pt; cursor: pointer;">삭제</button></td>';
itemsHtml += '</tr>';
// 원화환산 공급가액 행 추가 (숨김)
itemsHtml += '<tr class="total-krw-row" style="display: none;">';
@@ -843,6 +1011,9 @@ function fn_loadTemplateData(templateObjId){
if(recipient && recipient !== "") {
// OBJID로 셀렉트박스 선택
$("#recipient").val(recipient).trigger('change');
// 담당자 목록 로드 (저장된 수신인도 함께 전달)
fn_loadCustomerContact(recipient, contactPerson);
}
// 플래그 해제
@@ -883,9 +1054,12 @@ function fn_loadTemplateData(templateObjId){
var note = item.NOTE || item.note || '';
var partObjId = item.PART_OBJID || item.part_objid || '';
// 단가와 금액에 콤마 추가
// 통화 기호 가져오기
var currencySymbol = getCurrencySymbol();
// 단가와 금액에 콤마 및 통화 기호 추가
var unitPriceFormatted = unitPrice ? addComma(unitPrice) : '';
var amountFormatted = amount ? addComma(amount) : '';
var amountFormatted = amount ? (currencySymbol + addComma(amount)) : '';
itemsHtml += '<tr id="' + itemId + '">';
itemsHtml += '<td>' + (i + 1) + '</td>';
@@ -908,12 +1082,12 @@ function fn_loadTemplateData(templateObjId){
itemsHtml += '</tr>';
}
// 계 행 추가
itemsHtml += '<tr class="total-row">';
itemsHtml += '<td colspan="6" style="text-align: center; font-weight: bold; background-color: #f0f0f0;">계</td>';
itemsHtml += '<td class="text-right" style="font-weight: bold; background-color: #f0f0f0;"><span id="totalAmount">0</span></td>';
itemsHtml += '<td style="background-color: #f0f0f0;"></td>';
itemsHtml += '</tr>';
// 계 행 추가
itemsHtml += '<tr class="total-row">';
itemsHtml += '<td colspan="6" style="text-align: center; font-weight: bold; background-color: #f0f0f0;">계</td>';
itemsHtml += '<td class="text-right" style="font-weight: bold; background-color: #f0f0f0;"><span id="totalAmount">0</span></td>';
itemsHtml += '<td style="background-color: #f0f0f0; text-align: center;"><button type="button" class="btn-delete-total-row" style="padding: 2px 8px; font-size: 9pt; cursor: pointer;">삭제</button></td>';
itemsHtml += '</tr>';
// 원화환산 공급가액 행 추가 (숨김)
itemsHtml += '<tr class="total-krw-row" style="display: none;">';
@@ -1006,6 +1180,14 @@ function fn_loadTemplateData(templateObjId){
// 합계 계산
fn_calculateTotal();
// 계 행 표시 여부 복원
var showTotalRow = template.SHOW_TOTAL_ROW || template.show_total_row || "Y";
if(showTotalRow === "N") {
$(".total-row").hide();
} else {
$(".total-row").show();
}
// 결재상태에 따라 버튼 제어
fn_controlButtons();
@@ -1027,27 +1209,42 @@ function fn_loadTemplateData(templateObjId){
}
// 저장
// 고객사 담당자 정보 로드
function fn_loadCustomerContact(customerObjId) {
// 고객사 담당자 목록 로드
function fn_loadCustomerContact(customerObjId, selectedContact) {
if(!customerObjId || customerObjId === "") {
$("#contact_person").val("");
$("#contact_person").empty().append('<option value="">담당자 선택</option>');
return;
}
$.ajax({
url: "/contractMgmt/getCustomerContactInfo.do",
url: "/contractMgmt/getCustomerManagerList.do",
type: "POST",
data: { customerObjId: customerObjId },
dataType: "json",
success: function(data) {
if(data && data.contactPerson) {
$("#contact_person").val(data.contactPerson + " 귀하");
$("#contact_person").empty();
$("#contact_person").append('<option value="">담당자 선택</option>');
if(data && data.managers && data.managers.length > 0) {
for(var i = 0; i < data.managers.length; i++) {
var manager = data.managers[i];
if(manager.name && manager.name !== "") {
$("#contact_person").append('<option value="' + manager.name + ' 귀하">' + manager.name + ' 귀하</option>');
}
}
} else {
$("#contact_person").val("구매 담당자님 귀하");
$("#contact_person").append('<option value="구매 담당자님 귀하">구매 담당자님 귀하</option>');
}
// 저장된 수신인이 있으면 선택
if(selectedContact && selectedContact !== "") {
$("#contact_person").val(selectedContact);
}
},
error: function() {
$("#contact_person").val("구매 담당자님 귀하");
$("#contact_person").empty();
$("#contact_person").append('<option value="">담당자 선택</option>');
$("#contact_person").append('<option value="구매 담당자님 귀하">구매 담당자님 귀하</option>');
}
});
}
@@ -1066,10 +1263,10 @@ function fn_save() {
part_objid: row.find(".item-part-objid").val() || "", // part_objid 추가
description: row.find(".item-desc").val() || "",
specification: row.find(".item-spec").val() || "",
quantity: quantity.replace(/,/g, ""), // 콤마 제거
quantity: quantity.replace(/[^0-9]/g, ""), // 숫자만 추출
unit: row.find(".item-unit").val() || "",
unit_price: unitPrice.replace(/,/g, ""), // 콤마 제거
amount: amount.replace(/,/g, "").replace(/₩/g, ""), // 콤마와 ₩ 제거
unit_price: unitPrice.replace(/[^0-9]/g, ""), // 숫자만 추출
amount: amount.replace(/[^0-9]/g, ""), // 숫자만 추출
note: row.find(".item-note").val() || ""
});
});
@@ -1090,6 +1287,9 @@ function fn_save() {
// 디버깅: 품목 데이터 확인
console.log("저장할 품목 데이터:", items);
// 계 행 표시 여부 확인
var showTotalRow = $(".total-row").is(":visible") ? "Y" : "N";
var formData = {
objId: contractObjId,
template_type: "1",
@@ -1107,6 +1307,7 @@ function fn_save() {
note2: $("#note2").val(),
note3: $("#note3").val(),
note4: $("#note4").val(),
show_total_row: showTotalRow, // 계 행 표시 여부
items: JSON.stringify(items)
};
@@ -1189,7 +1390,9 @@ function fn_save() {
<tr>
<td class="label">수신인</td>
<td class="editable">
<input type="text" id="contact_person" value="구매 담당자님 귀하" readonly style="background-color: #f5f5f5;">
<select id="contact_person" style="width: 100%; border: none; font-size: 9pt; padding: 2px;">
<option value="">담당자 선택</option>
</select>
</td>
</tr>
<tr>
@@ -1258,12 +1461,12 @@ function fn_save() {
<td class="text-right editable"><input type="text" class="item-amount" value="" readonly></td>
<td class="editable"><input type="text" class="item-note" value=""></td>
</tr>
<!-- 계 행 -->
<tr class="total-row">
<td colspan="6" style="text-align: center; font-weight: bold; background-color: #f0f0f0;">계</td>
<td class="text-right" style="font-weight: bold; background-color: #f0f0f0;"><span id="totalAmount">0</span></td>
<td style="background-color: #f0f0f0;"></td>
</tr>
<!-- 계 행 -->
<tr class="total-row">
<td colspan="6" style="text-align: center; font-weight: bold; background-color: #f0f0f0;">계</td>
<td class="text-right" style="font-weight: bold; background-color: #f0f0f0;"><span id="totalAmount">0</span></td>
<td style="background-color: #f0f0f0; text-align: center;"><button type="button" class="btn-delete-total-row" style="padding: 2px 8px; font-size: 9pt; cursor: pointer;">삭제</button></td>
</tr>
<!-- 원화환산 공급가액 행 (숨김) -->
<tr class="total-krw-row" style="display: none;">
<td colspan="6" style="text-align: center; font-weight: bold; background-color: #e8f4f8;">원화환산 공급가액 (KRW)</td>

File diff suppressed because it is too large Load Diff

View File

@@ -24,6 +24,11 @@ $(document).ready(function(){
//날짜
_fnc_datepick();
// 그리드 높이 동적 계산 (Total 합계 영역 + 여유 공간 80px)
fnc_calculateContentHeight("gridDiv", 80);
$(window).resize(function() {
fnc_calculateContentHeight("gridDiv", 80);
});
$('.select2').select2();
@@ -96,14 +101,14 @@ $(document).ready(function(){
});
var columns = [
{formatter:"rowSelection", titleFormatter:"rowSelection", hozAlign:"center", headerSort:false, width:40, frozen:true},
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '영업번호', field : 'CONTRACT_NO', frozen:true,
formatter:fnc_createGridAnchorTag,
cellClick:function(e, cell){
var objid = fnc_checkNull(cell.getData().OBJID);
fn_projectConceptDetail(objid);
}
},
// rowSelection 제거 - fnc_tabul_search의 showCheck 파라미터로 자동 추가됨
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '영업번호', field : 'CONTRACT_NO', frozen:true,
formatter:fnc_createGridAnchorTag,
cellClick:function(e, cell){
var objid = fnc_checkNull(cell.getData().OBJID);
fn_projectConceptDetail(objid);
}
},
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '주문유형', field : 'CATEGORY_NAME' },
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '발주일', field : 'ORDER_DATE' },
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '발주번호', field : 'PO_NO' },
@@ -182,64 +187,49 @@ var columns = [
//var grid;
function fn_search(){
// 그리드 조회 및 Total 합계 업데이트를 위한 커스텀 AJAX
$.ajax({
url: "/contractMgmt/contractGridList.do",
type: "POST",
data: $("#form1").serializeObject(),
dataType: "json",
beforeSend: function(){
_startLoading("Loading...");
},
complete: function(){
_endLoading();
},
success: function(response) {
// 그리드 데이터 설정
if(_tabulGrid){
_tabulGrid.setData(response.RESULTLIST || []);
} else {
// 그리드 초기화
_tabulGrid = new Tabulator("#mainGrid", {
layout: _tabul_layout_fitColumns,
columns: columns,
data: response.RESULTLIST || [],
selectable: true
});
}
// 조회된 전체 데이터의 합계 계산
var totalSupplyPrice = 0;
var totalVat = 0;
var totalAmount = 0;
if(response.RESULTLIST && response.RESULTLIST.length > 0) {
response.RESULTLIST.forEach(function(row) {
var supplyPrice = parseFloat(row.ORDER_SUPPLY_PRICE_SUM || 0);
var vat = parseFloat(row.ORDER_VAT_SUM || 0);
var amount = parseFloat(row.ORDER_TOTAL_AMOUNT_SUM || 0);
totalSupplyPrice += supplyPrice;
totalVat += vat;
totalAmount += amount;
});
}
// 합계 표시
$("#totalSupplyPrice").text(Number(totalSupplyPrice).toLocaleString());
$("#totalVat").text(Number(totalVat).toLocaleString());
$("#totalAmount").text(Number(totalAmount).toLocaleString());
// 페이징 HTML 업데이트
if(response.PAGE_HTML){
$(".table_paging_wrap").html(response.PAGE_HTML);
}
},
error: function(jqxhr, status, error){
alert("데이터 조회 중 오류가 발생했습니다.");
console.error(error);
}
});
// fnc_tabul_search로 페이징 처리
_tabulGrid = fnc_tabul_search(
_tabul_layout_fitColumns,
_tabulGrid,
"/contractMgmt/contractGridList.do",
columns,
true
);
// 데이터 렌더링 완료 후 합계 계산 (한 번만 실행)
if(_tabulGrid) {
// 기존 이벤트 제거 후 재등록
_tabulGrid.off("renderComplete");
_tabulGrid.on("renderComplete", function(){
fn_calculateTotalFromGrid();
});
}
}
// 그리드에 표시된 데이터의 원화총액 합계 계산
function fn_calculateTotalFromGrid(){
if(!_tabulGrid) {
console.log("⚠️ [주문서관리] 그리드가 초기화되지 않음");
$("#totalAmount").text("0");
return;
}
// 현재 그리드에 표시된 데이터만 가져오기
var data = _tabulGrid.getData();
var totalAmountKRW = 0;
console.log("🔍 [주문서관리] 표시된 데이터 개수:", data.length);
if(data.length > 0) {
// ORDER_TOTAL_AMOUNT_KRW 합산
data.forEach(function(row) {
var amountKRW = parseFloat(row.ORDER_TOTAL_AMOUNT_KRW || 0);
totalAmountKRW += amountKRW;
});
}
console.log("✅ [주문서관리] 표시된 데이터 합계:", totalAmountKRW);
$("#totalAmount").text(Number(totalAmountKRW).toLocaleString());
}
function _fnc_datepick(){
@@ -716,23 +706,17 @@ function openProjectFormPopUp(objId){
<input type="text" name="due_end_date" id="due_end_date" style="width:90px;" autocomplete="off" value="${param.due_end_date}" class="date_icon">
</td>
</tr>
</table>
</div>
<!-- Total 합계 표시 영역 -->
<div style="padding: 3px 10px; background: #f5f5f5; border-radius: 3px; margin: 10px 0;">
<span style="font-weight: bold; font-size: 12px; margin-right: 20px;">
Total 공급가액: <span id="totalSupplyPrice" style="color: #2196F3;">0</span> 원
</span>
<span style="font-weight: bold; font-size: 12px; margin-right: 20px;">
Total 부가세: <span id="totalVat" style="color: #FF9800;">0</span> 원
</span>
<span style="font-weight: bold; font-size: 12px;">
Total 총액: <span id="totalAmount" style="color: #4CAF50;">0</span> 원
</span>
</div>
<%@include file= "/WEB-INF/view/common/common_gridArea.jsp" %>
</table>
</div>
<!-- Total 합계 표시 영역 (그리드 위) -->
<div style="padding:5px 10px; background: #f5f5f5;">
<span style="font-weight: bold; font-size: 13px;">
수주 금액 : <span id="totalAmount" style="color: #4CAF50;">0</span> 원
</span>
</div>
<%@include file= "/WEB-INF/view/common/common_gridArea.jsp" %>
</div>
</div>
</form>

View File

@@ -219,10 +219,15 @@
html += '<td><input type="text" class="item-part-no" value="' + (item.PART_NO || '') + '" readonly style="background:#f5f5f5;" /></td>';
html += '<td><input type="text" class="item-part-name" value="' + (item.PART_NAME || '') + '" readonly style="background:#f5f5f5;" /></td>';
html += '<td><input type="text" class="item-serial-no" value="' + serialNoDisplay + '" readonly style="background:#f5f5f5;" title="' + serialNo + '" /></td>';
// ORDER_QUANTITY가 없으면 QUANTITY 사용 (견적서에서 가져온 값)
html += '<td><input type="text" class="item-quantity" value="' + (item.ORDER_QUANTITY || item.QUANTITY || '') + '" numberOnly required /></td>';
// ORDER_UNIT_PRICE 수정 가능
html += '<td><input type="text" class="item-unit-price" value="' + (item.ORDER_UNIT_PRICE || '') + '" numberOnly required /></td>';
// ORDER_SUPPLY_PRICE 자동 계산
html += '<td><input type="text" class="item-supply-price" value="' + (item.ORDER_SUPPLY_PRICE || '') + '" numberOnly readonly style="background:#f5f5f5;" /></td>';
html += '<td><input type="text" class="item-vat" value="' + (item.ORDER_VAT || '') + '" numberOnly /></td>'; // readonly 제거
// ORDER_VAT 수정 가능
html += '<td><input type="text" class="item-vat" value="' + (item.ORDER_VAT || '') + '" numberOnly /></td>';
// ORDER_TOTAL_AMOUNT 자동 계산
html += '<td><input type="text" class="item-total-amount" value="' + (item.ORDER_TOTAL_AMOUNT || '') + '" numberOnly readonly style="background:#f5f5f5;" /></td>';
html += '<td style="text-align:center;">-</td>'; // 삭제 불가
html += '<input type="hidden" class="item-objid" value="' + (item.OBJID || '') + '" />';

View File

@@ -257,21 +257,48 @@
// 사업자등록증 파일 삭제
function deleteBusRegFile(fileObjId){
if(!confirm("파일을 삭제하시겠습니까?")){
return;
}
$.ajax({
url: "/common/deleteFile.do",
type: "POST",
data: {objId: fileObjId},
dataType: "json",
success: function(data){
Swal.fire("삭제되었습니다.");
loadBusRegFile();
},
error: function(){
Swal.fire("삭제 중 오류가 발생했습니다.");
Swal.fire({
title: '파일을 삭제하시겠습니까?',
text: '삭제된 파일은 복구할 수 없습니다.',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: '삭제',
cancelButtonText: '취소'
}).then((result) => {
if (result.isConfirmed) {
$.ajax({
url: "/common/deleteFile.do",
type: "POST",
data: {objId: fileObjId},
dataType: "json",
success: function(data){
if(data.success){
Swal.fire({
icon: 'success',
title: '삭제되었습니다.',
showConfirmButton: false,
timer: 1500
});
loadBusRegFile();
}else{
Swal.fire({
icon: 'error',
title: '삭제 실패',
text: data.message || '파일 삭제 중 오류가 발생했습니다.'
});
}
},
error: function(xhr, status, error){
console.error("삭제 오류:", error);
Swal.fire({
icon: 'error',
title: '삭제 실패',
text: '파일 삭제 중 오류가 발생했습니다.'
});
}
});
}
});
}

View File

@@ -53,6 +53,11 @@
$("#btnBulkRegister").click(function(){
fn_bulkRegister();
});
// 거래명세서 출력 버튼
$("#btnTransactionStatement").click(function(){
fn_printTransactionStatement();
});
});
// 날짜 선택기 초기화 함수
@@ -125,6 +130,58 @@
fn_centerPopup(popup_width, popup_height, url);
}
// 그리드 데이터를 전달하는 판매등록 팝업
function fn_openSaleRegPopupWithData(rowData){
console.log("=== fn_openSaleRegPopupWithData 호출 ===");
console.log("rowData:", rowData);
var popup_width = 850;
var popup_height = 550;
// 기본 파라미터만 전달 (orderNo, saleNo)
// 잔량 계산은 Controller에서 자동으로 처리됨
var params = "orderNo=" + encodeURIComponent(rowData.PROJECT_NO);
params += "&saleNo=" + (rowData.SALE_NO ? encodeURIComponent(rowData.SALE_NO) : "");
// 금액 정보는 Controller에서 자동으로 불러옴 (수주 데이터 기반)
// URL 파라미터로 전달하지 않음
var url = "/salesMgmt/salesRegForm.do?" + params;
console.log("최종 URL:", url);
fn_centerPopup(popup_width, popup_height, url);
}
// 거래명세서 출력 함수
function fn_printTransactionStatement() {
var selectedData = _tabulGrid.getSelectedData();
if(selectedData.length === 0) {
alert("거래명세서를 출력할 항목을 선택해주세요.");
return;
}
// 같은 거래처인지 확인
var firstCustomer = selectedData[0].CUSTOMER;
for(var i = 1; i < selectedData.length; i++) {
if(selectedData[i].CUSTOMER !== firstCustomer) {
alert("같은 거래처만 선택 가능합니다.\n선택한 거래처: " + firstCustomer + ", " + selectedData[i].CUSTOMER);
return;
}
}
// 프로젝트 번호들을 수집
var projectNos = selectedData.map(function(row) {
return row.PROJECT_NO;
}).join(',');
// 새로 만든 거래명세서 팝업 열기
var popup_width = 1000;
var popup_height = 800;
var url = "/salesMgmt/transactionStatementForm.do?projectNos=" + encodeURIComponent(projectNos);
fn_centerPopup(popup_width, popup_height, url);
}
function fn_FileRegist(objId, docType, docTypeName){
var popup_width = 800;
var popup_height = 680;
@@ -139,6 +196,18 @@
<script type="text/javascript">
// 새로운 테이블 구조에 맞게 컬럼 정의 수정
var columns = [
{
formatter: "rowSelection",
titleFormatter: "rowSelection",
headerHozAlign: 'center',
hozAlign: 'center',
width: 50,
frozen: true,
headerSort: false,
cellClick: function(e, cell){
e.stopPropagation();
}
},
{headerHozAlign : 'center', hozAlign : 'center', width : '120', title : '프로젝트번호', field : 'PROJECT_NO', frozen : true,
formatter: fnc_createGridAnchorTag,
cellClick: function(e, cell){
@@ -151,11 +220,39 @@ var columns = [
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '발주일', field : 'ORDER_DATE'},
{headerHozAlign : 'center', hozAlign : 'center', width : '120', title : '발주번호', field : 'PO_NO'},
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '요청납기', field : 'REQUEST_DATE'},
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '출하일', field : 'SHIPPING_DATE'},
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '출하일', field : 'SHIPPING_DATE',
cellClick: function(e, cell) {
var projectNo = cell.getRow().getData().PROJECT_NO;
if(projectNo) {
fn_openShippingDetail(projectNo);
}
}
},
{headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '고객사', field : 'CUSTOMER'},
{headerHozAlign : 'center', hozAlign : 'left', width : '180', title : '품명', field : 'PRODUCT_NAME'},
{headerHozAlign : 'center', hozAlign : 'right', width : '100', title : '견적수량', field : 'ESTIMATE_QUANTITY',
formatter: function(cell) {
var value = cell.getValue();
if(!value || value === '' || value === '0') return '';
// "X 외 Y건" 형식인지 확인
if(typeof value === 'string' && value.includes('외')) {
return value;
}
// 숫자인 경우 포맷팅
return Number(value).toLocaleString();
}
},
{headerHozAlign : 'center', hozAlign : 'right', width : '100', title : '수주수량', field : 'ORDER_QUANTITY',
formatter: "money", formatterParams: {thousand: ",", symbolAfter: "", precision: false}
formatter: function(cell) {
var value = cell.getValue();
if(!value || value === '' || value === '0') return '';
// "X 외 Y건" 형식인지 확인
if(typeof value === 'string' && value.includes('외')) {
return value;
}
// 숫자인 경우 포맷팅
return Number(value).toLocaleString();
}
},
{headerHozAlign : 'center', hozAlign : 'right', width : '100', title : '판매수량', field : 'SALES_QUANTITY',
formatter: "money", formatterParams: {thousand: ",", symbolAfter: "", precision: false}
@@ -282,6 +379,23 @@ function fn_search(){
});
}
// 출하일 상세 내역 팝업
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");
}
// 출하지시/판매등록 함수 (1건만 선택 가능)
function fn_bulkRegister(){
if(!_tabulGrid){
@@ -306,8 +420,8 @@ function fn_bulkRegister(){
// 선택한 1건의 항목 가져오기
var selectedRow = selectedRows[0];
// 판매등록 팝업 열기
fn_openSaleRegPopup(selectedRow.PROJECT_NO, selectedRow.SALE_NO);
// 판매등록 팝업 열기 (그리드 데이터 전달)
fn_openSaleRegPopupWithData(selectedRow);
}
</script>
</head>
@@ -323,6 +437,7 @@ function fn_bulkRegister(){
</h2>
<div class="btnArea">
<input type="button" value="조회" class="plm_btns" id="btnSearch">
<input type="button" value="거래명세서 출력" class="plm_btns" id="btnTransactionStatement" style="background-color: #2196F3; color: white;">
<input type="button" value="출하지시/판매등록" class="plm_btns" id="btnBulkRegister" style="background-color: #4CAF50; color: white;">
</div>
</div>

View File

@@ -3,6 +3,8 @@
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@include file="/init_new.jsp"%>
<%
PersonBean person = (PersonBean) session.getAttribute(Constants.PERSON_BEAN);
@@ -19,6 +21,15 @@
var snCounter = 1;
$(function() {
console.log("=== salesRegForm.jsp 로드 ===");
console.log("SALES_QUANTITY: ${saleInfo.SALES_QUANTITY}");
console.log("SALES_UNIT_PRICE: ${saleInfo.SALES_UNIT_PRICE}");
console.log("SALES_SUPPLY_PRICE: ${saleInfo.SALES_SUPPLY_PRICE}");
console.log("SALES_VAT: ${saleInfo.SALES_VAT}");
console.log("SALES_TOTAL_AMOUNT: ${saleInfo.SALES_TOTAL_AMOUNT}");
console.log("SALES_CURRENCY: ${saleInfo.SALES_CURRENCY}");
console.log("SALES_EXCHANGE_RATE: ${saleInfo.SALES_EXCHANGE_RATE}");
$('.select2').select2();
// 날짜 선택기 초기화
@@ -33,18 +44,6 @@
// 판매환종 초기값 설정 (견적환종과 동기화)
initializeSalesCurrency();
// 페이지 로드 시 금액 자동 계산 (수주 데이터가 있을 때)
setTimeout(function() {
var quantity = parseFloat($("#salesQuantity").val()) || 0;
var unitPrice = parseFloat($("#salesUnitPrice").val()) || 0;
// 수주 데이터가 있으면 자동 계산
if(quantity > 0 || unitPrice > 0) {
fn_calculateSupplyPrice();
console.log("페이지 로드 시 금액 자동 계산 완료");
}
}, 500);
// S/N 필드 클릭 이벤트
$("#serialNo").click(function() {
fn_openSnManagePopup();
@@ -75,13 +74,77 @@
self.close();
});
// 저장 버튼
$("#btnSave").click(function() {
fn_save();
});
// 저장 버튼
$("#btnSave").click(function() {
fn_save();
});
// 품목이 여러 개인 경우 자동으로 첫 번째 품목 선택 - 주석처리: 품목은 하나만 존재
/*
if($(".item-radio").length > 0) {
$(".item-radio").first().prop("checked", true);
fn_calculateSelectedItem();
}
*/
});
// 판매공급가액 계산 함수
// === Phase 2: 품목 선택 관련 함수 (단일 선택) - 주석처리: 품목은 하나만 존재 ===
/*
// 행 클릭 시 라디오 버튼 선택
function fn_selectItem(itemObjid) {
$(".item-radio[value='" + itemObjid + "']").prop("checked", true);
fn_calculateSelectedItem();
}
// 선택한 품목의 정보를 입력 필드에 반영
function fn_calculateSelectedItem() {
var selectedRadio = $(".item-radio:checked");
if(selectedRadio.length === 0) {
$("#selectedItemInfo").text("품목을 선택하세요");
return;
}
var quantity = parseFloat(selectedRadio.data("quantity")) || 0;
var unitPrice = parseFloat(selectedRadio.data("unit-price")) || 0;
var supplyPrice = parseFloat(selectedRadio.data("supply-price")) || 0;
var vat = parseFloat(selectedRadio.data("vat")) || 0;
var totalAmount = parseFloat(selectedRadio.data("total")) || 0;
// 선택한 품목 정보 표시
var itemObjid = selectedRadio.val();
var itemRow = $("tr[data-item-objid='" + itemObjid + "']");
var partNo = itemRow.find("td:eq(1)").text().trim();
var partName = itemRow.find("td:eq(2)").text().trim();
$("#selectedItemInfo").html(
"<strong>" + partName + "</strong> (" + partNo + ") - " +
"수량: <strong>" + quantity.toLocaleString() + "</strong>개 | " +
"공급가액: <strong>" + supplyPrice.toLocaleString() + "</strong>원"
);
// 입력 필드에 자동 반영
$("#salesQuantity").val(quantity);
$("#salesUnitPrice").val(unitPrice);
$("#salesSupplyPrice").val(supplyPrice);
$("#salesVat").val(vat);
$("#salesTotalAmount").val(totalAmount);
// hidden 필드에 선택한 품목 OBJID 저장
if($("#selectedItemObjid").length === 0) {
$("<input>").attr({
type: "hidden",
id: "selectedItemObjid",
name: "selectedItemObjid"
}).appendTo("form");
}
$("#selectedItemObjid").val(itemObjid);
}
*/
// === 기존 함수들 ===
// 판매공급가액 계산 함수
function fn_calculateSupplyPrice() {
var currency = $("#salesCurrency").val();
var exchangeRate = parseFloat($("#salesExchangeRate").val()) || 1;
@@ -120,6 +183,7 @@
// 판매환종 초기값 설정 (견적환종과 동기화하되, 사용자가 변경 가능)
function initializeSalesCurrency() {
// Controller에서 계산한 값 사용 (잔량 기반)
var existingSalesCurrency = "${saleInfo.SALES_CURRENCY}";
var contractCurrency = "${orderInfo.SALES_CURRENCY}"; // 견적환종 (SALES_CURRENCY로 통일)
var contractExchangeRate = "${orderInfo.SALES_EXCHANGE_RATE}"; // 견적환율
@@ -511,23 +575,78 @@
}
console.log("=== S/N 처리 완료 ===");
if (confirm("저장하시겠습니까?")) {
if (confirm("저장하시겠습니까?")) {
$.ajax({
url : "/salesMgmt/saveSales.do",
type : "POST",
data : $("#form1").serialize(),
dataType : "json",
success : function(data) {
alert(data.msg);
// 저장 후 잔량 확인을 위해 서버에 다시 조회
$.ajax({
url : "/salesMgmt/saveSales.do",
type : "POST",
data : $("#form1").serialize(),
dataType : "json",
success : function(data) {
alert(data.msg);
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();
}
self.close();
},
error : function(jqxhr, status, error) {
alert("저장 중 오류가 발생했습니다.");
}
});
},
error : function(jqxhr, status, error) {
console.log("=== AJAX 에러 ===");
console.log("status:", status);
console.log("error:", error);
console.log("responseText:", jqxhr.responseText);
console.log("status code:", jqxhr.status);
alert("저장 중 오류가 발생했습니다.\n상태: " + status + "\n에러: " + error);
}
});
}
}
@@ -609,6 +728,67 @@
<col width="35%" />
</colgroup>
<!-- 품목 선택 영역 (Phase 2) - 주석처리: 품목은 하나만 존재 -->
<%--
<c:if test="${not empty projectItems and fn:length(projectItems) > 1}">
<tr>
<td colspan="4" style="padding:10px;">
<div style="border:1px solid #ddd; padding:10px; background:#f9f9f9;">
<strong style="display:block; margin-bottom:8px;">📦 품목 선택 (${fn:length(projectItems)}개) - 하나만 선택 가능</strong>
<table style="width:100%; border-collapse:collapse;">
<thead>
<tr style="background:#e9ecef; border-bottom:2px solid #dee2e6;">
<th style="padding:8px; text-align:center; width:50px;">선택</th>
<th style="padding:8px; text-align:left;">품번</th>
<th style="padding:8px; text-align:left;">품명</th>
<th style="padding:8px; text-align:right;">수량</th>
<th style="padding:8px; text-align:right;">단가</th>
<th style="padding:8px; text-align:right;">공급가액</th>
</tr>
</thead>
<tbody id="itemListBody">
<c:forEach var="item" items="${projectItems}" varStatus="status">
<tr style="border-bottom:1px solid #dee2e6; cursor:pointer;"
data-item-objid="${item.ITEM_OBJID}"
onclick="fn_selectItem('${item.ITEM_OBJID}')">
<td style="padding:8px; text-align:center;">
<input type="radio" name="selectedItem" class="item-radio"
value="${item.ITEM_OBJID}"
data-objid="${item.ITEM_OBJID}"
data-quantity="${item.QUANTITY}"
data-unit-price="${item.UNIT_PRICE}"
data-supply-price="${item.SUPPLY_PRICE}"
data-vat="${item.VAT}"
data-total="${item.TOTAL_AMOUNT}"
onchange="fn_calculateSelectedItem()" />
</td>
<td style="padding:8px;">${item.PART_NO}</td>
<td style="padding:8px;">${item.PART_NAME}</td>
<td style="padding:8px; text-align:right;">
<fmt:formatNumber value="${item.QUANTITY}" pattern="#,##0" />
</td>
<td style="padding:8px; text-align:right;">
<fmt:formatNumber value="${item.UNIT_PRICE}" pattern="#,##0" />
</td>
<td style="padding:8px; text-align:right;">
<fmt:formatNumber value="${item.SUPPLY_PRICE}" pattern="#,##0" />
</td>
</tr>
</c:forEach>
</tbody>
</table>
<div style="margin-top:10px; padding:8px; background:#d1ecf1; border:1px solid #0c5460; border-radius:4px;">
<strong>💡 선택한 품목:</strong>
<span id="selectedItemInfo" style="margin-left:10px; color:#0c5460;">
품목을 선택하세요
</span>
</div>
</div>
</td>
</tr>
</c:if>
--%>
<!-- 첫번째 행: S/N -->
<tr>
<td class="input_title"><label for="serialNo">S/N</label></td>
@@ -621,11 +801,19 @@
</td>
</tr>
<!-- 두번째 행: 판매수량, 출하일 -->
<!-- 두번째 행: 판매수량 (잔량 자동 표시), 출하일 -->
<tr>
<td class="input_title"><label for="salesQuantity">판매수량</label></td>
<td class="input_title">
<label for="salesQuantity">판매수량</label>
<span style="color:#666; font-size:11px; display:block; margin-top:2px;">
(잔량: ${saleInfo.SALES_QUANTITY}개)
</span>
</td>
<td>
<input type="number" name="salesQuantity" id="salesQuantity" value="${saleInfo.SALES_QUANTITY}" onchange="fn_calculateSupplyPrice()" />
<input type="number" name="salesQuantity" id="salesQuantity"
value="${saleInfo.SALES_QUANTITY}"
onchange="fn_calculateSupplyPrice()"
placeholder="판매할 수량 입력" />
</td>
<td class="input_title"><label for="shippingDate">출하일</label></td>
<td>
@@ -681,11 +869,15 @@
<tr>
<td class="input_title"><label for="salesUnitPrice">판매단가</label></td>
<td>
<input type="number" name="salesUnitPrice" id="salesUnitPrice" value="${saleInfo.SALES_UNIT_PRICE}" onchange="fn_calculateSupplyPrice()" />
<input type="number" name="salesUnitPrice" id="salesUnitPrice"
value="${saleInfo.SALES_UNIT_PRICE}"
onchange="fn_calculateSupplyPrice()" />
</td>
<td class="input_title"><label for="salesSupplyPrice">판매공급가액</label></td>
<td>
<input type="number" name="salesSupplyPrice" id="salesSupplyPrice" value="${saleInfo.SALES_SUPPLY_PRICE}" readonly style="background-color:#f5f5f5;" />
<input type="number" name="salesSupplyPrice" id="salesSupplyPrice"
value="${saleInfo.SALES_SUPPLY_PRICE}"
readonly style="background-color:#f5f5f5;" />
</td>
</tr>
@@ -693,11 +885,15 @@
<tr>
<td class="input_title"><label for="salesVat">판매부가세</label></td>
<td>
<input type="number" name="salesVat" id="salesVat" value="${saleInfo.SALES_VAT}" onchange="fn_calculateTotalAmount()" />
<input type="number" name="salesVat" id="salesVat"
value="${saleInfo.SALES_VAT}"
readonly style="background-color:#f5f5f5;" />
</td>
<td class="input_title"><label for="salesTotalAmount">판매총액</label></td>
<td>
<input type="number" name="salesTotalAmount" id="salesTotalAmount" value="${saleInfo.SALES_TOTAL_AMOUNT}" readonly style="background-color:#f5f5f5;" />
<input type="number" name="salesTotalAmount" id="salesTotalAmount"
value="${saleInfo.SALES_TOTAL_AMOUNT}"
readonly style="background-color:#f5f5f5;" />
</td>
</tr>
@@ -712,7 +908,9 @@
</td>
<td class="input_title"><label for="salesExchangeRate">판매환율</label></td>
<td>
<input type="number" name="salesExchangeRate" id="salesExchangeRate" step="0.01" value="${saleInfo.SALES_EXCHANGE_RATE}" onchange="fn_recalculateByExchangeRate()" />
<input type="number" name="salesExchangeRate" id="salesExchangeRate" step="0.01"
value="${saleInfo.SALES_EXCHANGE_RATE}"
onchange="fn_recalculateByExchangeRate()" />
</td>
</tr>

View File

@@ -0,0 +1,97 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>출하일 상세 내역</title>
<link rel="stylesheet" href="/css/basic.css">
<script src="/js/jquery-2.1.4.min.js"></script>
<style>
body {
margin: 0;
padding: 20px;
font-family: 'Malgun Gothic', sans-serif;
}
.popup-header {
font-size: 18px;
font-weight: bold;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #333;
}
.shipping-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.shipping-table th {
background-color: #f5f5f5;
padding: 10px;
border: 1px solid #ddd;
text-align: center;
font-weight: bold;
}
.shipping-table td {
padding: 10px;
border: 1px solid #ddd;
text-align: center;
}
.btn-close {
padding: 8px 20px;
background-color: #666;
color: white;
border: none;
cursor: pointer;
font-size: 14px;
}
.btn-close:hover {
background-color: #444;
}
.btn-wrap {
text-align: center;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="popup-header">
출하일 상세 내역 - ${projectNo}
</div>
<table class="shipping-table">
<thead>
<tr>
<th>출하일</th>
<th>출하수량</th>
<th>출하지시상태</th>
<th>S/N</th>
</tr>
</thead>
<tbody>
<c:choose>
<c:when test="${empty shippingList}">
<tr>
<td colspan="4">출하 내역이 없습니다.</td>
</tr>
</c:when>
<c:otherwise>
<c:forEach items="${shippingList}" var="item">
<tr>
<td>${item.shipping_date}</td>
<td>${item.shipping_quantity}</td>
<td>${item.shipping_order_status}</td>
<td>${item.serial_no}</td>
</tr>
</c:forEach>
</c:otherwise>
</c:choose>
</tbody>
</table>
<div class="btn-wrap">
<button type="button" class="btn-close" onclick="window.close();">닫기</button>
</div>
</body>
</html>

View File

@@ -0,0 +1,674 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page import="java.util.*" %>
<%@include file= "/init.jsp" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>거래명세서</title>
<style>
@media print {
.no-print { display: none !important; }
@page { margin: 5mm; }
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "맑은 고딕", "Malgun Gothic", sans-serif;
font-size: 11pt;
line-height: 1.4;
padding: 10px;
background-color: #f0f0f0;
}
.container {
width: 210mm;
min-height: 297mm;
margin: 0 auto;
border: 2px solid #000;
background-color: white;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
/* 제목 */
.header {
text-align: center;
font-size: 24pt;
font-weight: bold;
padding: 15px 0;
border-bottom: 2px solid #000;
letter-spacing: 8px;
}
/* 상단 정보 테이블 */
.info-table {
width: 100%;
border-collapse: collapse;
}
.info-table td {
border: 1px solid #000;
padding: 6px 10px;
font-size: 9pt;
vertical-align: middle;
}
.row-1 td, .row-2 td, .row-3 td, .row-4 td, .row-5 td {
height: 30px;
}
.date-cell {
text-align: left;
width: 30%;
padding: 8px 12px;
}
.date-label {
font-weight: bold;
margin-right: 6px;
font-size: 9pt;
}
.reg-label-cell {
background-color: #e8e8e8;
font-weight: bold;
text-align: center;
width: 15%;
font-size: 9pt;
}
.reg-value-cell {
font-size: 13pt;
font-weight: bold;
text-align: center;
width: 40%;
}
.stamp-cell {
width: 15%;
text-align: center;
padding: 5px;
vertical-align: middle;
}
.stamp-box {
width: 70px;
height: 140px;
border: 1px solid #666;
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 9pt;
color: #333;
line-height: 1.4;
}
.receiver-cell {
text-align: left;
padding: 8px 12px;
width: 30%;
}
.receiver-name {
font-size: 13pt;
font-weight: bold;
display: inline;
margin-right: 8px;
}
.receiver-suffix {
font-size: 12pt;
font-weight: bold;
display: inline;
}
.supplier-label-cell {
background-color: #e8e8e8;
font-weight: bold;
text-align: center;
width: 30px;
line-height: 1.5;
font-size: 10pt;
padding: 4px 2px;
vertical-align: middle;
}
.supplier-row-1, .supplier-row-2, .supplier-row-3, .supplier-row-4 {
text-align: left;
font-size: 9pt;
padding: 6px 10px;
}
.supplier-inline {
display: block;
}
.supplier-label-inline {
font-weight: bold;
margin-right: 8px;
font-size: 8pt;
}
.supply-text-cell {
text-align: left;
font-size: 9pt;
padding: 8px 12px;
width: 30%;
}
/* 합계 금액 */
.amount-row {
display: flex;
align-items: center;
padding: 8px 15px;
border-bottom: 2px solid #000;
background-color: #f9f9f9;
height: 40px;
}
.amount-label {
font-size: 10pt;
margin-right: 15px;
}
.amount-text {
font-size: 13pt;
font-weight: bold;
margin-right: 8px;
}
.amount-won-symbol {
font-size: 16pt;
font-weight: bold;
margin: 0 10px;
}
.amount-number {
font-size: 15pt;
font-weight: bold;
flex: 1;
text-align: right;
padding-right: 15px;
}
/* 품목 테이블 */
.items-table {
width: 100%;
border-collapse: collapse;
}
.items-table th,
.items-table td {
border: 1px solid #000;
padding: 5px 8px;
font-size: 9pt;
text-align: center;
}
.items-table th {
background-color: #e8e8e8;
font-weight: bold;
height: 32px;
}
.items-table td {
height: 28px;
}
.items-table td[contenteditable="true"] {
background-color: #fffef0;
cursor: text;
}
.text-left {
text-align: left !important;
padding-left: 10px !important;
}
.text-right {
text-align: right !important;
padding-right: 10px !important;
}
.total-row {
background-color: #e8e8e8;
font-weight: bold;
}
/* 비고 */
.note-section {
border-top: 1px solid #000;
padding: 10px 15px;
min-height: 90px;
}
.note-title {
font-weight: bold;
font-size: 9pt;
margin-bottom: 6px;
}
.note-content {
padding: 3px;
min-height: 60px;
font-size: 9pt;
line-height: 1.5;
}
.note-content[contenteditable="true"] {
background-color: #fffef0;
cursor: text;
}
/* 하단 */
.footer-table {
width: 100%;
border-collapse: collapse;
}
.footer-table td {
border: 1px solid #000;
padding: 12px 15px;
width: 50%;
vertical-align: top;
height: 90px;
}
.footer-label {
font-weight: bold;
font-size: 10pt;
margin-bottom: 25px;
}
.footer-sign {
text-align: right;
font-size: 10pt;
}
/* 버튼 */
.button-area {
text-align: center;
margin: 15px 0;
}
.btn {
padding: 10px 30px;
margin: 0 5px;
font-size: 14px;
cursor: pointer;
border: none;
border-radius: 3px;
}
.btn-print {
background-color: #2196F3;
color: white;
}
.btn-close {
background-color: #f44336;
color: white;
}
</style>
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script>
$(document).ready(function(){
var today = new Date();
var days = ['일', '월', '화', '수', '목', '금', '토'];
var dateStr = days[today.getDay()] + "요일, " + (today.getMonth() + 1) + "월 " + today.getDate() + ", " + today.getFullYear();
$("#deliveryDate").text(dateStr);
fn_loadData();
});
function fn_loadData() {
var projectNos = "${param.projectNos}";
console.log("=== 거래명세서 데이터 로드 ===");
console.log("프로젝트 번호:", projectNos);
if(!projectNos || projectNos === "") {
alert("프로젝트 번호가 없습니다.");
return;
}
$.ajax({
url: "/salesMgmt/getTransactionStatementData.do",
type: "POST",
data: { projectNos: projectNos },
dataType: "json",
success: function(data) {
console.log("=== 서버 응답 데이터 ===");
console.log("전체 데이터:", JSON.stringify(data, null, 2));
console.log("success:", data.success);
console.log("customerName:", data.customerName);
console.log("items:", data.items);
console.log("supplierName:", data.supplierName);
if(data && data.success) {
fn_fillData(data);
} else {
alert("데이터 조회 실패: " + (data.message || "알 수 없는 오류"));
}
},
error: function(xhr, status, error) {
console.log("=== AJAX 에러 ===");
console.log("status:", status);
console.log("error:", error);
console.log("responseText:", xhr.responseText);
alert("데이터 조회 중 오류 발생");
}
});
}
function fn_fillData(data) {
console.log("=== fn_fillData 호출 ===");
console.log("customerName:", data.customerName);
console.log("items:", data.items);
// 수신자 정보
if(data.customerName) {
$("#receiverName").text(data.customerName);
}
// 공급자 정보
if(data.supplierRegNo) {
$("#registrationNo").text(data.supplierRegNo);
}
if(data.supplierName) {
$("#supplierName").text(data.supplierName);
}
if(data.supplierAddress) {
$("#supplierAddr").text(data.supplierAddress);
}
if(data.supplierBusiness) {
$("#supplierBiz").text(data.supplierBusiness);
}
if(data.supplierType) {
$("#supplierType").text(data.supplierType);
}
if(data.supplierContact) {
$("#supplierTel").text(data.supplierContact);
}
// 공급 텍스트
if(data.note) {
$("#supplyText").text(data.note);
}
var totalSupply = 0;
var totalVat = 0;
var tbody = $("#itemsBody");
tbody.empty();
if(data.items && data.items.length > 0) {
console.log("품목 개수:", data.items.length);
$.each(data.items, function(i, item) {
console.log("품목 " + (i+1) + ":", item);
var supply = parseInt(item.SUPPLYPRICE || item.supplyPrice || 0);
var vat = parseInt(item.VAT || item.vat || 0);
totalSupply += supply;
totalVat += vat;
var row = $("<tr>");
row.append($("<td contenteditable='true' class='text-left'>").text(item.PRODUCTNAME || item.productName || ""));
row.append($("<td contenteditable='true'>").text(item.SPEC || item.spec || ""));
row.append($("<td contenteditable='true'>").text(item.QUANTITY || item.quantity || ""));
row.append($("<td contenteditable='true' class='text-right'>").text(fn_num(item.UNITPRICE || item.unitPrice || 0)));
row.append($("<td contenteditable='true' class='text-right'>").text(fn_num(supply)));
row.append($("<td contenteditable='true' class='text-right'>").text(fn_num(vat)));
tbody.append(row);
});
// 빈 행 추가 (최소 7개 행 유지)
for(var i = data.items.length; i < 7; i++) {
var row = $("<tr>");
for(var j = 0; j < 6; j++) {
row.append($("<td contenteditable='true'>").html("&nbsp;"));
}
tbody.append(row);
}
} else {
console.log("품목 데이터가 없습니다.");
// 빈 행 7개 추가
for(var i = 0; i < 7; i++) {
var row = $("<tr>");
for(var j = 0; j < 6; j++) {
row.append($("<td contenteditable='true'>").html("&nbsp;"));
}
tbody.append(row);
}
}
// 합계 행
var totalRow = $("<tr class='total-row'>");
totalRow.append($("<td colspan='2'>").text("계"));
totalRow.append($("<td>").text(data.items ? data.items.length : 0));
totalRow.append($("<td>").html("&nbsp;"));
totalRow.append($("<td class='text-right'>").text(fn_num(totalSupply)));
totalRow.append($("<td class='text-right'>").text(fn_num(totalVat)));
tbody.append(totalRow);
// 합계 금액
var total = totalSupply + totalVat;
console.log("총 공급가액:", totalSupply);
console.log("총 세액:", totalVat);
console.log("총 금액:", total);
$("#totalText").text(total.toString());
$("#totalNum").text(fn_num(total));
}
function fn_num(n) {
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function fn_save() {
if(!confirm("수정한 내용을 저장하시겠습니까?")) {
return;
}
// 수정된 데이터 수집
var data = {
projectNos: "${param.projectNos}",
deliveryDate: $("#deliveryDate").text(),
receiverName: $("#receiverName").text(),
registrationNo: $("#registrationNo").text(),
supplierName: $("#supplierName").text(),
supplierAddr: $("#supplierAddr").text(),
supplierBiz: $("#supplierBiz").text(),
supplierType: $("#supplierType").text(),
supplierTel: $("#supplierTel").text(),
supplyText: $("#supplyText").text(),
totalAmount: $("#totalText").text(),
noteContent: $("#noteContent").text(),
items: []
};
// 품목 데이터 수집
$("#itemsBody tr:not(.total-row)").each(function() {
var cells = $(this).find("td");
if(cells.length === 6) {
var item = {
productName: $(cells[0]).text().trim(),
spec: $(cells[1]).text().trim(),
quantity: $(cells[2]).text().trim(),
unitPrice: $(cells[3]).text().trim(),
supplyPrice: $(cells[4]).text().trim(),
vat: $(cells[5]).text().trim()
};
// 빈 행이 아닌 경우만 추가
if(item.productName || item.spec || item.quantity) {
data.items.push(item);
}
}
});
$.ajax({
url: "/salesMgmt/saveTransactionStatement.do",
type: "POST",
data: JSON.stringify(data),
contentType: "application/json",
dataType: "json",
success: function(response) {
if(response && response.success) {
alert("저장되었습니다.");
} else {
alert("저장 실패: " + (response.message || "알 수 없는 오류"));
}
},
error: function() {
alert("저장 중 오류가 발생했습니다.");
}
});
}
function fn_print() {
window.print();
}
function fn_close() {
window.close();
}
</script>
</head>
<body>
<div class="container">
<!-- 제목 -->
<div class="header">거래 명세서</div>
<!-- 상단 정보 테이블 -->
<table class="info-table">
<!-- 첫번째 행: 납품일 (왼쪽만) -->
<tr class="row-1">
<td class="date-cell" rowspan="2">
<span class="date-label">납품일:</span>
<span id="deliveryDate" contenteditable="true">수요일, 9월 24, 2025</span>
</td>
<td class="reg-label-cell">등록 번호</td>
<td class="reg-value-cell">
<span id="registrationNo" contenteditable="true">314-81-75146</span>
</td>
<td class="stamp-cell" rowspan="5">
<div class="stamp-box">
<div>성 명</div>
<div contenteditable="true" style="margin-top: 3px;">이동현</div>
</div>
</td>
</tr>
<!-- 두번째 행 -->
<tr class="row-2">
<td class="supplier-label-cell" rowspan="4">공<br>급<br>자</td>
<td class="supplier-row-1">
<div class="supplier-inline">
<span class="supplier-label-inline">상호(법인명)</span>
<span contenteditable="true" id="supplierName">㈜알피에스</span>
</div>
</td>
</tr>
<!-- 세번째 행: 수신자 + 공급자 -->
<tr class="row-3">
<td class="receiver-cell" rowspan="2">
<span class="receiver-name" contenteditable="true" id="receiverName">㈜OOO</span>
<span class="receiver-suffix">귀하</span>
</td>
<td class="supplier-row-2">
<div class="supplier-inline">
<span class="supplier-label-inline">사업장주소</span>
<span contenteditable="true" id="supplierAddr">대전광역시 유성구 국제과학10로 8</span>
</div>
</td>
</tr>
<!-- 네번째 행 -->
<tr class="row-4">
<td class="supplier-row-3">
<div class="supplier-inline">
<span class="supplier-label-inline">업 태</span>
<span contenteditable="true" id="supplierBiz">제조</span>
<span class="supplier-label-inline" style="margin-left: 15px;">종 목</span>
<span contenteditable="true" id="supplierType">금속절삭가공기계의</span>
</div>
</td>
</tr>
<!-- 다섯번째 행 -->
<tr class="row-5">
<td class="supply-text-cell">
<span contenteditable="true" id="supplyText">아래와 같이 공급합니다.</span>
</td>
<td class="supplier-row-4">
<div class="supplier-inline">
<span class="supplier-label-inline">전화 번호</span>
<span contenteditable="true" id="supplierTel">TEL:042-602-3300/FAX:042-672</span>
</div>
</td>
</tr>
</table>
<!-- 합계 금액 -->
<div class="amount-row">
<span class="amount-label">(공급가액+세액)</span>
<span class="amount-text"><span id="totalText" contenteditable="true">3300000</span> 원정</span>
<span class="amount-won-symbol">₩</span>
<span class="amount-won-symbol">₩</span>
<span class="amount-number"><span id="totalNum" contenteditable="true">3,300,000</span></span>
</div>
<!-- 품목 테이블 -->
<table class="items-table">
<thead>
<tr>
<th style="width: 25%;">품 명</th>
<th style="width: 15%;">규 격</th>
<th style="width: 10%;">수 량</th>
<th style="width: 15%;">단 가</th>
<th style="width: 17.5%;">공 급 가 액</th>
<th style="width: 17.5%;">세 액</th>
</tr>
</thead>
<tbody id="itemsBody">
</tbody>
</table>
<!-- 비고 -->
<div class="note-section">
<div class="note-title">&lt;비고&gt;</div>
<div class="note-content" contenteditable="true" id="noteContent">Spindle S/N : 187-1062, 1096, 1099</div>
</div>
<!-- 하단 -->
<table class="footer-table">
<tr>
<td>
<div class="footer-label">인계확인 :</div>
<div class="footer-sign">(인)</div>
</td>
<td>
<div class="footer-label">인수확인 :</div>
<div class="footer-sign">(인)</div>
</td>
</tr>
</table>
</div>
<!-- 버튼 -->
<div class="button-area no-print">
<button class="btn btn-print" onclick="fn_save()" style="background-color: #4CAF50;">저장</button>
<button class="btn btn-print" onclick="fn_print()">인쇄</button>
<button class="btn btn-close" onclick="fn_close()">닫기</button>
</div>
</body>
</html>

View File

@@ -783,6 +783,21 @@ public class MailUtil {
, ArrayList<HashMap> attachFileList
, String mailType
) {
// 기본적으로 실제 제목을 mail_log에도 사용
return sendMailWithAttachFileUTF8(fromUserId, fromEmail, toUserIdList, toEmailList, ccEmailList, bccEmailList,
important, subject, contents, attachFileList, mailType, subject);
}
/**
* 메일 발송 (UTF-8, 파일 첨부 가능) - mail_log용 제목 별도 지정 가능
*/
public static boolean sendMailWithAttachFileUTF8(String fromUserId, String fromEmail
, ArrayList<String> toUserIdList, ArrayList<String> toEmailList, ArrayList<String> ccEmailList, ArrayList<String> bccEmailList
, String important, String subject, String contents
, ArrayList<HashMap> attachFileList
, String mailType
, String subjectForLog // mail_log에 저장될 제목 (OBJID 포함)
) {
if(Constants.Mail.sendMailSwitch == false){
System.out.println("MailUtil.sendMailWithAttachFileUTF8 ::: Constants.Mail.sendMailSwitch is FALSE");
@@ -798,6 +813,11 @@ public class MailUtil {
contents = CommonUtils.checkNull(contents, "empty contents");
mailType = CommonUtils.checkNull(mailType, "empty mailType");
// mail_log용 제목이 없으면 실제 제목 사용
if(subjectForLog == null || "".equals(subjectForLog.trim())) {
subjectForLog = subject;
}
System.out.println("MailUtil.sendMailWithAttachFileUTF8()..");
System.out.println("MailUtil.sendMailWithAttachFileUTF8(fromUserId ):"+fromUserId);
System.out.println("MailUtil.sendMailWithAttachFileUTF8(fromEmail ):"+fromEmail);
@@ -896,18 +916,65 @@ public class MailUtil {
message.setContent(multipart);
message.setSentDate(new Date());
// 메일 발송
Transport.send(message);
System.out.println("메일 발송 성공 (UTF-8)");
//◆◆◆ 3. db log & send mail ◆◆◆
SqlSession sqlSession = null;
boolean mailSendSuccess = false;
return true;
Map paramMap = new HashMap();
try{
if(Constants.Mail.dbLogWrite){
sqlSession = SqlMapConfig.getInstance().getSqlSession();
String objId = CommonUtils.createObjId();
paramMap.put("objId" , objId);
paramMap.put("systemName" , Constants.SYSTEM_NAME);
paramMap.put("sendUserId" , fromUserId);
paramMap.put("fromAddr" , fromEmail);
paramMap.put("receptionUserId", toUserIdList.toString());
paramMap.put("receiverTo" , toEmailList.toString());
paramMap.put("title" , subjectForLog); // mail_log용 제목 (OBJID 포함)
paramMap.put("contents" , contents);
paramMap.put("mailType" , mailType);
sqlSession.insert("mail.insertMailLog", paramMap);
System.out.println("메일 발송 로그 기록 (UTF-8) ==============================================");
System.out.println("paramMap >>> "+paramMap);
}
//◆◆◆ send mail ◆◆◆
Transport.send(message);
mailSendSuccess = true;
System.out.println("메일 발송 성공 (UTF-8)");
} catch (Exception e) {
System.out.println("메일 발송 실패 (UTF-8): " + e.getMessage());
e.printStackTrace();
if(Constants.Mail.dbLogWrite){
System.out.println("메일 발송후 paramMap >> "+paramMap);
sqlSession.update("mail.updateMailSendedSuccess", paramMap);
}
}catch(Exception sqle){
System.out.println("메일 발송 실패 (UTF-8): " + sqle.getMessage());
sqle.printStackTrace();
if(Constants.Mail.dbLogWrite){
try{
sqlSession.update("mail.updateMailSendedFail", paramMap);
}catch(Exception e2){
e2.printStackTrace();
}
}
return false;
}finally{
if(sqlSession != null) sqlSession.close();
}
return mailSendSuccess;
} catch (Exception e) {
System.out.println("메일 발송 실패 (UTF-8): " + e.getMessage());
e.printStackTrace();
return false;
}
}
/**
* 첨부파일 목록을 파일하나로 압축해서 반환

View File

@@ -168,6 +168,28 @@ public class CommonController {
return "/ajax/ajaxResult";
}
/**
* 파일을 삭제한다. (논리적삭제, status : Deleted) - JSON 응답
* @param request
* @param paramMap
* @return
*/
@RequestMapping("/common/deleteFile.do")
@ResponseBody
public Map<String, Object> deleteFile(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map<String, Object> result = new HashMap<String, Object>();
try {
String msg = commonService.deleteFileInfoLogical(paramMap);
result.put("success", true);
result.put("message", msg);
} catch (Exception e) {
e.printStackTrace();
result.put("success", false);
result.put("message", "파일 삭제 중 오류가 발생했습니다.");
}
return result;
}
/**
* 파일 다운로드
* @param request

View File

@@ -3205,4 +3205,32 @@ SELECT option_objid::VARCHAR AS CODE
WHERE USER_ID = #{userId}
AND MASTER_OBJID::varchar = #{masterObjid}
</select>
<!-- 고객사 정보 조회 -->
<select id="getSupplyInfo" parameterType="map" resultType="map">
SELECT
OBJID,
SUPPLY_CODE,
SUPPLY_NAME,
REG_NO,
SUPPLY_ADDRESS,
SUPPLY_BUSNAME,
SUPPLY_STOCKNAME,
SUPPLY_TEL_NO,
SUPPLY_FAX_NO,
CHARGE_USER_NAME,
PAYMENT_METHOD,
MANAGER1_NAME,
MANAGER1_EMAIL,
MANAGER2_NAME,
MANAGER2_EMAIL,
MANAGER3_NAME,
MANAGER3_EMAIL,
MANAGER4_NAME,
MANAGER4_EMAIL,
MANAGER5_NAME,
MANAGER5_EMAIL
FROM SUPPLY_MNG
WHERE OBJID = #{objId}::numeric
</select>
</mapper>

View File

@@ -269,7 +269,24 @@ public class ContractMgmtController {
@ResponseBody
@RequestMapping("/contractMgmt/contractGridList.do")
public Map getProductKindSpecListPaging(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
commonService.selectListPagingNew("contractMgmt.contractGridList", request, paramMap);
try {
// 페이징 데이터 조회
commonService.selectListPagingNew("contractMgmt.contractGridList", request, paramMap);
// 조회된 데이터의 전체 합계 조회
Map<String, Object> totalAmount = (Map<String, Object>) commonService.selectOne("contractMgmt.getContractGridTotalAmount", request, paramMap);
// 합계 데이터 추가
if(totalAmount != null) {
paramMap.put("TOTAL_AMOUNT_KRW", totalAmount.get("totalAmountKRW"));
} else {
paramMap.put("TOTAL_AMOUNT_KRW", 0);
}
} catch(Exception e) {
e.printStackTrace();
paramMap.put("TOTAL_AMOUNT_KRW", 0);
}
return paramMap;
}
@@ -1950,22 +1967,56 @@ public class ContractMgmtController {
try{
Map estimate = null;
List<Map> items = new ArrayList<Map>();
Map<String, String> code_map = new HashMap<String, String>();
// templateObjId가 있으면 기존 견적서 조회 (견적현황에서 클릭한 경우)
if(!"".equals(templateObjId) && !"-1".equals(templateObjId)){
paramMap.put("templateObjId", templateObjId);
estimate = contractMgmtService.getEstimateTemplateByObjId(paramMap);
items = contractMgmtService.getEstimateTemplateItemsByTemplateObjId(paramMap);
// Map 키를 대문자로 변환
if(estimate != null) {
estimate = CommonUtils.toUpperCaseMapKey(estimate);
}
if(items != null && items.size() > 0) {
items = CommonUtils.keyChangeUpperList((ArrayList)items);
}
}
// objId만 있으면 CONTRACT 정보로 견적서 신규 작성
else if(!"".equals(objId) && !"-1".equals(objId)){
// 기존 견적서 데이터 조회
// 계약 정보 조회
estimate = contractMgmtService.getEstimateTemplateInfo(paramMap);
items = contractMgmtService.getEstimateTemplateItems(paramMap);
// 계약 품목 조회 (CONTRACT_ITEM에서)
paramMap.put("contractObjId", objId);
items = contractMgmtService.getContractItems(paramMap);
// Map 키를 대문자로 변환
if(estimate != null) {
estimate = CommonUtils.toUpperCaseMapKey(estimate);
}
if(items != null && items.size() > 0) {
items = CommonUtils.keyChangeUpperList((ArrayList)items);
}
}
request.setAttribute("estimate", estimate);
request.setAttribute("items", items);
// 고객사 코드맵 추가
String customer_cd = "";
if(estimate != null) {
// templateObjId가 있으면 RECIPIENT 사용 (저장된 견적서)
if(!"".equals(templateObjId) && !"-1".equals(templateObjId)) {
if(estimate.get("RECIPIENT") != null) {
customer_cd = CommonUtils.nullToEmpty((String)estimate.get("RECIPIENT"));
}
}
// 신규 작성이면 CUSTOMER_OBJID 사용
else if(estimate.get("CUSTOMER_OBJID") != null) {
customer_cd = CommonUtils.nullToEmpty((String)estimate.get("CUSTOMER_OBJID"));
}
}
code_map.put("customer_cd", commonService.bizMakeOptionList("", customer_cd, "common.getsupplyselect"));
request.setAttribute("estimate", estimate);
request.setAttribute("items", items);
request.setAttribute("code_map", code_map);
} catch (Exception e) {
e.printStackTrace();
@@ -2040,7 +2091,7 @@ public class ContractMgmtController {
}
/**
* 견적서 저장 (AJAX)
* 일반 견적서 저장 (AJAX) - Template 1
* @param request
* @param paramMap
* @return
@@ -2055,7 +2106,7 @@ public class ContractMgmtController {
paramMap.put("userId", person.getUserId());
// 합계 정보 로그 (디버깅용)
System.out.println("견적서 저장 - 합계: " + paramMap.get("total_amount") + ", 원화환산: " + paramMap.get("total_amount_krw"));
System.out.println("일반 견적서 저장 - 합계: " + paramMap.get("total_amount") + ", 원화환산: " + paramMap.get("total_amount_krw"));
contractMgmtService.saveEstimateTemplate(request, paramMap);
@@ -2070,6 +2121,36 @@ public class ContractMgmtController {
return resultMap;
}
/**
* 장비 견적서 저장 (AJAX) - Template 2
* @param request
* @param paramMap
* @return
*/
@ResponseBody
@RequestMapping(value="/contractMgmt/saveEstimate2.do", method=RequestMethod.POST)
public Map saveEstimate2(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map resultMap = new HashMap();
try {
PersonBean person = (PersonBean)request.getSession().getAttribute(Constants.PERSON_BEAN);
paramMap.put("userId", person.getUserId());
System.out.println("장비 견적서 저장");
contractMgmtService.saveEstimateTemplate2(request, paramMap);
resultMap.put("result", "success");
} catch (Exception e) {
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", e.getMessage());
}
return resultMap;
}
/**
* PDF 청크 업로드 (AJAX)
* @param request
@@ -2125,6 +2206,115 @@ public class ContractMgmtController {
return resultMap;
}
/**
* 견적서 메일 작성 팝업
* @param request
* @param paramMap - contractObjId
* @return
*/
@RequestMapping("/contractMgmt/estimateMailFormPopup.do")
public String estimateMailFormPopup(HttpServletRequest request, @RequestParam Map paramMap){
return "/contractMgmt/estimateMailFormPopup";
}
/**
* 계약 정보 조회 (메일 발송용) (AJAX)
* @param request
* @param paramMap - objId (CONTRACT_OBJID)
* @return
*/
@ResponseBody
@RequestMapping(value="/contractMgmt/getContractInfoForMail.do", method=RequestMethod.POST)
public Map getContractInfoForMail(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map resultMap = new HashMap();
try {
String objId = CommonUtils.checkNull(paramMap.get("objId"));
if("".equals(objId)){
resultMap.put("result", "error");
resultMap.put("message", "계약 OBJID가 없습니다.");
return resultMap;
}
// 계약 정보 조회
Map contractInfo = contractMgmtService.getContractInfoForMail(paramMap);
if(contractInfo != null && !contractInfo.isEmpty()){
// Map 키를 대문자로 변환
contractInfo = CommonUtils.toUpperCaseMapKey(contractInfo);
resultMap.put("result", "success");
resultMap.put("contractInfo", contractInfo);
} else {
resultMap.put("result", "error");
resultMap.put("message", "계약 정보를 찾을 수 없습니다.");
}
} catch (Exception e) {
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", "계약 정보 조회 중 오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
/**
* 견적서 메일 발송 (커스텀) (AJAX)
* @param request
* @param paramMap - objId, pdfSessionId, toEmails, ccEmails, subject, contents
* @return
*/
@ResponseBody
@RequestMapping(value="/contractMgmt/sendEstimateMailCustom.do", method=RequestMethod.POST)
public Map sendEstimateMailCustom(HttpServletRequest request,
@RequestParam Map<String, Object> paramMap){
Map resultMap = new HashMap();
try {
String objId = CommonUtils.checkNull(paramMap.get("objId"));
String toEmails = CommonUtils.checkNull(paramMap.get("toEmails"));
String subject = CommonUtils.checkNull(paramMap.get("subject"));
String contents = CommonUtils.checkNull(paramMap.get("contents"));
// 필수 파라미터 검증
if("".equals(objId)){
resultMap.put("result", "error");
resultMap.put("message", "계약 OBJID가 없습니다.");
return resultMap;
}
if("".equals(toEmails)){
resultMap.put("result", "error");
resultMap.put("message", "수신인이 없습니다.");
return resultMap;
}
if("".equals(subject)){
resultMap.put("result", "error");
resultMap.put("message", "제목이 없습니다.");
return resultMap;
}
if("".equals(contents)){
resultMap.put("result", "error");
resultMap.put("message", "내용이 없습니다.");
return resultMap;
}
// 메일 발송 서비스 호출
resultMap = contractMgmtService.sendEstimateMailCustom(request, paramMap);
} catch (Exception e) {
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", "메일 발송 중 오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
/**
* 주문서관리 리스트
@@ -2186,6 +2376,41 @@ public class ContractMgmtController {
return paramMap;
}
/**
* 주문서관리 - Total 합계 조회
* @param request
* @param paramMap
* @return
*/
@ResponseBody
@RequestMapping("/contractMgmt/getOrderTotalAmount.do")
public Map getOrderTotalAmount(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map<String, Object> resultMap = new HashMap<String, Object>();
try {
// Total 합계 조회
Map<String, Object> totalData = contractMgmtService.getOrderTotalAmount(request, paramMap);
if(totalData != null) {
resultMap.put("result", "success");
resultMap.put("totalSupplyPrice", totalData.get("TOTAL_SUPPLY_PRICE"));
resultMap.put("totalVat", totalData.get("TOTAL_VAT"));
resultMap.put("totalAmount", totalData.get("TOTAL_AMOUNT"));
} else {
resultMap.put("result", "success");
resultMap.put("totalSupplyPrice", 0);
resultMap.put("totalVat", 0);
resultMap.put("totalAmount", 0);
}
} catch (Exception e) {
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", e.getMessage());
}
return resultMap;
}
/**
* 수주등록 팝업 페이지
* @param session
@@ -2267,17 +2492,9 @@ public class ContractMgmtController {
Map resultMap = new HashMap();
try {
// 결재완료 상태인 경우 최종 견적서 템플릿에서 품목 조회
String useEstimateTemplate = CommonUtils.checkNull(paramMap.get("useEstimateTemplate"));
List<Map> items = null;
if("Y".equals(useEstimateTemplate)) {
// 최종 견적서 템플릿의 품목 조회
items = contractMgmtService.getEstimateTemplateItemsForOrder(paramMap);
} else {
// 기존 방식: CONTRACT_ITEM에서 조회
items = contractMgmtService.getContractItems(paramMap);
}
// getContractItems()에서 ORDER_* 정보가 없으면 자동으로 견적서에서 가져옴
// 일반 견적서와 장비 견적서 모두 처리 가능
List<Map> items = contractMgmtService.getContractItems(paramMap);
resultMap.put("result", "success");
resultMap.put("items", items);
@@ -2297,4 +2514,113 @@ public class ContractMgmtController {
request.setAttribute("docTypeName", CommonUtils.checkNull(paramMap.get("docTypeName")));
return "/contractMgmt/FileRegistPopup";
}
/**
* 영업정보의 품목 목록 조회 (견적서 작성 시 사용)
*/
@RequestMapping(value="/contractMgmt/getContractItemList.do", method=RequestMethod.POST)
@ResponseBody
public Map<String, Object> getContractItemList(@RequestParam Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<String, Object>();
try {
String contractObjId = CommonUtils.checkNull((String)paramMap.get("contractObjId"));
if(CommonUtils.isEmpty(contractObjId)) {
resultMap.put("result", "error");
resultMap.put("message", "영업정보 OBJID가 없습니다.");
return resultMap;
}
// 영업정보 조회 (고객사 정보 포함)
Map<String, Object> contractInfo = contractMgmtService.getContractInfo(contractObjId);
// 품목 목록 조회
List<Map<String, Object>> items = contractMgmtService.getContractItemList(contractObjId);
// 대문자로 키 변환
List<Map<String, Object>> upperItems = new ArrayList<Map<String, Object>>();
for(Map<String, Object> item : items) {
upperItems.add(CommonUtils.toUpperCaseMapKey(item));
}
resultMap.put("result", "success");
resultMap.put("items", upperItems);
// 고객사 정보 및 환율 정보 추가
if(contractInfo != null) {
Map<String, Object> upperContractInfo = CommonUtils.toUpperCaseMapKey(contractInfo);
resultMap.put("customerObjId", upperContractInfo.get("CUSTOMER_OBJID"));
resultMap.put("exchangeRate", upperContractInfo.get("EXCHANGE_RATE"));
resultMap.put("currencyName", upperContractInfo.get("CONTRACT_CURRENCY_NAME"));
}
} catch(Exception e) {
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", e.getMessage());
}
return resultMap;
}
/**
* 고객사 담당자 목록 조회
*/
@RequestMapping(value="/contractMgmt/getCustomerManagerList.do", method=RequestMethod.POST)
@ResponseBody
public Map<String, Object> getCustomerManagerList(@RequestParam Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<String, Object>();
try {
String customerObjId = CommonUtils.checkNull((String)paramMap.get("customerObjId"));
if(CommonUtils.isEmpty(customerObjId)) {
resultMap.put("result", "error");
resultMap.put("message", "고객사 OBJID가 없습니다.");
return resultMap;
}
// 고객사 정보 조회
Map<String, Object> customerInfo = contractMgmtService.getCustomerInfo(customerObjId);
System.out.println("=== 고객사 정보 조회 결과 ===");
System.out.println("customerInfo: " + customerInfo);
if(customerInfo != null) {
List<Map<String, Object>> managers = new ArrayList<Map<String, Object>>();
// manager1 ~ manager5 추출 (소문자 키로 접근)
for(int i = 1; i <= 5; i++) {
String managerName = CommonUtils.checkNull((String)customerInfo.get("manager" + i + "_name"));
String managerEmail = CommonUtils.checkNull((String)customerInfo.get("manager" + i + "_email"));
System.out.println("manager" + i + "_name: " + managerName);
System.out.println("manager" + i + "_email: " + managerEmail);
if(!CommonUtils.isEmpty(managerName)) {
Map<String, Object> manager = new HashMap<String, Object>();
manager.put("name", managerName);
manager.put("email", managerEmail);
managers.add(manager);
}
}
System.out.println("최종 managers 개수: " + managers.size());
resultMap.put("result", "success");
resultMap.put("managers", managers);
} else {
resultMap.put("result", "success");
resultMap.put("managers", new ArrayList<Map<String, Object>>());
}
} catch(Exception e) {
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", e.getMessage());
}
return resultMap;
}
}

View File

@@ -282,11 +282,48 @@ public class SalesNcollectMgmtController {
}
// orderNo가 있으면 기존 판매 정보 조회
else if(paramMap.get("orderNo") != null && !paramMap.get("orderNo").equals("")) {
saleInfo = salesNcollectMgmtService.getSaleInfo(paramMap);
salesCurrency = CommonUtils.nullToEmpty((String)saleInfo.get("SALES_CURRENCY"));
System.out.println("=== salesRegForm.do 파라미터 확인 ===");
System.out.println("orderNo: " + paramMap.get("orderNo"));
System.out.println("salesQuantity: " + paramMap.get("salesQuantity"));
System.out.println("salesUnitPrice: " + paramMap.get("salesUnitPrice"));
System.out.println("salesSupplyPrice: " + paramMap.get("salesSupplyPrice"));
System.out.println("salesVat: " + paramMap.get("salesVat"));
System.out.println("salesTotalAmount: " + paramMap.get("salesTotalAmount"));
System.out.println("salesCurrency: " + paramMap.get("salesCurrency"));
System.out.println("salesExchangeRate: " + paramMap.get("salesExchangeRate"));
saleInfo = salesNcollectMgmtService.getSaleInfo(paramMap);
salesCurrency = CommonUtils.nullToEmpty((String)saleInfo.get("SALES_CURRENCY"));
System.out.println("=== DB에서 조회한 saleInfo ===");
System.out.println("ORDER_QUANTITY: " + saleInfo.get("ORDER_QUANTITY"));
System.out.println("SALES_QUANTITY: " + saleInfo.get("SALES_QUANTITY"));
System.out.println("SALES_UNIT_PRICE: " + saleInfo.get("SALES_UNIT_PRICE"));
System.out.println("SALES_SUPPLY_PRICE: " + saleInfo.get("SALES_SUPPLY_PRICE"));
// URL 파라미터로 그리드 데이터가 전달된 경우 우선 사용
if(paramMap.get("salesQuantity") != null && !paramMap.get("salesQuantity").equals("")) {
System.out.println("=== URL 파라미터로 그리드 데이터 설정 ===");
if(saleInfo == null) saleInfo = new HashMap<String, Object>();
saleInfo.put("SALES_UNIT_PRICE", paramMap.get("salesUnitPrice"));
saleInfo.put("SALES_SUPPLY_PRICE", paramMap.get("salesSupplyPrice"));
saleInfo.put("SALES_VAT", paramMap.get("salesVat"));
saleInfo.put("SALES_TOTAL_AMOUNT", paramMap.get("salesTotalAmount"));
if(paramMap.get("salesCurrency") != null) {
saleInfo.put("SALES_CURRENCY", paramMap.get("salesCurrency"));
salesCurrency = CommonUtils.nullToEmpty((String)paramMap.get("salesCurrency"));
}
if(paramMap.get("salesExchangeRate") != null) {
saleInfo.put("SALES_EXCHANGE_RATE", paramMap.get("salesExchangeRate"));
}
System.out.println("설정 후 SALES_UNIT_PRICE: " + saleInfo.get("SALES_UNIT_PRICE"));
System.out.println("설정 후 SALES_SUPPLY_PRICE: " + saleInfo.get("SALES_SUPPLY_PRICE"));
System.out.println("설정 후 SALES_VAT: " + saleInfo.get("SALES_VAT"));
System.out.println("설정 후 SALES_TOTAL_AMOUNT: " + saleInfo.get("SALES_TOTAL_AMOUNT"));
}
// 판매 정보가 비어있거나 금액이 0이면 수주 데이터로 채우기
if(saleInfo == null ||
else if(saleInfo == null ||
(saleInfo.get("SALES_SUPPLY_PRICE") == null ||
"0".equals(String.valueOf(saleInfo.get("SALES_SUPPLY_PRICE"))) ||
Integer.parseInt(String.valueOf(saleInfo.get("SALES_SUPPLY_PRICE"))) == 0)) {
@@ -298,7 +335,9 @@ public class SalesNcollectMgmtController {
if(orderData != null && orderData.get("SALES_SUPPLY_PRICE") != null) {
// 수주 데이터를 saleInfo에 병합 (기존 S/N 등은 유지)
if(saleInfo == null) saleInfo = new HashMap<String, Object>();
saleInfo.put("SALES_QUANTITY", orderData.get("SALES_QUANTITY"));
// SALES_QUANTITY는 나중에 잔량으로 계산하므로 여기서는 설정하지 않음
// saleInfo.put("SALES_QUANTITY", orderData.get("SALES_QUANTITY")); // 제거
saleInfo.put("ORDER_QUANTITY", orderData.get("SALES_QUANTITY")); // 주문수량은 ORDER_QUANTITY로 저장
saleInfo.put("SALES_UNIT_PRICE", orderData.get("SALES_UNIT_PRICE"));
saleInfo.put("SALES_SUPPLY_PRICE", orderData.get("SALES_SUPPLY_PRICE"));
saleInfo.put("SALES_VAT", orderData.get("SALES_VAT"));
@@ -310,13 +349,54 @@ public class SalesNcollectMgmtController {
saleInfo.put("SHIPPING_DATE", orderData.get("SHIPPING_DATE"));
}
salesCurrency = CommonUtils.nullToEmpty((String)orderData.get("SALES_CURRENCY"));
}
}
// 잔량 계산: ORDER_QUANTITY - 모든 분할 출하의 합계
if(saleInfo != null && saleInfo.get("ORDER_QUANTITY") != null) {
int orderQuantity = Integer.parseInt(String.valueOf(saleInfo.get("ORDER_QUANTITY")).split("\\.")[0]);
// 모든 분할 출하의 합계 조회
int totalSoldQuantity = 0;
try {
Map<String, Object> totalResult = salesNcollectMgmtService.getTotalSalesQuantity(paramMap);
System.out.println("getTotalSalesQuantity 결과: " + totalResult);
if(totalResult != null && totalResult.get("total") != null) {
System.out.println("total 값: " + totalResult.get("total"));
totalSoldQuantity = Integer.parseInt(String.valueOf(totalResult.get("total")).split("\\.")[0]);
}
} catch(Exception e) {
System.out.println("getTotalSalesQuantity 에러 발생!");
e.printStackTrace();
}
request.setAttribute("saleInfo", saleInfo);
int remainingQuantity = orderQuantity - totalSoldQuantity;
// orderInfo로 견적 정보 전달 (saleInfo가 이미 모든 필요한 정보를 포함)
request.setAttribute("orderInfo", saleInfo);
System.out.println("=== 잔량 계산 ===");
System.out.println("주문수량 (ORDER_QUANTITY): " + orderQuantity);
System.out.println("총 판매 수량 (모든 분할 출하 합계): " + totalSoldQuantity);
System.out.println("잔량 (remainingQuantity): " + remainingQuantity);
// 잔량을 판매수량으로 설정 (정수)
saleInfo.put("SALES_QUANTITY", remainingQuantity > 0 ? remainingQuantity : orderQuantity);
System.out.println("설정 후 SALES_QUANTITY: " + saleInfo.get("SALES_QUANTITY"));
}
// 프로젝트의 모든 품목 조회 (Phase 2) - 주석처리: 품목은 하나만 존재
/*
List<Map<String, Object>> projectItems = null;
if(paramMap.get("orderNo") != null && !paramMap.get("orderNo").equals("")) {
projectItems = salesNcollectMgmtService.getProjectItems(paramMap);
System.out.println("=== 프로젝트 품목 조회 ===");
System.out.println("품목 개수: " + (projectItems != null ? projectItems.size() : 0));
request.setAttribute("projectItems", projectItems);
}
*/
request.setAttribute("saleInfo", saleInfo);
// orderInfo로 견적 정보 전달 (saleInfo가 이미 모든 필요한 정보를 포함)
request.setAttribute("orderInfo", saleInfo);
}
// 환종(통화) - 공통코드 0001533
@@ -418,6 +498,96 @@ public class SalesNcollectMgmtController {
return resultMap;
}
/**
* <pre>
* 출하일 상세 내역 팝업
* </pre>
* @param request
* @param paramMap - projectNo
* @return String
*/
@RequestMapping(value = "/salesMgmt/shippingDetailPopup.do", method = RequestMethod.GET)
public String showShippingDetailPopup(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
try {
String projectNo = (String) paramMap.get("projectNo");
List<Map<String, Object>> shippingList = salesNcollectMgmtService.getShippingDetailList(projectNo);
request.setAttribute("shippingList", shippingList);
request.setAttribute("projectNo", projectNo);
} catch (Exception e) {
e.printStackTrace();
}
return "/salesmgmt/salesMgmt/shippingDetailPopup";
}
/**
* 거래명세서 폼 표시
*/
@RequestMapping(value = "/salesMgmt/transactionStatementForm.do", method = RequestMethod.GET)
public String showTransactionStatementForm(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
return "/salesmgmt/salesMgmt/transactionStatementForm";
}
/**
* 거래명세서 데이터 조회
*/
@RequestMapping(value = "/salesMgmt/getTransactionStatementData.do", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> getTransactionStatementData(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<String, Object>();
try {
String projectNos = (String) paramMap.get("projectNos");
if(projectNos == null || projectNos.isEmpty()) {
resultMap.put("success", false);
resultMap.put("message", "프로젝트 번호가 없습니다.");
return resultMap;
}
// 거래명세서 데이터 조회
Map<String, Object> statementData = salesNcollectMgmtService.getTransactionStatementData(paramMap);
resultMap.put("success", true);
resultMap.putAll(statementData);
} catch (Exception e) {
e.printStackTrace();
resultMap.put("success", false);
resultMap.put("message", "데이터 조회 중 오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
/**
* 거래명세서 저장 (차수 관리 없음)
*/
@RequestMapping(value = "/salesMgmt/saveTransactionStatement.do", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> saveTransactionStatement(HttpServletRequest request, @org.springframework.web.bind.annotation.RequestBody Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<String, Object>();
try {
// 거래명세서는 차수 관리가 불필요하므로 단순히 성공 응답만 반환
// 실제로는 PDF 생성 또는 로그 기록 등의 작업을 수행할 수 있음
System.out.println("=== 거래명세서 저장 ===");
System.out.println("프로젝트 번호: " + paramMap.get("projectNos"));
System.out.println("납품일: " + paramMap.get("deliveryDate"));
System.out.println("비고: " + paramMap.get("noteContent"));
resultMap.put("success", true);
resultMap.put("message", "저장되었습니다.");
} catch (Exception e) {
e.printStackTrace();
resultMap.put("success", false);
resultMap.put("message", "저장 중 오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
/**
* <pre>
* 계약관리 목록 조회

View File

@@ -1,124 +0,0 @@
/*
* ContractMgmtController
*
* 1.0
*
* 2021.10.01
*
* Copyright ions
*/
package com.pms.salesmgmt.controller;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.pms.common.utils.CommonUtils;
import com.pms.salesmgmt.service.AccountService;
import com.pms.salesmgmt.service.ContractMgmtService;
import com.pms.salesmgmt.service.DeliveryService;
import com.pms.salesmgmt.service.SalesMgmtCommonService;
/**
* <pre>
* 계약관리 Controller
* </pre>
* @since 2021.10.01
* @author kim
* @version 1.0
*
* <pre>
* << 개정 이력 >>
*
* 수정일 수정자 수정내용
* ---------------- --------------------- --------------------------------------------------------
* 2021.10.01 김효일 최초작성
*
* </pre>
*
*/
@Controller
public class SampleController {
/** 계약관리 Service */
@Autowired
private DeliveryService deliveryService;
/** 결제예정 Service */
@Autowired
private AccountService accountService;
/**
* <pre>
* 납기예정 목록 조회
* </pre>
* @param request
* @param paramMap - 계약관리 검색 정보
* @return String
*
* <pre>
* << 개정 이력 >>
*
* 수정일 수정자 수정내용
* ---------------- --------------------- ----------------------------------------------------------
* 2021.10.01 김효일 최초작성
*
* </pre>
*/
@RequestMapping(value = " /contractMgmt/tabSample1.do", method = RequestMethod.GET)
public String tabSample1(HttpServletRequest request
, @RequestParam Map<String, Object> paramMap) {
try {
List<Map<String,Object>> list = deliveryService.getDeliveryAllByOrderNo(request, paramMap);
System.out.println("paramMap>>>>>>>>>>>>> " + paramMap);
request.setAttribute("LIST", list);
} catch(Exception e) {
e.printStackTrace();
}
return "/salesmgmt/sample/tabContractMgmtListSample1";
}
/**
* <pre>
* 결제예정 목록 조회
* </pre>
* @param request
* @param paramMap - 계약관리 검색 정보
* @return String
*
* <pre>
* << 개정 이력 >>
*
* 수정일 수정자 수정내용
* ---------------- --------------------- ----------------------------------------------------------
* 2021.10.01 김효일 최초작성
*
* </pre>
*/
@RequestMapping(value = " /contractMgmt/tabSample2.do", method = RequestMethod.GET)
public String tabSample2(HttpServletRequest request
, @RequestParam Map<String, Object> paramMap) {
try {
List<Map<String,Object>> list = accountService.getAccountAllByOrderNo(request, paramMap);
request.setAttribute("LIST", list);
} catch(Exception e) {
e.printStackTrace();
}
return "/salesmgmt/sample/tabContractMgmtListSample2";
}
}

View File

@@ -790,6 +790,102 @@
ORDER BY REGDATE DESC
</select>
<!-- 주문서관리 Total 합계 조회 (조회된 데이터 전체 합계) -->
<select id="getContractGridTotalAmount" parameterType="map" resultType="map">
/* contractMgmt.getContractGridTotalAmount */
SELECT
COALESCE(SUM(COALESCE(T.ORDER_TOTAL_AMOUNT_KRW, 0)), 0) AS "totalAmountKRW"
FROM
<include refid="contractBase"/> T
WHERE 1=1
<if test="Year !=null and Year != '' ">
AND SUBSTR(CONTRACT_DATE,0,5) = #{Year}
</if>
<if test="category_cd !=null and category_cd != '' ">
AND category_cd = #{category_cd}
</if>
<if test="customer_objid !=null and customer_objid != '' ">
AND customer_objid = #{customer_objid}
</if>
<if test="product != null and product !='' ">
AND product = #{product}
</if>
<if test="status_cd !=null and status_cd !=''">
AND status_cd = #{status_cd}
</if>
<if test="result_cd !=null and result_cd !=''">
AND result_cd = #{result_cd}
</if>
<if test="contract_result !=null and contract_result !=''">
AND contract_result = #{contract_result}
</if>
<if test="contract_start_date != null and !''.equals(contract_start_date)">
AND TO_DATE(CONTRACT_DATE,'YYYY-MM-DD') <![CDATA[ >= ]]> TO_DATE(#{contract_start_date}, 'YYYY-MM-DD')
</if>
<if test="contract_end_date != null and !''.equals(contract_end_date)">
AND TO_DATE(CONTRACT_DATE,'YYYY-MM-DD') <![CDATA[ <= ]]> TO_DATE(#{contract_end_date}, 'YYYY-MM-DD')
</if>
<if test="pm_user_id !=null and pm_user_id !=''">
AND pm_user_id = #{pm_user_id}
</if>
<if test="contract_month != null and !''.equals(contract_month)">
AND TO_DATE(TO_CHAR(TO_DATE(CONTRACT_DATE,'YYYY-MM-DD'),'YYYY-MM-DD'),'YYYY-MM') <![CDATA[ = ]]> TO_DATE(SUBSTRING(#{contract_month} FROM 1 FOR 4) || '-' || SUBSTRING(#{contract_month} FROM 5 FOR 2), 'YYYY-MM')
</if>
<!-- 견적관리 추가 검색조건 -->
<if test="appr_status !=null and appr_status != '' ">
AND APPR_STATUS = #{appr_status}
</if>
<if test="area_cd != null and area_cd !='' ">
AND AREA_CD = #{area_cd}
</if>
<if test="paid_type != null and paid_type !='' ">
AND PAID_TYPE = #{paid_type}
</if>
<!-- 품번/품명 검색: PART_OBJID로 정확하게 검색 -->
<if test="search_partObjId != null and search_partObjId != ''">
AND EXISTS (
SELECT 1
FROM CONTRACT_ITEM CI
WHERE CI.CONTRACT_OBJID = T.OBJID
AND CI.STATUS = 'ACTIVE'
AND CI.PART_OBJID = #{search_partObjId}
)
</if>
<if test="search_serialNo != null and search_serialNo != ''">
AND UPPER(SERIAL_NO) LIKE UPPER('%${search_serialNo}%')
</if>
<if test="search_poNo != null and search_poNo != ''">
AND UPPER(PO_NO) LIKE UPPER('%${search_poNo}%')
</if>
<if test="order_start_date != null and !''.equals(order_start_date)">
AND TO_DATE(ORDER_DATE,'YYYY-MM-DD') <![CDATA[ >= ]]> TO_DATE(#{order_start_date}, 'YYYY-MM-DD')
</if>
<if test="order_end_date != null and !''.equals(order_end_date)">
AND TO_DATE(ORDER_DATE,'YYYY-MM-DD') <![CDATA[ <= ]]> TO_DATE(#{order_end_date}, 'YYYY-MM-DD')
</if>
<if test="due_start_date != null and !''.equals(due_start_date)">
AND TO_DATE(DUE_DATE,'YYYY-MM-DD') <![CDATA[ >= ]]> TO_DATE(#{due_start_date}, 'YYYY-MM-DD')
</if>
<if test="due_end_date != null and !''.equals(due_end_date)">
AND TO_DATE(DUE_DATE,'YYYY-MM-DD') <![CDATA[ <= ]]> TO_DATE(#{due_end_date}, 'YYYY-MM-DD')
</if>
</select>
<select id="contractList_bak" parameterType="map" resultType="map">
SELECT *
FROM (
@@ -2356,6 +2452,16 @@ SELECT
,BUS_REG_NO
,OFFICE_NO
,EMAIL
,MANAGER1_NAME
,MANAGER1_EMAIL
,MANAGER2_NAME
,MANAGER2_EMAIL
,MANAGER3_NAME
,MANAGER3_EMAIL
,MANAGER4_NAME
,MANAGER4_EMAIL
,MANAGER5_NAME
,MANAGER5_EMAIL
FROM SUPPLY_MNG
WHERE OBJID = #{objid}::numeric
</select>
@@ -3860,7 +3966,14 @@ ORDER BY ASM.SUPPLY_NAME
FROM
CONTRACT_MGMT AS T
WHERE
OBJID::VARCHAR = #{objId}
<choose>
<when test="templateObjId != null and templateObjId != '' and templateObjId != '-1' and (objId == null or objId == '' or objId == '-1')">
OBJID::VARCHAR = (SELECT CONTRACT_OBJID FROM ESTIMATE_TEMPLATE WHERE OBJID = #{templateObjId})
</when>
<otherwise>
OBJID::VARCHAR = #{objId}
</otherwise>
</choose>
</select>
<!-- 견적서 템플릿 목록 조회 (CONTRACT_OBJID 기준) -->
@@ -3925,6 +4038,12 @@ ORDER BY ASM.SUPPLY_NAME
ET.TOTAL_AMOUNT_KRW,
ET.MANAGER_NAME,
ET.MANAGER_CONTACT,
ET.PART_NAME,
ET.PART_OBJID,
ET.NOTES_CONTENT,
ET.VALIDITY_PERIOD,
ET.CATEGORIES_JSON,
ET.GROUP1_SUBTOTAL,
ET.WRITER,
ET.REGDATE,
ET.CHG_USER_ID,
@@ -3950,12 +4069,19 @@ ORDER BY ASM.SUPPLY_NAME
ESTIMATE_TEMPLATE ET
LEFT JOIN CONTRACT_MGMT CM ON ET.CONTRACT_OBJID = CM.OBJID
WHERE
ET.CONTRACT_OBJID = #{objId}
<if test="template_type != null and template_type != ''">
AND ET.TEMPLATE_TYPE = #{template_type}
</if>
ORDER BY ET.REGDATE DESC
LIMIT 1
<choose>
<when test="templateObjId != null and templateObjId != '' and templateObjId != '-1'">
ET.OBJID = #{templateObjId}
</when>
<otherwise>
ET.CONTRACT_OBJID = #{objId}
<if test="template_type != null and template_type != ''">
AND ET.TEMPLATE_TYPE = #{template_type}
</if>
ORDER BY ET.REGDATE DESC
LIMIT 1
</otherwise>
</choose>
</select>
<!-- 견적서 템플릿 데이터 조회 (OBJID 기준) -->
@@ -3981,6 +4107,13 @@ ORDER BY ASM.SUPPLY_NAME
ET.TOTAL_AMOUNT_KRW,
ET.MANAGER_NAME,
ET.MANAGER_CONTACT,
ET.SHOW_TOTAL_ROW,
ET.PART_NAME,
ET.PART_OBJID,
ET.NOTES_CONTENT,
ET.VALIDITY_PERIOD,
ET.CATEGORIES_JSON,
ET.GROUP1_SUBTOTAL,
ET.WRITER,
TO_CHAR(ET.REGDATE, 'YYYY-MM-DD HH24:MI') AS REGDATE,
ET.CHG_USER_ID,
@@ -4059,6 +4192,30 @@ ORDER BY ASM.SUPPLY_NAME
ORDER BY SEQ
</select>
<!-- 견적서 템플릿 품목 조회 (PART_OBJID로) -->
<select id="getEstimateTemplateItemByPartObjId" parameterType="map" resultType="map">
SELECT
OBJID,
TEMPLATE_OBJID,
SEQ,
CATEGORY,
PART_OBJID,
DESCRIPTION,
SPECIFICATION,
QUANTITY,
UNIT,
UNIT_PRICE,
AMOUNT,
NOTE,
REMARK
FROM
ESTIMATE_TEMPLATE_ITEM
WHERE
TEMPLATE_OBJID = #{templateObjId}
AND PART_OBJID = #{partObjId}
LIMIT 1
</select>
<!-- 견적서 템플릿 저장 -->
<insert id="insertEstimateTemplate" parameterType="map">
INSERT INTO ESTIMATE_TEMPLATE (
@@ -4082,6 +4239,7 @@ ORDER BY ASM.SUPPLY_NAME
TOTAL_AMOUNT_KRW,
MANAGER_NAME,
MANAGER_CONTACT,
SHOW_TOTAL_ROW,
WRITER,
REGDATE,
CHG_USER_ID,
@@ -4107,6 +4265,7 @@ ORDER BY ASM.SUPPLY_NAME
#{total_amount_krw},
#{manager_name},
#{manager_contact},
#{show_total_row},
#{writer},
NOW(),
#{chg_user_id},
@@ -4135,6 +4294,7 @@ ORDER BY ASM.SUPPLY_NAME
TOTAL_AMOUNT_KRW = #{total_amount_krw},
MANAGER_NAME = #{manager_name},
MANAGER_CONTACT = #{manager_contact},
SHOW_TOTAL_ROW = #{show_total_row},
CHG_USER_ID = #{chg_user_id},
CHGDATE = NOW()
WHERE
@@ -4192,6 +4352,67 @@ WHERE
OBJID = #{template_objid}
</update>
<!-- 장비 견적서 템플릿 신규 저장 (Template 2) -->
<insert id="insertEstimateTemplate2" parameterType="map">
INSERT INTO ESTIMATE_TEMPLATE (
OBJID,
CONTRACT_OBJID,
TEMPLATE_TYPE,
EXECUTOR_DATE,
RECIPIENT,
PART_NAME,
PART_OBJID,
NOTES_CONTENT,
VALIDITY_PERIOD,
CATEGORIES_JSON,
GROUP1_SUBTOTAL,
TOTAL_AMOUNT,
TOTAL_AMOUNT_KRW,
WRITER,
REGDATE,
CHG_USER_ID,
CHGDATE
) VALUES (
#{template_objid},
#{contract_objid},
'2',
#{executor_date},
#{recipient},
#{part_name},
#{part_objid},
#{notes_content},
#{validity_period},
#{categories_json},
#{group1_subtotal},
#{total_amount},
#{total_amount_krw},
#{writer},
NOW(),
#{chg_user_id},
NOW()
)
</insert>
<!-- 장비 견적서 템플릿 수정 (Template 2) -->
<update id="updateEstimateTemplate2" parameterType="map">
UPDATE ESTIMATE_TEMPLATE
SET
EXECUTOR_DATE = #{executor_date},
RECIPIENT = #{recipient},
PART_NAME = #{part_name},
PART_OBJID = #{part_objid},
NOTES_CONTENT = #{notes_content},
VALIDITY_PERIOD = #{validity_period},
CATEGORIES_JSON = #{categories_json},
GROUP1_SUBTOTAL = #{group1_subtotal},
TOTAL_AMOUNT = #{total_amount},
TOTAL_AMOUNT_KRW = #{total_amount_krw},
CHG_USER_ID = #{chg_user_id},
CHGDATE = NOW()
WHERE
OBJID = #{template_objid}
</update>
<!-- 최종 차수 견적서 조회 (메일 발송용) -->
<select id="getLatestEstimateTemplate" parameterType="map" resultType="map">
SELECT
@@ -4349,14 +4570,112 @@ WHERE
</if>
ORDER BY REGDATE DESC
</select>
<!-- 주문서관리 Total 합계 조회 -->
<select id="getOrderTotalAmount" parameterType="map" resultType="map">
SELECT
COALESCE(SUM(COALESCE(T.ORDER_SUPPLY_PRICE_SUM, 0)), 0) AS TOTAL_SUPPLY_PRICE,
COALESCE(SUM(COALESCE(T.ORDER_VAT_SUM, 0)), 0) AS TOTAL_VAT,
COALESCE(SUM(COALESCE(T.ORDER_TOTAL_AMOUNT_SUM, 0)), 0) AS TOTAL_AMOUNT
FROM
<include refid="contractBase"/> T
WHERE 1=1
<if test="Year !=null and Year != '' ">
AND SUBSTR(CONTRACT_DATE,0,5) = #{Year}
</if>
<if test="category_cd !=null and category_cd != '' ">
AND category_cd = #{category_cd}
</if>
<if test="customer_objid !=null and customer_objid != '' ">
AND customer_objid = #{customer_objid}
</if>
<if test="product != null and product !='' ">
AND product = #{product}
</if>
<if test="status_cd !=null and status_cd !=''">
AND status_cd = #{status_cd}
</if>
<if test="result_cd !=null and result_cd !=''">
AND result_cd = #{result_cd}
</if>
<if test="contract_result !=null and contract_result !=''">
AND contract_result = #{contract_result}
</if>
<if test="contract_start_date != null and !''.equals(contract_start_date)">
AND TO_DATE(CONTRACT_DATE,'YYYY-MM-DD') <![CDATA[ >= ]]> TO_DATE(#{contract_start_date}, 'YYYY-MM-DD')
</if>
<if test="contract_end_date != null and !''.equals(contract_end_date)">
AND TO_DATE(CONTRACT_DATE,'YYYY-MM-DD') <![CDATA[ <= ]]> TO_DATE(#{contract_end_date}, 'YYYY-MM-DD')
</if>
<if test="pm_user_id !=null and pm_user_id !=''">
AND pm_user_id = #{pm_user_id}
</if>
<if test="contract_month != null and !''.equals(contract_month)">
AND TO_DATE(TO_CHAR(TO_DATE(CONTRACT_DATE,'YYYY-MM-DD'),'YYYY-MM-DD'),'YYYY-MM') <![CDATA[ = ]]> TO_DATE(SUBSTRING(#{contract_month} FROM 1 FOR 4) || '-' || SUBSTRING(#{contract_month} FROM 5 FOR 2), 'YYYY-MM')
</if>
<!-- 견적관리 추가 검색조건 -->
<if test="appr_status !=null and appr_status != '' ">
AND APPR_STATUS = #{appr_status}
</if>
<if test="area_cd != null and area_cd !='' ">
AND AREA_CD = #{area_cd}
</if>
<if test="paid_type != null and paid_type !='' ">
AND PAID_TYPE = #{paid_type}
</if>
<!-- 품번/품명 검색: PART_OBJID로 정확하게 검색 -->
<if test="search_partObjId != null and search_partObjId != ''">
AND EXISTS (
SELECT 1
FROM CONTRACT_ITEM CI
WHERE CI.CONTRACT_OBJID = T.OBJID
AND CI.STATUS = 'ACTIVE'
AND CI.PART_OBJID = #{search_partObjId}
)
</if>
<if test="search_serialNo != null and search_serialNo != ''">
AND UPPER(SERIAL_NO) LIKE UPPER('%${search_serialNo}%')
</if>
<if test="search_poNo != null and search_poNo != ''">
AND UPPER(PO_NO) LIKE UPPER('%${search_poNo}%')
</if>
<if test="order_start_date != null and !''.equals(order_start_date)">
AND TO_DATE(ORDER_DATE,'YYYY-MM-DD') <![CDATA[ >= ]]> TO_DATE(#{order_start_date}, 'YYYY-MM-DD')
</if>
<if test="order_end_date != null and !''.equals(order_end_date)">
AND TO_DATE(ORDER_DATE,'YYYY-MM-DD') <![CDATA[ <= ]]> TO_DATE(#{order_end_date}, 'YYYY-MM-DD')
</if>
<if test="due_start_date != null and !''.equals(due_start_date)">
AND TO_DATE(DUE_DATE,'YYYY-MM-DD') <![CDATA[ >= ]]> TO_DATE(#{due_start_date}, 'YYYY-MM-DD')
</if>
<if test="due_end_date != null and !''.equals(due_end_date)">
AND TO_DATE(DUE_DATE,'YYYY-MM-DD') <![CDATA[ <= ]]> TO_DATE(#{due_end_date}, 'YYYY-MM-DD')
</if>
</select>
<!-- 영업정보 조회 (수주등록용) -->
<select id="getContractInfo" parameterType="map" resultType="map">
SELECT
OBJID,
CONTRACT_NO,
CUSTOMER_OBJID,
CONTRACT_CURRENCY,
(SELECT CODE_NAME FROM TB_CODE WHERE CODE_ID = CONTRACT_CURRENCY) AS CONTRACT_CURRENCY_NAME,
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CONTRACT_CURRENCY) AS CONTRACT_CURRENCY_NAME,
EXCHANGE_RATE,
QUANTITY,
PART_NO,
@@ -4582,8 +4901,8 @@ WHERE
CI.CONTRACT_OBJID,
CI.SEQ,
CI.PART_OBJID,
CI.PART_NO,
CI.PART_NAME,
COALESCE(NULLIF(CI.PART_NO, ''), PM.PART_NO) AS PART_NO,
COALESCE(NULLIF(CI.PART_NAME, ''), PM.PART_NAME) AS PART_NAME,
CI.QUANTITY,
CI.DUE_DATE,
CI.CUSTOMER_REQUEST,
@@ -4597,6 +4916,8 @@ WHERE
LEFT JOIN CONTRACT_ITEM_SERIAL CIS
ON CI.OBJID = CIS.ITEM_OBJID
AND CIS.STATUS = 'ACTIVE'
LEFT JOIN PART_MNG PM
ON CI.PART_OBJID = PM.OBJID
WHERE
CI.CONTRACT_OBJID = #{contractObjId}
AND CI.STATUS = 'ACTIVE'
@@ -4606,7 +4927,9 @@ WHERE
CI.SEQ,
CI.PART_OBJID,
CI.PART_NO,
CI.PART_NAME,
CI.PART_NAME,
PM.PART_NO,
PM.PART_NAME,
CI.QUANTITY,
CI.DUE_DATE,
CI.CUSTOMER_REQUEST,

View File

@@ -875,16 +875,32 @@
(SELECT CM.PRODUCTION_STATUS FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PRODUCTION_STATUS,
-- 판매 관련 필드들 (sales_registration 테이블에서 한 번에 가져오기)
COALESCE(SR.shipping_order_status, '') AS SHIPPING_ORDER_STATUS,
COALESCE(SR.sales_quantity, 0) AS SALES_QUANTITY,
-- 판매수량: 모든 분할 출하의 합계
COALESCE(
(SELECT SUM(sales_quantity)
FROM sales_registration
WHERE project_no LIKE T.PROJECT_NO || '%'),
0
) AS SALES_QUANTITY,
COALESCE(SR.sales_unit_price, 0) AS SALES_UNIT_PRICE,
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_total_amount, 0) AS SALES_TOTAL_AMOUNT_KRW,
-- 잔량 계산: 수주수량 - 판매수량
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(T.QUANTITY::numeric, 0) - COALESCE(
(SELECT SUM(sales_quantity)
FROM sales_registration
WHERE project_no LIKE T.PROJECT_NO || '%'),
0
) AS REMAINING_QUANTITY,
-- 잔량원화총액 계산: (수주수량 - 모든 분할 출하 합계) * 판매단가
(COALESCE(T.QUANTITY::numeric, 0) - COALESCE(
(SELECT SUM(sales_quantity)
FROM sales_registration
WHERE project_no LIKE T.PROJECT_NO || '%'),
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,
@@ -1397,29 +1413,12 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
NULL,
</otherwise>
</choose>
#{shippingMethod},
#{manager},
#{incoterms},
#{cretEmpNo}
)
ON CONFLICT (project_no)
DO UPDATE SET
shipping_order_status = EXCLUDED.shipping_order_status,
serial_no = EXCLUDED.serial_no,
sales_quantity = EXCLUDED.sales_quantity,
sales_unit_price = EXCLUDED.sales_unit_price,
sales_supply_price = EXCLUDED.sales_supply_price,
sales_vat = EXCLUDED.sales_vat,
sales_total_amount = EXCLUDED.sales_total_amount,
sales_currency = EXCLUDED.sales_currency,
sales_exchange_rate = EXCLUDED.sales_exchange_rate,
shipping_date = EXCLUDED.shipping_date,
shipping_method = EXCLUDED.shipping_method,
manager_user_id = EXCLUDED.manager_user_id,
incoterms = EXCLUDED.incoterms,
upd_date = NOW(),
upd_user_id = EXCLUDED.reg_user_id
</insert>
#{shippingMethod},
#{manager},
#{incoterms},
#{cretEmpNo}
)
</insert>
<!--
/**
@@ -1488,9 +1487,15 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
AND UPPER(STATUS) = 'ACTIVE'
) THEN 'Y' ELSE 'N' END AS ORDER_ATTACH,
(SELECT CM.PRODUCTION_STATUS FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PRODUCTION_STATUS,
-- 판매 관련 필드들 (sales_registration 테이블에서 가져오기)
COALESCE(SR.shipping_order_status, '') AS SHIPPING_ORDER_STATUS,
COALESCE(SR.sales_quantity, 0) AS SALES_QUANTITY,
-- 판매 관련 필드들 (sales_registration 테이블에서 가져오기)
SR.sale_no AS SALE_NO,
COALESCE(SR.shipping_order_status, '') AS SHIPPING_ORDER_STATUS,
-- 주문수량 (PROJECT_MGMT에서 가져오기) - 잔량 계산용
COALESCE(T.QUANTITY::NUMERIC, 0) AS ORDER_QUANTITY,
-- 판매수량 (sales_registration에서 가져오기) - 이미 판매한 수량
COALESCE(SR.sales_quantity, 0) AS SALES_QUANTITY,
COALESCE(SR.sales_unit_price, 0) AS SALES_UNIT_PRICE,
COALESCE(SR.sales_supply_price, 0) AS SALES_SUPPLY_PRICE,
COALESCE(SR.sales_vat, 0) AS SALES_VAT,
@@ -1498,7 +1503,7 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
COALESCE(SR.sales_total_amount, 0) AS SALES_TOTAL_AMOUNT_KRW,
COALESCE(NULLIF(SR.sales_currency, ''), CM.CONTRACT_CURRENCY) AS SALES_CURRENCY,
CODE_NAME(COALESCE(NULLIF(SR.sales_currency, ''), CM.CONTRACT_CURRENCY)) AS SALES_CURRENCY_NAME,
COALESCE(NULLIF(SR.sales_exchange_rate, 0), CM.EXCHANGE_RATE::numeric, 0) AS SALES_EXCHANGE_RATE,
COALESCE(NULLIF(SR.sales_exchange_rate, 0), NULLIF(CM.EXCHANGE_RATE, '')::numeric, 0) AS SALES_EXCHANGE_RATE,
COALESCE(TO_CHAR(SR.shipping_date, 'YYYY-MM-DD'), '') AS SHIPPING_DATE,
COALESCE(SR.shipping_method, '') AS SHIPPING_METHOD,
COALESCE(SR.manager_user_id, T.PM_USER_ID) AS MANAGER,
@@ -1566,40 +1571,43 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
-->
<select id="getOrderDataByOrderNo" parameterType="map" resultType="map">
/* salesNcollectMgmt.getOrderDataByOrderNo - orderNo로 판매등록용 수주 데이터 조회 */
SELECT
-- 기본 정보
CM.CONTRACT_NO AS ORDER_NO,
CM.OBJID AS CONTRACT_OBJID,
-- 수주 금액 정보 (CONTRACT_ITEM 테이블에서 합산) - VARCHAR 타입이므로 NUMERIC으로 캐스팅
COALESCE(SUM(CI.ORDER_QUANTITY::NUMERIC), 0) AS SALES_QUANTITY,
COALESCE(ROUND(AVG(CI.ORDER_UNIT_PRICE::NUMERIC), 2), 0) AS SALES_UNIT_PRICE,
COALESCE(SUM(CI.ORDER_SUPPLY_PRICE::NUMERIC), 0) AS SALES_SUPPLY_PRICE,
COALESCE(SUM(CI.ORDER_VAT::NUMERIC), 0) AS SALES_VAT,
COALESCE(SUM(CI.ORDER_TOTAL_AMOUNT::NUMERIC), 0) AS SALES_TOTAL_AMOUNT,
-- 환종 정보
CM.CONTRACT_CURRENCY AS SALES_CURRENCY,
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,
-- 담당자
CM.PM_USER_ID AS MANAGER
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
CM.CONTRACT_NO,
CM.OBJID,
CM.CONTRACT_CURRENCY,
CM.EXCHANGE_RATE,
CM.ORDER_DATE,
CM.PM_USER_ID
SELECT
-- 기본 정보
CM.CONTRACT_NO AS ORDER_NO,
CM.OBJID AS CONTRACT_OBJID,
-- 수주 수량 정보 (PROJECT_MGMT에서 직접 가져오기)
COALESCE(PM.QUANTITY::NUMERIC, 0) AS SALES_QUANTITY,
-- 수주 금액 정보 (CONTRACT_ITEM 테이블에서 합산) - VARCHAR 타입이므로 NULLIF로 빈 문자열 제거 후 NUMERIC 캐스팅
COALESCE(ROUND(AVG(NULLIF(CI.ORDER_UNIT_PRICE, '')::NUMERIC), 2), 0) AS SALES_UNIT_PRICE,
COALESCE(SUM(NULLIF(CI.ORDER_SUPPLY_PRICE, '')::NUMERIC), 0) AS SALES_SUPPLY_PRICE,
COALESCE(SUM(NULLIF(CI.ORDER_VAT, '')::NUMERIC), 0) AS SALES_VAT,
COALESCE(SUM(NULLIF(CI.ORDER_TOTAL_AMOUNT, '')::NUMERIC), 0) AS SALES_TOTAL_AMOUNT,
-- 환종 정보
CM.CONTRACT_CURRENCY AS SALES_CURRENCY,
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,
-- 담당자
CM.PM_USER_ID AS MANAGER
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
CM.CONTRACT_NO,
CM.OBJID,
CM.CONTRACT_CURRENCY,
CM.EXCHANGE_RATE,
CM.ORDER_DATE,
CM.PM_USER_ID,
PM.QUANTITY
</select>
<!--
@@ -1619,5 +1627,159 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
WHERE OBJID::VARCHAR = #{OBJID}
</update>
<!-- 모든 분할 출하의 총 판매 수량 조회 -->
<select id="getTotalSalesQuantity" parameterType="map" resultType="map">
/* salesNcollectMgmt.getTotalSalesQuantity - project_no로 시작하는 모든 레코드의 판매 수량 합계 */
SELECT COALESCE(SUM(NULLIF(sales_quantity, '')::NUMERIC), 0) as total
FROM sales_registration
WHERE project_no LIKE #{orderNo} || '%'
</select>
<!-- sales_registration 개수 조회 (분할 출하 순번용) -->
<select id="getSaleRegistrationCount" parameterType="map" resultType="map">
/* salesNcollectMgmt.getSaleRegistrationCount - project_no로 시작하는 레코드 개수 */
SELECT COUNT(*) as count
FROM sales_registration
WHERE project_no LIKE #{orderNo} || '%'
</select>
<!-- sales_registration DELETE (기존 데이터 삭제) -->
<delete id="deleteSaleRegistration" parameterType="map">
/* salesNcollectMgmt.deleteSaleRegistration - sales_registration 삭제 */
DELETE FROM sales_registration
WHERE project_no = #{orderNo}
</delete>
<!-- sales_registration UPDATE (기존 데이터 수정) -->
<update id="updateSaleRegistration" parameterType="map">
/* salesNcollectMgmt.updateSaleRegistration - sales_registration 업데이트 */
UPDATE sales_registration
SET
shipping_order_status = <choose>
<when test="shippingOrderStatus != null and shippingOrderStatus != ''">
#{shippingOrderStatus},
</when>
<otherwise>
'',
</otherwise>
</choose>
serial_no = #{serialNo},
sales_quantity = #{salesQuantity}::integer,
sales_unit_price = #{salesUnitPrice}::numeric,
sales_supply_price = #{salesSupplyPrice}::numeric,
sales_vat = #{salesVat}::numeric,
sales_total_amount = #{salesTotalAmount}::numeric,
sales_currency = #{salesCurrency},
sales_exchange_rate = #{salesExchangeRate}::numeric,
shipping_date = <choose>
<when test="shippingDate != null and shippingDate != ''">
TO_DATE(#{shippingDate}, 'YYYY-MM-DD'),
</when>
<otherwise>
NULL,
</otherwise>
</choose>
shipping_method = #{shippingMethod},
manager_user_id = #{manager},
incoterms = #{incoterms},
upd_date = NOW(),
upd_user_id = #{cretEmpNo}
WHERE project_no = #{orderNo}
</update>
<!-- ========================================
분할 출하 관련 쿼리 (shipment_log 테이블 활용)
======================================== -->
<!-- shipment_log의 split_quantity 합산 조회 -->
<select id="getShipmentLogTotal" parameterType="map" resultType="map">
/* salesNcollectMgmt.getShipmentLogTotal - shipment_log의 split_quantity 합산 */
SELECT COALESCE(SUM(split_quantity), 0) as total
FROM shipment_log SL
INNER JOIN project_mgmt PM ON SL.target_objid = PM.OBJID::VARCHAR
WHERE PM.PROJECT_NO = #{orderNo}
</select>
<!-- shipment_log에 분할 출하 기록 저장 -->
<insert id="insertShipmentLog" parameterType="map">
/* salesNcollectMgmt.insertShipmentLog - 분할 출하 기록 저장 */
INSERT INTO shipment_log (
target_objid, log_type, log_message, split_quantity, original_quantity,
remaining_quantity, shipping_status, shipping_date, shipping_method,
sales_unit_price, sales_supply_price, sales_vat, sales_total_amount,
sales_currency, sales_exchange_rate, manager_user_id, incoterms,
serial_no, parent_sale_no, reg_user_id
) VALUES (
#{targetObjid}, 'SPLIT_SHIPMENT', '분할 출하',
#{salesQuantity}::integer, #{originalQuantity}::integer, #{remainingQuantity}::integer,
#{shippingOrderStatus},
<choose>
<when test="shippingDate != null and shippingDate != ''">
TO_DATE(#{shippingDate}, 'YYYY-MM-DD'),
</when>
<otherwise>NULL,</otherwise>
</choose>
#{shippingMethod}, #{salesUnitPrice}::numeric, #{salesSupplyPrice}::numeric,
#{salesVat}::numeric, #{salesTotalAmount}::numeric, #{salesCurrency},
#{salesExchangeRate}::numeric, #{manager}, #{incoterms}, #{serialNo},
#{parentSaleNo}::integer, #{cretEmpNo}
)
</insert>
<!-- PROJECT_MGMT의 OBJID 조회 (shipment_log의 target_objid로 사용) -->
<select id="getProjectObjid" parameterType="map" resultType="map">
/* salesNcollectMgmt.getProjectObjid - PROJECT_MGMT의 OBJID 조회 */
SELECT OBJID FROM PROJECT_MGMT WHERE PROJECT_NO = #{orderNo}
</select>
<!-- 출하일 상세 내역 조회 (모든 분할 출하 포함) -->
<select id="getShippingDetailList" parameterType="map" resultType="map">
/* salesNcollectMgmt.getShippingDetailList - 모든 분할 출하 포함 */
SELECT
COALESCE(TO_CHAR(SR.shipping_date, 'YYYY-MM-DD'), '미등록') AS shipping_date,
COALESCE(SR.sales_quantity, 0) AS shipping_quantity,
COALESCE(SR.shipping_order_status, '미등록') AS shipping_order_status,
COALESCE(SR.serial_no, '-') AS serial_no,
SR.project_no,
CASE
WHEN SR.project_no = #{projectNo} THEN '최초 출하'
ELSE '분할 출하 ' || SUBSTRING(SR.project_no FROM LENGTH(#{projectNo}) + 2)
END AS source
FROM sales_registration SR
WHERE SR.project_no LIKE #{projectNo} || '%'
ORDER BY shipping_date DESC, project_no
</select>
<!-- 거래명세서 - 고객 정보 조회 -->
<select id="getCustomerInfoByProjectNo" parameterType="map" resultType="map">
/* salesNcollectMgmt.getCustomerInfoByProjectNo - 프로젝트 번호로 고객 정보 조회 */
SELECT
SM.SUPPLY_NAME AS CUSTOMER_NAME,
SM.BUSINESS_NO AS CUSTOMER_REG_NO,
SM.ADDRESS AS CUSTOMER_ADDRESS,
SM.BUSINESS_TYPE AS CUSTOMER_BUSINESS,
SM.BUSINESS_ITEM AS CUSTOMER_TYPE,
SM.TEL_NO AS CUSTOMER_CONTACT
FROM PROJECT_MGMT PM
INNER JOIN SUPPLY_MNG SM ON PM.CUSTOMER_OBJID::NUMERIC = SM.OBJID
WHERE PM.PROJECT_NO = #{projectNo}
</select>
<!-- 거래명세서 - 품목 정보 조회 -->
<select id="getTransactionStatementItem" parameterType="map" resultType="map">
/* salesNcollectMgmt.getTransactionStatementItem - 프로젝트 번호로 품목 정보 조회 */
SELECT
PM.PART_NAME AS productName,
PM.PART_NO AS spec,
COALESCE(SR.sales_quantity, PM.QUANTITY) AS quantity,
COALESCE(SR.sales_unit_price, 0) AS unitPrice,
COALESCE(SR.sales_supply_price, 0) AS supplyPrice,
COALESCE(SR.sales_vat, 0) AS vat
FROM PROJECT_MGMT PM
LEFT JOIN sales_registration SR ON PM.PROJECT_NO = SR.project_no
WHERE PM.PROJECT_NO = #{projectNo}
</select>
</mapper>

View File

@@ -28,6 +28,8 @@ import org.json.simple.parser.JSONParser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.google.gson.Gson;
import com.pms.common.Message;
import com.pms.common.SqlMapConfig;
import com.pms.common.bean.PersonBean;
@@ -1249,17 +1251,39 @@ public class ContractMgmtService {
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
String objId = CommonUtils.checkNull(paramMap.get("objId"));
String templateObjId = CommonUtils.checkNull(paramMap.get("templateObjId"));
String templateType = CommonUtils.checkNull(paramMap.get("template_type"));
if(!"".equals(objId) && !"-1".equals(objId)){
System.out.println("=== getEstimateTemplateInfo 파라미터 ===");
System.out.println("objId: " + objId);
System.out.println("templateObjId: " + templateObjId);
System.out.println("templateType: " + templateType);
// templateObjId가 있으면 직접 조회
if(!"".equals(templateObjId) && !"-1".equals(templateObjId)){
paramMap.put("templateObjId", templateObjId);
}
// objId 또는 templateObjId 중 하나라도 있으면 조회
if((!"".equals(objId) && !"-1".equals(objId)) || (!"".equals(templateObjId) && !"-1".equals(templateObjId))){
// 견적서 기본 정보 조회 (CONTRACT_MGMT 테이블에서)
resultMap = (Map) sqlSession.selectOne("contractMgmt.getEstimateTemplateInfo", paramMap);
Map baseInfo = (Map) sqlSession.selectOne("contractMgmt.getEstimateTemplateInfo", paramMap);
System.out.println("baseInfo: " + baseInfo);
if(baseInfo != null){
resultMap = CommonUtils.toUpperCaseMapKey(baseInfo);
System.out.println("baseInfo (변환 후): " + resultMap);
}
// 견적서 템플릿 정보 조회 (ESTIMATE_TEMPLATE 테이블에서, 있는 경우)
Map templateInfo = (Map) sqlSession.selectOne("contractMgmt.getEstimateTemplateData", paramMap);
System.out.println("templateInfo: " + templateInfo);
if(templateInfo != null && !templateInfo.isEmpty()){
templateInfo = CommonUtils.toUpperCaseMapKey(templateInfo);
System.out.println("templateInfo (변환 후): " + templateInfo);
resultMap.putAll(templateInfo);
}
System.out.println("최종 resultMap 크기: " + resultMap.size());
}
}catch(Exception e){
@@ -1284,9 +1308,54 @@ public class ContractMgmtService {
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
String objId = CommonUtils.checkNull(paramMap.get("objId"));
String templateObjId = CommonUtils.checkNull(paramMap.get("templateObjId"));
String templateType = CommonUtils.checkNull(paramMap.get("template_type"));
if(!"".equals(objId) && !"-1".equals(objId)){
resultList = sqlSession.selectList("contractMgmt.getEstimateTemplateItems", paramMap);
System.out.println("=== getEstimateTemplateItems 파라미터 ===");
System.out.println("objId: " + objId);
System.out.println("templateObjId: " + templateObjId);
System.out.println("templateType: " + templateType);
// templateObjId가 있으면 우선 사용, 없으면 objId 사용
String targetId = !"".equals(templateObjId) && !"-1".equals(templateObjId) ? templateObjId : objId;
if(!"".equals(targetId) && !"-1".equals(targetId)){
// 장비 견적서(Template 2)인 경우 categories_json에서 데이터 가져오기
if("2".equals(templateType)){
// templateObjId가 있으면 직접 조회, 없으면 contract_objid로 조회
if(!"".equals(templateObjId) && !"-1".equals(templateObjId)){
paramMap.put("templateObjId", templateObjId);
}
// ESTIMATE_TEMPLATE에서 categories_json 조회
Map templateData = (Map) sqlSession.selectOne("contractMgmt.getEstimateTemplateData", paramMap);
System.out.println("=== getEstimateTemplateItems 디버깅 ===");
System.out.println("templateData (변환 전): " + templateData);
if(templateData != null){
// 대문자로 변환
templateData = CommonUtils.toUpperCaseMapKey(templateData);
System.out.println("templateData (변환 후): " + templateData);
// 대소문자 모두 체크 (안전장치)
String categoriesJson = CommonUtils.checkNull(templateData.get("CATEGORIES_JSON"));
if("".equals(categoriesJson)) categoriesJson = CommonUtils.checkNull(templateData.get("categories_json"));
System.out.println("categoriesJson: " + categoriesJson);
if(categoriesJson != null && !categoriesJson.isEmpty()){
// JSON 파싱 (Gson 사용)
com.google.gson.Gson gson = new com.google.gson.Gson();
java.lang.reflect.Type listType = new com.google.gson.reflect.TypeToken<List<Map>>(){}.getType();
resultList = gson.fromJson(categoriesJson, listType);
System.out.println("파싱된 resultList 크기: " + resultList.size());
}
}
}
// 일반 견적서(Template 1)인 경우 기존 방식대로 ESTIMATE_TEMPLATE_ITEM에서 조회
else {
resultList = sqlSession.selectList("contractMgmt.getEstimateTemplateItems", paramMap);
}
}
}catch(Exception e){
@@ -1413,20 +1482,20 @@ public class ContractMgmtService {
PersonBean person = (PersonBean)request.getSession().getAttribute(Constants.PERSON_BEAN);
String userId = person.getUserId();
String objId = CommonUtils.checkNull(paramMap.get("objId")); // CONTRACT_OBJID
String templateObjId = CommonUtils.checkNull(paramMap.get("templateObjId")); // 기존 템플릿 수정 시
String templateType = CommonUtils.checkNull(paramMap.get("template_type"));
String itemsJson = CommonUtils.checkNull(paramMap.get("items"));
String categoriesJson = CommonUtils.checkNull(paramMap.get("categories"));
// 합계 정보 (일반 견적서용)
String totalAmount = CommonUtils.checkNull(paramMap.get("total_amount"));
String totalAmountKrw = CommonUtils.checkNull(paramMap.get("total_amount_krw"));
paramMap.put("writer", userId);
paramMap.put("chg_user_id", userId);
paramMap.put("total_amount", totalAmount);
paramMap.put("total_amount_krw", totalAmountKrw);
String objId = CommonUtils.checkNull(paramMap.get("objId")); // CONTRACT_OBJID
String templateObjId = CommonUtils.checkNull(paramMap.get("templateObjId")); // 기존 템플릿 수정 시
String templateType = CommonUtils.checkNull(paramMap.get("template_type"));
String itemsJson = CommonUtils.checkNull(paramMap.get("items"));
String categoriesJson = CommonUtils.checkNull(paramMap.get("categories"));
// 합계 정보 (일반 견적서용)
String totalAmount = CommonUtils.checkNull(paramMap.get("total_amount"));
String totalAmountKrw = CommonUtils.checkNull(paramMap.get("total_amount_krw"));
paramMap.put("writer", userId);
paramMap.put("chg_user_id", userId);
paramMap.put("total_amount", totalAmount);
paramMap.put("total_amount_krw", totalAmountKrw);
// 기존 템플릿 수정인지 신규 작성인지 확인
// 중요: templateObjId가 명시적으로 있을 때만 수정, 없으면 항상 신규 작성
@@ -1487,6 +1556,189 @@ public class ContractMgmtService {
return resultMap;
}
/**
* 장비 견적서 저장 (Template 2)
* @param request
* @param paramMap
* @return
*/
public Map saveEstimateTemplate2(HttpServletRequest request, Map paramMap) throws Exception {
Map resultMap = new HashMap();
SqlSession sqlSession = null;
try{
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
String userId = CommonUtils.checkNull(paramMap.get("userId"));
String objId = CommonUtils.checkNull(paramMap.get("objId"));
String templateObjId = CommonUtils.checkNull(paramMap.get("templateObjId"));
String categoriesJson = CommonUtils.checkNull(paramMap.get("categories"));
paramMap.put("writer", userId);
paramMap.put("chg_user_id", userId);
paramMap.put("template_type", "2"); // 장비 견적서
// 기존 템플릿 수정인지 신규 작성인지 확인
boolean isUpdate = false;
if(!"".equals(templateObjId) && !"-1".equals(templateObjId)){
Map existingTemplate = (Map) sqlSession.selectOne("contractMgmt.getEstimateTemplateByObjId", paramMap);
if(existingTemplate != null && !existingTemplate.isEmpty()){
isUpdate = true;
paramMap.put("template_objid", templateObjId);
// objId가 없으면 기존 템플릿에서 가져오기
if("".equals(objId) || "-1".equals(objId)){
existingTemplate = CommonUtils.toUpperCaseMapKey(existingTemplate);
objId = CommonUtils.checkNull(existingTemplate.get("CONTRACT_OBJID"));
paramMap.put("contract_objid", objId);
}
}
}
if(isUpdate){
// 기존 견적서 수정
sqlSession.update("contractMgmt.updateEstimateTemplate2", paramMap);
} else {
// 신규 견적서 작성
templateObjId = CommonUtils.createObjId();
paramMap.put("template_objid", templateObjId);
paramMap.put("contract_objid", objId);
sqlSession.insert("contractMgmt.insertEstimateTemplate2", paramMap);
}
// 카테고리 정보 저장 (categories_json 필드에 저장)
if(!"".equals(categoriesJson)){
paramMap.put("categories_json", categoriesJson);
sqlSession.update("contractMgmt.updateEstimateTemplateCategories", paramMap);
}
// 장비 견적서도 ESTIMATE_TEMPLATE_ITEM에 저장 (일관성을 위해)
// 기존 품목 삭제
sqlSession.delete("contractMgmt.deleteEstimateTemplateItems", paramMap);
// 품목 정보 생성 및 저장
String partName = CommonUtils.checkNull(paramMap.get("part_name"));
String partObjId = CommonUtils.checkNull(paramMap.get("part_objid"));
Object totalAmount = paramMap.get("total_amount");
if(!"".equals(partName) && totalAmount != null) {
try {
long amount = Long.parseLong(totalAmount.toString().replaceAll("[^0-9]", ""));
// 수량은 categories JSON에서 CNC Machine의 quantity를 가져옴
int quantity = 1; // 기본값
System.out.println("=== 장비 견적서 수량 추출 시작 ===");
System.out.println("categoriesJson: " + categoriesJson);
if(!"".equals(categoriesJson)) {
try {
// JSON 파싱
org.json.simple.parser.JSONParser parser = new org.json.simple.parser.JSONParser();
org.json.simple.JSONArray categoriesArray = (org.json.simple.JSONArray) parser.parse(categoriesJson);
System.out.println("categories 배열 크기: " + categoriesArray.size());
// CNC Machine 카테고리 찾기
for(int i = 0; i < categoriesArray.size(); i++) {
org.json.simple.JSONObject category = (org.json.simple.JSONObject) categoriesArray.get(i);
String categoryId = (String) category.get("category");
System.out.println("카테고리 " + i + ": " + categoryId);
if("cnc_machine".equals(categoryId)) {
System.out.println("CNC Machine 카테고리 찾음!");
// cnc_machine의 quantity는 items 배열 안에 있음
org.json.simple.JSONArray items = (org.json.simple.JSONArray) category.get("items");
System.out.println("items 배열: " + items);
if(items != null && items.size() > 0) {
org.json.simple.JSONObject firstItem = (org.json.simple.JSONObject) items.get(0);
Object qtyObj = firstItem.get("quantity");
System.out.println("quantity 객체: " + qtyObj);
System.out.println("quantity 타입: " + (qtyObj != null ? qtyObj.getClass().getName() : "null"));
if(qtyObj != null && !"".equals(qtyObj.toString().trim())) {
// quantity는 텍스트 형식일 수 있으므로 첫 줄만 추출
String qtyStr = qtyObj.toString().split("\n")[0].trim();
System.out.println("추출된 수량 문자열: '" + qtyStr + "'");
if(!qtyStr.isEmpty()) {
String numOnly = qtyStr.replaceAll("[^0-9]", "");
System.out.println("숫자만 추출: '" + numOnly + "'");
if(!numOnly.isEmpty()) {
quantity = Integer.parseInt(numOnly);
System.out.println("최종 수량: " + quantity);
}
}
} else {
System.out.println("quantity가 null이거나 비어있음");
}
} else {
System.out.println("items 배열이 비어있음");
}
break;
}
}
System.out.println("최종 결정된 수량: " + quantity);
} catch(Exception e) {
System.out.println("수량 추출 실패, 기본값 1 사용");
e.printStackTrace();
}
} else {
System.out.println("categoriesJson이 비어있음");
}
// 단가 계산 (총액 / 수량)
long unitPrice = quantity > 0 ? amount / quantity : amount;
// JSON 형식으로 품목 데이터 생성
String itemJson = "[{" +
"\"seq\": 1," +
"\"category\": \"\"," +
"\"part_objid\": \"" + partObjId + "\"," +
"\"description\": \"" + partName + "\"," +
"\"specification\": \"\"," +
"\"quantity\": " + quantity + "," +
"\"unit\": \"EA\"," +
"\"unit_price\": " + unitPrice + "," +
"\"amount\": " + amount + "," +
"\"note\": \"\"," +
"\"remark\": \"\"" +
"}]";
paramMap.put("items_json", itemJson);
sqlSession.insert("contractMgmt.insertEstimateTemplateItems", paramMap);
System.out.println("장비 견적서 품목 저장 완료 - 품명: " + partName + ", 수량: " + quantity + ", 단가: " + unitPrice);
} catch(Exception e) {
System.out.println("장비 견적서 품목 저장 실패: " + e.getMessage());
e.printStackTrace();
// 품목 저장 실패해도 견적서는 저장되도록 예외를 던지지 않음
}
}
sqlSession.commit();
resultMap.put("result", "success");
resultMap.put("msg", Message.SAVE_SUCCESS);
}catch(Exception e){
if(sqlSession != null) sqlSession.rollback();
e.printStackTrace();
throw e; // 예외를 컨트롤러로 전달
}finally{
if(sqlSession != null) sqlSession.close();
}
return resultMap;
}
/**
* 견적서 메일 발송
* @param request
@@ -1530,26 +1782,61 @@ public class ContractMgmtService {
String templateType = CommonUtils.checkNull(estimateTemplate.get("template_type"));
if("".equals(templateType)) templateType = CommonUtils.checkNull(estimateTemplate.get("TEMPLATE_TYPE"));
// 3. 견적서 품목 조회
Map itemParam = new HashMap();
itemParam.put("templateObjId", templateObjId);
List<Map> estimateItems = sqlSession.selectList("contractMgmt.getEstimateTemplateItemsByTemplateObjId", itemParam);
System.out.println("===== Estimate Items Debug =====");
System.out.println("Items count: " + (estimateItems != null ? estimateItems.size() : 0));
if(estimateItems != null && !estimateItems.isEmpty()){
for(int i = 0; i < Math.min(3, estimateItems.size()); i++){
Map item = estimateItems.get(i);
System.out.println("Item " + (i+1) + " keys: " + item.keySet());
// 3. 견적서 품목 조회
List<Map> estimateItems = new ArrayList<Map>();
if("2".equals(templateType)) {
// 장비 견적서 (Template 2): CATEGORIES_JSON 파싱
String categoriesJson = CommonUtils.checkNull(estimateTemplate.get("categories_json"));
if("".equals(categoriesJson)) categoriesJson = CommonUtils.checkNull(estimateTemplate.get("CATEGORIES_JSON"));
if(!"".equals(categoriesJson)) {
try {
Gson gson = new Gson();
List<Map> categories = gson.fromJson(categoriesJson, List.class);
// CNC Machine 카테고리의 items 추출
for(Map category : categories) {
String categoryName = CommonUtils.checkNull(category.get("category"));
if("cnc_machine".equals(categoryName)) {
List<Map> items = (List<Map>) category.get("items");
if(items != null && !items.isEmpty()) {
estimateItems.addAll(items);
}
break;
}
}
} catch(Exception e) {
System.out.println("CATEGORIES_JSON 파싱 오류: " + e.getMessage());
e.printStackTrace();
}
}
System.out.println("================================");
} else {
// 일반 견적서 (Template 1): ESTIMATE_TEMPLATE_ITEM 테이블 조회
Map itemParam = new HashMap();
itemParam.put("templateObjId", templateObjId);
estimateItems = sqlSession.selectList("contractMgmt.getEstimateTemplateItemsByTemplateObjId", itemParam);
}
System.out.println("===== Estimate Items Debug =====");
System.out.println("Template Type: " + templateType);
System.out.println("Items count: " + (estimateItems != null ? estimateItems.size() : 0));
if(estimateItems != null && !estimateItems.isEmpty()){
for(int i = 0; i < Math.min(3, estimateItems.size()); i++){
Map item = estimateItems.get(i);
System.out.println("Item " + (i+1) + " keys: " + item.keySet());
}
}
System.out.println("================================");
// 4. 메일 제목 생성 (CONTRACT_OBJID 포함)
// 4. 메일 제목 생성
String contractNo = CommonUtils.checkNull(contractInfo.get("contract_no"));
String customerName = CommonUtils.checkNull(contractInfo.get("customer_name"));
String subject = "[" + customerName + "] " + contractNo + " 견적서 [OBJID:" + objId + "]";
String subject = "[" + customerName + "] " + contractNo + " 견적서";
// mail_log 조회용 제목 (OBJID 포함)
String subjectForLog = subject + " [OBJID:" + objId + "]";
// 5. 메일 내용 생성 (간단한 텍스트 버전)
String contents = makeEstimateMailContents(contractInfo, estimateTemplate, estimateItems);
@@ -1600,7 +1887,8 @@ public class ContractMgmtService {
System.out.println("From UserId: " + fromUserId);
System.out.println("To Email: " + toEmailList);
System.out.println("CC Email: " + ccEmailList);
System.out.println("Subject: " + subject);
System.out.println("Subject (실제 메일): " + subject);
System.out.println("Subject (mail_log): " + subjectForLog);
System.out.println("첨부파일 개수: " + attachFileList.size());
System.out.println("========================");
@@ -1615,10 +1903,11 @@ public class ContractMgmtService {
ccEmailList,
new ArrayList<String>(), // bccEmailList (빈 리스트)
null, // important
subject,
subject, // 실제 메일 제목 (OBJID 없음)
contents,
attachFileList.size() > 0 ? attachFileList : null, // PDF 첨부
"CONTRACT_ESTIMATE"
"CONTRACT_ESTIMATE",
subjectForLog // mail_log용 제목 (OBJID 포함)
);
System.out.println("메일 발송 결과: " + mailSent);
@@ -1986,6 +2275,32 @@ private String encodeImageToBase64(String imagePath) {
return resultMap != null ? resultMap : new HashMap<String, Object>();
}
/**
* 고객사 정보 조회
*/
public Map<String, Object> getCustomerInfo(String customerObjId) {
Map<String, Object> resultMap = new HashMap<String, Object>();
SqlSession sqlSession = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("objId", customerObjId);
resultMap = (Map<String, Object>) sqlSession.selectOne("common.getSupplyInfo", paramMap);
} catch(Exception e) {
e.printStackTrace();
} finally {
if(sqlSession != null) {
sqlSession.close();
}
}
return resultMap != null ? resultMap : new HashMap<String, Object>();
}
/**
* 수주정보 조회 (영업정보와 동일)
* @param objId
@@ -2029,9 +2344,76 @@ private String encodeImageToBase64(String imagePath) {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
items = sqlSession.selectList("contractMgmt.getContractItems", paramMap);
System.out.println("=== getContractItems 디버깅 시작 ===");
System.out.println("조회된 품목 수: " + (items != null ? items.size() : 0));
// ORDER_* 정보가 없으면 견적서에서 가져오기
if(items != null && !items.isEmpty()) {
for(Map item : items) {
// ORDER_QUANTITY가 없거나 0이면 견적서에서 가져오기
Object orderQty = item.get("order_quantity");
System.out.println("품목 PART_NO: " + item.get("part_no") + ", ORDER_QUANTITY: " + orderQty);
if(orderQty == null || "".equals(orderQty.toString().trim()) || "0".equals(orderQty.toString().trim())) {
System.out.println("ORDER_QUANTITY가 비어있음 - 견적서에서 조회 시작");
// 최종 견적서 조회
Map estimateParam = new HashMap();
estimateParam.put("objId", paramMap.get("contractObjId"));
Map latestEstimate = sqlSession.selectOne("contractMgmt.getLatestEstimateTemplate", estimateParam);
System.out.println("최종 견적서 조회 결과: " + (latestEstimate != null ? "있음" : "없음"));
if(latestEstimate != null) {
// 일반 견적서와 장비 견적서 모두 ESTIMATE_TEMPLATE_ITEM에서 조회
Map itemParam = new HashMap();
itemParam.put("templateObjId", latestEstimate.get("OBJID"));
itemParam.put("partObjId", item.get("part_objid"));
System.out.println("견적서 품목 조회 - templateObjId: " + latestEstimate.get("OBJID") + ", partObjId: " + item.get("part_objid"));
Map estimateItem = sqlSession.selectOne("contractMgmt.getEstimateTemplateItemByPartObjId", itemParam);
System.out.println("견적서 품목 조회 결과: " + (estimateItem != null ? "있음" : "없음"));
if(estimateItem != null) {
System.out.println("견적서 품목 - quantity: " + estimateItem.get("quantity") + ", unit_price: " + estimateItem.get("unit_price"));
// 견적서의 수량, 단가 정보를 ORDER_* 필드에 매핑
item.put("order_quantity", estimateItem.get("quantity"));
item.put("order_unit_price", estimateItem.get("unit_price"));
// 공급가액 계산
Object quantity = estimateItem.get("quantity");
Object unitPrice = estimateItem.get("unit_price");
long supplyPrice = 0;
if(quantity != null && unitPrice != null) {
try {
long qty = Long.parseLong(quantity.toString().replaceAll("[^0-9]", ""));
long price = Long.parseLong(unitPrice.toString().replaceAll("[^0-9]", ""));
supplyPrice = qty * price;
} catch(Exception e) {
System.out.println("금액 계산 실패: " + e.getMessage());
}
}
item.put("order_supply_price", supplyPrice);
item.put("order_vat", Math.round(supplyPrice * 0.1));
item.put("order_total_amount", supplyPrice + Math.round(supplyPrice * 0.1));
System.out.println("계산 완료 - 공급가액: " + supplyPrice + ", 부가세: " + Math.round(supplyPrice * 0.1));
}
}
}
}
}
// 대문자 변환
items = CommonUtils.keyChangeUpperList(items);
System.out.println("=== getContractItems 디버깅 종료 ===");
}catch(Exception e){
e.printStackTrace();
}finally{
@@ -2041,9 +2423,49 @@ private String encodeImageToBase64(String imagePath) {
return items;
}
/**
* 주문서관리 Total 합계 조회
* @param request
* @param paramMap - 검색 조건
* @return Map - TOTAL_SUPPLY_PRICE, TOTAL_VAT, TOTAL_AMOUNT
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public Map<String, Object> getOrderTotalAmount(HttpServletRequest request, Map<String, Object> paramMap){
SqlSession sqlSession = null;
Map<String, Object> resultMap = new HashMap<String, Object>();
try{
sqlSession = SqlMapConfig.getInstance().getSqlSession();
resultMap = sqlSession.selectOne("contractMgmt.getOrderTotalAmount", paramMap);
// 대문자 변환
if(resultMap != null) {
resultMap = CommonUtils.keyChangeUpperMap(resultMap);
} else {
// 데이터가 없을 경우 기본값 설정
resultMap = new HashMap<String, Object>();
resultMap.put("TOTAL_SUPPLY_PRICE", 0);
resultMap.put("TOTAL_VAT", 0);
resultMap.put("TOTAL_AMOUNT", 0);
}
}catch(Exception e){
e.printStackTrace();
}finally{
if(sqlSession != null) sqlSession.close();
}
return resultMap;
}
/**
* 견적서 템플릿 품목 조회 (수주등록용)
* 최종 견적서의 품목 정보를 수주등록 형식으로 변환하여 반환
*
* 참고: 이 메서드는 사용되지 않음. getContractItems()를 사용하세요.
* 일반 견적서와 장비 견적서 모두 ESTIMATE_TEMPLATE_ITEM에 저장되므로
* 동일한 방식으로 처리 가능
*
* @param paramMap - contractObjId
* @return
*/
@@ -2058,15 +2480,17 @@ private String encodeImageToBase64(String imagePath) {
// 1. 최종 견적서 템플릿 조회
Map templateParam = new HashMap();
templateParam.put("objId", paramMap.get("contractObjId"));
Map template = sqlSession.selectOne("contractMgmt.getEstimateTemplateData", templateParam);
Map template = sqlSession.selectOne("contractMgmt.getLatestEstimateTemplate", templateParam);
if(template != null) {
// 2. 견적서 템플릿의 품목 조회
template = CommonUtils.toUpperCaseMapKey(template);
// 일반 견적서와 장비 견적서 모두 ESTIMATE_TEMPLATE_ITEM에서 조회
Map itemParam = new HashMap();
itemParam.put("templateObjId", template.get("objid"));
itemParam.put("templateObjId", template.get("OBJID"));
List<Map> templateItems = sqlSession.selectList("contractMgmt.getEstimateTemplateItemsByTemplateObjId", itemParam);
// 3. 수주등록 형식으로 변환
// 수주등록 형식으로 변환
for(Map templateItem : templateItems) {
Map orderItem = new HashMap();
@@ -2668,4 +3092,182 @@ private String encodeImageToBase64(String imagePath) {
return null;
}
}
/**
* 계약 정보 조회 (메일 발송용)
* @param paramMap - objId (CONTRACT_OBJID)
* @return
*/
public Map getContractInfoForMail(Map paramMap) {
SqlSession sqlSession = null;
Map contractInfo = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
contractInfo = (Map) sqlSession.selectOne("contractMgmt.getContractInfoForMail", paramMap);
} catch(Exception e) {
e.printStackTrace();
} finally {
if(sqlSession != null) sqlSession.close();
}
return contractInfo;
}
/**
* 견적서 메일 발송 (커스텀)
* @param request
* @param paramMap - objId, pdfSessionId, toEmails, ccEmails, subject, contents
* @return
*/
public Map sendEstimateMailCustom(HttpServletRequest request, Map paramMap) {
Map resultMap = new HashMap();
SqlSession sqlSession = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
String objId = CommonUtils.checkNull(paramMap.get("objId"));
String toEmailsStr = CommonUtils.checkNull(paramMap.get("toEmails"));
String ccEmailsStr = CommonUtils.checkNull(paramMap.get("ccEmails"));
String subject = CommonUtils.checkNull(paramMap.get("subject"));
String contents = CommonUtils.checkNull(paramMap.get("contents"));
String pdfSessionId = CommonUtils.checkNull(paramMap.get("pdfSessionId"));
// 1. 계약 정보 조회
Map contractInfo = (Map) sqlSession.selectOne("contractMgmt.getContractInfoForMail", paramMap);
if(contractInfo == null || contractInfo.isEmpty()) {
resultMap.put("result", "error");
resultMap.put("message", "계약 정보를 찾을 수 없습니다.");
return resultMap;
}
// 2. 최종 차수 견적서 조회
Map estimateTemplate = (Map) sqlSession.selectOne("contractMgmt.getLatestEstimateTemplate", paramMap);
if(estimateTemplate == null || estimateTemplate.isEmpty()) {
resultMap.put("result", "error");
resultMap.put("message", "견적서를 찾을 수 없습니다.");
return resultMap;
}
// 3. 수신인 이메일 리스트 생성
ArrayList<String> toEmailList = new ArrayList<String>();
if(!"".equals(toEmailsStr)) {
String[] toEmails = toEmailsStr.split(",");
for(String email : toEmails) {
String trimmedEmail = email.trim();
if(!"".equals(trimmedEmail)) {
toEmailList.add(trimmedEmail);
}
}
}
if(toEmailList.isEmpty()) {
resultMap.put("result", "error");
resultMap.put("message", "수신인 이메일이 없습니다.");
return resultMap;
}
// 4. 참조 이메일 리스트 생성
ArrayList<String> ccEmailList = new ArrayList<String>();
// 작성자 이메일 자동 추가
String writerEmail = CommonUtils.checkNull(contractInfo.get("writer_email"));
if("".equals(writerEmail)) writerEmail = CommonUtils.checkNull(contractInfo.get("WRITER_EMAIL"));
if(!"".equals(writerEmail)) {
ccEmailList.add(writerEmail);
}
// 사용자가 입력한 참조 이메일 추가
if(!"".equals(ccEmailsStr)) {
String[] ccEmails = ccEmailsStr.split(",");
for(String email : ccEmails) {
String trimmedEmail = email.trim();
if(!"".equals(trimmedEmail) && !ccEmailList.contains(trimmedEmail)) {
ccEmailList.add(trimmedEmail);
}
}
}
// 5. PDF 파일 처리
ArrayList<HashMap> attachFileList = new ArrayList<HashMap>();
if(!"".equals(pdfSessionId)) {
File pdfFile = getPdfFromSession(pdfSessionId, estimateTemplate);
if(pdfFile != null && pdfFile.exists()) {
HashMap<String, String> fileMap = new HashMap<String, String>();
fileMap.put(Constants.Db.COL_FILE_REAL_NAME, pdfFile.getName());
fileMap.put(Constants.Db.COL_FILE_SAVED_NAME, pdfFile.getName());
fileMap.put(Constants.Db.COL_FILE_PATH, pdfFile.getParent());
attachFileList.add(fileMap);
System.out.println("PDF 파일 첨부 완료: " + pdfFile.getAbsolutePath());
} else {
System.out.println("PDF 파일을 찾을 수 없습니다: " + pdfSessionId);
}
}
// 6. 메일 발송
PersonBean person = (PersonBean)request.getSession().getAttribute(Constants.PERSON_BEAN);
String fromUserId = person.getUserId();
// mail_log 조회용 제목 (OBJID 포함)
String subjectForLog = subject + " [OBJID:" + objId + "]";
System.out.println("===== 커스텀 메일 발송 시도 =====");
System.out.println("From UserId: " + fromUserId);
System.out.println("To Email: " + toEmailList);
System.out.println("CC Email: " + ccEmailList);
System.out.println("Subject (실제 메일): " + subject);
System.out.println("Subject (mail_log): " + subjectForLog);
System.out.println("첨부파일 개수: " + attachFileList.size());
System.out.println("================================");
// HTML 형식으로 내용 변환 (줄바꿈 처리)
String htmlContents = contents.replace("\n", "<br/>");
boolean mailSent = false;
try {
mailSent = MailUtil.sendMailWithAttachFileUTF8(
fromUserId,
null, // fromEmail (자동으로 SMTP 설정 사용)
new ArrayList<String>(), // toUserIdList (빈 리스트)
toEmailList,
ccEmailList,
new ArrayList<String>(), // bccEmailList (빈 리스트)
null, // important
subject, // 실제 메일 제목 (OBJID 없음)
htmlContents,
attachFileList.size() > 0 ? attachFileList : null,
"CONTRACT_ESTIMATE",
subjectForLog // mail_log용 제목 (OBJID 포함)
);
System.out.println("메일 발송 결과: " + mailSent);
} catch(Exception mailEx) {
System.out.println("메일 발송 중 예외 발생: " + mailEx.getMessage());
mailEx.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", "메일 발송 중 예외가 발생했습니다: " + mailEx.getMessage());
return resultMap;
}
if(mailSent) {
resultMap.put("result", "success");
resultMap.put("message", "견적서가 성공적으로 발송되었습니다.");
} else {
resultMap.put("result", "error");
resultMap.put("message", "메일 발송에 실패했습니다.");
}
} catch(Exception e) {
resultMap.put("result", "error");
resultMap.put("message", "메일 발송 중 오류가 발생했습니다: " + e.getMessage());
e.printStackTrace();
} finally {
if(sqlSession != null) sqlSession.close();
}
return resultMap;
}
}

View File

@@ -308,10 +308,39 @@ public class SalesNcollectMgmtService {
}
}
return CommonUtils.toUpperCaseMapKey(resultMap);
return CommonUtils.toUpperCaseMapKey(resultMap);
}
/**
* 프로젝트의 모든 품목 조회 - 주석처리: 품목은 하나만 존재
*/
/*
public List<Map<String, Object>> getProjectItems(Map<String, Object> paramMap) {
List<Map<String, Object>> resultList = new ArrayList<Map<String, Object>>();
SqlSession sqlSession = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
resultList = sqlSession.selectList("salesNcollectMgmt.getProjectItems", paramMap);
} catch(Exception e) {
e.printStackTrace();
} finally {
if(sqlSession != null) {
sqlSession.close();
}
}
public Map<String, Object> saveSaleRegistration(HttpServletRequest request, Map<String, Object> paramMap) {
// 대문자 키로 변환
List<Map<String, Object>> upperCaseList = new ArrayList<Map<String, Object>>();
for(Map<String, Object> item : resultList) {
upperCaseList.add(CommonUtils.toUpperCaseMapKey(item));
}
return upperCaseList;
}
*/
public Map<String, Object> saveSaleRegistration(HttpServletRequest request, Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<String, Object>();
SqlSession sqlSession = null;
@@ -321,18 +350,87 @@ public class SalesNcollectMgmtService {
PersonBean person = (PersonBean) request.getSession().getAttribute(Constants.PERSON_BEAN);
paramMap.put("cretEmpNo", person.getUserId());
// sales_registration 테이블에 판매 데이터 저장 (ON CONFLICT로 자동 UPDATE)
String projectNo = (String) paramMap.get("orderNo");
System.out.println("=== saveSaleRegistration 시작 ===");
System.out.println("projectNo: " + projectNo);
System.out.println("salesQuantity: " + paramMap.get("salesQuantity"));
// 기존 판매 데이터가 있는지 확인
Map<String, Object> checkParam = new HashMap<String, Object>();
checkParam.put("orderNo", projectNo);
Map<String, Object> existingSale = sqlSession.selectOne("salesNcollectMgmt.getSaleInfo", checkParam);
System.out.println("existingSale: " + (existingSale != null ? "있음" : "없음"));
// SALE_NO가 null이면 첫 판매, 있으면 분할 출하
Object saleNoObj = existingSale != null ? existingSale.get("SALE_NO") : null;
if(saleNoObj == null) saleNoObj = existingSale != null ? existingSale.get("sale_no") : null;
System.out.println("SALE_NO 확인: " + saleNoObj);
if(saleNoObj == null) {
// 첫 판매 등록: sales_registration에 INSERT
System.out.println("첫 판매 등록 - sales_registration에 INSERT");
sqlSession.insert("salesNcollectMgmt.insertSaleRegistration", paramMap);
} else {
// 분할 출하: shipment_log에 INSERT
System.out.println("분할 출하 - shipment_log에 INSERT");
// existingSale은 이미 대문자 키로 변환되어 있음 (CommonUtils.toUpperCaseMapKey)
// 하지만 getSaleInfo에서는 변환 안 함 - 직접 확인
Object orderQtyObj = existingSale.get("ORDER_QUANTITY");
if(orderQtyObj == null) orderQtyObj = existingSale.get("order_quantity");
// saleNoObj는 이미 위에서 선언됨 - 재사용
// Object saleNoObj = existingSale.get("SALE_NO"); // 중복 제거
// if(saleNoObj == null) saleNoObj = existingSale.get("sale_no"); // 이미 위에서 처리
System.out.println("ORDER_QUANTITY: " + orderQtyObj);
System.out.println("SALE_NO: " + saleNoObj);
if(orderQtyObj == null || saleNoObj == null) {
System.out.println("=== existingSale 전체 내용 ===");
for(Object key : existingSale.keySet()) {
System.out.println(key + ": " + existingSale.get(key));
}
throw new RuntimeException("ORDER_QUANTITY 또는 SALE_NO가 null입니다");
}
paramMap.put("targetObjid", projectNo);
paramMap.put("originalQuantity", orderQtyObj);
// 잔량 계산
int orderQuantity = Integer.parseInt(String.valueOf(orderQtyObj).split("\\.")[0]);
int salesQuantity = Integer.parseInt(String.valueOf(paramMap.get("salesQuantity")));
int remainingQuantity = orderQuantity - salesQuantity;
System.out.println("orderQuantity: " + orderQuantity);
System.out.println("salesQuantity: " + salesQuantity);
System.out.println("remainingQuantity: " + remainingQuantity);
paramMap.put("remainingQuantity", remainingQuantity);
paramMap.put("parentSaleNo", saleNoObj);
System.out.println("shipment_log INSERT 직전 파라미터:");
System.out.println(" targetObjid: " + paramMap.get("targetObjid"));
System.out.println(" parentSaleNo: " + paramMap.get("parentSaleNo"));
System.out.println(" cretEmpNo: " + paramMap.get("cretEmpNo"));
sqlSession.insert("salesNcollectMgmt.insertShipmentLog", paramMap);
}
sqlSession.commit();
resultMap.put("result", true);
resultMap.put("msg", "저장되었습니다.");
System.out.println("=== 저장 성공 ===");
} catch(Exception e) {
if(sqlSession != null) {
sqlSession.rollback();
}
resultMap.put("result", false);
resultMap.put("msg", "저장 중 오류가 발생했습니다.");
System.out.println("=== 저장 실패 ===");
System.out.println("에러 메시지: " + e.getMessage());
e.printStackTrace();
} finally {
if(sqlSession != null) {
@@ -918,4 +1016,133 @@ public class SalesNcollectMgmtService {
}
return resultMap;
}
/**
* 모든 분할 출하의 총 판매 수량 조회
* @param paramMap
* @return Map
*/
public Map<String, Object> getTotalSalesQuantity(Map<String, Object> paramMap) {
SqlSession sqlSession = null;
Map<String, Object> result = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
result = sqlSession.selectOne("salesNcollectMgmt.getTotalSalesQuantity", paramMap);
} catch(Exception e) {
e.printStackTrace();
} finally {
if(sqlSession != null) {
sqlSession.close();
}
}
return result;
}
/**
* 출하일 상세 내역 조회
* @param projectNo
* @return List
*/
public List<Map<String, Object>> getShippingDetailList(String projectNo) {
SqlSession sqlSession = null;
List<Map<String, Object>> resultList = new ArrayList<Map<String, Object>>();
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("projectNo", projectNo);
resultList = sqlSession.selectList("salesNcollectMgmt.getShippingDetailList", paramMap);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
return resultList;
}
/**
* 거래명세서 데이터 조회
* @param paramMap - projectNos (쉼표로 구분된 프로젝트 번호들)
* @return Map
*/
public Map<String, Object> getTransactionStatementData(Map<String, Object> paramMap) {
SqlSession sqlSession = null;
Map<String, Object> resultMap = new HashMap<String, Object>();
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
String projectNos = (String) paramMap.get("projectNos");
System.out.println("=== getTransactionStatementData 시작 ===");
System.out.println("projectNos: " + projectNos);
String[] projectNoArray = projectNos.split(",");
// 첫 번째 프로젝트의 고객 정보 조회
Map<String, Object> firstProjectParam = new HashMap<String, Object>();
firstProjectParam.put("projectNo", projectNoArray[0].trim());
System.out.println("고객 정보 조회 - projectNo: " + projectNoArray[0].trim());
Map<String, Object> customerInfo = sqlSession.selectOne("salesNcollectMgmt.getCustomerInfoByProjectNo", firstProjectParam);
System.out.println("고객 정보 조회 결과: " + customerInfo);
if(customerInfo != null) {
resultMap.put("customerName", customerInfo.get("CUSTOMER_NAME"));
resultMap.put("customerRegNo", customerInfo.get("CUSTOMER_REG_NO"));
resultMap.put("customerAddress", customerInfo.get("CUSTOMER_ADDRESS"));
resultMap.put("customerBusiness", customerInfo.get("CUSTOMER_BUSINESS"));
resultMap.put("customerType", customerInfo.get("CUSTOMER_TYPE"));
resultMap.put("customerContact", customerInfo.get("CUSTOMER_CONTACT"));
System.out.println("고객명: " + customerInfo.get("CUSTOMER_NAME"));
} else {
System.out.println("고객 정보가 null입니다!");
}
// 공급자 정보 (회사 정보)
resultMap.put("supplierName", "㈜압피에스");
resultMap.put("supplierRegNo", "314-81-75146");
resultMap.put("supplierAddress", "대전광역시 유성구 국제과학10로 8");
resultMap.put("supplierBusiness", "제조");
resultMap.put("supplierType", "금속절삭가공기계의");
resultMap.put("supplierContact", "TEL:042-602-3300/FAX:042-672");
// 품목 정보 조회
List<Map<String, Object>> items = new ArrayList<Map<String, Object>>();
for(String projectNo : projectNoArray) {
Map<String, Object> itemParam = new HashMap<String, Object>();
itemParam.put("projectNo", projectNo.trim());
System.out.println("품목 정보 조회 - projectNo: " + projectNo.trim());
Map<String, Object> item = sqlSession.selectOne("salesNcollectMgmt.getTransactionStatementItem", itemParam);
System.out.println("품목 정보 조회 결과: " + item);
if(item != null) {
items.add(item);
}
}
resultMap.put("items", items);
System.out.println("총 품목 개수: " + items.size());
// 비고
resultMap.put("note", "아래와 같이 공급합니다.");
System.out.println("=== resultMap 최종 ===");
System.out.println("customerName: " + resultMap.get("customerName"));
System.out.println("items size: " + ((List)resultMap.get("items")).size());
System.out.println("supplierName: " + resultMap.get("supplierName"));
} catch (Exception e) {
System.out.println("=== 에러 발생 ===");
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
return resultMap;
}
}