Compare commits

...

37 Commits

Author SHA1 Message Date
leeheejin
e21c83bf06 auto commit 2025-11-17 18:15:25 +09:00
leeheejin
0b5b6a0d3f auto commit 2025-11-17 17:52:03 +09:00
leeheejin
fd4283f3cf auto commit 2025-11-17 17:29:04 +09:00
leeheejin
c1921855e6 봄 복사 기능 러프하게 구현(테스트 아직 못함) 결과 일단 저장 2025-11-17 10:03:29 +09:00
eff22bbc4c Merge pull request 'feature/20251113-hj' (#66) from feature/20251113-hj into main
Reviewed-on: #66
2025-11-14 00:52:05 +00:00
5a582862d6 Merge branch 'main' into feature/20251113-hj 2025-11-14 09:51:05 +09:00
leeheejin
61dbf15b0f 생산관리 ui 조금이랑 수정한 부분 2025-11-14 09:50:13 +09:00
789deac5da 다중 선택 삭제 시 하나만 삭제되는 오류 수정 2025-11-14 09:35:19 +09:00
leeheejin
1f2bdee683 Merge branch 'main' of https://g.wace.me/chpark/wace_plm into V2025111104 2025-11-14 09:16:24 +09:00
leeheejin
66968a4463 거래명세서 완료 2025-11-14 09:15:11 +09:00
ddcaf60e5e Merge pull request '거래명세서 수정' (#65) from V2025111104 into main
Reviewed-on: #65
2025-11-13 08:53:03 +00:00
leeheejin
f2627d9683 거래명세서 수정 2025-11-13 17:52:46 +09:00
514c360a78 Merge pull request 'feature/estimate-template-improvements' (#64) from feature/estimate-template-improvements into main
Reviewed-on: #64
2025-11-13 08:51:46 +00:00
leeheejin
08f5ba1cdb 엑셀다운로드도 출하일 외 여러건 2025-11-13 17:40:11 +09:00
leeheejin
6432bab110 출하일 여러개일때 외 여러건 표시 2025-11-13 17:37:17 +09:00
leeheejin
67f694be65 거래명세서 수정 2025-11-13 17:26:28 +09:00
leeheejin
7195dfb5f1 커밋 2025-11-13 17:00:31 +09:00
leeheejin
c2f2350b83 사라지기전에 2025-11-13 16:52:18 +09:00
leeheejin
849df44333 컬럼추가들 2025-11-13 15:00:02 +09:00
leeheejin
cf5806add5 거래명세서 완료 2025-11-13 14:35:13 +09:00
leeheejin
b567dedcaa 날짜수정 잘 들어감 2025-11-13 13:56:10 +09:00
leeheejin
56aa05513c 거래명세서커밋 2025-11-13 13:44:01 +09:00
leeheejin
258ed89c84 프로젝트번호 빈배열오류해결 2025-11-13 11:07:30 +09:00
leeheejin
32deed0726 Merge branch 'main' of https://g.wace.me/chpark/wace_plm into V2025111104 2025-11-13 10:59:59 +09:00
leeheejin
76aeae96fc 잔량 거래명세서데이터베이스저장완료 출하일눌렀을대 보임 2025-11-13 10:58:12 +09:00
leeheejin
f9a6dd145a 커밋밋 2025-11-13 10:04:01 +09:00
8e2092976c Merge pull request 'ㅇㅇ' (#63) from feature/estimate-template-improvements into main
Reviewed-on: #63
2025-11-13 00:57:30 +00:00
leeheejin
5d60957bca 일단저장 2025-11-13 09:55:36 +09:00
leeheejin
27daa36137 Merge branch 'V2025111104' 2025-11-12 18:56:54 +09:00
leeheejin
c394900a83 auto commit 2025-11-12 18:56:43 +09:00
78281c5b86 Merge pull request 'fix: Use official Python image for backup container' (#62) from feature/estimate-template-improvements into main
Reviewed-on: #62
2025-11-12 09:48:48 +00: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
68dc1f096a Merge pull request 'feature/estimate-template-improvements' (#61) from feature/estimate-template-improvements into main
Reviewed-on: #61
2025-11-12 09:21:29 +00: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
42 changed files with 8308 additions and 820 deletions

View File

@@ -1,4 +1,4 @@
FROM dockerhub.wace.me/tomcat:7.0.94-jre7-alpine.arm64 AS Development
FROM dockerhub.wace.me/tomcat:7.0.94-jre8-alpine.arm64 AS Development
# Remove default webapps
RUN rm -rf /usr/local/tomcat/webapps/*

View File

@@ -2530,18 +2530,18 @@ SELECT option_objid::VARCHAR AS CODE
AND PRODUCT_MGMT_OBJID::VARCHAR = #{codeId}::VARCHAR
</select>
<select id="getRevNoselect" parameterType="map" resultType="map">
SELECT
T.OBJID::varchar AS CODE
,T.REV AS NAME
,T1.PRODUCT_CODE AS CODE_CD
,'' AS STATUS
,T.spec_name AS ID
,'' AS EXT_VAL
FROM PART_BOM_REPORT T,PRODUCT_MGMT T1
WHERE T.PRODUCT_MGMT_OBJID = T1.OBJID
AND T1.OBJID = #{code}::numeric
ORDER BY T.REV
<select id="getRevNoselect" parameterType="map" resultType="map">
SELECT
T.OBJID::varchar AS CODE
,COALESCE(T.REVISION, T.REV, '1.0') AS NAME
,T1.PRODUCT_CODE AS CODE_CD
,'' AS STATUS
,T.spec_name AS ID
,'' AS EXT_VAL
FROM PART_BOM_REPORT T,PRODUCT_MGMT T1
WHERE T.PRODUCT_MGMT_OBJID = T1.OBJID
AND T1.OBJID = #{code}::numeric
ORDER BY COALESCE(T.REVISION, T.REV, '1.0')
</select>
@@ -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

@@ -2870,8 +2870,9 @@
ORDER BY SUBSTRING(PROJECT_NO,POSITION('-' IN PROJECT_NO)+1) DESC
</select>
<!-- M-BOM 관리 목록 조회 -->
<!-- M-BOM 관리 목록 조회 - 품목별로 나눠서 보이기 (판매관리와 동일) -->
<select id="mBomMgmtGridList" parameterType="map" resultType="com.pms.common.UpperKeyMap">
/* productionplanning.mBomMgmtGridList - 품목별로 나눠서 조회 (CONTRACT_ITEM 기준) */
SELECT
PM.OBJID,
PM.CONTRACT_OBJID,
@@ -2906,54 +2907,40 @@
ELSE ''
END
) AS PAID_TYPE_NAME,
COALESCE(PM.PART_NO, '') AS PART_NO,
COALESCE(PM.PART_NAME, '') AS PART_NAME,
-- S/N: CONTRACT_ITEM_SERIAL에서 조회
-- 품목 정보: CONTRACT_ITEM에서 가져오기 (품목별로 펼쳐서 보이기)
COALESCE(CI.PART_NO, PM.PART_NO, '') AS PART_NO,
COALESCE(CI.PART_NAME, PM.PART_NAME, '') AS PART_NAME,
CI.PART_OBJID,
-- S/N: 해당 품목의 시리얼 번호만 표시
(SELECT
CASE
WHEN COUNT(*) = 0 THEN ''
WHEN COUNT(*) = 1 THEN MIN(CIS.SERIAL_NO)
ELSE MIN(CIS.SERIAL_NO) || ' 외 ' || (COUNT(*) - 1)::TEXT || '건'
END
FROM CONTRACT_ITEM CI
LEFT JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID AND UPPER(CIS.STATUS) = 'ACTIVE'
WHERE CI.CONTRACT_OBJID = PM.CONTRACT_OBJID
AND CI.PART_OBJID = PM.PART_OBJID
AND CI.STATUS = 'ACTIVE'
FROM CONTRACT_ITEM_SERIAL CIS
WHERE CIS.ITEM_OBJID = CI.OBJID
AND UPPER(CIS.STATUS) = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL) AS SERIAL_NO,
COALESCE(PM.QUANTITY::numeric, 0) AS QUANTITY,
-- 요청납기: CONTRACT_ITEM 우선, 없으면 PROJECT_MGMT, 없으면 CONTRACT_MGMT
COALESCE(
(SELECT CI.DUE_DATE
FROM CONTRACT_ITEM CI
WHERE CI.CONTRACT_OBJID = PM.CONTRACT_OBJID
AND CI.PART_OBJID = PM.PART_OBJID
AND CI.STATUS = 'ACTIVE' LIMIT 1),
PM.DUE_DATE,
CM.req_del_date
) AS REQ_DEL_DATE,
-- 수량: CONTRACT_ITEM의 수량
COALESCE(CI.ORDER_QUANTITY::numeric, PM.QUANTITY::numeric, 0) AS QUANTITY,
-- 요청납기: CONTRACT_ITEM 우선
COALESCE(CI.DUE_DATE, PM.DUE_DATE, CM.req_del_date) AS REQ_DEL_DATE,
-- 고객요청사항: CONTRACT_ITEM에서 가져옴
COALESCE(
(SELECT CI.CUSTOMER_REQUEST
FROM CONTRACT_ITEM CI
WHERE CI.CONTRACT_OBJID = PM.CONTRACT_OBJID
AND CI.PART_OBJID = PM.PART_OBJID
AND CI.STATUS = 'ACTIVE' LIMIT 1),
''
) AS CUSTOMER_REQUEST,
-- E-BOM 정보: PM.PART_OBJID가 E-BOM OBJID를 직접 가리킴
PM.PART_OBJID AS BOM_REPORT_OBJID,
COALESCE(CI.CUSTOMER_REQUEST, '') AS CUSTOMER_REQUEST,
-- E-BOM 정보: CI.PART_OBJID가 E-BOM OBJID를 가리킴
COALESCE(CI.PART_OBJID, PM.PART_OBJID) AS BOM_REPORT_OBJID,
COALESCE(
(SELECT PBR.STATUS
FROM PART_BOM_REPORT PBR
WHERE PBR.OBJID::VARCHAR = PM.PART_OBJID
WHERE PBR.OBJID::VARCHAR = COALESCE(CI.PART_OBJID, PM.PART_OBJID)
LIMIT 1),
''
) AS EBOM_STATUS,
COALESCE(
(SELECT TO_CHAR(PBR.REGDATE, 'YYYY-MM-DD')
FROM PART_BOM_REPORT PBR
WHERE PBR.OBJID::VARCHAR = PM.PART_OBJID
WHERE PBR.OBJID::VARCHAR = COALESCE(CI.PART_OBJID, PM.PART_OBJID)
LIMIT 1),
''
) AS EBOM_REGDATE,
@@ -2963,18 +2950,27 @@
FROM
PROJECT_MGMT PM
LEFT JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID
-- CONTRACT_ITEM과 LEFT JOIN하여 품목별로 펼쳐서 보이기
LEFT JOIN CONTRACT_ITEM CI ON CM.OBJID::VARCHAR = CI.CONTRACT_OBJID
AND CI.STATUS = 'ACTIVE'
WHERE 1=1
AND PM.PROJECT_NO IS NOT NULL
AND PM.PROJECT_NO != ''
<!-- 품번 검색 (대소문자 구분 없음) -->
<if test="search_part_no != null and search_part_no != ''">
AND UPPER(PM.PART_NO) LIKE '%' || UPPER(#{search_part_no}) || '%'
AND (
UPPER(PM.PART_NO) LIKE '%' || UPPER(#{search_part_no}) || '%'
OR UPPER(CI.PART_NO) LIKE '%' || UPPER(#{search_part_no}) || '%'
)
</if>
<!-- 품명 검색 (대소문자 구분 없음) -->
<if test="search_part_name != null and search_part_name != ''">
AND UPPER(PM.PART_NAME) LIKE '%' || UPPER(#{search_part_name}) || '%'
AND (
UPPER(PM.PART_NAME) LIKE '%' || UPPER(#{search_part_name}) || '%'
OR UPPER(CI.PART_NAME) LIKE '%' || UPPER(#{search_part_name}) || '%'
)
</if>
ORDER BY PM.REGDATE DESC
ORDER BY PM.REGDATE DESC, CI.PART_NO
</select>
<!-- E-BOM을 PROJECT_MGMT에 할당 -->
@@ -3003,4 +2999,299 @@
LEFT JOIN USER_INFO UI ON UI.USER_ID = T.WRITER
WHERE T.OBJID::VARCHAR = #{objid}
</select>
<!-- M-BOM 상세 조회 (PROJECT_MGMT + CONTRACT_MGMT 정보) -->
<select id="getProjectMgmtDetail" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
PM.OBJID,
PM.CONTRACT_OBJID,
PM.PROJECT_NO,
PM.BOM_REPORT_OBJID,
PM.PART_NO,
PM.PART_NAME,
CM.CATEGORY_CD,
COALESCE(
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CM.CATEGORY_CD LIMIT 1),
''
) AS CATEGORY_NAME,
CM.PRODUCT,
COALESCE(
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CM.PRODUCT LIMIT 1),
''
) AS PRODUCT_NAME,
CM.AREA_CD,
COALESCE(
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CM.AREA_CD LIMIT 1),
''
) AS AREA_NAME,
CM.CUSTOMER_OBJID,
COALESCE(
(SELECT SUPPLY_NAME FROM SUPPLY_MNG WHERE OBJID = CM.CUSTOMER_OBJID::NUMERIC LIMIT 1),
''
) AS CUSTOMER_NAME,
CM.PAID_TYPE,
CM.REQ_DEL_DATE,
TO_CHAR(PM.REGDATE, 'YYYY-MM-DD') AS RECEIPT_DATE,
TO_CHAR(PM.REGDATE, 'YYYY-MM-DD') AS MBOM_REGDATE
FROM
PROJECT_MGMT PM
INNER JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID
WHERE
PM.OBJID::VARCHAR = #{objId}
LIMIT 1
</select>
<!-- E-BOM 목록 조회 -->
<select id="getEbomList" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
T.OBJID,
T.PRODUCT_CD,
COALESCE(
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = T.PRODUCT_CD LIMIT 1),
''
) AS PRODUCT_NAME,
T.PART_NO,
T.PART_NAME,
T.STATUS,
T.REVISION,
TO_CHAR(T.REGDATE, 'YYYY-MM-DD') AS REG_DATE,
UI.USER_NAME AS WRITER_NAME,
UI.DEPT_NAME,
COALESCE(PM.MATERIAL, '') AS MATERIAL,
COALESCE(PM.MAKER, '') AS SUPPLIER
FROM
PART_BOM_REPORT T
LEFT JOIN USER_INFO UI ON UI.USER_ID = T.WRITER
LEFT JOIN PART_MNG PM ON PM.PART_NO = T.PART_NO AND PM.STATUS = 'release'
WHERE 1=1
<!-- 품번, 품명이 비어있지 않은 것만 조회 -->
AND T.PART_NO IS NOT NULL
AND TRIM(T.PART_NO) != ''
AND T.PART_NAME IS NOT NULL
AND TRIM(T.PART_NAME) != ''
<!-- 품번 검색 -->
<if test="search_part_no != null and search_part_no != ''">
AND UPPER(T.PART_NO) LIKE '%' || UPPER(#{search_part_no}) || '%'
</if>
<!-- 품명 검색 -->
<if test="search_part_name != null and search_part_name != ''">
AND UPPER(T.PART_NAME) LIKE '%' || UPPER(#{search_part_name}) || '%'
</if>
<!-- 재료 검색 -->
<if test="search_material != null and search_material != ''">
AND UPPER(PM.MATERIAL) LIKE '%' || UPPER(#{search_material}) || '%'
</if>
<!-- 공급업체 검색 -->
<if test="search_supplier != null and search_supplier != ''">
AND UPPER(PM.MAKER) LIKE '%' || UPPER(#{search_supplier}) || '%'
</if>
ORDER BY T.REGDATE DESC
LIMIT 100
</select>
<!-- M-BOM 목록 조회 -->
<select id="getMbomList" resultType="map" parameterType="map">
SELECT
MH.OBJID,
MH.MBOM_NO,
MH.PART_NO,
MH.PART_NAME,
MH.SAVE_DATE,
MH.CREATE_USER,
MH.CREATE_DATE,
MH.UPDATE_USER,
MH.UPDATE_DATE
FROM
MBOM_HEADER MH
WHERE 1=1
<!-- 품번 검색 -->
<if test="partNo != null and partNo != ''">
AND UPPER(MH.PART_NO) LIKE '%' || UPPER(#{partNo}) || '%'
</if>
<!-- 품명 검색 -->
<if test="partName != null and partName != ''">
AND UPPER(MH.PART_NAME) LIKE '%' || UPPER(#{partName}) || '%'
</if>
<!-- M-BOM 품번 검색 -->
<if test="mbomPartNo != null and mbomPartNo != ''">
AND UPPER(MH.MBOM_NO) LIKE '%' || UPPER(#{mbomPartNo}) || '%'
</if>
<!-- 저장일 검색 -->
<if test="saveDate != null and saveDate != ''">
AND DATE(MH.SAVE_DATE) = #{saveDate}
</if>
ORDER BY MH.SAVE_DATE DESC
LIMIT 100
</select>
<!-- M-BOM 상세 데이터 조회 (프로젝트별) -->
<select id="getMbomDetailByProject" resultType="map" parameterType="map">
SELECT
MD.OBJID,
MD.MBOM_HEADER_OBJID,
MD.PART_NO,
MD.PART_NAME,
MD.QTY,
MD.QTY_TEMP,
MD.ITEM_QTY,
MD.LEVEL,
MD.REVISION,
MD.SPEC,
MD.PRODUCT_NAME,
MD.STATUS_NAME,
MD.LEVEL_1,
MD.LEVEL_2,
MD.LEVEL_3,
MD.LEVEL_4,
MD.LEVEL_5,
MD.LEVEL_6,
MD.LEVEL_7,
MD.LEVEL_8,
MD.LEVEL_9,
MD.LEVEL_10
FROM
MBOM_DETAIL MD
INNER JOIN
MBOM_HEADER MH ON MD.MBOM_HEADER_OBJID = MH.OBJID
WHERE
MH.PROJECT_MGMT_OBJID = #{objId}
ORDER BY
MD.LEVEL, MD.PART_NO
</select>
<!-- M-BOM 헤더 저장 -->
<insert id="insertMbomHeader" parameterType="map">
<selectKey keyProperty="OBJID" resultType="string" order="BEFORE">
SELECT REPLACE(UUID(),'-','') FROM DUAL
</selectKey>
INSERT INTO MBOM_HEADER (
OBJID,
MBOM_NO,
PART_NO,
PART_NAME,
SAVE_DATE,
CREATE_USER,
CREATE_DATE,
UPDATE_USER,
UPDATE_DATE
) VALUES (
#{OBJID},
CONCAT('MBOM-', DATE_FORMAT(NOW(), '%Y%m%d%H%i%s')),
#{PART_NO},
#{PART_NAME},
NOW(),
#{CREATE_USER},
NOW(),
#{UPDATE_USER},
NOW()
)
</insert>
<!-- M-BOM 상세 저장 -->
<insert id="insertMbomDetail" parameterType="map">
<selectKey keyProperty="OBJID" resultType="string" order="BEFORE">
SELECT REPLACE(UUID(),'-','') FROM DUAL
</selectKey>
INSERT INTO MBOM_DETAIL (
OBJID,
MBOM_HEADER_OBJID,
PART_NO,
PART_NAME,
QTY,
LEVEL,
REVISION,
SPEC,
PRODUCT_NAME,
STATUS_NAME,
CREATE_USER,
CREATE_DATE,
UPDATE_USER,
UPDATE_DATE
) VALUES (
#{OBJID},
#{MBOM_HEADER_OBJID},
#{PART_NO},
#{PART_NAME},
#{QTY},
#{LEVEL},
#{REVISION},
#{SPEC},
#{PRODUCT_NAME},
#{STATUS_NAME},
#{CREATE_USER},
NOW(),
#{UPDATE_USER},
NOW()
)
</insert>
<!-- E-BOM을 M-BOM으로 복사 -->
<insert id="saveMbomFromEbom" parameterType="map">
/* productionplanning.saveMbomFromEbom - E-BOM을 M-BOM으로 복사 */
/* 1. 새로운 PART_BOM_REPORT 생성 (M-BOM용) */
WITH new_bom_report AS (
INSERT INTO PART_BOM_REPORT (
OBJID,
PART_NO,
PART_NAME,
WRITER,
REGDATE,
STATUS
) VALUES (
(SELECT (FLOOR(RANDOM() * 1000000000) + 1000000000)::INTEGER),
#{PART_NO},
#{PART_NAME},
#{USER_ID},
NOW(),
'Y'
)
RETURNING OBJID
),
/* 2. E-BOM의 BOM_PART_QTY 데이터를 새 M-BOM으로 복사 */
copied_bom_data AS (
INSERT INTO BOM_PART_QTY (
BOM_REPORT_OBJID,
OBJID,
PARENT_OBJID,
CHILD_OBJID,
PARENT_PART_NO,
PART_NO,
QTY,
ITEM_QTY,
QTY_TEMP,
REGDATE,
WRITER,
SEQ,
STATUS,
LAST_PART_OBJID
)
SELECT
(SELECT OBJID FROM new_bom_report),
(SELECT (FLOOR(RANDOM() * 1000000000) + 1000000000 + ROW_NUMBER() OVER (ORDER BY OBJID))::INTEGER),
PARENT_OBJID,
CHILD_OBJID,
PARENT_PART_NO,
PART_NO,
QTY,
ITEM_QTY,
QTY_TEMP,
NOW(),
#{USER_ID},
ROW_NUMBER() OVER (ORDER BY OBJID),
'adding',
LAST_PART_OBJID
FROM BOM_PART_QTY
WHERE BOM_REPORT_OBJID::VARCHAR = #{SOURCE_BOM_OBJID}
AND COALESCE(STATUS, '') NOT IN ('deleting', 'deleted')
RETURNING 1
)
/* 3. PROJECT_MGMT 업데이트 */
UPDATE PROJECT_MGMT
SET
BOM_REPORT_OBJID = (SELECT OBJID FROM new_bom_report),
MBOM_STATUS = 'Y',
PART_NO = #{PART_NO},
PART_NAME = #{PART_NAME}
WHERE OBJID::VARCHAR = #{PROJECT_OBJID}
</insert>
</mapper>

View File

@@ -7627,29 +7627,65 @@ SELECT
<!-- //영업정보 수정시 프로젝트 정보 업데이트 -->
<update id="ModifyProjectByContract" parameterType="map">
UPDATE PROJECT_MGMT
SET
DUE_DATE = #{due_date}
,CUSTOMER_PROJECT_NAME = #{customer_project_name}
,LOCATION = #{location}
,SETUP = #{setup}
,FACILITY = #{facility}
,FACILITY_TYPE = #{facility_type}
,FACILITY_DEPTH = #{facility_depth}
,CONTRACT_DATE = #{contract_date}
,PO_NO = #{po_no}
,PM_USER_ID = #{pm_user_id}
,CONTRACT_CURRENCY = #{contract_currency}
,CONTRACT_PRICE_CURRENCY = #{contract_price_currency}
,CONTRACT_PRICE = #{contract_price}
,PROJECT_NAME = #{project_name}
,CONTRACT_DEL_DATE = #{contract_del_date}
,REQ_DEL_DATE = #{req_del_date}
,CONTRACT_COMPANY = #{contract_company}
,MANUFACTURE_PLANT = #{manufacture_plant}
,PART_OBJID = #{part_objid}
,PART_NO = #{part_no}
,PART_NAME = #{part_name}
,QUANTITY = #{quantity}
<set>
<if test="due_date != null and due_date != ''">
DUE_DATE = #{due_date},
</if>
<if test="customer_project_name != null and customer_project_name != ''">
CUSTOMER_PROJECT_NAME = #{customer_project_name},
</if>
<if test="location != null and location != ''">
LOCATION = #{location},
</if>
<if test="setup != null and setup != ''">
SETUP = #{setup},
</if>
<if test="facility != null and facility != ''">
FACILITY = #{facility},
</if>
<if test="facility_type != null and facility_type != ''">
FACILITY_TYPE = #{facility_type},
</if>
<if test="facility_depth != null and facility_depth != ''">
FACILITY_DEPTH = #{facility_depth},
</if>
<if test="contract_date != null and contract_date != ''">
CONTRACT_DATE = #{contract_date},
</if>
<if test="po_no != null and po_no != ''">
PO_NO = #{po_no},
</if>
<if test="pm_user_id != null and pm_user_id != ''">
PM_USER_ID = #{pm_user_id},
</if>
<if test="contract_currency != null and contract_currency != ''">
CONTRACT_CURRENCY = #{contract_currency},
</if>
<if test="contract_price_currency != null and contract_price_currency != ''">
CONTRACT_PRICE_CURRENCY = #{contract_price_currency},
</if>
<if test="contract_price != null and contract_price != ''">
CONTRACT_PRICE = #{contract_price},
</if>
<if test="project_name != null and project_name != ''">
PROJECT_NAME = #{project_name},
</if>
<if test="contract_del_date != null and contract_del_date != ''">
CONTRACT_DEL_DATE = #{contract_del_date},
</if>
<if test="req_del_date != null and req_del_date != ''">
REQ_DEL_DATE = #{req_del_date},
</if>
<if test="contract_company != null and contract_company != ''">
CONTRACT_COMPANY = #{contract_company},
</if>
<if test="manufacture_plant != null and manufacture_plant != ''">
MANUFACTURE_PLANT = #{manufacture_plant},
</if>
<if test="quantity != null and quantity != ''">
QUANTITY = #{quantity}
</if>
</set>
WHERE CONTRACT_OBJID = #{objId}
AND PART_OBJID = #{part_objid}
</update>

View File

@@ -523,13 +523,20 @@
,A.APPROVAL_OBJID
,A.ROUTE_OBJID
,(SELECT objid FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = T.OBJID order by regdate desc limit 1) AS EST_OBJID
-- 최근 차수 견적서 합계 정보
,(SELECT TOTAL_AMOUNT FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = T.OBJID ORDER BY REGDATE DESC LIMIT 1) AS EST_TOTAL_AMOUNT
,(SELECT TOTAL_AMOUNT_KRW FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = T.OBJID ORDER BY REGDATE DESC LIMIT 1) AS EST_TOTAL_AMOUNT_KRW
-- 수주 합계 정보 (CONTRACT_MGMT 테이블에 저장된 값 사용)
,T.ORDER_SUPPLY_PRICE AS ORDER_SUPPLY_PRICE_SUM
,T.ORDER_VAT AS ORDER_VAT_SUM
,T.ORDER_TOTAL_AMOUNT AS ORDER_TOTAL_AMOUNT_SUM
-- 최근 차수 견적서 합계 정보
,(SELECT TOTAL_AMOUNT FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = T.OBJID ORDER BY REGDATE DESC LIMIT 1) AS EST_TOTAL_AMOUNT
,(SELECT TOTAL_AMOUNT_KRW FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = T.OBJID ORDER BY REGDATE DESC LIMIT 1) AS EST_TOTAL_AMOUNT_KRW
-- 견적수량 (ESTIMATE_TEMPLATE_ITEM의 수량 합계)
,(SELECT COALESCE(SUM(CAST(QUANTITY AS NUMERIC)), 0) FROM ESTIMATE_TEMPLATE_ITEM WHERE TEMPLATE_OBJID = (SELECT objid FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = T.OBJID ORDER BY REGDATE DESC LIMIT 1)) AS ESTIMATE_QUANTITY
-- 수주 합계 정보 (CONTRACT_MGMT 테이블에 저장된 값 사용)
,T.ORDER_SUPPLY_PRICE AS ORDER_SUPPLY_PRICE_SUM
,T.ORDER_VAT AS ORDER_VAT_SUM
,T.ORDER_TOTAL_AMOUNT AS ORDER_TOTAL_AMOUNT_SUM
-- 수주수량 (CONTRACT_MGMT의 QUANTITY 또는 CONTRACT_ITEM 합계)
,COALESCE(
NULLIF(T.QUANTITY, '')::NUMERIC,
(SELECT COALESCE(SUM(CAST(QUANTITY AS NUMERIC)), 0) FROM CONTRACT_ITEM WHERE CONTRACT_OBJID = T.OBJID AND STATUS = 'ACTIVE')
) AS ORDER_QUANTITY
,CASE
WHEN T.ORDER_TOTAL_AMOUNT IS NOT NULL AND T.ORDER_TOTAL_AMOUNT != ''
AND T.EXCHANGE_RATE IS NOT NULL AND T.EXCHANGE_RATE != ''
@@ -790,6 +797,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 (
@@ -1411,8 +1514,11 @@
SELECT
A.OBJID
,A.CATEGORY_CD
,CODE_NAME(A.CATEGORY_CD) AS CATEGORY_NAME
,A.CUSTOMER_OBJID
,(SELECT SUPPLY_NAME FROM SUPPLY_MNG AS O WHERE O.OBJID = A.CUSTOMER_OBJID::NUMERIC) AS CUSTOMER_NAME
,A.PRODUCT
,CODE_NAME(A.PRODUCT) AS PRODUCT_NAME
,A.CUSTOMER_PROJECT_NAME
,A.STATUS_CD
,A.DUE_DATE
@@ -1455,12 +1561,14 @@
,A.EST_COMP_DATE
,A.EST_RESULT_CD
,A.AREA_CD
,CODE_NAME(A.AREA_CD) AS AREA_NAME
,A.TARGET_PROJECT_NO
,A.TARGET_PROJECT_NO_DIRECT
,A.CUSTOMER_PRODUCTION_NO
,A.MECHANICAL_TYPE
,A.OVERHAUL_ORDER
,A.PAID_TYPE
,(case when A.PAID_TYPE = 'paid' then '유상' when A.PAID_TYPE = 'free' then '무상' else A.PAID_TYPE end) AS PAID_TYPE_NAME
,A.RECEIPT_DATE
,A.PART_NO
,A.PART_NAME
@@ -3511,7 +3619,9 @@ ORDER BY ASM.SUPPLY_NAME
<select id="getProjectListBycontractObjid" parameterType="map" resultType="map">
SELECT
PROJECT_NAME
OBJID,
PROJECT_NAME,
PROJECT_NO
FROM
PROJECT_MGMT
WHERE CONTRACT_OBJID = #{objId}
@@ -3870,7 +3980,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 기준) -->
@@ -3935,6 +4052,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,
@@ -3960,12 +4083,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 기준) -->
@@ -3991,6 +4121,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,
@@ -4069,6 +4206,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 (
@@ -4092,6 +4253,7 @@ ORDER BY ASM.SUPPLY_NAME
TOTAL_AMOUNT_KRW,
MANAGER_NAME,
MANAGER_CONTACT,
SHOW_TOTAL_ROW,
WRITER,
REGDATE,
CHG_USER_ID,
@@ -4117,6 +4279,7 @@ ORDER BY ASM.SUPPLY_NAME
#{total_amount_krw},
#{manager_name},
#{manager_contact},
#{show_total_row},
#{writer},
NOW(),
#{chg_user_id},
@@ -4145,6 +4308,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
@@ -4202,6 +4366,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
@@ -4359,14 +4584,130 @@ 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,
(SELECT SUPPLY_NAME FROM SUPPLY_MNG WHERE OBJID = CONTRACT_MGMT.CUSTOMER_OBJID::NUMERIC) AS CUSTOMER_NAME,
-- 주문유형
CATEGORY_CD,
CODE_NAME(CATEGORY_CD) AS CATEGORY_NAME,
-- 제품구분
PRODUCT,
CODE_NAME(PRODUCT) AS PRODUCT_NAME,
-- 국내/해외
AREA_CD,
CODE_NAME(AREA_CD) AS AREA_NAME,
-- 유/무상
PAID_TYPE,
-- 접수일 (이미 날짜 형식이면 그대로, YYYYMMDD 형식이면 변환)
CASE
WHEN RECEIPT_DATE ~ '^\d{8}$' THEN TO_CHAR(TO_DATE(RECEIPT_DATE, 'YYYYMMDD'), 'YYYY-MM-DD')
ELSE RECEIPT_DATE
END AS RECEIPT_DATE,
-- 환종/환율
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,
@@ -4592,8 +4933,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,
@@ -4607,6 +4948,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'
@@ -4616,7 +4959,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

@@ -819,6 +819,7 @@
T.OBJID,
T.PROJECT_NO,
T.CONTRACT_OBJID,
T.SALES_DEADLINE_DATE,
CODE_NAME(T.CATEGORY_CD) AS ORDER_TYPE,
CODE_NAME(T.PRODUCT) AS PRODUCT_TYPE,
CODE_NAME(T.AREA_CD) AS NATION,
@@ -875,16 +876,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,
-- 잔량 계산: 수주수량 - shipment_log의 split_quantity 합계
COALESCE(T.QUANTITY::numeric, 0) - COALESCE(
(SELECT SUM(COALESCE(split_quantity, 0))
FROM shipment_log
WHERE target_objid = T.PROJECT_NO),
0
) AS REMAINING_QUANTITY,
-- 잔량원화총액 계산: (수주수량 - shipment_log 합계) * 판매단가
(COALESCE(T.QUANTITY::numeric, 0) - COALESCE(
(SELECT SUM(COALESCE(split_quantity, 0))
FROM shipment_log
WHERE target_objid = T.PROJECT_NO),
0
)) * COALESCE(SR.sales_unit_price, 0) AS REMAINING_AMOUNT_KRW,
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,
@@ -895,10 +912,10 @@
CASE
WHEN COUNT(DISTINCT shipping_date) = 0 THEN ''
WHEN COUNT(DISTINCT shipping_date) = 1 THEN TO_CHAR(MIN(shipping_date), 'YYYY-MM-DD')
ELSE TO_CHAR(MIN(shipping_date), 'YYYY-MM-DD') || '' || (COUNT(DISTINCT shipping_date) - 1)::TEXT
ELSE TO_CHAR(MIN(shipping_date), 'YYYY-MM-DD') || '' || (COUNT(DISTINCT shipping_date) - 1)::TEXT || '건'
END
FROM shipment_log
WHERE target_objid = T.OBJID::VARCHAR
WHERE target_objid = T.PROJECT_NO
AND shipping_date IS NOT NULL
AND UPPER(COALESCE(shipping_status, '')) != 'CANCELLED'),
COALESCE(TO_CHAR(SR.shipping_date, 'YYYY-MM-DD'), '')
@@ -910,12 +927,17 @@
) AS MANAGER,
COALESCE(SR.incoterms, '') AS INCOTERMS,
T.SALES_STATUS,
T.OBJID::VARCHAR AS SALE_NO,
'ORIGINAL' AS RECORD_TYPE,
'' AS SPLIT_LOG_ID
FROM PROJECT_MGMT AS T
LEFT JOIN sales_registration SR ON T.PROJECT_NO = SR.project_no
WHERE 1 = 1
T.OBJID::VARCHAR AS SALE_NO,
'ORIGINAL' AS RECORD_TYPE,
'' AS SPLIT_LOG_ID,
-- 거래명세서 존재 여부
CASE WHEN EXISTS(
SELECT 1 FROM NSWOS100_TBL
WHERE OdOrderNo = T.PROJECT_NO
) THEN 'Y' ELSE 'N' END AS HAS_TRANSACTION_STATEMENT
FROM PROJECT_MGMT AS T
LEFT JOIN sales_registration SR ON T.PROJECT_NO = SR.project_no
WHERE 1 = 1
AND T.PROJECT_NO IS NOT NULL
AND T.PROJECT_NO != ''
<if test="orderType != null and orderType != ''">
@@ -1397,29 +1419,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 +1493,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,13 +1509,16 @@ 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,
COALESCE(SR.incoterms, '') AS INCOTERMS
FROM PROJECT_MGMT AS T
LEFT JOIN sales_registration SR ON T.PROJECT_NO = SR.project_no
<if test="saleNo != null and saleNo != ''">
AND SR.sale_no = #{saleNo}::integer
</if>
LEFT JOIN CONTRACT_MGMT CM ON CM.OBJID::VARCHAR = T.CONTRACT_OBJID
WHERE T.PROJECT_NO = #{orderNo}
</select>
@@ -1566,40 +1580,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 +1636,342 @@ 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(
CASE
WHEN sales_quantity IS NULL THEN 0
WHEN TRIM(CAST(sales_quantity AS TEXT)) = '' THEN 0
ELSE CAST(sales_quantity AS NUMERIC)
END
), 0) as total
FROM sales_registration
WHERE project_no LIKE #{orderNo} || '%'
</select>
<!-- shipment_log에서 총 출하 수량 조회 -->
<select id="getTotalShippedQuantity" parameterType="map" resultType="int">
/* salesNcollectMgmt.getTotalShippedQuantity - shipment_log의 split_quantity 합계 */
SELECT COALESCE(SUM(split_quantity), 0)
FROM shipment_log
WHERE target_objid = #{projectNo}
</select>
<!-- 프로젝트 기본 정보 조회 (CONTRACT_OBJID 포함) -->
<select id="getProjectInfo" parameterType="map" resultType="map">
/* salesNcollectMgmt.getProjectInfo - PROJECT_MGMT의 상세 정보 조회 (최신 1개) */
SELECT
PM.CONTRACT_OBJID,
PM.PART_NO,
PM.PART_NAME,
-- 요청납기 (CONTRACT_ITEM의 DUE_DATE 우선, 없으면 PROJECT_MGMT.DUE_DATE)
COALESCE(
(SELECT CI.DUE_DATE
FROM CONTRACT_ITEM CI
WHERE CI.CONTRACT_OBJID = PM.CONTRACT_OBJID
AND CI.PART_OBJID = PM.PART_OBJID
AND CI.STATUS = 'ACTIVE'
LIMIT 1),
PM.DUE_DATE
) AS REQ_DEL_DATE,
-- S/N 조회 (CONTRACT_ITEM_SERIAL에서)
(SELECT STRING_AGG(CIS.SERIAL_NO, ', ' ORDER BY CIS.SERIAL_NO)
FROM CONTRACT_ITEM CI
JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID
WHERE CI.CONTRACT_OBJID = PM.CONTRACT_OBJID
AND CI.PART_OBJID = PM.PART_OBJID
AND CI.STATUS = 'ACTIVE'
AND UPPER(CIS.STATUS) = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL
) AS SERIAL_NO,
-- 고객요청사항 (CONTRACT_ITEM에서)
(SELECT CI.CUSTOMER_REQUEST
FROM CONTRACT_ITEM CI
WHERE CI.CONTRACT_OBJID = PM.CONTRACT_OBJID
AND CI.PART_OBJID = PM.PART_OBJID
AND CI.STATUS = 'ACTIVE'
LIMIT 1
) AS CUSTOMER_REQUEST,
-- 반납사유 (CONTRACT_ITEM에서)
(SELECT CI.RETURN_REASON
FROM CONTRACT_ITEM CI
WHERE CI.CONTRACT_OBJID = PM.CONTRACT_OBJID
AND CI.PART_OBJID = PM.PART_OBJID
AND CI.STATUS = 'ACTIVE'
LIMIT 1
) AS RETURN_REASON
FROM PROJECT_MGMT PM
WHERE PM.PROJECT_NO = #{orderNo}
ORDER BY PM.REGDATE DESC
LIMIT 1
</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, #{managerUserId}, #{incoterms}, #{serialNo},
#{parentSaleNo}::integer, #{cretEmpNo}
)
</insert>
<!-- sales_registration의 수량을 shipment_log 합계로 업데이트 -->
<update id="updateSalesQuantityFromShipmentLog" parameterType="map">
/* salesNcollectMgmt.updateSalesQuantityFromShipmentLog - shipment_log 합계로 sales_quantity 업데이트 */
UPDATE sales_registration
SET sales_quantity = (
SELECT COALESCE(SUM(split_quantity), 0)
FROM shipment_log
WHERE target_objid = #{projectNo}
),
sales_supply_price = (
SELECT COALESCE(SUM(sales_supply_price), 0)
FROM shipment_log
WHERE target_objid = #{projectNo}
),
sales_vat = (
SELECT COALESCE(SUM(sales_vat), 0)
FROM shipment_log
WHERE target_objid = #{projectNo}
),
sales_total_amount = (
SELECT COALESCE(SUM(sales_total_amount), 0)
FROM shipment_log
WHERE target_objid = #{projectNo}
)
WHERE sale_no = #{saleNo}
</update>
<!-- 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 - shipment_log에서 모든 분할 출하 조회 */
SELECT
COALESCE(TO_CHAR(SL.shipping_date, 'YYYY-MM-DD'), '미등록') AS shipping_date,
COALESCE(SL.split_quantity, 0) AS shipping_quantity,
COALESCE(SL.shipping_status, '미등록') AS shipping_order_status,
COALESCE(SL.serial_no, '-') AS serial_no,
SL.target_objid AS project_no,
'분할 출하 ' || SL.log_id AS source,
TO_CHAR(SL.reg_date, 'YYYY-MM-DD HH24:MI:SS') AS reg_date
FROM shipment_log SL
WHERE SL.target_objid = #{projectNo}
ORDER BY SL.shipping_date DESC, SL.log_id DESC
</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>
<!-- 거래명세서 저장 - NSWOS100_TBL 테이블 사용 -->
<insert id="saveTransactionStatement" parameterType="map">
/* salesNcollectMgmt.saveTransactionStatement - 거래명세서 저장 */
INSERT INTO NSWOS100_TBL (
SuVndCd /* 업체코드 */
,IssueDt /* 작성일자 */
,IssueNo /* 거래명세서번호 */
,IsNo /* 순번 */
,ProdCd /* 기종코드 */
,OdOrderNo /* 발주번호 */
,ImItemId /* 품번 */
,RmDueDt /* 납기일자 */
,RmOrderQty /* 발주수량 */
,RmRcptQty /* 입고처리수량 */
,RmRemQty /* 잔량 */
,IsDt /* 납기일자 */
,IsQty /* 납품수량 */
,IsPrice /* 납품단가 */
,IsAmount /* 납품금액 */
) VALUES (
#{suVndCd} /* 업체코드 */
,#{issueDt} /* 작성일자 */
,#{issueNo}::integer /* 거래명세서번호 */
,#{isNo}::integer /* 순번 */
,COALESCE(#{prodCd}, '') /* 기종코드 */
,COALESCE(#{odOrderNo}, '') /* 발주번호 */
,COALESCE(#{imItemId}, '') /* 품번 */
,COALESCE(#{rmDueDt}, '') /* 납기일자 */
,COALESCE(#{rmOrderQty}, 0)::integer /* 발주수량 */
,COALESCE(#{rmRcptQty}, 0)::integer /* 입고처리수량 */
,COALESCE(#{rmRemQty}, 0)::integer /* 잔량 */
,COALESCE(#{isDt}, '') /* 납기일자 */
,COALESCE(#{isQty}, 0)::integer /* 납품수량 */
,COALESCE(#{isPrice}, 0)::numeric /* 납품단가 */
,COALESCE(#{isAmount}, 0)::numeric /* 납품금액 */
) ON CONFLICT (SuVndCd, IssueDt, IssueNo, IsNo) DO
UPDATE SET
ProdCd = COALESCE(#{prodCd}, '')
,OdOrderNo = COALESCE(#{odOrderNo}, '')
,ImItemId = COALESCE(#{imItemId}, '')
,RmDueDt = COALESCE(#{rmDueDt}, '')
,RmOrderQty = COALESCE(#{rmOrderQty}, 0)::integer
,RmRcptQty = COALESCE(#{rmRcptQty}, 0)::integer
,RmRemQty = COALESCE(#{rmRemQty}, 0)::integer
,IsDt = COALESCE(#{isDt}, '')
,IsQty = COALESCE(#{isQty}, 0)::integer
,IsPrice = COALESCE(#{isPrice}, 0)::numeric
,IsAmount = COALESCE(#{isAmount}, 0)::numeric
</insert>
<!-- 거래명세서 번호 생성 -->
<select id="getNextTransactionStatementNo" parameterType="map" resultType="int">
/* salesNcollectMgmt.getNextTransactionStatementNo - 거래명세서 번호 생성 */
SELECT COALESCE(MAX(IssueNo), 0) + 1 AS nextNo
FROM NSWOS100_TBL
WHERE SuVndCd = #{suVndCd}
AND IssueDt = #{issueDt}
</select>
<select id="getAllSerialNumbers" parameterType="map" resultType="string">
/* salesNcollectMgmt.getAllSerialNumbers - 프로젝트의 모든 S/N 조회 */
SELECT CIS.SERIAL_NO
FROM PROJECT_MGMT PM
JOIN CONTRACT_ITEM CI ON CI.CONTRACT_OBJID = PM.CONTRACT_OBJID
AND CI.PART_OBJID = PM.PART_OBJID
AND CI.STATUS = 'ACTIVE'
JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID
AND UPPER(CIS.STATUS) = 'ACTIVE'
WHERE PM.PROJECT_NO = #{projectNo}
AND CIS.SERIAL_NO IS NOT NULL
ORDER BY CIS.SERIAL_NO
</select>
<select id="getSavedTransactionStatement" parameterType="map" resultType="map">
/* salesNcollectMgmt.getSavedTransactionStatement - 저장된 거래명세서 조회 */
SELECT
SuVndCd,
IssueDt,
IssueNo,
IsNo,
ProdCd,
OdOrderNo,
ImItemId,
RmDueDt,
RmOrderQty,
RmRcptQty,
RmRemQty,
IsDt,
IsQty,
IsPrice,
IsAmount
FROM NSWOS100_TBL
WHERE OdOrderNo = #{projectNo}
ORDER BY IsNo
</select>
</mapper>

View File

@@ -246,9 +246,23 @@ var columns = [
return dueDate;
}
},
{headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '고객사', field : 'CUSTOMER_NAME' },
{headerHozAlign : 'center', hozAlign : 'left', width : '180', title : '품명', field : 'ITEM_SUMMARY' },
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '유/무상', field : 'PAID_TYPE' },
{headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '고객사', field : 'CUSTOMER_NAME' },
{headerHozAlign : 'center', hozAlign : 'left', width : '180', title : '품명', field : 'ITEM_SUMMARY' },
{headerHozAlign : 'center', hozAlign : 'right', width : '100', title : '견적수량', field : 'ESTIMATE_QUANTITY',
formatter: function(cell) {
var value = cell.getValue();
if(!value || value === '' || value === '0') return '';
return Number(value).toLocaleString();
}
},
{headerHozAlign : 'center', hozAlign : 'right', width : '100', title : '수주수량', field : 'ORDER_QUANTITY',
formatter: function(cell) {
var value = cell.getValue();
if(!value || value === '' || value === '0') return '';
return Number(value).toLocaleString();
}
},
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '유/무상', field : 'PAID_TYPE' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '제품구분', field : 'PRODUCT_NAME' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '88', title : '국내/해외', field : 'AREA_NAME' },
// {headerHozAlign : 'center', hozAlign : 'left', width : '100', title : '품번', field : 'PART_NO' },
@@ -457,10 +471,19 @@ function _fnc_datepick(){
function fn_delete(){
var checkedObj = _tabulGrid.getSelectedData();
if(0 < checkedObj.length){
var objId = fnc_checkNull(checkedObj[0].OBJID);
//if(confirm("선택한 정보를 삭제하시겠습니까?")){
// 선택된 모든 항목의 OBJID를 배열로 수집
var objIds = [];
for(var i = 0; i < checkedObj.length; i++){
objIds.push(fnc_checkNull(checkedObj[i].OBJID));
}
var deleteCount = objIds.length;
var confirmMessage = deleteCount === 1
? '선택한 견적요청정보를 삭제하시겠습니까?'
: '선택한 ' + deleteCount + '개의 견적요청정보를 삭제하시겠습니까?';
Swal.fire({
title: '선택한 고객정보를 삭제하시겠습니까?',
title: confirmMessage,
text: '',
icon: 'warning',
@@ -474,17 +497,21 @@ function fn_delete(){
}).then(result => {
// 만약 Promise리턴을 받으면,
if (result.isConfirmed) { // 만약 모달창에서 confirm 버튼을 눌렀다면
//var param = $("#form1").serialize();
// 여러 개의 objId를 배열로 전송
$.ajax({
type : "POST",
url : "/contractMgmt/deleteContractMngInfo.do",
data: {
"objId":objId
"objId": objIds
},
traditional: true, // 배열 파라미터를 올바르게 전송
dataType:"json",
success:function(data){
if(data.result == 'true'){
Swal.fire("삭제되었습니다.");
var successMessage = deleteCount === 1
? '삭제되었습니다.'
: deleteCount + '개의 항목이 삭제되었습니다.';
Swal.fire(successMessage);
fn_search();
};
}

View File

@@ -1993,21 +1993,21 @@
</table>
</td>
</tr>
</table>
<!-- 품목정보 영역 -->
<div class="form_popup_title" style="margin-top:15px;">
<span>품목정보</span>
<button type="button" id="btnAddItem" class="plm_btns" style="float:right; margin-top:-5px;">품목 추가</button>
</div>
<table class="">
<colgroup>
<col width="100%" />
</colgroup>
<tr>
<td>
<div style="max-height:300px; overflow-y:auto;">
<table class="pmsPopuptable" id="itemListTable">
</table>
<!-- 품목정보 영역 -->
<div class="form_popup_title" style="margin-top:15px;">
<span>품목정보</span>
<button type="button" id="btnAddItem" class="plm_btns" style="float:right; margin-top:-5px;">품목 추가</button>
</div>
<table class="">
<colgroup>
<col width="100%" />
</colgroup>
<tr>
<td>
<div style="max-height:300px; overflow-y:auto;">
<table class="pmsPopuptable" id="itemListTable">
<colgroup>
<col width="4%" /> <!-- 번호 -->
<col width="12%" /> <!-- 품번 -->

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();
@@ -70,6 +75,32 @@ $(document).ready(function(){
}
});
//결재상신
$("#btnApproval").click(function(){
var selectedData = _tabulGrid.getSelectedData();
if(selectedData.length<1){
Swal.fire("결재상신할 행을 선택해주십시오.");
return false;
}else if(selectedData.length>1){
Swal.fire("한번에 한개의 결재만 가능합니다.");
return false;
}else{
var targetStatus = fnc_checkNull(selectedData[0].APPR_STATUS);
var status = fnc_checkNull(selectedData[0].STATUS);
if(targetStatus == "결재완료" || targetStatus == "결재중" || status == "cancel"){
Swal.fire("작성중/결재반려인 상태만 결재상신 가능합니다.");
return false;
}else{
if(confirm("결재상신 하시겠습니까?")){
var objId = fnc_checkNull(selectedData[0].OBJID);
var title = encodeURIComponent(fnc_checkNull(selectedData[0].CONTRACT_NO));
window.open("/approval/registApproval.do?targetType=CONTRACT_ORDER&targetObjId="+objId+"&approvalTitle="+title,"registApproval","width=700,height=700");
}
}
}
});
//엔터 조회
$("input").keyup(function(e){
@@ -96,14 +127,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' },
@@ -122,9 +153,23 @@ var columns = [
return dueDate;
}
},
{headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '고객사', field : 'CUSTOMER_NAME' },
{headerHozAlign : 'center', hozAlign : 'left', width : '180', title : '품명', field : 'ITEM_SUMMARY' },
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '유/무상', field : 'PAID_TYPE' },
{headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '고객사', field : 'CUSTOMER_NAME' },
{headerHozAlign : 'center', hozAlign : 'left', width : '180', title : '품명', field : 'ITEM_SUMMARY' },
{headerHozAlign : 'center', hozAlign : 'right', width : '100', title : '견적수량', field : 'ESTIMATE_QUANTITY',
formatter: function(cell) {
var value = cell.getValue();
if(!value || value === '' || value === '0') return '';
return Number(value).toLocaleString();
}
},
{headerHozAlign : 'center', hozAlign : 'right', width : '100', title : '수주수량', field : 'ORDER_QUANTITY',
formatter: function(cell) {
var value = cell.getValue();
if(!value || value === '' || value === '0') return '';
return Number(value).toLocaleString();
}
},
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '유/무상', field : 'PAID_TYPE' },
{headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '수주상태', field : 'CONTRACT_RESULT_NAME' },
{headerHozAlign : 'center', hozAlign : 'right', width : '120', title : '공급가액', field : 'ORDER_SUPPLY_PRICE_SUM',
formatter:"money", formatterParams:{thousand:",", symbolAfter:"", precision:false,},
@@ -182,64 +227,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(){
@@ -606,10 +636,11 @@ function openProjectFormPopUp(objId){
<h2>
<span>영업관리_주문서관리</span>
</h2>
<div class="btnArea">
<input type="button" value="조회" class="plm_btns" id="btnSearch" name="btnSearch">
<input type="button" value="수주등록" class="plm_btns btnRegist">
</div>
<div class="btnArea">
<input type="button" value="조회" class="plm_btns" id="btnSearch" name="btnSearch">
<input type="button" value="수주등록" class="plm_btns btnRegist">
<input type="button" value="결재상신" class="plm_btns" id="btnApproval">
</div>
</div>
<div id="plmSearchZon">
<table>
@@ -716,23 +747,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

@@ -7,212 +7,630 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<link href="/css/tabulator/tabulator.min.css" rel="stylesheet">
<script type="text/javascript" src="/js/tabulator/tabulator.min.js"></script>
<style>
::-webkit-scrollbar {
width: 10px;
height: 15px;
}
body, html {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
}
/* 상단 입력 영역 */
.top-input-section {
padding: 15px 20px;
background: #f9f9f9;
border-bottom: 2px solid #ddd;
}
.input-table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
.input-table td {
padding: 5px 8px;
vertical-align: middle;
}
.input-table label {
font-weight: bold;
display: block;
white-space: nowrap;
}
.input-table input[type="text"] {
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 3px;
width: 100%;
box-sizing: border-box;
}
.bom-select-table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
table-layout: fixed;
}
.bom-select-table td {
padding: 5px 8px;
vertical-align: middle;
}
.bom-select-table label {
font-weight: bold;
display: block;
white-space: nowrap;
}
.bom-select-table input[type="text"] {
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 3px;
width: 100%;
box-sizing: border-box;
}
/* 하단 버튼 영역 */
.bottom-button-section {
padding: 15px 20px;
background: #f9f9f9;
border-top: 2px solid #ddd;
text-align: center;
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 100;
}
/* 그리드 영역 */
#structureTableWrap1 {
padding: 15px 20px;
height: calc(100vh - 260px);
overflow: auto;
padding-bottom: 80px;
}
#structureName {
margin-bottom: 10px;
font-weight: bold;
padding: 8px 0;
border-bottom: 1px solid #eee;
}
#structureName2 {
margin-bottom: 10px;
font-size: 12px;
color: #666;
}
/* Tabulator 커스텀 스타일 */
.tabulator-row.level-1 { background-color: #fde9d9 !important; }
.tabulator-row.level-2 { background-color: #daeef3 !important; }
.tabulator-row.level-3 { background-color: #e4dfec !important; }
.tabulator-row.level-4 { background-color: #ebf1de !important; }
.tabulator-row.level-5 { background-color: #f2f2f2 !important; }
.tabulator-row.level-6 { background-color: #f2dcdb !important; }
.tabulator-row.level-7 { background-color: #eeece1 !important; }
.tabulator-row.level-8 { background-color: #dce6f1 !important; }
.tabulator-row.level-9 { background-color: #FFFFEB !important; }
.tabulator-row.level-10 { background-color: #ffffff !important; }
</style>
<script>
var _tabulGrid;
var selectedRowData = null;
var selectedBomObjId = null; // 선택된 BOM의 OBJID
var selectedBomType = null; // 'EBOM' 또는 'MBOM'
var bomGridData = []; // BOM 그리드 데이터
$(function(){
$(document).ready(function(){
// 페이지 로드 시 프로젝트 정보가 있으면 품번/품명 자동 입력
<c:if test="${not empty projectInfo}">
console.log("projectInfo가 있습니다. 품번/품명 설정 중...");
$("#COPY_PART_NO").val("${projectInfo.PART_NO}");
$("#COPY_PART_NAME").val("${projectInfo.PART_NAME}");
// E-BOM 품번은 사용자가 직접 입력하도록 비워둠 (M-BOM 품번과 다를 수 있음)
</c:if>
// 담기 버튼 - 선택한 BOM을 복사 대상으로 설정
$("#btnAddItem").click(function(){
var partNo = $("#COPY_PART_NO").val().trim();
var partName = $("#COPY_PART_NAME").val().trim();
if(""!="${product_code}"){
$("#TARGET_PRODUCT_MGMT_OBJID").val("${product_code}");
fnc_getProductRevtList("TARGET_REV","${product_code}");
if(!partNo || !partName) {
Swal.fire('품번과 품명을 입력해주세요.');
return;
}
// 선택된 BOM이 있는지 확인
if(!selectedBomObjId) {
Swal.fire('E-BOM 또는 M-BOM을 먼저 선택해주세요.');
return;
}
$("#btnConfirm").click(function(){
if(fnc_validate("form1")){
if($("#REV").val()==""){
Swal.fire('Source Rev를 선택해 주세요');
return;
}
if($("#TARGET_PRODUCT_MGMT_OBJID").val()==""){
Swal.fire('대상 기종명을 선택해 주세요');
return;
}
if($("#PRODUCT_MGMT_OBJID").val() == $("#TARGET_PRODUCT_MGMT_OBJID").val() && $("#REV").val() == $("#TARGET_REV").val()){
Swal.fire("같은 기종 같은 REV로 복사 할수 없습니다.");
return;
}
if(confirm("복사하시겠습니까?")){
$.ajax({
url:"/partMng/saveBomCopy.do",
type:"POST",
data:$("#form1").serialize(),
dataType:"json",
success:function(data){
if(data=="SUCCESS"){
Swal.fire('복사되었습니다');
}else{
Swal.fire('복사에 실패하였습니다.');
}
opener.fn_search();
self.close();
},
error: function(jqxhr, status, error){
}
});
}
}
Swal.fire({
title: '담기 완료',
text: '선택한 BOM이 복사 대상으로 설정되었습니다.',
icon: 'success'
});
$("#btnClose").click(function(){
self.close(0);
});
/* $("#PRODUCT_MGMT_OBJID").change(function(){
fn_productSpecList(this.value, "PRODUCT_MGMT_SPEC", "");
}); */
$("#REV").change(function(){
$("#SPEC_NAME").val("");
var spec_name = $("#REV").find("option:selected").attr("id");
$("#SPEC_NAME").val(spec_name);
});
$("#TARGET_REV").change(function(){
$("#TARGET_SPEC_NAME").val("");
var spec_name = $("#TARGET_REV").find("option:selected").attr("ids");
$("#TARGET_SPEC_NAME").val(spec_name);
if(this.value!=""){
$("#TARGET_SPEC_NAME").attr("readonly",true);
}else{
$("#TARGET_SPEC_NAME").attr("readonly",false);
}
});
$("#TARGET_PRODUCT_MGMT_OBJID").change(function(){
fnc_getProductRevtList("TARGET_REV",this.value);
});
fnc_getProductMgmtList("PRODUCT_MGMT_OBJID", "${product_code}");
//fn_productSpecList("${product_code}", "PRODUCT_MGMT_SPEC", "${product_mgmt_spec}");
//fnc_productUPGNEWList("","${product_mgmt_spec}","PRODUCT_MGMT_UPG", "${upg_no}");
$("#PRODUCT_MGMT_SPEC").change(function(){
fnc_productUPGList("",this.value,"PRODUCT_MGMT_UPG", "${resultMap.PRODUCT_MGMT_UPG}");
});
$('.select2').select2();
});
// 저장 버튼 - E-BOM을 M-BOM으로 복사
$("#btnSaveItem").click(function(){
fn_saveBomCopy();
});
// E-BOM 선택 버튼
$("#btnSelectEbom").click(function(){
var ebomPartNo = $("#EBOM_PART_NO").val().trim();
if(!ebomPartNo) {
Swal.fire('E-BOM 품번을 입력해주세요.');
return;
}
// E-BOM 조회 후 미리보기 로드
fn_loadBomPreview(ebomPartNo, 'EBOM');
});
// M-BOM 선택 버튼
$("#btnSelectMbom").click(function(){
var mbomPartNo = $("#MBOM_PART_NO").val().trim();
if(!mbomPartNo) {
Swal.fire('M-BOM 품번을 입력해주세요.');
return;
}
// M-BOM 조회 후 미리보기 로드
fn_loadBomPreview(mbomPartNo, 'MBOM');
});
// Excel 다운로드 버튼
$("#btnExcel").click(function() {
fn_excel();
});
// 도면 업로드 버튼 클릭
$("#btnDrawingUpload").click(function() {
$("#drawingFiles").click();
});
// 파일 선택 이벤트
$("#drawingFiles").change(function() {
fn_uploadDrawingFiles(this.files);
});
// 초기 그리드 생성 (빈 그리드)
fn_initGrid([], 3);
});
// BOM 미리보기 로드
function fn_loadBomPreview(partNo, bomType) {
console.log("fn_loadBomPreview 호출:", partNo, bomType);
// 먼저 품번으로 BOM OBJID 조회
$.ajax({
url: "/partMng/getBomObjIdByPartNo.do",
type: "POST",
data: { partNo: partNo },
dataType: "json",
success: function(response) {
console.log("getBomObjIdByPartNo 응답:", response);
if(response && response.OBJID) {
$("#bomPartName").text(partNo);
// 전역 변수에 저장
selectedBomObjId = response.OBJID;
selectedBomType = bomType;
// BOM 트리 데이터 로드
fn_loadBomTree(response.OBJID);
} else {
console.error("BOM OBJID를 찾을 수 없음:", response);
Swal.fire('해당 품번의 BOM을 찾을 수 없습니다.');
}
},
error: function(xhr, status, error) {
console.error("getBomObjIdByPartNo 에러:", xhr, status, error);
Swal.fire('BOM 조회 중 오류가 발생했습니다.');
}
});
}
// BOM 트리 데이터 로드
function fn_loadBomTree(bomObjId) {
console.log("fn_loadBomTree 호출:", bomObjId);
$.ajax({
url: "/partMng/getStructureTreeJson.do",
type: "POST",
data: {
objId: bomObjId,
bomReportObjid: bomObjId,
search_type: "working"
},
dataType: "json",
success: function(response) {
console.log("getStructureTreeJson 응답:", response);
if(response && response.length > 0) {
var maxLevel = response[0].MAX_LEVEL || 3;
// Level 필드 생성
var processedData = [];
response.forEach(function(item) {
for(var i = 1; i <= maxLevel; i++) {
item['LEVEL_' + i] = (item.LEVEL == i) ? '*' : '';
}
processedData.push(item);
});
// 전역 변수에 저장
bomGridData = processedData;
fn_initGrid(processedData, maxLevel);
} else {
console.warn("BOM 트리 데이터가 비어있음");
bomGridData = [];
fn_initGrid([], 3);
}
},
error: function(xhr, status, error) {
console.error("getStructureTreeJson 에러:", xhr, status, error);
Swal.fire('BOM 트리 조회 중 오류가 발생했습니다.');
}
});
}
// 도면 파일 업로드
function fn_uploadDrawingFiles(files) {
if(!files || files.length === 0) {
Swal.fire('파일을 선택해주세요.');
return;
}
Swal.fire('도면 다중 업로드 기능은 구현 예정입니다.');
}
// Tabulator 그리드 초기화
function fn_initGrid(data, maxLevel) {
maxLevel = maxLevel || 3; // 기본 3레벨
// 데이터에서 최대 레벨 계산
if(data && data.length > 0) {
data.forEach(function(row) {
if(row.LEVEL && row.LEVEL > maxLevel) {
maxLevel = row.LEVEL;
}
});
}
// 컬럼 정의
var columns = [
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 60,
title: '선택',
field: 'RADIO',
formatter: function(cell) {
var rowData = cell.getData();
return '<input type="radio" name="checkedPartNo" value="' + (rowData.CHILD_OBJID || '') + '">';
},
cellClick: function(e, cell) {
var radio = $(e.target);
if(radio.is(':radio')) {
selectedRowData = cell.getData();
$('input[name=checkedPartNo]').not(radio).prop('checked', false);
}
}
}
];
// 수준 컬럼 그룹
var levelColumns = [];
for(var i = 1; i <= maxLevel; i++) {
levelColumns.push({
headerHozAlign: 'center',
hozAlign: 'center',
width: 30,
title: i,
field: 'LEVEL_' + i,
formatter: function(cell) {
return cell.getValue() === '*' ? '*' : '';
}
});
}
columns.push({
title: '수준',
headerHozAlign: 'center',
columns: levelColumns
});
// 나머지 컬럼 추가
columns.push(
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
title: '품번',
field: 'PART_NO'
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 200,
title: '품명',
field: 'PART_NAME'
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 60,
title: '수량',
field: 'QTY_TEMP'
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '항목 수량',
field: 'ITEM_QTY'
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 60,
title: '3D',
field: 'CU01_CNT',
formatter: function(cell) {
var rowData = cell.getData();
var iconClass = (rowData.CU01_CNT && rowData.CU01_CNT > 0) ? 'file_icon' : 'file_empty_icon';
return '<a href="#" class="File ' + iconClass + '"></a>';
}
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 60,
title: '2D',
field: 'CU02_CNT',
formatter: function(cell) {
var rowData = cell.getData();
var iconClass = (rowData.CU02_CNT && rowData.CU02_CNT > 0) ? 'file_icon' : 'file_empty_icon';
return '<a href="#" class="File ' + iconClass + '"></a>';
}
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 60,
title: 'PDF',
field: 'CU03_CNT',
formatter: function(cell) {
var rowData = cell.getData();
var iconClass = (rowData.CU03_CNT && rowData.CU03_CNT > 0) ? 'file_icon' : 'file_empty_icon';
return '<a href="#" class="File ' + iconClass + '"></a>';
}
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 100,
title: '재료',
field: 'MATERIAL'
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 120,
title: '열처리경도',
field: 'HEAT_TREATMENT_HARDNESS'
}
);
// 기존 그리드 제거
if(_tabulGrid) {
_tabulGrid.destroy();
}
// Tabulator 생성
_tabulGrid = new Tabulator("#structureGrid", {
data: data,
layout: "fitColumns",
height: "calc(100vh - 360px)",
columns: columns,
rowFormatter: function(row) {
var data = row.getData();
if(data.LEVEL) {
$(row.getElement()).addClass('level-' + data.LEVEL);
}
},
placeholder: "BOM 데이터를 조회하려면 E-BOM 또는 M-BOM 품번을 입력하고 선택 버튼을 클릭하세요."
});
}
// BOM 복사 저장
function fn_saveBomCopy() {
var copyPartNo = $("#COPY_PART_NO").val().trim();
var copyPartName = $("#COPY_PART_NAME").val().trim();
var targetObjId = $("#TARGET_OBJID").val();
// 유효성 검사
if(!copyPartNo || !copyPartName) {
Swal.fire('품번과 품명을 입력해주세요.');
return;
}
if(!selectedBomObjId) {
Swal.fire('복사할 BOM을 선택해주세요.');
return;
}
if(bomGridData.length === 0) {
Swal.fire('복사할 BOM 데이터가 없습니다.');
return;
}
// 확인 메시지
var confirmMessage = targetObjId
? '선택한 ' + selectedBomType + '을(를) M-BOM으로 복사하시겠습니까?\n기존 M-BOM이 있다면 초기화됩니다.'
: '선택한 ' + selectedBomType + '을(를) 새로운 품번(' + copyPartNo + ')으로 복사하시겠습니까?';
Swal.fire({
title: 'BOM 복사',
text: confirmMessage,
icon: 'question',
showCancelButton: true,
confirmButtonText: '복사',
cancelButtonText: '취소'
}).then((result) => {
if(result.isConfirmed) {
// 저장 처리
Swal.fire({
title: '저장 중...',
text: 'BOM을 복사하고 있습니다.',
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
}
});
$.ajax({
url: "/partMng/saveBomCopy.do",
type: "POST",
data: JSON.stringify({
targetObjId: targetObjId, // M-BOM 관리에서 선택한 프로젝트 OBJID
sourceBomObjId: selectedBomObjId,
sourceBomType: selectedBomType,
targetPartNo: copyPartNo,
targetPartName: copyPartName,
bomData: bomGridData
}),
contentType: "application/json",
dataType: "json",
success: function(response) {
Swal.close();
if(response && response.result === 'success') {
Swal.fire({
title: '저장 완료',
text: 'M-BOM이 성공적으로 생성되었습니다.',
icon: 'success'
}).then(() => {
// 부모 창(M-BOM 관리) 새로고침하여 아이콘 업데이트
if(window.opener && !window.opener.closed) {
window.opener.location.reload();
}
window.close();
});
} else {
Swal.fire({
title: '저장 실패',
text: response.message || 'BOM 복사 중 오류가 발생했습니다.',
icon: 'error'
});
}
},
error: function(xhr, status, error) {
Swal.close();
console.error('Save error:', error);
Swal.fire({
title: '저장 실패',
text: '서버 오류가 발생했습니다: ' + error,
icon: 'error'
});
}
});
}
});
}
// 닫기
function fn_close() {
window.close();
}
// Excel 다운로드
function fn_excel() {
if(_tabulGrid) {
_tabulGrid.download("xlsx", "BOM_Copy.xlsx", {sheetName: "BOM"});
}
}
</script>
</head>
<body class="backcolor">
<form name="form1" id="form1" action="" method="post">
<div class="plm_menu_name">
<h2>
<span>BOM COPY</span>
</h2>
</div>
<div id="businessPopupFormWrap">
<table class="pmsPopupForm">
<colgroup>
<col width="48%;">
<col width="*">
<col width="48%;">
</colgroup>
<tr>
<td>
<h3>SOURCE</h3>
<br>
<font color="red">※복사할 Version 을 선택하세요.</font>
<table width="100%">
<tr >
<td class="input_title"><label for="">기종(모델)명</label></td>
<td class="input_sub_title" >
<select style="width:100%;" name="PRODUCT_MGMT_OBJID" id="PRODUCT_MGMT_OBJID" class="select2" disabled="disabled"></select>
</td>
</tr>
<tr>
<td class="input_title"><label for="">Source Version</label></td>
<td class="input_sub_title" >
<select style="width:100%;" name="REV" id="REV" class="select2" autocomplete="off" required reqTitle="원본Version">
<option value="">선택</option>
${code_map.rev}
</select>
</td>
</tr>
<tr>
<td class="input_title"><label for="">사양명</label></td>
<td class="input_sub_title" >
<input type="text" id="SPEC_NAME" name="SPEC_NAME" disabled="disabled" readonly>
</td>
</tr>
</table>
</td>
<td>
<table width="100%">
<tr>
</tr>
</table>
</td>
<td>
<h3>TARGET</h3>
<br>
<font color="red">※Version 미선택시 새로운 Version으로 신규 생성 되며 기등록된 BOM구조는 삭제 됩니다.</font>
<table width="100%">
<tr>
<td class="input_title"><label for="">대상 기종명</label></td>
<td class="input_sub_title" >
<select style="width:100%;" name="TARGET_PRODUCT_MGMT_OBJID" id="TARGET_PRODUCT_MGMT_OBJID" class="select2" autocomplete="off" required reqTitle="대상기종">
<option value="">선택</option>
${code_map.product_code}
</select>
</td>
</tr>
<tr>
<td class="input_title"><label for="">Target Version</label></td>
<td class="input_sub_title" >
<select style="width:100%;" name="TARGET_REV" id="TARGET_REV" class="select2"></select>
</td>
</tr>
<tr>
<td class="input_title"><label for="">대상 사양명</label></td>
<td class="input_sub_title" >
<input type="text" id="TARGET_SPEC_NAME" name="TARGET_SPEC_NAME" required reqTitle="대상사양명">
</td>
</tr>
</table>
</td>
</tr>
</table>
<div class="btn_wrap" style="clear:both;">
<div class="plm_btn_wrap_center">
<input type="button" value="복사" class="plm_btns" id="btnConfirm">
<input type="button" value="닫기" class="plm_btns" id="btnClose">
</div>
</div>
</div>
<form name="form1" id="form1" action="" method="post">
<!-- Hidden fields -->
<input type="hidden" id="TARGET_OBJID" name="TARGET_OBJID" value="${param.objId}">
<!-- 상단: 품번/품명 입력 및 BOM 선택 영역 -->
<div class="top-input-section">
<table class="pmsPopuptable" style="margin-bottom: 10px;">
<colgroup>
<col width="10%">
<col width="25%">
<col width="10%">
<col width="25%">
<col width="30%">
</colgroup>
<tr>
<td class="input_title"><label for="COPY_PART_NO">품번</label></td>
<td>
<input type="text" id="COPY_PART_NO" name="COPY_PART_NO" style="width: 100%;">
</td>
<td class="input_title"><label for="COPY_PART_NAME">품명</label></td>
<td>
<input type="text" id="COPY_PART_NAME" name="COPY_PART_NAME" style="width: 100%;">
</td>
<td style="text-align: center;">
<input type="button" value="담기" class="plm_btns" id="btnAddItem">
<input type="button" value="저장" class="plm_btns" id="btnSaveItem">
</td>
</tr>
</table>
<table class="pmsPopuptable">
<colgroup>
<col width="15%">
<col width="85%">
</colgroup>
<tr>
<td class="input_title"><label for="EBOM_PART_NO">E-BOM 품번</label></td>
<td>
<input type="text" id="EBOM_PART_NO" name="EBOM_PART_NO" style="width: 400px; margin-right: 10px;">
<input type="button" value="E-BOM 선택" class="plm_btns" id="btnSelectEbom">
</td>
</tr>
<tr>
<td class="input_title"><label for="MBOM_PART_NO">M-BOM 품번</label></td>
<td>
<input type="text" id="MBOM_PART_NO" name="MBOM_PART_NO" style="width: 400px; margin-right: 10px;">
<input type="button" value="M-BOM 선택" class="plm_btns" id="btnSelectMbom">
</td>
</tr>
</table>
</div>
<!-- 중간: BOM 미리보기 그리드 -->
<div id="structureTableWrap1">
<div id="structureName">
<span id="bomPartName" style="font-weight: bold; color: #333;"></span>
<input type="button" value="Excel Download" class="plm_btns structure_btn" id="btnExcel" style="float:right;">
<input type="button" value="도면 다중 업로드" class="plm_btns structure_btn" id="btnDrawingUpload" style="float:right; margin-right:5px;">
<input type="file" id="drawingFiles" multiple style="display:none;" accept=".stp,.step,.dwg,.dxf,.pdf">
</div>
<div id="structureGrid"></div>
</div>
</form>
</body>
</html>
</html>

View File

@@ -422,16 +422,14 @@ function fn_changeRelatePartInfo(objId,rightObjId,leftObjId,leftPartNoQty,leftPa
}
</script>
</head>
<body class="backcolor" style="border:border:1px solid #ccc;">
<body>
<form name="form1" id="form1" action="" method="post">
<input type="hidden" name="objId" id="objId" value="${param.objId}" />
<div id="structurePopupBtnW" style="padding-top:20px;">
<div id="structurePopupBtnW">
<input type="button" value="변경" class="plm_btns" id="moveChange">
<br>
<input type="button" value="<<" class="plm_btns" id="moveLeft" style="margin-left:0px;">
<br>
<input type="button" value=">>" class="plm_btns" id="moveRight" style="margin-left:0px;">
<input type="button" value="<<" class="plm_btns" id="moveLeft">
<input type="button" value=">>" class="plm_btns" id="moveRight">
</div>
</form>
</body>

View File

@@ -0,0 +1,439 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*" %>
<%@include file= "/init.jsp" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<script>
// confirm/alert 헬퍼 함수
function showConfirm(options) {
if(typeof options === 'string') {
alert(options);
return Promise.resolve({isConfirmed: true});
}
var message = '';
if(options.title) message += options.title + '\n\n';
if(options.html) {
// HTML 태그 제거
message += options.html.replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]+>/g, '');
} else if(options.text) {
message += options.text;
}
if(options.showCancelButton !== false && (options.icon === 'warning' || options.icon === 'question')) {
var result = confirm(message);
return Promise.resolve({isConfirmed: result});
} else {
alert(message);
return Promise.resolve({isConfirmed: true});
}
}
$(function(){
$('.select2').select2();
//Part 연결
$("#moveLeft").click(function(){
// Tabulator에서 선택된 오른쪽 행 데이터 가져오기
var rightFrame = parent.frames['rightFrame'];
var rightSelectedRows = rightFrame.getSelectedRows ? rightFrame.getSelectedRows() : [];
if(rightSelectedRows.length === 0) {
showConfirm("선택된 파트가 없습니다.");
return false;
}
// 왼쪽 프레임에서 선택된 행 데이터 가져오기
var leftPartNoObj = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document);
var leftPartChildObjId = null;
var leftPartNo = null;
var leftPartNoQty = null;
var leftParentObjId = null;
var leftPartLastObjId = null;
var leftQtyParObjId = null;
var leftParentParts = "";
if(leftPartNoObj.length > 0) {
leftPartChildObjId = leftPartNoObj.val();
leftPartNo = leftPartNoObj.attr("data-PART_NO");
leftPartNoQty = leftPartNoObj.attr("data-PART_NO_QTY");
leftParentObjId = leftPartNoObj.attr("data-OBJID");
leftPartLastObjId = leftPartNoObj.attr("data-LAST_PART_OBJID");
leftQtyParObjId = leftPartNoObj.attr("data-PART_OBJID");
leftParentParts = leftPartNoObj.attr("data-PARENT_PARTS") || "";
}
// 같은 Part를 연결한건지 체크
var isSamePart = false;
for(var i = 0; i < rightSelectedRows.length; i++){
var rowData = rightSelectedRows[i].getData();
var rightPartNo = rowData.PART_NO;
if(rightPartNo == leftPartNo){
showConfirm({
title: '연결 불가',
html: '오류 Part No : <strong>['+rightPartNo+']</strong><br>같은 Part No끼리 연결할 수 없습니다.',
icon: 'error'
});
isSamePart = true;
break;
}
}
if(isSamePart) return false;
// 연결하려는 part가 상위에 있는 part인지 확인
var deniedPartArr = [];
if(fnc_checkNull(leftParentParts).indexOf(",") > 0){
deniedPartArr = leftParentParts.split(",");
}
var isDeniedPart = false;
for(var i = 0; i < rightSelectedRows.length; i++){
var rowData = rightSelectedRows[i].getData();
var rightPartNo = rowData.PART_NO;
var rightPartType = rowData.PART_TYPE;
if("unique" == rightPartType){
for(var j = 0 ; j < deniedPartArr.length ; j++){
if(rightPartNo == deniedPartArr[j]){
showConfirm({
title: '연결 불가',
html: '오류 Part No : <strong>['+rightPartNo+']</strong><br>이미 상위에 등록된 Part No 입니다.',
icon: 'error'
});
isDeniedPart = true;
break;
}
}
if(isDeniedPart) break;
}
}
if(isDeniedPart) return;
// 선택된 파트의 OBJID 배열 생성
var rightCheckedArr = [];
for(var i = 0; i < rightSelectedRows.length; i++){
var rowData = rightSelectedRows[i].getData();
rightCheckedArr.push(rowData.OBJID);
}
if(fnc_checkNull(leftPartNo) == ""){
var flag = fn_checkSameTopPartNo(rightCheckedArr);
if(flag == "true"){
showConfirm({
title: '중복 등록 불가',
text: '1레벨에 같은 Part No가 중복 등록될 수 없습니다.',
icon: 'error'
});
return;
}
}
// Part 연결 확인
showConfirm({
title: 'Part 연결',
text: '선택한 Part를 연결하시겠습니까?',
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '연결',
cancelButtonText: '취소',
reverseButtons: false
}).then(result => {
if (result.isConfirmed) {
fn_relatePartInfo(leftPartChildObjId, rightCheckedArr, leftPartNoQty, leftPartLastObjId, leftPartChildObjId, leftQtyParObjId);
}
});
});
//end of Part 연결
//연결된 part 삭제
$("#moveRight").click(function(){
var leftPartNoObj = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document);
var leftPartChildObjId = leftPartNoObj.val();
var leftPartNo = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-PART_NO");
var leftParentPartNo = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-PARENT_PART_NO");
var leftParentObjId = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-PARENT_OBJID");
var leftPartLastObjId = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-LAST_PART_OBJID");
fn_deletePartRelateInfo(leftPartNoObj.val(), leftPartLastObjId, leftParentPartNo, leftParentObjId, leftPartChildObjId);
});
//end of 연결된 part 삭제
//연결된 part 변경
$("#moveChange").click(function(){
var leftPartNoList = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document);
if(leftPartNoList.length === 0){
showConfirm("선택된 파트가 없습니다.");
return false;
}
if(leftPartNoList.length > 1){
showConfirm("한번에 1개의 파트만 변경가능합니다.");
return false;
}
var leftPartNo = leftPartNoList.attr("data-PART_NO");
var leftPartObjid = leftPartNoList.attr("data-BOM_LAST_PART_OBJID");
var leftParentPartObjid = leftPartNoList.attr("data-PARENT_PART_NO");
var leftPartChildObjId = leftPartNoList.val();
var leftPartBomQtyObjId = leftPartNoList.attr("data-OBJID");
// Tabulator에서 선택된 오른쪽 행 데이터 가져오기
var rightFrame = parent.frames['rightFrame'];
var rightSelectedRows = rightFrame.getSelectedRows ? rightFrame.getSelectedRows() : [];
if(rightSelectedRows.length === 0){
showConfirm("선택된 파트가 없습니다.");
return false;
}
if(rightSelectedRows.length > 1){
showConfirm("한번에 1개의 파트만 변경가능합니다.");
return false;
}
var rightRowData = rightSelectedRows[0].getData();
var rightPartNo = rightRowData.PART_NO;
var rightPartRev = rightRowData.REVISION || "";
var rightObjId = rightRowData.OBJID;
// Part 변경 확인
showConfirm({
title: 'Part 변경',
html: '선택한 Part를 변경하시겠습니까?<br><br>' +
'<strong>기존:</strong> ' + leftPartNo + '<br>' +
'<strong>변경:</strong> ' + rightPartNo,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '변경',
cancelButtonText: '취소',
reverseButtons: false
}).then(result => {
if (result.isConfirmed) {
fn_changeRelatePartInfo(leftPartBomQtyObjId, rightObjId, leftPartChildObjId, leftParentPartObjid, leftPartChildObjId, leftPartObjid, rightPartNo, rightPartRev);
}
});
});
});
//1레벨에 같은 Part No가 등록되어있는지 확인.
function fn_checkSameTopPartNo(rightCheckedArr){
var result = false;
$.ajax({
url: "/productionplanning/checkSameTopPartNo.do",
method: 'post',
traditional: true, // 배열 파라미터 처리를 위한 옵션
data: {"OBJID":$("#objId").val(), "rightCheckedArr[]":rightCheckedArr}, // [] 추가
dataType: 'json',
async:false,
success: function(data) {
result = data.result;
}
, error: function(jqxhr, status, error){
/* alert(jqxhr.statusText + ", " + status + ", " + error);
alert(jqxhr.status);
alert(jqxhr.responseText); */
}
});
return result;
}
//end of 1레벨에 같은 Part No가 등록되어있는지 확인.
//구조 연결 해제
function fn_deletePartRelateInfo(leftObjId, leftPartLastObjId, leftParentPartNo, leftParentObjId, leftPartChildObjId){
if(leftObjId == null){
showConfirm("연결 해제할 Part를 선택해 주시기 바랍니다.");
return;
}
showConfirm({
title: 'Part 연결 해제',
text: '선택한 Part의 연결을 해제하시겠습니까?',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '연결 해제',
cancelButtonText: '취소',
reverseButtons: false
}).then(result => {
if (!result.isConfirmed) return;
fn_executeDeletePartRelateInfo(leftObjId, leftPartLastObjId, leftParentPartNo, leftParentObjId, leftPartChildObjId);
});
}
// 실제 Part 연결 해제 실행 함수
function fn_executeDeletePartRelateInfo(leftObjId, leftPartLastObjId, leftParentPartNo, leftParentObjId, leftPartChildObjId){
$.ajax({
url: "/productionplanning/deleteStatusPartRelateInfo.do",
method: 'post',
data: {"OBJID":$("#objId").val(), "leftObjId":leftObjId, "partObjId":leftPartLastObjId, "BOM_REPORT_OBJID":$("#objId").val()
, "leftPartChildObjId":leftPartChildObjId, "leftParentPartNo":leftParentPartNo, "leftParentObjId":leftParentObjId},
dataType: 'json',
success: function(data) {
if(data.result){
$(parent.frames['leftFrame'].document.location.reload());
//$(parent.frames['rightFrame'].fn_searchPart());
// 부모 창(M-BOM 목록) 새로고침
if(window.opener && window.opener.fn_search) {
window.opener.fn_search();
}
}
}
, error: function(jqxhr, status, error){
/* alert(jqxhr.statusText + ", " + status + ", " + error);
alert(jqxhr.status);
alert(jqxhr.responseText); */
}
});
}
//end of 구조 연결 해제
//구조 연결
function fn_relatePartInfo(leftObjId, rightCheckedArr, leftPartNoQty, leftPartLastObjId, leftPartChildObjId, leftQtyParObjId){
if(typeof rightCheckedArr != "undefined" && rightCheckedArr.length == 0){
showConfirm("선택된 Part가 없습니다.");
return;
}
if(leftObjId == null){
showConfirm({
title: '1레벨 등록 확인',
html: '좌측에 선택된 Part정보가 없습니다.<br>이대로 연결하면 1레벨로 등록됩니다.<br><br>진행하시겠습니까?',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '진행',
cancelButtonText: '취소',
reverseButtons: false
}).then(result => {
if (result.isConfirmed) {
fn_executeRelatePartInfo(leftObjId, rightCheckedArr, leftPartNoQty, leftPartLastObjId, leftPartChildObjId, leftQtyParObjId);
}
});
return;
}
fn_executeRelatePartInfo(leftObjId, rightCheckedArr, leftPartNoQty, leftPartLastObjId, leftPartChildObjId, leftQtyParObjId);
}
// 실제 Part 연결 실행 함수
function fn_executeRelatePartInfo(leftObjId, rightCheckedArr, leftPartNoQty, leftPartLastObjId, leftPartChildObjId, leftQtyParObjId){
$.ajax({
url: "/productionplanning/relatePartInfo.do",
method: 'post',
traditional: true, // 배열 파라미터 처리를 위한 옵션
data: {
"leftObjId": leftObjId,
"leftPartNoQty": leftPartNoQty,
"OBJID": $("#objId").val(),
"rightCheckedArr[]": rightCheckedArr, // Spring의 @RequestParam(value = "rightCheckedArr[]") 형식
"partObjId": leftPartLastObjId,
"BOM_REPORT_OBJID": $("#objId").val(),
"leftPartChildObjId": leftPartChildObjId,
"leftQtyParObjId": leftQtyParObjId
},
dataType: 'json',
async:false,
success: function(data) {
if(data.result){
// 왼쪽 프레임 새로고침
$(parent.frames['leftFrame'].document.location.reload());
// 오른쪽 프레임 선택 해제 (Tabulator)
var rightFrame = parent.frames['rightFrame'];
if(rightFrame.clearSelection) {
rightFrame.clearSelection();
}
// 부모 창(M-BOM 목록) 새로고침
if(window.opener && window.opener.fn_search) {
window.opener.fn_search();
}
}
}
, error: function(jqxhr, status, error){
/* alert(jqxhr.statusText + ", " + status + ", " + error);
alert(jqxhr.status);
alert(jqxhr.responseText); */
}
});
}
//end of 구조 연결
//구조 연결 변경
function fn_changeRelatePartInfo(objId,rightObjId,leftObjId,leftPartNoQty,leftPartChildObjId,leftPartObjid,rightPartNo,rightPartRev){
$.ajax({
url: "/productionplanning/changeRelatePartInfo.do",
method: 'post',
data: {"rightObjId":rightObjId, "OBJID":objId,"BOM_REPORT_OBJID":$("#objId").val(),
"leftObjId":leftObjId,"leftPartNoQty":leftPartNoQty,"leftPartChildObjId":leftPartChildObjId,
"leftPartObjid":leftPartObjid, "rightPartNo":rightPartNo, "rightPartRev":rightPartRev},
dataType: 'json',
async:false,
success: function(data) {
if(data.result){
// 왼쪽 프레임 새로고침
$(parent.frames['leftFrame'].document.location.reload());
// 오른쪽 프레임 검색 다시 수행
var rightFrame = parent.frames['rightFrame'];
if(rightFrame.fn_searchPart) {
rightFrame.fn_searchPart();
}
// 오른쪽 프레임 선택 해제 (Tabulator)
if(rightFrame.clearSelection) {
rightFrame.clearSelection();
}
// 부모 창(M-BOM 목록) 새로고침
if(window.opener && window.opener.fn_search) {
window.opener.fn_search();
}
}
}
, error: function(jqxhr, status, error){
/* alert(jqxhr.statusText + ", " + status + ", " + error);
alert(jqxhr.status);
alert(jqxhr.responseText); */
}
});
}
</script>
</head>
<body class="backcolor" style="border:border:1px solid #ccc;">
<form name="form1" id="form1" action="" method="post">
<input type="hidden" name="objId" id="objId" value="${param.objId}" />
<div id="structurePopupBtnW" style="padding-top:20px;">
<input type="button" value="변경" class="plm_btns" id="moveChange">
<br>
<input type="button" value="<<" class="plm_btns" id="moveLeft" style="margin-left:0px;">
<br>
<input type="button" value=">>" class="plm_btns" id="moveRight" style="margin-left:0px;">
</div>
</form>
</body>
</html>

View File

@@ -0,0 +1,584 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*" %>
<%@include file= "/init.jsp" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<style>
body, html {
margin: 0;
padding: 0;
overflow: hidden;
height: auto;
}
#plmSearchZon td.label,
#plmSearchZon td:first-child {
text-align: right;
padding-right: 5px;
}
</style>
<script>
// confirm/alert 헬퍼 함수
function showConfirm(options) {
if(typeof options === 'string') {
alert(options);
return Promise.resolve({isConfirmed: true});
}
var message = '';
if(options.title) message += options.title + '\n\n';
if(options.html) {
// HTML 태그 제거
message += options.html.replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]+>/g, '');
} else if(options.text) {
message += options.text;
}
if(options.showCancelButton !== false && (options.icon === 'warning' || options.icon === 'question')) {
var result = confirm(message);
return Promise.resolve({isConfirmed: result});
} else {
alert(message);
return Promise.resolve({isConfirmed: true});
}
}
$(function(){
$('.select2').select2();
//Part 연결
$("#moveLeft").click(function(){
// Tabulator에서 선택된 오른쪽 행 데이터 가져오기
var rightFrame = parent.frames['rightFrame'];
var rightSelectedRows = rightFrame.getSelectedRows ? rightFrame.getSelectedRows() : [];
if(rightSelectedRows.length === 0) {
showConfirm("선택된 파트가 없습니다.");
return false;
}
// 왼쪽 프레임에서 선택된 행 데이터 가져오기
var leftPartNoObj = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document);
var leftPartChildObjId = null;
var leftPartNo = null;
var leftPartNoQty = null;
var leftParentObjId = null;
var leftPartLastObjId = null;
var leftQtyParObjId = null;
var leftParentParts = "";
if(leftPartNoObj.length > 0) {
leftPartChildObjId = leftPartNoObj.val();
leftPartNo = leftPartNoObj.attr("data-PART_NO");
leftPartNoQty = leftPartNoObj.attr("data-PART_NO_QTY");
leftParentObjId = leftPartNoObj.attr("data-OBJID");
leftPartLastObjId = leftPartNoObj.attr("data-LAST_PART_OBJID");
leftQtyParObjId = leftPartNoObj.attr("data-PART_OBJID");
leftParentParts = leftPartNoObj.attr("data-PARENT_PARTS") || "";
}
// 같은 Part를 연결한건지 체크
var isSamePart = false;
for(var i = 0; i < rightSelectedRows.length; i++){
var rowData = rightSelectedRows[i].getData();
var rightPartNo = rowData.PART_NO;
if(rightPartNo == leftPartNo){
showConfirm({
title: '연결 불가',
html: '오류 Part No : <strong>['+rightPartNo+']</strong><br>같은 Part No끼리 연결할 수 없습니다.',
icon: 'error'
});
isSamePart = true;
break;
}
}
if(isSamePart) return false;
// 연결하려는 part가 상위에 있는 part인지 확인
var deniedPartArr = [];
if(fnc_checkNull(leftParentParts).indexOf(",") > 0){
deniedPartArr = leftParentParts.split(",");
}
var isDeniedPart = false;
for(var i = 0; i < rightSelectedRows.length; i++){
var rowData = rightSelectedRows[i].getData();
var rightPartNo = rowData.PART_NO;
var rightPartType = rowData.PART_TYPE;
if("unique" == rightPartType){
for(var j = 0 ; j < deniedPartArr.length ; j++){
if(rightPartNo == deniedPartArr[j]){
showConfirm({
title: '연결 불가',
html: '오류 Part No : <strong>['+rightPartNo+']</strong><br>이미 상위에 등록된 Part No 입니다.',
icon: 'error'
});
isDeniedPart = true;
break;
}
}
if(isDeniedPart) break;
}
}
if(isDeniedPart) return;
// 선택된 파트의 OBJID 배열 생성
var rightCheckedArr = [];
for(var i = 0; i < rightSelectedRows.length; i++){
var rowData = rightSelectedRows[i].getData();
rightCheckedArr.push(rowData.OBJID);
}
if(fnc_checkNull(leftPartNo) == ""){
var flag = fn_checkSameTopPartNo(rightCheckedArr);
if(flag == "true"){
showConfirm({
title: '중복 등록 불가',
text: '1레벨에 같은 Part No가 중복 등록될 수 없습니다.',
icon: 'error'
});
return;
}
}
// Part 연결 확인
showConfirm({
title: 'Part 연결',
text: '선택한 Part를 연결하시겠습니까?',
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '연결',
cancelButtonText: '취소',
reverseButtons: false
}).then(result => {
if (result.isConfirmed) {
fn_relatePartInfo(leftPartChildObjId, rightCheckedArr, leftPartNoQty, leftPartLastObjId, leftPartChildObjId, leftQtyParObjId);
}
});
});
//end of Part 연결
//연결된 part 삭제
$("#moveRight").click(function(){
var leftPartNoObj = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document);
var leftPartChildObjId = leftPartNoObj.val();
var leftPartNo = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-PART_NO");
var leftParentPartNo = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-PARENT_PART_NO");
var leftParentObjId = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-PARENT_OBJID");
var leftPartLastObjId = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-LAST_PART_OBJID");
fn_deletePartRelateInfo(leftPartNoObj.val(), leftPartLastObjId, leftParentPartNo, leftParentObjId, leftPartChildObjId);
});
//end of 연결된 part 삭제
//연결된 part 변경
$("#moveChange").click(function(){
var leftPartNoList = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document);
if(leftPartNoList.length === 0){
showConfirm("선택된 파트가 없습니다.");
return false;
}
if(leftPartNoList.length > 1){
showConfirm("한번에 1개의 파트만 변경가능합니다.");
return false;
}
var leftPartNo = leftPartNoList.attr("data-PART_NO");
var leftPartObjid = leftPartNoList.attr("data-BOM_LAST_PART_OBJID");
var leftParentPartObjid = leftPartNoList.attr("data-PARENT_PART_NO");
var leftPartChildObjId = leftPartNoList.val();
var leftPartBomQtyObjId = leftPartNoList.attr("data-OBJID");
// Tabulator에서 선택된 오른쪽 행 데이터 가져오기
var rightFrame = parent.frames['rightFrame'];
var rightSelectedRows = rightFrame.getSelectedRows ? rightFrame.getSelectedRows() : [];
if(rightSelectedRows.length === 0){
showConfirm("선택된 파트가 없습니다.");
return false;
}
if(rightSelectedRows.length > 1){
showConfirm("한번에 1개의 파트만 변경가능합니다.");
return false;
}
var rightRowData = rightSelectedRows[0].getData();
var rightPartNo = rightRowData.PART_NO;
var rightPartRev = rightRowData.REVISION || "";
var rightObjId = rightRowData.OBJID;
// Part 변경 확인
showConfirm({
title: 'Part 변경',
html: '선택한 Part를 변경하시겠습니까?<br><br>' +
'<strong>기존:</strong> ' + leftPartNo + '<br>' +
'<strong>변경:</strong> ' + rightPartNo,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '변경',
cancelButtonText: '취소',
reverseButtons: false
}).then(result => {
if (result.isConfirmed) {
fn_changeRelatePartInfo(leftPartBomQtyObjId, rightObjId, leftPartChildObjId, leftParentPartObjid, leftPartChildObjId, leftPartObjid, rightPartNo, rightPartRev);
}
});
});
// 조회 버튼 클릭
$("#btnSearch").click(function(){
fn_searchMbom();
});
// 저장 버튼 클릭
$("#btnSave").click(function(){
fn_saveMbom();
});
// BOM 복사 버튼 클릭
$("#btnBomCopy").click(function(){
fn_openBomCopyPopup();
});
});
//1레벨에 같은 Part No가 등록되어있는지 확인.
function fn_checkSameTopPartNo(rightCheckedArr){
var result = false;
$.ajax({
url: "/productionplanning/checkSameTopPartNo.do",
method: 'post',
traditional: true, // 배열 파라미터 처리를 위한 옵션
data: {"OBJID":$("#objId").val(), "rightCheckedArr[]":rightCheckedArr}, // [] 추가
dataType: 'json',
async:false,
success: function(data) {
result = data.result;
}
, error: function(jqxhr, status, error){
/* alert(jqxhr.statusText + ", " + status + ", " + error);
alert(jqxhr.status);
alert(jqxhr.responseText); */
}
});
return result;
}
//end of 1레벨에 같은 Part No가 등록되어있는지 확인.
//구조 연결 해제
function fn_deletePartRelateInfo(leftObjId, leftPartLastObjId, leftParentPartNo, leftParentObjId, leftPartChildObjId){
if(leftObjId == null){
showConfirm("연결 해제할 Part를 선택해 주시기 바랍니다.");
return;
}
showConfirm({
title: 'Part 연결 해제',
text: '선택한 Part의 연결을 해제하시겠습니까?',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '연결 해제',
cancelButtonText: '취소',
reverseButtons: false
}).then(result => {
if (!result.isConfirmed) return;
fn_executeDeletePartRelateInfo(leftObjId, leftPartLastObjId, leftParentPartNo, leftParentObjId, leftPartChildObjId);
});
}
// 실제 Part 연결 해제 실행 함수
function fn_executeDeletePartRelateInfo(leftObjId, leftPartLastObjId, leftParentPartNo, leftParentObjId, leftPartChildObjId){
$.ajax({
url: "/productionplanning/deleteStatusPartRelateInfo.do",
method: 'post',
data: {"OBJID":$("#objId").val(), "leftObjId":leftObjId, "partObjId":leftPartLastObjId, "BOM_REPORT_OBJID":$("#objId").val()
, "leftPartChildObjId":leftPartChildObjId, "leftParentPartNo":leftParentPartNo, "leftParentObjId":leftParentObjId},
dataType: 'json',
success: function(data) {
if(data.result){
$(parent.frames['leftFrame'].document.location.reload());
//$(parent.frames['rightFrame'].fn_searchPart());
// 부모 창(E-BOM 목록) 새로고침
if(window.opener && window.opener.fn_search) {
window.opener.fn_search();
}
}
}
, error: function(jqxhr, status, error){
/* alert(jqxhr.statusText + ", " + status + ", " + error);
alert(jqxhr.status);
alert(jqxhr.responseText); */
}
});
}
//end of 구조 연결 해제
//구조 연결
function fn_relatePartInfo(leftObjId, rightCheckedArr, leftPartNoQty, leftPartLastObjId, leftPartChildObjId, leftQtyParObjId){
if(typeof rightCheckedArr != "undefined" && rightCheckedArr.length == 0){
showConfirm("선택된 Part가 없습니다.");
return;
}
if(leftObjId == null){
showConfirm({
title: '1레벨 등록 확인',
html: '좌측에 선택된 Part정보가 없습니다.<br>이대로 연결하면 1레벨로 등록됩니다.<br><br>진행하시겠습니까?',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '진행',
cancelButtonText: '취소',
reverseButtons: false
}).then(result => {
if (result.isConfirmed) {
fn_executeRelatePartInfo(leftObjId, rightCheckedArr, leftPartNoQty, leftPartLastObjId, leftPartChildObjId, leftQtyParObjId);
}
});
return;
}
fn_executeRelatePartInfo(leftObjId, rightCheckedArr, leftPartNoQty, leftPartLastObjId, leftPartChildObjId, leftQtyParObjId);
}
// 실제 Part 연결 실행 함수
function fn_executeRelatePartInfo(leftObjId, rightCheckedArr, leftPartNoQty, leftPartLastObjId, leftPartChildObjId, leftQtyParObjId){
$.ajax({
url: "/productionplanning/relatePartInfo.do",
method: 'post',
traditional: true, // 배열 파라미터 처리를 위한 옵션
data: {
"leftObjId": leftObjId,
"leftPartNoQty": leftPartNoQty,
"OBJID": $("#objId").val(),
"rightCheckedArr[]": rightCheckedArr, // Spring의 @RequestParam(value = "rightCheckedArr[]") 형식
"partObjId": leftPartLastObjId,
"BOM_REPORT_OBJID": $("#objId").val(),
"leftPartChildObjId": leftPartChildObjId,
"leftQtyParObjId": leftQtyParObjId
},
dataType: 'json',
async:false,
success: function(data) {
if(data.result){
// 왼쪽 프레임 새로고침
$(parent.frames['leftFrame'].document.location.reload());
// 오른쪽 프레임 선택 해제 (Tabulator)
var rightFrame = parent.frames['rightFrame'];
if(rightFrame.clearSelection) {
rightFrame.clearSelection();
}
// 부모 창(E-BOM 목록) 새로고침
if(window.opener && window.opener.fn_search) {
window.opener.fn_search();
}
}
}
, error: function(jqxhr, status, error){
/* alert(jqxhr.statusText + ", " + status + ", " + error);
alert(jqxhr.status);
alert(jqxhr.responseText); */
}
});
}
//end of 구조 연결
//구조 연결 변경
function fn_changeRelatePartInfo(objId,rightObjId,leftObjId,leftPartNoQty,leftPartChildObjId,leftPartObjid,rightPartNo,rightPartRev){
$.ajax({
url: "/productionplanning/changeRelatePartInfo.do",
method: 'post',
data: {"rightObjId":rightObjId, "OBJID":objId,"BOM_REPORT_OBJID":$("#objId").val(),
"leftObjId":leftObjId,"leftPartNoQty":leftPartNoQty,"leftPartChildObjId":leftPartChildObjId,
"leftPartObjid":leftPartObjid, "rightPartNo":rightPartNo, "rightPartRev":rightPartRev},
dataType: 'json',
async:false,
success: function(data) {
if(data.result){
// 왼쪽 프레임 새로고침
$(parent.frames['leftFrame'].document.location.reload());
// 오른쪽 프레임 검색 다시 수행
var rightFrame = parent.frames['rightFrame'];
if(rightFrame.fn_searchPart) {
rightFrame.fn_searchPart();
}
// 오른쪽 프레임 선택 해제 (Tabulator)
if(rightFrame.clearSelection) {
rightFrame.clearSelection();
}
// 부모 창(E-BOM 목록) 새로고침
if(window.opener && window.opener.fn_search) {
window.opener.fn_search();
}
}
}
, error: function(jqxhr, status, error){
/* alert(jqxhr.statusText + ", " + status + ", " + error);
alert(jqxhr.status);
alert(jqxhr.responseText); */
}
});
}
// M-BOM 조회
function fn_searchMbom() {
var partNo = $("#search_part_no").val().trim();
var partName = $("#search_part_name").val().trim();
var mbomPartNo = $("#search_mbom_part_no").val().trim();
var saveDate = $("#search_save_date").val().trim();
// 왼쪽 프레임의 조회 함수 호출
var leftFrame = parent.frames['leftFrame'];
if(leftFrame && leftFrame.fn_searchMbom) {
leftFrame.fn_searchMbom({
partNo: partNo,
partName: partName,
mbomPartNo: mbomPartNo,
saveDate: saveDate
});
}
}
// M-BOM 저장
function fn_saveMbom() {
// 왼쪽 프레임에서 M-BOM 트리 데이터 가져오기
var leftFrame = parent.frames['leftFrame'];
if(!leftFrame) {
alert("M-BOM 데이터를 가져올 수 없습니다.");
return;
}
// 트리 데이터 수집 (leftFrame에서 구현 필요)
var mbomData = leftFrame.getMbomTreeData ? leftFrame.getMbomTreeData() : null;
if(!mbomData || mbomData.length === 0) {
alert("저장할 M-BOM 데이터가 없습니다.");
return;
}
// 저장 확인
if(!confirm("M-BOM을 저장하시겠습니까?")) {
return;
}
// 저장 API 호출
$.ajax({
url: "/productionplanning/saveMbom.do",
method: 'post',
data: JSON.stringify({
mbomData: mbomData,
partNo: $("#search_part_no").val().trim(),
partName: $("#search_part_name").val().trim()
}),
contentType: 'application/json',
dataType: 'json',
success: function(data) {
if(data && data.result === "success") {
alert("M-BOM이 저장되었습니다.");
// 조회 새로고침
fn_searchMbom();
// 부모 창 새로고침
if(window.opener && window.opener.fn_search) {
window.opener.fn_search();
}
} else {
alert("M-BOM 저장에 실패했습니다.");
}
},
error: function(jqxhr, status, error){
console.error("M-BOM 저장 오류:", error);
alert("M-BOM 저장 중 오류가 발생했습니다.");
}
});
}
// BOM 복사 팝업 열기
function fn_openBomCopyPopup() {
var objId = $("#objId").val();
if(!objId || objId === "-1") {
alert("프로젝트 정보를 찾을 수 없습니다.");
return;
}
// BOM 복사 팝업 열기 (저번에 만든 화면)
var url = "/partMng/structureBomCopyFormPopup.do?objId=" + objId;
var popupName = "bomCopyPopup";
var popupWidth = 1400;
var popupHeight = 800;
var left = (screen.width - popupWidth) / 2;
var top = (screen.height - popupHeight) / 2;
window.open(url, popupName, "width=" + popupWidth + ",height=" + popupHeight + ",left=" + left + ",top=" + top + ",scrollbars=yes,resizable=yes");
}
</script>
</head>
<body>
<form name="form1" id="form1" action="" method="post">
<input type="hidden" name="objId" id="objId" value="${param.objId}" />
<div id="plmSearchZon">
<table>
<tr>
<td><label for="search_part_no">품번</label></td>
<td>
<input type="text" name="search_part_no" id="search_part_no" value="">
</td>
<td class="label"><label for="search_part_name">품명</label></td>
<td>
<input type="text" name="search_part_name" id="search_part_name" value="">
</td>
</tr>
<tr>
<td><label for="search_mbom_part_no">M-BOM 품번</label></td>
<td>
<input type="text" name="search_mbom_part_no" id="search_mbom_part_no" value="">
</td>
<td class="label"><label for="search_save_date">저장일</label></td>
<td>
<input type="date" name="search_save_date" id="search_save_date" value="">
</td>
<td>
<input type="button" value="조회" class="plm_btns" id="btnSearch">
<input type="button" value="저장" class="plm_btns" id="btnSave">
<input type="button" value="BOM 복사" class="plm_btns" id="btnBomCopy">
</td>
</tr>
</table>
</div>
</form>
</body>
</html>

View File

@@ -34,6 +34,11 @@ $(document).ready(function(){
fn_search();
});
// BOM 복사 버튼
$("#btnBomCopy").click(function(){
fn_openBomCopyPopup();
});
// 전체 체크박스
$(document).on('click', '#checkAll', function() {
$('.rowCheck').prop('checked', $(this).prop('checked'));
@@ -61,7 +66,7 @@ var columns = [
{title:'CONTRACT_OBJID', field:'CONTRACT_OBJID', visible: false},
{title:'BOM_REPORT_OBJID', field:'BOM_REPORT_OBJID', visible: false},
// 체크박스
// 1. 체크박스
{
headerHozAlign: 'center',
hozAlign: 'center',
@@ -74,7 +79,7 @@ var columns = [
headerSort: false
},
// 프로젝트번호
// 2. 프로젝트번호
{
headerHozAlign: 'center',
hozAlign: 'left',
@@ -83,7 +88,7 @@ var columns = [
field: 'PROJECT_NO'
},
// 주문유형
// 3. 주문유형
{
headerHozAlign: 'center',
hozAlign: 'center',
@@ -92,7 +97,7 @@ var columns = [
field: 'CATEGORY_NAME'
},
// 제품구분
// 4. 제품구분
{
headerHozAlign: 'center',
hozAlign: 'center',
@@ -101,7 +106,7 @@ var columns = [
field: 'PRODUCT_NAME'
},
// 국내/해외
// 5. 국내/해외
{
headerHozAlign: 'center',
hozAlign: 'center',
@@ -110,7 +115,7 @@ var columns = [
field: 'AREA_NAME'
},
// 접수일
// 6. 접수일
{
headerHozAlign: 'center',
hozAlign: 'center',
@@ -119,7 +124,7 @@ var columns = [
field: 'RECEIPT_DATE'
},
// 고객사
// 7. 고객사
{
headerHozAlign: 'center',
hozAlign: 'left',
@@ -128,7 +133,7 @@ var columns = [
field: 'CUSTOMER_NAME'
},
// 유/무상
// 8. 유/무상
{
headerHozAlign: 'center',
hozAlign: 'center',
@@ -137,7 +142,7 @@ var columns = [
field: 'PAID_TYPE_NAME'
},
// 품번
// 9. 품번
{
headerHozAlign: 'center',
hozAlign: 'left',
@@ -146,7 +151,7 @@ var columns = [
field: 'PART_NO'
},
// 품명
// 10. 품명
{
headerHozAlign: 'center',
hozAlign: 'left',
@@ -155,7 +160,7 @@ var columns = [
field: 'PART_NAME'
},
// S/N
// 11. S/N
{
headerHozAlign: 'center',
hozAlign: 'center',
@@ -164,7 +169,7 @@ var columns = [
field: 'SERIAL_NO'
},
// 수주수량
// 12. 수주수량
{
headerHozAlign: 'center',
hozAlign: 'right',
@@ -173,7 +178,7 @@ var columns = [
field: 'QUANTITY'
},
// 요청납기
// 13. 요청납기
{
headerHozAlign: 'center',
hozAlign: 'center',
@@ -182,7 +187,7 @@ var columns = [
field: 'REQ_DEL_DATE'
},
// 고객사요청사항
// 14. 고객사요청사항
{
headerHozAlign: 'center',
hozAlign: 'left',
@@ -191,35 +196,7 @@ var columns = [
field: 'CUSTOMER_REQUEST'
},
// E-BOM
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: 'E-BOM',
field: 'EBOM_STATUS',
formatter: fnc_subInfoValueFormatter,
cellClick: function(e, cell) {
var bomReportObjid = fnc_checkNull(cell.getData().BOM_REPORT_OBJID);
var projectMgmtObjid = fnc_checkNull(cell.getData().OBJID);
var partNo = fnc_checkNull(cell.getData().PART_NO);
var partName = fnc_checkNull(cell.getData().PART_NAME);
// E-BOM이 있든 없든 선택 팝업 열기 (할당된 경우 상세 + 변경 가능)
fn_openEBomSelectPopup(projectMgmtObjid, partNo, partName, bomReportObjid);
}
},
// E-BOM 작성일
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '작성일',
field: 'EBOM_REGDATE'
},
// M-BOM
// 15. M-BOM
{
headerHozAlign: 'center',
hozAlign: 'center',
@@ -228,33 +205,58 @@ var columns = [
field: 'MBOM_STATUS',
formatter: fnc_subInfoValueFormatter,
cellClick: function(e, cell) {
var objid = fnc_checkNull(cell.getData().OBJID);
fn_openMBomPopup(objid);
var rowData = cell.getData();
var objid = fnc_checkNull(rowData.OBJID);
var mbomStatus = fnc_checkNull(rowData.MBOM_STATUS);
// 파란색(저장된 M-BOM)일 때만 팝업 열기
if(mbomStatus !== '' && mbomStatus !== '0') {
fn_openMBomFormPopup(objid);
} else {
Swal.fire({
title: '알림',
text: 'M-BOM이 생성되지 않았습니다.\nBOM 복사 버튼을 통해 먼저 M-BOM을 생성해주세요.',
icon: 'info'
});
}
}
},
// M-BOM Version
// 16. 최종저장일 (M-BOM 작성일)
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 80,
title: 'Version',
field: 'MBOM_VERSION'
},
// M-BOM 작성일
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '작성일',
width: 120,
title: '최종저장일',
field: 'MBOM_REGDATE'
}
];
// 검색 함수
function fn_search(){
_tabulGrid = fnc_tabul_search(_tabul_layout_fitColumns, _tabulGrid, "/productionplanning/mBomMgmtGridList.do", columns, true);
// showCheck를 false로 설정하여 자동 체크박스 제거 (columns에 이미 체크박스 정의되어 있음)
_tabulGrid = fnc_tabul_search(_tabul_layout_fitColumns, _tabulGrid, "/productionplanning/mBomMgmtGridList.do", columns, false);
// 그리드 로드 완료 후 행 클릭 이벤트 추가
if(_tabulGrid) {
_tabulGrid.on("rowClick", function(e, row){
// 체크박스를 직접 클릭한 경우는 제외
if($(e.target).is('input[type="checkbox"]')) {
return;
}
// M-BOM 아이콘을 클릭한 경우는 제외
if($(e.target).closest('.clip_icon, .hyphen_icon').length > 0) {
return;
}
// 행 클릭 시 체크박스 토글
var checkbox = $(row.getElement()).find('.rowCheck');
if(checkbox.length > 0) {
checkbox.prop('checked', !checkbox.prop('checked'));
}
});
}
}
// E-BOM 팝업
@@ -281,12 +283,82 @@ function fn_openEBomSelectPopup(projectMgmtObjid, partNo, partName, bomReportObj
fn_centerPopup(popup_width, popup_height, url, 'ebomSelectPopup');
}
// M-BOM 팝업
// M-BOM 조회 팝업 (읽기 전용)
function fn_openMBomPopup(objId) {
var popup_width = 1000;
var popup_height = 700;
var url = "/productionplanning/mBomViewPopup.do?objId=" + objId;
fn_centerPopup(popup_width, popup_height, url, 'mbomViewPopup');
}
// M-BOM 편집 팝업 (왼쪽 트리 + 중앙 버튼 + 오른쪽 E-BOM)
function fn_openMBomFormPopup(objId) {
var popup_width = 1800;
var popup_height = 800;
var popup_height = 900;
var url = "/productionplanning/mBomFormPopup.do?objId=" + objId;
fn_centerPopup(popup_width, popup_height, url, 'mbomPopup');
fn_centerPopup(popup_width, popup_height, url, 'mbomFormPopup');
}
// BOM 복사 팝업
function fn_openBomCopyPopup() {
// 체크된 행이 있는지 확인
var checkedRows = $('.rowCheck:checked');
if(checkedRows.length === 0) {
Swal.fire({
title: '선택 필요',
text: 'BOM을 복사할 프로젝트를 선택해주세요.',
icon: 'warning'
});
return;
}
if(checkedRows.length > 1) {
Swal.fire({
title: '선택 오류',
text: '한 번에 하나의 프로젝트만 선택해주세요.',
icon: 'warning'
});
return;
}
// 선택된 행의 데이터 가져오기
var selectedObjId = checkedRows.first().data('objid');
var selectedRow = _tabulGrid.searchRows("OBJID", "=", selectedObjId);
if(selectedRow.length === 0) {
Swal.fire('선택된 데이터를 찾을 수 없습니다.');
return;
}
var rowData = selectedRow[0].getData();
var mbomStatus = fnc_checkNull(rowData.MBOM_STATUS);
// M-BOM이 이미 생성되어 있는 경우 경고
if(mbomStatus !== '' && mbomStatus !== '0') {
Swal.fire({
title: '경고',
text: '저장된 M-BOM이 초기화 됩니다.\n계속하시겠습니까?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: '확인',
cancelButtonText: '취소'
}).then((result) => {
if(result.isConfirmed) {
fn_openBomCopyPopupWindow(selectedObjId);
}
});
} else {
fn_openBomCopyPopupWindow(selectedObjId);
}
}
// BOM 복사 팝업 창 열기
function fn_openBomCopyPopupWindow(objId) {
var popup_width = 1800;
var popup_height = 900;
var url = "/partMng/structureBomCopyFormPopup.do?objId=" + objId;
fn_centerPopup(popup_width, popup_height, url, 'bomCopyPopup');
}
</script>
@@ -300,6 +372,7 @@ function fn_openMBomPopup(objId) {
</h2>
<div class="btnArea">
<input type="button" class="plm_btns" value="조회" id="btnSearch">
<input type="button" class="plm_btns" value="BOM 복사" id="btnBomCopy">
</div>
</div>

View File

@@ -0,0 +1,146 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*" %>
<%@include file= "/init.jsp" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<script>
// confirm/alert 헬퍼 함수
function showConfirm(options) {
if(typeof options === 'string') {
alert(options);
return Promise.resolve({isConfirmed: true});
}
var message = '';
if(options.title) message += options.title + '\n\n';
if(options.html) {
// HTML 태그 제거
message += options.html.replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]+>/g, '');
} else if(options.text) {
message += options.text;
}
if(options.showCancelButton !== false && (options.icon === 'warning' || options.icon === 'question')) {
var result = confirm(message);
return Promise.resolve({isConfirmed: result});
} else {
alert(message);
return Promise.resolve({isConfirmed: true});
}
}
$(function(){
$('.select2').select2();
// E-BOM 할당 버튼 (변경)
$("#moveChange").click(function(){
showConfirm({
title: 'E-BOM 할당',
text: 'E-BOM을 선택하여 이 프로젝트에 할당하시겠습니까?',
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '할당',
cancelButtonText: '취소'
}).then(result => {
if (result.isConfirmed) {
fn_assignEbom();
}
});
});
// << 버튼 (현재는 사용 안 함)
$("#moveLeft").click(function(){
showConfirm("이 기능은 현재 사용할 수 없습니다.");
});
// >> 버튼 (현재는 사용 안 함)
$("#moveRight").click(function(){
showConfirm("이 기능은 현재 사용할 수 없습니다.");
});
});
// E-BOM 할당 함수
function fn_assignEbom(){
// 오른쪽 프레임에서 선택된 E-BOM 가져오기
var rightFrame = parent.frames['rightFrame'];
var rightSelectedRows = rightFrame.getSelectedRows ? rightFrame.getSelectedRows() : [];
if(rightSelectedRows.length === 0){
showConfirm("할당할 E-BOM을 선택해주세요.");
return false;
}
if(rightSelectedRows.length > 1){
showConfirm("한 번에 1개의 E-BOM만 할당할 수 있습니다.");
return false;
}
var rowData = rightSelectedRows[0].getData();
var bomReportObjid = rowData.OBJID;
$.ajax({
url: "/productionplanning/assignEbomToMbom.do",
method: 'post',
data: {
"projectMgmtObjid": $("#objId").val(),
"bomReportObjid": bomReportObjid
},
dataType: 'json',
success: function(data) {
if(data.result){
showConfirm({
title: '할당 완료',
text: 'E-BOM이 성공적으로 할당되었습니다.',
icon: 'success'
});
// 왼쪽 프레임 새로고침
parent.frames['leftFrame'].location.reload();
// 부모 창(M-BOM 목록) 새로고침
if(window.opener && window.opener.fn_search) {
window.opener.fn_search();
}
} else {
showConfirm({
title: '할당 실패',
text: 'E-BOM 할당 중 오류가 발생했습니다.',
icon: 'error'
});
}
},
error: function(jqxhr, status, error){
showConfirm({
title: '오류',
text: 'E-BOM 할당 중 오류가 발생했습니다.',
icon: 'error'
});
}
});
}
</script>
</head>
<body>
<form name="form1" id="form1" action="" method="post">
<input type="hidden" name="objId" id="objId" value="${param.objId}" />
<div id="structurePopupBtnW" style="padding-top:20px;">
<input type="button" value="변경" class="plm_btns" id="moveChange">
<br>
<input type="button" value="<<" class="plm_btns" id="moveLeft" style="margin-left:0px;">
<br>
<input type="button" value=">>" class="plm_btns" id="moveRight" style="margin-left:0px;">
</div>
</form>
</body>
</html>

View File

@@ -0,0 +1,7 @@
<%String objId = com.pms.common.utils.CommonUtils.checkNull(request.getParameter("objId"));%>
<frameset cols="*, 100px, *" border="0" noresize>
<frame src="/productionplanning/mBomPopupLeft.do?objId=<%=objId%>" name="leftFrame">
<frame src="/productionplanning/mBomPopupCenter.do?objId=<%=objId%>" name="centerFrame">
<frame src="/productionplanning/mBomPopupRight.do?objId=<%=objId%>" name="rightFrame">
</frameset><noframes></noframes>

View File

@@ -0,0 +1,14 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
java.util.Map map = (java.util.HashMap)request.getAttribute("info");
if(map == null || map.isEmpty()) {
response.sendRedirect("/common/error500.do");
return;
}
String objId = com.pms.common.utils.CommonUtils.checkNull(map.get("OBJID"));
%>
<frameset rows="100px, *" border="0" noresize>
<frame src="/productionplanning/mBomHeaderPopup.do?objId=<%=objId%>">
<frame src="/productionplanning/mBomBottomPopupFS.do?objId=<%=objId%>">
</frameset><noframes></noframes>

View File

@@ -0,0 +1,231 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*" %>
<%@include file= "/init.jsp" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<link href="/css/tabulator/tabulator.min.css" rel="stylesheet">
<script type="text/javascript" src="/js/tabulator/tabulator.min.js"></script>
<style>
::-webkit-scrollbar {
width: 10px;
height: 15px;
}
#mBomTableWrap {
width: 99%;
margin: 10px auto;
}
#mBomName {
margin-bottom: 10px;
font-weight: bold;
font-size: 16px;
}
/* Tabulator 커스텀 스타일 */
.tabulator-row.level-1 { background-color: #fde9d9 !important; }
.tabulator-row.level-2 { background-color: #daeef3 !important; }
.tabulator-row.level-3 { background-color: #e4dfec !important; }
.tabulator-row.level-4 { background-color: #ebf1de !important; }
.tabulator-row.level-5 { background-color: #f2f2f2 !important; }
.tabulator-row.level-6 { background-color: #f2dcdb !important; }
.tabulator-row.level-7 { background-color: #eeece1 !important; }
.tabulator-row.level-8 { background-color: #dce6f1 !important; }
.tabulator-row.level-9 { background-color: #FFFFEB !important; }
.tabulator-row.level-10 { background-color: #ffffff !important; }
</style>
<script>
var _tabulGrid;
$(function(){
// Tabulator 초기화
fn_initGrid();
});
// Tabulator 그리드 초기화
function fn_initGrid() {
var maxLevel = ${empty MAXLEV ? 1 : MAXLEV};
// 컬럼 정의
var columns = [];
// 수준 컬럼 그룹
var levelColumns = [];
for(var i = 1; i <= maxLevel; i++) {
levelColumns.push({
headerHozAlign: 'center',
hozAlign: 'center',
width: 30,
title: i,
field: 'LEVEL_' + i,
formatter: function(cell) {
return cell.getValue() === '*' ? '*' : '';
}
});
}
columns.push({
title: '수준',
headerHozAlign: 'center',
columns: levelColumns
});
// 나머지 컬럼 추가
columns.push(
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
title: '품번',
field: 'PART_NO'
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 250,
title: '품명',
field: 'PART_NAME'
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 80,
title: '수량',
field: 'QTY_TEMP'
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '항목 수량',
field: 'ITEM_QTY'
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 80,
title: 'Rev',
field: 'REVISION'
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
title: '규격',
field: 'SPEC'
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '제품구분',
field: 'PRODUCT_NAME'
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '상태',
field: 'STATUS_NAME'
}
);
// Tabulator 생성
_tabulGrid = new Tabulator("#mBomTableWrap", {
layout: "fitColumns",
height: "calc(100vh - 150px)",
columns: columns,
data: ${bomTreeListJson},
rowFormatter: function(row) {
var data = row.getData();
var level = data.LEVEL || 1;
row.getElement().classList.add('level-' + level);
}
});
}
// 필터 적용 함수
function fn_applyFilter() {
var partNo = $("#filterPartNo").val().trim();
var partName = $("#filterPartName").val().trim();
if(!partNo && !partName) {
_tabulGrid.clearFilter();
return;
}
_tabulGrid.setFilter([
[
{field: "PART_NO", type: "like", value: partNo},
{field: "PART_NAME", type: "like", value: partName}
]
]);
}
// 필터 초기화 함수
function fn_resetFilter() {
$("#filterPartNo").val("");
$("#filterPartName").val("");
_tabulGrid.clearFilter();
}
// M-BOM 조회 (헤더 프레임에서 호출)
function fn_searchMbom(searchParams) {
$.ajax({
url: "/productionplanning/getMbomList.do",
method: 'post',
data: searchParams,
dataType: 'json',
success: function(data) {
if(data && data.list) {
_tabulGrid.setData(data.list);
} else {
_tabulGrid.setData([]);
}
},
error: function(jqxhr, status, error){
console.error("M-BOM 조회 오류:", error);
_tabulGrid.setData([]);
}
});
}
// M-BOM 트리 데이터 수집 (저장용)
function getMbomTreeData() {
var allData = _tabulGrid.getData();
// 데이터 구조 변환 (필요한 필드만 추출)
var mbomData = allData.map(function(row) {
return {
PART_NO: row.PART_NO,
PART_NAME: row.PART_NAME,
QTY: row.QTY_TEMP || row.ITEM_QTY,
LEVEL: row.LEVEL,
REVISION: row.REVISION,
SPEC: row.SPEC,
PRODUCT_NAME: row.PRODUCT_NAME,
STATUS_NAME: row.STATUS_NAME,
OBJID: row.OBJID
};
});
return mbomData;
}
</script>
</head>
<body>
<div id="mBomName">
<c:choose>
<c:when test="${not empty ebomInfo}">
E-BOM: ${ebomInfo.PART_NO} - ${ebomInfo.PART_NAME}
</c:when>
<c:otherwise>
할당된 E-BOM이 없습니다.
</c:otherwise>
</c:choose>
</div>
<div id="mBomTableWrap"></div>
</body>
</html>

View File

@@ -0,0 +1,161 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*" %>
<%@include file= "/init.jsp" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<style>
body, html {
margin: 0;
padding: 10px;
height: 100%;
overflow: hidden;
}
#plmSearchZon {
margin-bottom: 10px;
}
#plmSearchZon table {
width: 100%;
}
#plmSearchZon td {
padding: 2px 5px;
}
#plmSearchZon td.label,
#plmSearchZon td:first-child {
text-align: right;
padding-right: 5px;
}
#ebomTable {
height: calc(100% - 80px);
}
</style>
<link href="/css/tabulator/tabulator.min.css" rel="stylesheet">
<script type="text/javascript" src="/js/tabulator/tabulator.min.js"></script>
</head>
<body class="backcolor">
<form name="form1" id="form1" action="" method="post">
<div id="plmSearchZon">
<table>
<tr>
<td><label for="search_part_no">품번</label></td>
<td>
<input type="text" name="search_part_no" id="search_part_no" value="">
</td>
<td class="label"><label for="search_part_name">품명</label></td>
<td>
<input type="text" name="search_part_name" id="search_part_name" value="">
</td>
</tr>
<tr>
<td><label for="search_material">재료</label></td>
<td>
<input type="text" name="search_material" id="search_material" value="">
</td>
<td class="label"><label for="search_supplier">공급업체</label></td>
<td>
<input type="text" name="search_supplier" id="search_supplier" value="">
</td>
<td>
<input type="button" value="조회" class="plm_btns" id="btnSearch">
</td>
</tr>
</table>
</div>
<div id="ebomTable"></div>
</form>
<script>
var ebomTable;
$(document).ready(function(){
initEbomTable();
fn_searchEbom();
// 조회 버튼 클릭 이벤트
$("#btnSearch").click(function(){
fn_searchEbom();
});
// 엔터키 이벤트
$("#search_part_no, #search_part_name, #search_material, #search_supplier").on('keypress', function(e){
if(e.keyCode == 13 || e.which == 13){
e.preventDefault();
fn_searchEbom();
}
});
});
// Tabulator 초기화
function initEbomTable() {
ebomTable = new Tabulator("#ebomTable", {
layout: "fitColumns",
height: "100%",
selectable: true,
columns: [
{formatter:"rowSelection", titleFormatter:"rowSelection", hozAlign:"center", vertAlign:"middle", headerSort:false, width: 40},
{title: "품번", field: "PART_NO", widthGrow: 1.5, vertAlign:"middle"},
{title: "품명", field: "PART_NAME", widthGrow: 2, vertAlign:"middle"},
{title: "재료", field: "MATERIAL", widthGrow: 1.2, vertAlign:"middle"},
{title: "공급업체", field: "SUPPLIER", widthGrow: 1.5, vertAlign:"middle"}
],
placeholder: "검색 결과가 없습니다."
});
}
// E-BOM 검색
function fn_searchEbom() {
var partNo = $("#search_part_no").val().trim();
var partName = $("#search_part_name").val().trim();
var material = $("#search_material").val().trim();
var supplier = $("#search_supplier").val().trim();
$.ajax({
url: "/productionplanning/getEbomList.do",
method: 'post',
data: {
"search_part_no": partNo,
"search_part_name": partName,
"search_material": material,
"search_supplier": supplier
},
dataType: 'json',
success: function(data) {
if(data && data.list) {
// 품번과 품명이 비어있지 않은 데이터만 필터링
var filteredList = data.list.filter(function(item) {
return item.PART_NO && item.PART_NO.trim() !== '' &&
item.PART_NAME && item.PART_NAME.trim() !== '';
});
ebomTable.setData(filteredList);
} else {
ebomTable.setData([]);
}
},
error: function(jqxhr, status, error){
console.error("E-BOM 조회 오류:", error);
ebomTable.setData([]);
}
});
}
// 선택된 행 가져오기 (중앙 프레임에서 호출)
function getSelectedRows() {
return ebomTable.getSelectedRows();
}
// 선택 해제
function clearSelection() {
ebomTable.deselectRow();
}
</script>
</body>
</html>

View File

@@ -0,0 +1,140 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*" %>
<%@include file= "/init.jsp" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<style>
body, html {
margin: 0;
padding: 0;
overflow: visible;
height: auto;
}
.plm_menu_name {
margin: 5px 15px 0 15px !important;
}
.plm_menu_name h2 {
margin: 5px 0 !important;
padding: 5px 0;
}
#plmSearchZon {
margin-bottom: 0 !important;
padding-bottom: 5px !important;
overflow: visible !important;
position: relative;
z-index: 1000;
}
.select2-container {
z-index: 9999 !important;
}
.select2-dropdown {
z-index: 9999 !important;
}
</style>
<script>
$(document).ready(function(){
// 엔터키 이벤트 바인딩
$("#filterPartNo, #filterPartName").on('keypress', function(e){
if(e.keyCode == 13 || e.which == 13){
e.preventDefault();
fn_applyFilter();
}
});
// 페이지 로드 시 품번/품명이 있으면 자동으로 필터 적용
var partNo = $("#filterPartNo").val().trim();
var partName = $("#filterPartName").val().trim();
if(partNo || partName) {
setTimeout(function() {
fn_applyFilter();
}, 500);
}
});
// 필터 적용 함수
function fn_applyFilter() {
var partNo = $("#filterPartNo").val().trim();
var partName = $("#filterPartName").val().trim();
try {
// 프레임 구조: parent(headerFs) -> frames[1](bottomFs) -> frames[0](leftFrame)
var leftFrame = parent.frames[1].frames['leftFrame'];
if(leftFrame && leftFrame.fn_applyFilter) {
leftFrame.$("#filterPartNo").val(partNo);
leftFrame.$("#filterPartName").val(partName);
leftFrame.fn_applyFilter();
} else {
console.error("leftFrame not found or fn_applyFilter not available");
}
} catch(e) {
console.error("Error accessing leftFrame:", e);
alert("필터를 적용할 수 없습니다. 프레임 구조를 확인해주세요.");
}
}
// 필터 초기화 함수
function fn_resetFilter() {
$("#filterPartNo").val("");
$("#filterPartName").val("");
try {
var leftFrame = parent.frames[1].frames['leftFrame'];
if(leftFrame && leftFrame.fn_resetFilter) {
leftFrame.fn_resetFilter();
}
} catch(e) {
console.error("Error accessing leftFrame:", e);
}
}
</script>
</head>
<body class="backcolor">
<form name="form1" id="form1" action="" method="post">
<div class="plm_menu_name">
<h2>
<span>M-BOM 확인</span>
<c:if test="${not empty projectNo}">
<span style="font-size: 14px; font-weight: normal; color: #666; margin-left: 20px;">
프로젝트번호: <strong style="color: #333;">${projectNo}</strong>
<c:if test="${not empty partNo}">
/ 품번: <strong style="color: #333;">${partNo}</strong>
</c:if>
<c:if test="${not empty partName}">
/ 품명: <strong style="color: #333;">${partName}</strong>
</c:if>
</span>
</c:if>
</h2>
</div>
<!-- 검색 필터 영역 -->
<div id="plmSearchZon">
<table>
<tr>
<td class="label"><label for="filterPartNo">품번</label></td>
<td>
<input type="text" id="filterPartNo" name="filterPartNo" value="${partNo}">
</td>
<td class="label"><label for="filterPartName">품명</label></td>
<td>
<input type="text" id="filterPartName" name="filterPartName" value="${partName}">
</td>
<td>
<button type="button" class="plm_btns" onclick="fn_applyFilter()">검색</button>
<button type="button" class="plm_btns" onclick="fn_resetFilter()">초기화</button>
</td>
</tr>
</table>
</div>
</form>
</body>
</html>

View File

@@ -0,0 +1,183 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*" %>
<%@include file= "/init.jsp" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<style>
body, html {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
}
.container {
display: flex;
flex-direction: column;
height: 100%;
}
.header {
padding: 15px 20px;
background: #f5f5f5;
border-bottom: 2px solid #ddd;
}
.header h3 {
margin: 0 0 10px 0;
color: #333;
}
.info-table {
width: 100%;
border-collapse: collapse;
}
.info-table td {
padding: 5px 10px;
}
.info-table label {
font-weight: bold;
margin-right: 10px;
}
.content {
flex: 1;
padding: 20px;
overflow: auto;
}
.tree-container {
border: 1px solid #ddd;
padding: 10px;
background: white;
min-height: 400px;
}
.tree-node {
padding: 5px 0;
cursor: default;
}
.tree-node-content {
padding: 5px 10px;
display: inline-block;
}
.tree-node-content:hover {
background: #f0f0f0;
}
.tree-children {
margin-left: 30px;
}
.footer {
padding: 15px 20px;
background: #f5f5f5;
border-top: 1px solid #ddd;
text-align: center;
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.12/themes/default/style.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.12/jstree.min.js"></script>
</head>
<body>
<div class="container">
<div class="header">
<h3>M-BOM 조회</h3>
<table class="info-table">
<tr>
<td>
<label>프로젝트 번호:</label>
<span id="projectNo">-</span>
</td>
<td>
<label>품번:</label>
<span id="partNo">-</span>
</td>
<td>
<label>품명:</label>
<span id="partName">-</span>
</td>
</tr>
</table>
</div>
<div class="content">
<div id="mbomTree" class="tree-container"></div>
</div>
<div class="footer">
<input type="button" value="닫기" class="plm_btns" onclick="window.close();">
</div>
</div>
<script>
var objId = "${param.objId}";
$(document).ready(function(){
loadProjectInfo();
loadMBomTree();
});
// 프로젝트 정보 로드
function loadProjectInfo() {
$.ajax({
url: "/productionplanning/getMBomHeaderInfo.do",
method: 'post',
data: { objId: objId },
dataType: 'json',
success: function(data) {
if(data && data.info) {
$("#projectNo").text(data.info.PROJECT_NO || '-');
$("#partNo").text(data.info.PART_NO || '-');
$("#partName").text(data.info.PART_NAME || '-');
}
},
error: function(jqxhr, status, error){
console.error("프로젝트 정보 로드 오류:", error);
}
});
}
// M-BOM 트리 로드
function loadMBomTree() {
$.ajax({
url: "/productionplanning/getMBomTree.do",
method: 'post',
data: { objId: objId },
dataType: 'json',
success: function(data) {
if(data && data.tree) {
initTree(data.tree);
} else {
$("#mbomTree").html('<p style="padding: 20px; text-align: center; color: #999;">저장된 M-BOM이 없습니다.</p>');
}
},
error: function(jqxhr, status, error){
console.error("M-BOM 트리 로드 오류:", error);
$("#mbomTree").html('<p style="padding: 20px; text-align: center; color: #f00;">데이터 로드 중 오류가 발생했습니다.</p>');
}
});
}
// jsTree 초기화
function initTree(treeData) {
$('#mbomTree').jstree({
'core': {
'data': treeData,
'check_callback': false,
'themes': {
'responsive': true
}
},
'plugins': ['types'],
'types': {
'default': {
'icon': 'fa fa-folder'
}
}
});
// 트리 로드 완료 후 모두 펼치기
$('#mbomTree').on('ready.jstree', function() {
$('#mbomTree').jstree('open_all');
});
}
</script>
</body>
</html>

View File

@@ -0,0 +1,177 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@include file="/init_new.jsp"%>
<%
PersonBean person = (PersonBean) session.getAttribute(Constants.PERSON_BEAN);
String userId = CommonUtils.checkNull(person.getUserId());
%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<style>
.project-detail-container {
padding: 20px;
background: white;
}
.section-title {
font-size: 14px;
font-weight: bold;
padding: 10px;
background: #f5f5f5;
border: 1px solid #ddd;
margin-bottom: 10px;
}
.info-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.info-table td {
padding: 8px;
border: 1px solid #ddd;
}
.info-label {
background: #f9f9f9;
font-weight: bold;
text-align: center;
width: 120px;
}
.item-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.item-table th, .item-table td {
padding: 8px;
border: 1px solid #ddd;
text-align: center;
}
.item-table th {
background: #f5f5f5;
font-weight: bold;
}
</style>
</head>
<body>
<section class="business_popup_min_width">
<div class="plm_menu_name">
<h2><span>프로젝트 상세 정보</span></h2>
</div>
<div class="project-detail-container">
<!-- 프로젝트 정보 -->
<div class="section-title">프로젝트정보</div>
<table class="info-table">
<tr>
<td class="info-label">주문유형</td>
<td>${info.CATEGORY_NAME}</td>
<td class="info-label">제품구분</td>
<td>${info.PRODUCT_NAME}</td>
<td class="info-label">국내/해외</td>
<td>${info.AREA_NAME}</td>
<td class="info-label">고객사</td>
<td>${info.CUSTOMER_NAME}</td>
</tr>
<tr>
<td class="info-label">유/무상</td>
<td>
<c:choose>
<c:when test="${info.PAID_TYPE == 'paid'}">유상</c:when>
<c:when test="${info.PAID_TYPE == 'free'}">무상</c:when>
<c:otherwise>${info.PAID_TYPE}</c:otherwise>
</c:choose>
</td>
<td class="info-label">접수일</td>
<td>${info.RECEIPT_DATE}</td>
<td class="info-label">견적환종</td>
<td>${info.CONTRACT_CURRENCY_NAME}</td>
<td class="info-label">견적환율</td>
<td>${info.EXCHANGE_RATE}</td>
</tr>
</table>
<!-- 품목정보 -->
<div class="section-title">품목정보</div>
<table class="item-table">
<thead>
<tr>
<th>No</th>
<th>품번</th>
<th>품명</th>
<th>S/N</th>
<th>요청납기</th>
<th>고객요청사항</th>
<th>반납사유</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>${info.PART_NO}</td>
<td>${info.PART_NAME}</td>
<td>${info.SERIAL_NO}</td>
<td>${info.REQ_DEL_DATE}</td>
<td>${info.CUSTOMER_REQUEST}</td>
<td>
<c:choose>
<c:when test="${not empty info.RETURN_REASON and info.RETURN_REASON != '-'}">
${info.RETURN_REASON}
</c:when>
<c:otherwise>-</c:otherwise>
</c:choose>
</td>
</tr>
</tbody>
</table>
<!-- 결재 라인 정보 -->
<%--
<c:if test="${not empty approvalLine}">
<div class="section-title">결재 정보</div>
<table class="item-table">
<thead>
<tr>
<th>순번</th>
<th>기안자</th>
<th>기안일</th>
<th>결재자</th>
<th>결재일</th>
<th>상태</th>
</tr>
</thead>
<tbody>
<c:forEach var="approval" items="${approvalLine}" varStatus="status">
<tr>
<td>${approval.SEQ}</td>
<td>${approval.WRITER}</td>
<td>${approval.REGDATE}</td>
<td>${approval.TARGET_USER_NAME}</td>
<td>${approval.PROC_DATE}</td>
<td>
<c:choose>
<c:when test="${approval.STATUS == 'complete'}">승인</c:when>
<c:when test="${approval.STATUS == 'reject'}">반려</c:when>
<c:when test="${approval.STATUS == 'READY'}">대기</c:when>
<c:otherwise>${approval.STATUS}</c:otherwise>
</c:choose>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</c:if>
--%>
<!-- 버튼 영역 -->
<div style="text-align:center; margin-top:20px;">
<input type="button" value="닫기" class="plm_btns" onclick="self.close();">
</div>
</div>
</section>
</body>
</html>

View File

@@ -33,6 +33,12 @@
$(document).ready(function(){
_fnc_datepick();
// 그리드 높이 동적 계산 (Total 합계 영역 + 여유 공간 80px)
fnc_calculateContentHeight("gridDiv", 80);
$(window).resize(function() {
fnc_calculateContentHeight("gridDiv", 80);
});
// select2가 로드되었을 때만 초기화
if(typeof $.fn !== 'undefined' && typeof $.fn.select2 === 'function') {
$('.select2').select2();
@@ -255,14 +261,14 @@ function fn_search(){
if(_tabulGrid){
_tabulGrid.setData(response.RESULTLIST || []);
} else {
// 그리드 초기화
_tabulGrid = new Tabulator("#mainGrid", {
layout: _tabul_layout_fitColumns,
height: "650px", // 그리드 고정 높이 설정
columns: columns,
data: response.RESULTLIST || [],
selectable: "highlight" // 다중 선택 가능하도록 설정
});
// 그리드 초기화
_tabulGrid = new Tabulator("#mainGrid", {
layout: _tabul_layout_fitColumns,
height: "100%", // 부모 컨테이너 높이에 맞춤
columns: columns,
data: response.RESULTLIST || [],
selectable: "highlight" // 다중 선택 가능하도록 설정
});
// 행 클릭으로 다중 선택
_tabulGrid.on("rowClick", function(e, row){

View File

@@ -30,6 +30,20 @@
_fnc_datepick(); // 날짜 선택기 초기화
$('.select2').select2(); // select2 초기화
// 그리드 높이 동적 계산 (Total 합계 영역 + 여유 공간 80px)
fnc_calculateContentHeight("gridDiv", 80);
// 디버그: 계산된 높이 확인
console.log("=== 그리드 높이 디버그 ===");
console.log("윈도우 높이:", $(window).height());
console.log("#gridDiv 높이:", $('#gridDiv').height());
console.log("#mainGrid 높이:", $('#mainGrid').height());
$(window).resize(function() {
fnc_calculateContentHeight("gridDiv", 80);
console.log("리사이즈 후 #gridDiv 높이:", $('#gridDiv').height());
});
// 품번/품명 Select2 AJAX 초기화
initPartSelect2Ajax("#search_partNo", "#search_partName", "#search_partObjId", {
debug: true
@@ -53,6 +67,11 @@
$("#btnBulkRegister").click(function(){
fn_bulkRegister();
});
// 거래명세서 출력 버튼
$("#btnTransactionStatement").click(function(){
fn_printTransactionStatement();
});
});
// 날짜 선택기 초기화 함수
@@ -83,19 +102,28 @@
// 현재 그리드 데이터 백업
var currentData = _tabulGrid.getData();
console.log("=== 엑셀 다운로드 디버그 ===");
console.log("첫 번째 행 데이터:", currentData[0]);
console.log("SHIPPING_DATE:", currentData[0].SHIPPING_DATE);
console.log("SHIPPING_DATE_WITH_COUNT:", currentData[0].SHIPPING_DATE_WITH_COUNT);
// 엑셀용 데이터 가공: 출하일을 분할출하 정보 포함하여 표시
var excelData = currentData.map(function(row) {
var excelRow = Object.assign({}, row);
// 출하일 가공: SHIPPING_DATE_WITH_COUNT 값을 SHIPPING_DATE에 덮어쓰기
// 예: "2025-10-20외1" 형식
if(excelRow.SHIPPING_DATE_WITH_COUNT) {
// 예: "2025-10-20 외 1건" 형식
if(excelRow.SHIPPING_DATE_WITH_COUNT && excelRow.SHIPPING_DATE_WITH_COUNT.trim() !== '') {
console.log("출하일 변경:", excelRow.SHIPPING_DATE, "→", excelRow.SHIPPING_DATE_WITH_COUNT);
excelRow.SHIPPING_DATE = excelRow.SHIPPING_DATE_WITH_COUNT;
}
return excelRow;
});
console.log("가공된 첫 번째 행:", excelData[0]);
console.log("가공된 SHIPPING_DATE:", excelData[0].SHIPPING_DATE);
// 엑셀용 데이터로 임시 설정
_tabulGrid.setData(excelData);
@@ -125,6 +153,70 @@
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 없음)
var params = "orderNo=" + encodeURIComponent(rowData.PROJECT_NO);
// saleNo는 전달하지 않음 (신규 등록 모드)
// 그리드에서 계산된 잔량 전달
if(rowData.REMAINING_QUANTITY != null) {
params += "&remainingQuantity=" + encodeURIComponent(rowData.REMAINING_QUANTITY);
}
// 금액 정보는 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;
}
// 거래명세서 출력 버튼은 항상 새로 작성 (저장된 데이터 무시)
localStorage.setItem('loadSavedStatement', 'false');
// 같은 거래처인지 확인
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;
}
}
// 그리드 데이터를 localStorage에 저장
console.log("=== 거래명세서 출력 - 선택된 데이터 ===");
console.log(selectedData);
localStorage.setItem('transactionStatementData', JSON.stringify(selectedData));
// 프로젝트 번호들을 수집
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;
@@ -155,19 +247,56 @@ var columns = [
formatter: fnc_createGridAnchorTag,
cellClick: function(e, cell){
var orderNo = cell.getData().PROJECT_NO;
var saleNo = cell.getData().SALE_NO;
fn_openSaleRegPopup(orderNo, saleNo);
// 프로젝트 번호 클릭 시: 결재 정보 조회 모드 (saleNo에 "detail" 전달)
fn_openSaleRegPopup(orderNo, "detail");
}
},
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '주문유형', field : 'ORDER_TYPE'},
{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_WITH_COUNT',
formatter: function(cell) {
var value = cell.getValue();
// SHIPPING_DATE_WITH_COUNT가 비어있으면 SHIPPING_DATE 사용
if(!value || value.trim() === '') {
var data = cell.getRow().getData();
return data.SHIPPING_DATE || '';
}
return value;
},
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}
@@ -202,7 +331,32 @@ var columns = [
formatter: "money", formatterParams: {thousand: ",", symbolAfter: "", precision: 2}
},
{headerHozAlign : 'center', hozAlign : 'center', width : '120', title : 'S/N', field : 'SERIAL_NO'},
{headerHozAlign : 'center', hozAlign : 'center', width : '120', title : '품번', field : 'PRODUCT_NO'}
{headerHozAlign : 'center', hozAlign : 'center', width : '120', title : '품번', field : 'PRODUCT_NO'},
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '거래명세서', field : 'TRANSACTION_STATEMENT',
formatter: function(cell) {
var data = cell.getRow().getData();
var projectNo = data.PROJECT_NO;
var hasStatement = data.HAS_TRANSACTION_STATEMENT === 'Y';
if(projectNo) {
if(hasStatement) {
return '<img src="/images/folder_blue.png" height="25px" width="25px" style="cursor:pointer;" title="거래명세서 보기">';
} else {
return '<img src="/images/file_empty.png" height="25px" width="25px" style="cursor:pointer;" title="거래명세서 작성">';
}
}
return '';
},
cellClick: function(e, cell) {
var data = cell.getRow().getData();
var projectNo = data.PROJECT_NO;
var hasStatement = data.HAS_TRANSACTION_STATEMENT === 'Y';
if(projectNo) {
fn_openTransactionStatementPopup(projectNo, hasStatement);
}
}
}
// {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '제품구분', field : 'PRODUCT_TYPE'},
// {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '국내/해외', field : 'NATION'},
// {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '접수일', field : 'RECEIPT_DATE'},
@@ -247,15 +401,22 @@ function fn_search(){
// 그리드 데이터 설정
if(_tabulGrid){
_tabulGrid.setData(response.RESULTLIST || []);
} else {
// 그리드 초기화
_tabulGrid = new Tabulator("#mainGrid", {
layout: _tabul_layout_fitColumns,
columns: columns,
data: response.RESULTLIST || [],
selectable: true
});
}
} else {
// 그리드 초기화
_tabulGrid = new Tabulator("#mainGrid", {
height: "100%",
layout: _tabul_layout_fitColumns,
columns: columns,
data: response.RESULTLIST || [],
selectable: true
});
// 그리드 생성 후 높이 재계산
setTimeout(function() {
console.log("그리드 생성 후 #mainGrid 높이:", $('#mainGrid').height());
console.log("그리드 생성 후 .tabulator 높이:", $('.tabulator').height());
}, 100);
}
// 조회된 전체 데이터의 판매원화총액 합계 계산
var totalSalesAmountKRW = 0;
@@ -283,7 +444,7 @@ function fn_search(){
// 페이징 HTML 업데이트
if(response.PAGE_HTML){
$("#pagingArea").html(response.PAGE_HTML);
$(".table_paging_wrap").html(response.PAGE_HTML);
}
},
error: function(xhr, status, error) {
@@ -294,6 +455,74 @@ function fn_search(){
});
}
// 출하일 상세 내역 팝업
// 거래명세서 팝업 열기 (단일 프로젝트)
function fn_openTransactionStatementPopup(projectNo, hasStatement) {
console.log("=== 거래명세서 팝업 열기 ===");
console.log("projectNo:", projectNo);
console.log("hasStatement:", hasStatement);
if(!projectNo) {
alert("프로젝트 번호가 없습니다.");
return;
}
// 흰색 폴더(저장 안 된 경우) 클릭 시 아무것도 안 함
if(!hasStatement) {
console.log("저장된 거래명세서가 없습니다. 팝업을 열지 않습니다.");
return;
}
// 해당 프로젝트의 데이터를 찾아서 localStorage에 저장
var allData = _tabulGrid.getData();
var projectData = allData.filter(function(row) {
return row.PROJECT_NO === projectNo;
});
if(projectData.length === 0) {
alert("프로젝트 데이터를 찾을 수 없습니다.");
return;
}
localStorage.setItem('transactionStatementData', JSON.stringify(projectData));
// 파란폴더(저장된 경우)만 여기까지 도달 → DB 데이터 로드
localStorage.setItem('loadSavedStatement', 'true');
// 거래명세서 팝업 열기
var url = "/salesMgmt/transactionStatementForm.do?projectNos=" + encodeURIComponent(projectNo);
var popup_width = 900;
var popup_height = 800;
var left = (screen.width - popup_width) / 2;
var top = (screen.height - popup_height) / 2;
var popup = window.open(url, "transactionStatement", "width=" + popup_width + ",height=" + popup_height + ",left=" + left + ",top=" + top + ",scrollbars=yes,resizable=yes");
// 팝업이 닫힐 때 그리드 새로고침
var checkPopup = setInterval(function() {
if(popup.closed) {
clearInterval(checkPopup);
console.log("거래명세서 팝업 닫힘 - 그리드 새로고침");
fn_search(); // 그리드 새로고침
}
}, 500);
}
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){
@@ -318,8 +547,8 @@ function fn_bulkRegister(){
// 선택한 1건의 항목 가져오기
var selectedRow = selectedRows[0];
// 판매등록 팝업 열기
fn_openSaleRegPopup(selectedRow.PROJECT_NO, selectedRow.SALE_NO);
// 판매등록 팝업 열기 (그리드 데이터 전달)
fn_openSaleRegPopupWithData(selectedRow);
}
</script>
</head>
@@ -335,6 +564,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>
@@ -444,16 +674,16 @@ function fn_bulkRegister(){
</table>
</div>
<!-- Total 합계 표시 영역 (판매원화총액 기준) -->
<div style="padding: 5px 10px; background: #f5f5f5; border-radius: 3px; margin: 10px 0;">
<span style="font-weight: bold; font-size: 13px;">
발주 금액 : <span id="totalSalesAmountKRW" style="color: #4CAF50;">0</span> 원
<span style="margin-left: 10px; font-size: 12px;">
(출고 : <span id="shippedAmountKRW" style="color: #2196F3;">0</span> 원,
미출고 : <span id="notShippedAmountKRW" style="color: #FF9800;">0</span> 원)
</span>
<!-- Total 합계 표시 영역 (판매원화총액 기준) -->
<div style=" background: #f5f5f5; border-radius: 3px; clear: both;">
<span style="font-weight: bold; font-size: 13px;">
발주 금액 : <span id="totalSalesAmountKRW" style="color: #4CAF50;">0</span> 원
<span style="margin-left: 10px; font-size: 12px;">
(출고 : <span id="shippedAmountKRW" style="color: #2196F3;">0</span> 원,
미출고 : <span id="notShippedAmountKRW" style="color: #FF9800;">0</span> 원)
</span>
</div>
</span>
</div>
<%@include file= "/WEB-INF/view/common/common_gridArea.jsp" %>
</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);
@@ -13,12 +15,22 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<script type="text/javascript">
// S/N 관리 전역 변수
var snList = [];
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 +45,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 +75,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 +184,7 @@
// 판매환종 초기값 설정 (견적환종과 동기화하되, 사용자가 변경 가능)
function initializeSalesCurrency() {
// Controller에서 계산한 값 사용 (잔량 기반)
var existingSalesCurrency = "${saleInfo.SALES_CURRENCY}";
var contractCurrency = "${orderInfo.SALES_CURRENCY}"; // 견적환종 (SALES_CURRENCY로 통일)
var contractExchangeRate = "${orderInfo.SALES_EXCHANGE_RATE}"; // 견적환율
@@ -511,23 +576,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 +729,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,38 +802,75 @@
</td>
</tr>
<!-- 두번째 행: 판매수량, 출하일 -->
<tr>
<td class="input_title"><label for="salesQuantity">판매수량</label></td>
<td>
<input type="number" name="salesQuantity" id="salesQuantity" value="${saleInfo.SALES_QUANTITY}" onchange="fn_calculateSupplyPrice()" />
</td>
<td class="input_title"><label for="shippingDate">출하일</label></td>
<td>
<input type="text" id="shippingDate" name="shippingDate" class="date_icon" value="${saleInfo.SHIPPING_DATE}" autocomplete="off" />
</td>
</tr>
<!-- 세번째 행: 출하방법, 담당자 -->
<tr>
<td class="input_title"><label for="shippingMethod">출하방법</label></td>
<td>
<select name="shippingMethod" id="shippingMethod" class="select2">
<option value="">선택</option>
<option value="내수/직납" ${saleInfo.SHIPPING_METHOD == '내수/직납' ? 'selected' : ''}>내수/직납</option>
<option value="내수/택배" ${saleInfo.SHIPPING_METHOD == '내수/택배' ? 'selected' : ''}>내수/택배</option>
<option value="내수/기타" ${saleInfo.SHIPPING_METHOD == '내수/기타' ? 'selected' : ''}>내수/기타</option>
<option value="수출" ${saleInfo.SHIPPING_METHOD == '수출' ? 'selected' : ''}>수출</option>
</select>
</td>
<td class="input_title"><label for="manager">담당자</label></td>
<td>
<select name="manager" id="manager" class="select2">
<option value="">선택</option>
${codeMap.managerList}
</select>
</td>
</tr>
<!-- 두번째 행: 수주수량, 판매수량 -->
<tr>
<td class="input_title"><label for="orderQuantity">수주수량</label></td>
<td>
<input type="number" name="orderQuantity" id="orderQuantity"
value="${saleInfo.ORDER_QUANTITY}"
readonly style="background-color: #f5f5f5;" />
</td>
<c:choose>
<c:when test="${param.saleNo != null && param.saleNo != ''}">
<!-- 수정 모드: 판매수량만 표시 (읽기 전용) -->
<td class="input_title"><label for="salesQuantity">판매수량</label></td>
<td>
<input type="number" name="salesQuantity" id="salesQuantity"
value="${saleInfo.SALES_QUANTITY}"
readonly style="background-color: #f5f5f5;" />
</td>
</c:when>
<c:otherwise>
<!-- 신규 등록 모드: 판매수량 입력 가능 + 잔량 표시 -->
<td class="input_title">
<label for="salesQuantity">판매수량</label>
<span style="color:#666; font-size:11px; display:block; margin-top:2px;">
(잔량: ${saleInfo.REMAINING_QUANTITY != null ? saleInfo.REMAINING_QUANTITY : saleInfo.ORDER_QUANTITY}개)
</span>
</td>
<td>
<input type="number" name="salesQuantity" id="salesQuantity"
value="${saleInfo.SALES_QUANTITY != null && saleInfo.SALES_QUANTITY != '' && saleInfo.SALES_QUANTITY != 0 ? saleInfo.SALES_QUANTITY : ''}"
onchange="fn_calculateSupplyPrice()"
placeholder="판매할 수량 입력 (잔량: ${saleInfo.REMAINING_QUANTITY != null ? saleInfo.REMAINING_QUANTITY : saleInfo.ORDER_QUANTITY}개)" />
</td>
</c:otherwise>
</c:choose>
</tr>
<!-- 세번째 행: 출하일, 출하방법 -->
<tr>
<td class="input_title"><label for="shippingDate">출하일</label></td>
<td>
<input type="text" id="shippingDate" name="shippingDate" class="date_icon" value="${saleInfo.SHIPPING_DATE}" autocomplete="off" />
</td>
<td class="input_title"><label for="shippingMethod">출하방법</label></td>
<td>
<select name="shippingMethod" id="shippingMethod" class="select2">
<option value="">선택</option>
<option value="직접배송" ${saleInfo.SHIPPING_METHOD == '직접배송' ? 'selected' : ''}>직접배송</option>
<option value="택배" ${saleInfo.SHIPPING_METHOD == '택배' ? 'selected' : ''}>택배</option>
<option value="화물" ${saleInfo.SHIPPING_METHOD == '화물' ? 'selected' : ''}>화물</option>
<option value="퀵서비스" ${saleInfo.SHIPPING_METHOD == '퀵서비스' ? 'selected' : ''}>퀵서비스</option>
<option value="기타" ${saleInfo.SHIPPING_METHOD == '기타' ? 'selected' : ''}>기타</option>
</select>
</td>
</tr>
<!-- 네번째 행: 담당자, INCOTERMS -->
<tr>
<td class="input_title"><label for="manager">담당자</label></td>
<td>
<select name="manager" id="manager" class="select2">
<option value="">선택</option>
${codeMap.managerList}
</select>
</td>
<td class="input_title"><label for="incoterms">INCOTERMS</label></td>
<td>
<input type="text" name="incoterms" id="incoterms" value="${saleInfo.INCOTERMS}" />
</td>
</tr>
</table>
</td>
@@ -681,11 +899,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 +915,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 +938,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>
@@ -745,7 +973,8 @@
</div>
</div>
</section>
</form>
</form>
</body>
</html>

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>

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

View File

@@ -59,7 +59,7 @@ function fnc_tabul_search(layoutParam, gridObj, searchURL, columnParam, showChec
if(showApp) allColumns = allColumns.concat(_columnAppStatus);
gridObj = new Tabulator("#mainGrid", {
height : "auto",//"640px",
height : "100%",
layout : fnc_checkNullDefaultValue(layoutParam, _tabul_layout_fitDataStretch), //fitDataFill fitDataStretch
//selectable: true,
placeholder : "조회된 정보가 없습니다.",

View File

@@ -16,6 +16,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@@ -888,23 +889,110 @@ public class PartMngController {
}
/**
* bom copy 저장
* BOM COPY (M-BOM 관리에서 호출)
* @param request
* @param paramMap
* @return
*/
@RequestMapping("/partMng/structureBomCopyFormPopup.do")
public String structureBomCopyFormPopup(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
try {
String objId = CommonUtils.checkNull((String)paramMap.get("objId"));
System.out.println("========== structureBomCopyFormPopup ==========");
System.out.println("objId: " + objId);
// objId를 항상 설정 (음수 포함)
request.setAttribute("TARGET_OBJID", objId);
// objId가 유효한 경우 프로젝트 정보 조회 (음수도 유효한 OBJID임)
if(!"".equals(objId) && !"-1".equals(objId)) {
try {
long objIdLong = Long.parseLong(objId);
System.out.println("objIdLong: " + objIdLong);
// PROJECT_MGMT 테이블에서 품번/품명 조회 (M-BOM 관리 리스트의 OBJID는 PROJECT_MGMT의 OBJID)
Map<String, Object> projectInfo = commonService.selectOne("productionplanning.getProjectMgmtDetail", request, paramMap);
System.out.println("projectInfo: " + projectInfo);
if(projectInfo != null) {
System.out.println("PART_NO: " + projectInfo.get("PART_NO"));
System.out.println("PART_NAME: " + projectInfo.get("PART_NAME"));
request.setAttribute("projectInfo", projectInfo);
} else {
System.out.println("projectInfo is null!");
}
} catch(NumberFormatException nfe) {
System.out.println("NumberFormatException: " + nfe.getMessage());
}
}
Map code_map = new HashMap();
code_map.put("rev", commonService.bizMakeOptionList("", "", "common.getRevNoselect"));
code_map.put("product_code", commonService.bizMakeOptionList("", "", "common.getProductNoselect"));
request.setAttribute("code_map", code_map);
} catch(Exception e) {
e.printStackTrace();
}
return "/partMng/structureBomCopyFormPopup";
}
/**
* bom copy 저장 (E-BOM을 M-BOM으로 복사)
*/
@RequestMapping("/partMng/saveBomCopy.do")
@ResponseBody
public String saveBomCopySave(HttpSession session, HttpServletRequest request, @RequestParam Map paramMap){
String result = "";
public Map<String, Object> saveBomCopySave(HttpSession session, HttpServletRequest request, @RequestBody Map<String, Object> paramMap){
Map<String, Object> resultMap = new HashMap<String, Object>();
try{
PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
paramMap.put("CONNECTUSERID", CommonUtils.checkNull(person.getUserId()));
System.out.println("========== saveBomCopy ==========");
System.out.println("paramMap: " + paramMap);
partMngService.saveBomCopySave(request, paramMap);
result = "SUCCESS";
PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
String userId = person.getUserId();
// targetObjId: PROJECT_MGMT의 OBJID
String targetObjId = CommonUtils.checkNull((String)paramMap.get("targetObjId"));
String sourceBomObjId = CommonUtils.checkNull((String)paramMap.get("sourceBomObjId"));
String targetPartNo = CommonUtils.checkNull((String)paramMap.get("targetPartNo"));
String targetPartName = CommonUtils.checkNull((String)paramMap.get("targetPartName"));
List<Map<String, Object>> bomData = (List<Map<String, Object>>)paramMap.get("bomData");
if(bomData == null || bomData.isEmpty()) {
resultMap.put("result", "fail");
resultMap.put("message", "복사할 BOM 데이터가 없습니다.");
return resultMap;
}
// M-BOM 저장
Map<String, Object> saveParam = new HashMap<String, Object>();
saveParam.put("PROJECT_OBJID", targetObjId);
saveParam.put("SOURCE_BOM_OBJID", sourceBomObjId);
saveParam.put("PART_NO", targetPartNo);
saveParam.put("PART_NAME", targetPartName);
saveParam.put("BOM_DATA", bomData);
saveParam.put("USER_ID", userId);
// SqlSession을 통해 직접 실행
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession();
try {
sqlSession.insert("productionplanning.saveMbomFromEbom", saveParam);
sqlSession.commit();
resultMap.put("result", "success");
resultMap.put("message", "M-BOM이 성공적으로 생성되었습니다.");
} finally {
if(sqlSession != null) {
sqlSession.close();
}
}
}catch(Exception e){
e.printStackTrace();
result = "FAIL";
resultMap.put("result", "fail");
resultMap.put("message", "BOM 복사 중 오류가 발생했습니다: " + e.getMessage());
}
return result;
return resultMap;
}
@@ -1782,6 +1870,44 @@ public class PartMngController {
return resultMap;
}
/**
* 품번으로 BOM OBJID 조회
* @param request
* @param paramMap
* @return
*/
@RequestMapping("/partMng/getBomObjIdByPartNo.do")
@ResponseBody
public Map<String, Object> getBomObjIdByPartNo(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map<String, Object> resultMap = new HashMap<String, Object>();
try{
String partNo = CommonUtils.checkNull(paramMap.get("partNo"));
if(partNo.isEmpty()) {
resultMap.put("OBJID", null);
return resultMap;
}
// 품번으로 BOM OBJID 조회
Map<String, Object> bomInfo = partMngService.getBomObjIdByPartNo(paramMap);
if(bomInfo != null) {
resultMap.put("OBJID", bomInfo.get("OBJID"));
resultMap.put("PART_NO", bomInfo.get("PART_NO"));
resultMap.put("PART_NAME", bomInfo.get("PART_NAME"));
} else {
resultMap.put("OBJID", null);
}
}catch(Exception e){
e.printStackTrace();
resultMap.put("OBJID", null);
}
return resultMap;
}
/**
* 품번 중복 체크 (PART_BOM_REPORT 테이블)
* @param request

View File

@@ -9,10 +9,12 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.google.gson.Gson;
import com.pms.common.service.BaseService;
import com.pms.common.utils.CommonUtils;
import com.pms.common.utils.Constants;
@@ -933,6 +935,208 @@ public class ProductionPlanningController extends BaseService {
return "/productionplanning/mBomEbomSelectPopup";
}
/**
* M-BOM 조회 팝업 (읽기 전용)
* @param request
* @param paramMap
* @return
*/
@RequestMapping("/productionplanning/mBomViewPopup.do")
public String mBomViewPopup(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
return "/productionplanning/mBomViewPopup";
}
/**
* M-BOM 복사 팝업 메인 (frameset)
* @param request
* @param paramMap
* @return
*/
@RequestMapping("/productionplanning/mBomFormPopup.do")
public String mBomFormPopup(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
try {
String objId = CommonUtils.checkNull(paramMap.get("objId"));
System.out.println("========== mBomFormPopup ==========");
System.out.println("objId: " + objId);
// PROJECT_MGMT 정보 조회
if(!"".equals(objId)) {
paramMap.put("objId", objId);
Map<String, Object> projectInfo = commonService.selectOne("productionplanning.getProjectMgmtDetail", request, paramMap);
System.out.println("projectInfo: " + projectInfo);
if(projectInfo != null && !projectInfo.isEmpty()) {
request.setAttribute("info", projectInfo);
} else {
System.out.println("projectInfo is null or empty!");
}
}
} catch(Exception e) {
e.printStackTrace();
}
return "/productionplanning/mBomPopupHeaderFs";
}
/**
* M-BOM 조회 팝업 상단 헤더
* @param request
* @param paramMap
* @return
*/
@RequestMapping("/productionplanning/mBomHeaderPopup.do")
public String mBomHeaderPopup(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
try {
String objId = CommonUtils.checkNull(paramMap.get("objId"));
if(!"".equals(objId)) {
paramMap.put("objId", objId);
Map<String, Object> projectInfo = commonService.selectOne("productionplanning.getProjectMgmtDetail", request, paramMap);
request.setAttribute("info", projectInfo);
}
} catch(Exception e) {
e.printStackTrace();
}
return "/productionplanning/mBomHeaderPopup";
}
/**
* M-BOM 조회 팝업 중앙 버튼 영역
* @param request
* @param paramMap
* @return
*/
@RequestMapping("/productionplanning/mBomCenterBtnPopup.do")
public String mBomCenterBtnPopup(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
return "/productionplanning/mBomCenterBtnPopup";
}
/**
* M-BOM 조회 팝업 중앙 버튼 영역
*/
@RequestMapping("/productionplanning/mBomPopupCenter.do")
public String mBomPopupCenter(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
return "/productionplanning/mBomPopupCenter";
}
/**
* M-BOM 조회 팝업 오른쪽 E-BOM 목록
* @param request
* @param paramMap
* @return
*/
@RequestMapping("/productionplanning/mBomPopupRight.do")
public String mBomPopupRight(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
return "/productionplanning/mBomPopupRight";
}
/**
* E-BOM 목록 조회 (AJAX)
* @param request
* @param paramMap
* @return
*/
@RequestMapping("/productionplanning/getEbomList.do")
@ResponseBody
public Map<String, Object> getEbomList(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
Map<String, Object> result = new HashMap<>();
try {
List<Map<String, Object>> list = commonService.selectList("productionplanning.getEbomList", request, paramMap);
result.put("list", list);
result.put("result", true);
} catch(Exception e) {
e.printStackTrace();
result.put("result", false);
result.put("list", new ArrayList<>());
}
return result;
}
/**
* M-BOM 조회 팝업 하단 frameset
* @param request
* @param paramMap
* @return
*/
@RequestMapping("/productionplanning/mBomBottomPopupFS.do")
public String mBomBottomPopupFS(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
return "/productionplanning/mBomPopupFs";
}
/**
* M-BOM 조회 팝업 왼쪽 트리
* @param request
* @param paramMap
* @return
*/
@RequestMapping("/productionplanning/mBomPopupLeft.do")
public String mBomPopupLeft(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
Gson gson = new Gson();
try {
String objId = CommonUtils.checkNull(paramMap.get("objId"));
// PROJECT_MGMT 정보 조회
if(!"".equals(objId)) {
paramMap.put("objId", objId);
Map<String, Object> projectInfo = commonService.selectOne("productionplanning.getProjectMgmtDetail", request, paramMap);
if(projectInfo != null) {
// 1. 먼저 저장된 M-BOM 데이터가 있는지 확인
List<Map<String, Object>> mbomDetailList = commonService.selectList("productionplanning.getMbomDetailByProject", request, paramMap);
if(mbomDetailList != null && !mbomDetailList.isEmpty()) {
// 저장된 M-BOM 데이터가 있으면 이를 표시
int maxLevel = 1;
for(Map<String, Object> item : mbomDetailList) {
Integer level = (Integer) item.get("LEVEL");
if(level != null && level > maxLevel) {
maxLevel = level;
}
}
request.setAttribute("MAXLEV", maxLevel);
request.setAttribute("bomTreeListJson", gson.toJson(mbomDetailList));
return "/productionplanning/mBomPopupLeft";
}
// 저장된 M-BOM이 없으면 할당된 E-BOM 정보 조회
String bomReportObjid = CommonUtils.checkNull(projectInfo.get("BOM_REPORT_OBJID"));
if(!"".equals(bomReportObjid)) {
Map<String, Object> ebomParam = new HashMap<>();
ebomParam.put("objId", bomReportObjid);
ebomParam.put("bomReportObjId", bomReportObjid);
// E-BOM 정보 조회
Map<String, Object> ebomInfo = commonService.selectOne("partMng.getBomReportInfo", request, ebomParam);
request.setAttribute("ebomInfo", ebomInfo);
// BOM 트리 데이터 조회 (레벨 정보 포함)
List<Map<String, Object>> bomTreeList = commonService.selectList("partMng.getBOMTreeList", request, ebomParam);
if(bomTreeList != null && !bomTreeList.isEmpty()) {
// MAX LEVEL 계산
int maxLevel = 1;
for(Map<String, Object> item : bomTreeList) {
Integer level = (Integer) item.get("LEVEL");
if(level != null && level > maxLevel) {
maxLevel = level;
}
}
request.setAttribute("MAXLEV", maxLevel);
request.setAttribute("bomTreeListJson", gson.toJson(bomTreeList));
return "/productionplanning/mBomPopupLeft";
}
}
}
}
} catch(Exception e) {
e.printStackTrace();
}
// 데이터가 없을 때 빈 배열 설정
request.setAttribute("MAXLEV", 1);
request.setAttribute("bomTreeListJson", "[]");
return "/productionplanning/mBomPopupLeft";
}
/**
* E-BOM을 M-BOM(PROJECT_MGMT)에 할당
* @param request
@@ -970,4 +1174,67 @@ public class ProductionPlanningController extends BaseService {
}
return resultMap;
}
/**
* M-BOM 목록 조회 (검색 조건 포함)
* @param request
* @param paramMap
* @return
*/
@ResponseBody
@RequestMapping("/productionplanning/getMbomList.do")
public Map<String, Object> getMbomList(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
Map<String, Object> result = new HashMap<>();
try {
// 검색 조건에 맞는 M-BOM 목록 조회
List<Map<String, Object>> list = commonService.selectList("productionplanning.getMbomList", request, paramMap);
result.put("list", list);
result.put("result", true);
} catch(Exception e) {
e.printStackTrace();
result.put("result", false);
result.put("list", new ArrayList<>());
}
return result;
}
/**
* M-BOM 저장
* @param request
* @param paramMap
* @return
*/
@ResponseBody
@RequestMapping("/productionplanning/saveMbom.do")
public Map<String, Object> saveMbom(HttpServletRequest request, @RequestBody Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<>();
try {
// M-BOM 데이터 저장 로직
List<Map<String, Object>> mbomData = (List<Map<String, Object>>) paramMap.get("mbomData");
String partNo = CommonUtils.checkNull(paramMap.get("partNo"));
String partName = CommonUtils.checkNull(paramMap.get("partName"));
if(mbomData == null || mbomData.isEmpty()) {
resultMap.put("result", "fail");
resultMap.put("message", "저장할 데이터가 없습니다.");
return resultMap;
}
// M-BOM 저장 서비스 호출
int saveResult = productionPlanningService.saveMbom(request, mbomData, partNo, partName);
if(saveResult > 0) {
resultMap.put("result", "success");
resultMap.put("message", "M-BOM이 저장되었습니다.");
} else {
resultMap.put("result", "fail");
resultMap.put("message", "저장에 실패했습니다.");
}
} catch(Exception e) {
e.printStackTrace();
resultMap.put("result", "fail");
resultMap.put("message", "오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
}

View File

@@ -2530,18 +2530,18 @@ SELECT option_objid::VARCHAR AS CODE
AND PRODUCT_MGMT_OBJID::VARCHAR = #{codeId}::VARCHAR
</select>
<select id="getRevNoselect" parameterType="map" resultType="map">
SELECT
T.OBJID::varchar AS CODE
,T.REV AS NAME
,T1.PRODUCT_CODE AS CODE_CD
,'' AS STATUS
,T.spec_name AS ID
,'' AS EXT_VAL
FROM PART_BOM_REPORT T,PRODUCT_MGMT T1
WHERE T.PRODUCT_MGMT_OBJID = T1.OBJID
AND T1.OBJID = #{code}::numeric
ORDER BY T.REV
<select id="getRevNoselect" parameterType="map" resultType="map">
SELECT
T.OBJID::varchar AS CODE
,COALESCE(T.REVISION, T.REV, '1.0') AS NAME
,T1.PRODUCT_CODE AS CODE_CD
,'' AS STATUS
,T.spec_name AS ID
,'' AS EXT_VAL
FROM PART_BOM_REPORT T,PRODUCT_MGMT T1
WHERE T.PRODUCT_MGMT_OBJID = T1.OBJID
AND T1.OBJID = #{code}::numeric
ORDER BY COALESCE(T.REVISION, T.REV, '1.0')
</select>

View File

@@ -2999,6 +2999,22 @@ SELECT T1.LEV, T1.BOM_REPORT_OBJID, T1.ROOT_PART_NO, T1.PATH, T1.LEAF, T2.*
ORDER BY REGDATE DESC
</select>
<!-- 품번으로 BOM OBJID 조회 -->
<select id="getBomObjIdByPartNo" parameterType="map" resultType="map">
SELECT
OBJID,
PART_NO,
PART_NAME
FROM PART_BOM_REPORT
WHERE PART_NO = #{partNo}
AND PART_NO IS NOT NULL
AND TRIM(PART_NO) != ''
AND PART_NAME IS NOT NULL
AND TRIM(PART_NAME) != ''
ORDER BY REGDATE DESC
LIMIT 1
</select>
<!-- BOM 복사를 위한 파트 목록 조회 -->
<select id="getBomPartListForCopy" parameterType="map" resultType="map">
SELECT

View File

@@ -2870,8 +2870,9 @@
ORDER BY SUBSTRING(PROJECT_NO,POSITION('-' IN PROJECT_NO)+1) DESC
</select>
<!-- M-BOM 관리 목록 조회 -->
<!-- M-BOM 관리 목록 조회 - 품목별로 나눠서 보이기 (판매관리와 동일) -->
<select id="mBomMgmtGridList" parameterType="map" resultType="com.pms.common.UpperKeyMap">
/* productionplanning.mBomMgmtGridList - 품목별로 나눠서 조회 (CONTRACT_ITEM 기준) */
SELECT
PM.OBJID,
PM.CONTRACT_OBJID,
@@ -2906,54 +2907,40 @@
ELSE ''
END
) AS PAID_TYPE_NAME,
COALESCE(PM.PART_NO, '') AS PART_NO,
COALESCE(PM.PART_NAME, '') AS PART_NAME,
-- S/N: CONTRACT_ITEM_SERIAL에서 조회
-- 품목 정보: CONTRACT_ITEM에서 가져오기 (품목별로 펼쳐서 보이기)
COALESCE(CI.PART_NO, PM.PART_NO, '') AS PART_NO,
COALESCE(CI.PART_NAME, PM.PART_NAME, '') AS PART_NAME,
CI.PART_OBJID,
-- S/N: 해당 품목의 시리얼 번호만 표시
(SELECT
CASE
WHEN COUNT(*) = 0 THEN ''
WHEN COUNT(*) = 1 THEN MIN(CIS.SERIAL_NO)
ELSE MIN(CIS.SERIAL_NO) || ' 외 ' || (COUNT(*) - 1)::TEXT || '건'
END
FROM CONTRACT_ITEM CI
LEFT JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID AND UPPER(CIS.STATUS) = 'ACTIVE'
WHERE CI.CONTRACT_OBJID = PM.CONTRACT_OBJID
AND CI.PART_OBJID = PM.PART_OBJID
AND CI.STATUS = 'ACTIVE'
FROM CONTRACT_ITEM_SERIAL CIS
WHERE CIS.ITEM_OBJID = CI.OBJID
AND UPPER(CIS.STATUS) = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL) AS SERIAL_NO,
COALESCE(PM.QUANTITY::numeric, 0) AS QUANTITY,
-- 요청납기: CONTRACT_ITEM 우선, 없으면 PROJECT_MGMT, 없으면 CONTRACT_MGMT
COALESCE(
(SELECT CI.DUE_DATE
FROM CONTRACT_ITEM CI
WHERE CI.CONTRACT_OBJID = PM.CONTRACT_OBJID
AND CI.PART_OBJID = PM.PART_OBJID
AND CI.STATUS = 'ACTIVE' LIMIT 1),
PM.DUE_DATE,
CM.req_del_date
) AS REQ_DEL_DATE,
-- 수량: CONTRACT_ITEM의 수량
COALESCE(CI.ORDER_QUANTITY::numeric, PM.QUANTITY::numeric, 0) AS QUANTITY,
-- 요청납기: CONTRACT_ITEM 우선
COALESCE(CI.DUE_DATE, PM.DUE_DATE, CM.req_del_date) AS REQ_DEL_DATE,
-- 고객요청사항: CONTRACT_ITEM에서 가져옴
COALESCE(
(SELECT CI.CUSTOMER_REQUEST
FROM CONTRACT_ITEM CI
WHERE CI.CONTRACT_OBJID = PM.CONTRACT_OBJID
AND CI.PART_OBJID = PM.PART_OBJID
AND CI.STATUS = 'ACTIVE' LIMIT 1),
''
) AS CUSTOMER_REQUEST,
-- E-BOM 정보: PM.PART_OBJID가 E-BOM OBJID를 직접 가리킴
PM.PART_OBJID AS BOM_REPORT_OBJID,
COALESCE(CI.CUSTOMER_REQUEST, '') AS CUSTOMER_REQUEST,
-- E-BOM 정보: CI.PART_OBJID가 E-BOM OBJID를 가리킴
COALESCE(CI.PART_OBJID, PM.PART_OBJID) AS BOM_REPORT_OBJID,
COALESCE(
(SELECT PBR.STATUS
FROM PART_BOM_REPORT PBR
WHERE PBR.OBJID::VARCHAR = PM.PART_OBJID
WHERE PBR.OBJID::VARCHAR = COALESCE(CI.PART_OBJID, PM.PART_OBJID)
LIMIT 1),
''
) AS EBOM_STATUS,
COALESCE(
(SELECT TO_CHAR(PBR.REGDATE, 'YYYY-MM-DD')
FROM PART_BOM_REPORT PBR
WHERE PBR.OBJID::VARCHAR = PM.PART_OBJID
WHERE PBR.OBJID::VARCHAR = COALESCE(CI.PART_OBJID, PM.PART_OBJID)
LIMIT 1),
''
) AS EBOM_REGDATE,
@@ -2963,18 +2950,27 @@
FROM
PROJECT_MGMT PM
LEFT JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID
-- CONTRACT_ITEM과 LEFT JOIN하여 품목별로 펼쳐서 보이기
LEFT JOIN CONTRACT_ITEM CI ON CM.OBJID::VARCHAR = CI.CONTRACT_OBJID
AND CI.STATUS = 'ACTIVE'
WHERE 1=1
AND PM.PROJECT_NO IS NOT NULL
AND PM.PROJECT_NO != ''
<!-- 품번 검색 (대소문자 구분 없음) -->
<if test="search_part_no != null and search_part_no != ''">
AND UPPER(PM.PART_NO) LIKE '%' || UPPER(#{search_part_no}) || '%'
AND (
UPPER(PM.PART_NO) LIKE '%' || UPPER(#{search_part_no}) || '%'
OR UPPER(CI.PART_NO) LIKE '%' || UPPER(#{search_part_no}) || '%'
)
</if>
<!-- 품명 검색 (대소문자 구분 없음) -->
<if test="search_part_name != null and search_part_name != ''">
AND UPPER(PM.PART_NAME) LIKE '%' || UPPER(#{search_part_name}) || '%'
AND (
UPPER(PM.PART_NAME) LIKE '%' || UPPER(#{search_part_name}) || '%'
OR UPPER(CI.PART_NAME) LIKE '%' || UPPER(#{search_part_name}) || '%'
)
</if>
ORDER BY PM.REGDATE DESC
ORDER BY PM.REGDATE DESC, CI.PART_NO
</select>
<!-- E-BOM을 PROJECT_MGMT에 할당 -->
@@ -3003,4 +2999,299 @@
LEFT JOIN USER_INFO UI ON UI.USER_ID = T.WRITER
WHERE T.OBJID::VARCHAR = #{objid}
</select>
<!-- M-BOM 상세 조회 (PROJECT_MGMT + CONTRACT_MGMT 정보) -->
<select id="getProjectMgmtDetail" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
PM.OBJID,
PM.CONTRACT_OBJID,
PM.PROJECT_NO,
PM.BOM_REPORT_OBJID,
PM.PART_NO,
PM.PART_NAME,
CM.CATEGORY_CD,
COALESCE(
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CM.CATEGORY_CD LIMIT 1),
''
) AS CATEGORY_NAME,
CM.PRODUCT,
COALESCE(
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CM.PRODUCT LIMIT 1),
''
) AS PRODUCT_NAME,
CM.AREA_CD,
COALESCE(
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CM.AREA_CD LIMIT 1),
''
) AS AREA_NAME,
CM.CUSTOMER_OBJID,
COALESCE(
(SELECT SUPPLY_NAME FROM SUPPLY_MNG WHERE OBJID = CM.CUSTOMER_OBJID::NUMERIC LIMIT 1),
''
) AS CUSTOMER_NAME,
CM.PAID_TYPE,
CM.REQ_DEL_DATE,
TO_CHAR(PM.REGDATE, 'YYYY-MM-DD') AS RECEIPT_DATE,
TO_CHAR(PM.REGDATE, 'YYYY-MM-DD') AS MBOM_REGDATE
FROM
PROJECT_MGMT PM
INNER JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID
WHERE
PM.OBJID::VARCHAR = #{objId}
LIMIT 1
</select>
<!-- E-BOM 목록 조회 -->
<select id="getEbomList" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
T.OBJID,
T.PRODUCT_CD,
COALESCE(
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = T.PRODUCT_CD LIMIT 1),
''
) AS PRODUCT_NAME,
T.PART_NO,
T.PART_NAME,
T.STATUS,
T.REVISION,
TO_CHAR(T.REGDATE, 'YYYY-MM-DD') AS REG_DATE,
UI.USER_NAME AS WRITER_NAME,
UI.DEPT_NAME,
COALESCE(PM.MATERIAL, '') AS MATERIAL,
COALESCE(PM.MAKER, '') AS SUPPLIER
FROM
PART_BOM_REPORT T
LEFT JOIN USER_INFO UI ON UI.USER_ID = T.WRITER
LEFT JOIN PART_MNG PM ON PM.PART_NO = T.PART_NO AND PM.STATUS = 'release'
WHERE 1=1
<!-- 품번, 품명이 비어있지 않은 것만 조회 -->
AND T.PART_NO IS NOT NULL
AND TRIM(T.PART_NO) != ''
AND T.PART_NAME IS NOT NULL
AND TRIM(T.PART_NAME) != ''
<!-- 품번 검색 -->
<if test="search_part_no != null and search_part_no != ''">
AND UPPER(T.PART_NO) LIKE '%' || UPPER(#{search_part_no}) || '%'
</if>
<!-- 품명 검색 -->
<if test="search_part_name != null and search_part_name != ''">
AND UPPER(T.PART_NAME) LIKE '%' || UPPER(#{search_part_name}) || '%'
</if>
<!-- 재료 검색 -->
<if test="search_material != null and search_material != ''">
AND UPPER(PM.MATERIAL) LIKE '%' || UPPER(#{search_material}) || '%'
</if>
<!-- 공급업체 검색 -->
<if test="search_supplier != null and search_supplier != ''">
AND UPPER(PM.MAKER) LIKE '%' || UPPER(#{search_supplier}) || '%'
</if>
ORDER BY T.REGDATE DESC
LIMIT 100
</select>
<!-- M-BOM 목록 조회 -->
<select id="getMbomList" resultType="map" parameterType="map">
SELECT
MH.OBJID,
MH.MBOM_NO,
MH.PART_NO,
MH.PART_NAME,
MH.SAVE_DATE,
MH.CREATE_USER,
MH.CREATE_DATE,
MH.UPDATE_USER,
MH.UPDATE_DATE
FROM
MBOM_HEADER MH
WHERE 1=1
<!-- 품번 검색 -->
<if test="partNo != null and partNo != ''">
AND UPPER(MH.PART_NO) LIKE '%' || UPPER(#{partNo}) || '%'
</if>
<!-- 품명 검색 -->
<if test="partName != null and partName != ''">
AND UPPER(MH.PART_NAME) LIKE '%' || UPPER(#{partName}) || '%'
</if>
<!-- M-BOM 품번 검색 -->
<if test="mbomPartNo != null and mbomPartNo != ''">
AND UPPER(MH.MBOM_NO) LIKE '%' || UPPER(#{mbomPartNo}) || '%'
</if>
<!-- 저장일 검색 -->
<if test="saveDate != null and saveDate != ''">
AND DATE(MH.SAVE_DATE) = #{saveDate}
</if>
ORDER BY MH.SAVE_DATE DESC
LIMIT 100
</select>
<!-- M-BOM 상세 데이터 조회 (프로젝트별) -->
<select id="getMbomDetailByProject" resultType="map" parameterType="map">
SELECT
MD.OBJID,
MD.MBOM_HEADER_OBJID,
MD.PART_NO,
MD.PART_NAME,
MD.QTY,
MD.QTY_TEMP,
MD.ITEM_QTY,
MD.LEVEL,
MD.REVISION,
MD.SPEC,
MD.PRODUCT_NAME,
MD.STATUS_NAME,
MD.LEVEL_1,
MD.LEVEL_2,
MD.LEVEL_3,
MD.LEVEL_4,
MD.LEVEL_5,
MD.LEVEL_6,
MD.LEVEL_7,
MD.LEVEL_8,
MD.LEVEL_9,
MD.LEVEL_10
FROM
MBOM_DETAIL MD
INNER JOIN
MBOM_HEADER MH ON MD.MBOM_HEADER_OBJID = MH.OBJID
WHERE
MH.PROJECT_MGMT_OBJID = #{objId}
ORDER BY
MD.LEVEL, MD.PART_NO
</select>
<!-- M-BOM 헤더 저장 -->
<insert id="insertMbomHeader" parameterType="map">
<selectKey keyProperty="OBJID" resultType="string" order="BEFORE">
SELECT REPLACE(UUID(),'-','') FROM DUAL
</selectKey>
INSERT INTO MBOM_HEADER (
OBJID,
MBOM_NO,
PART_NO,
PART_NAME,
SAVE_DATE,
CREATE_USER,
CREATE_DATE,
UPDATE_USER,
UPDATE_DATE
) VALUES (
#{OBJID},
CONCAT('MBOM-', DATE_FORMAT(NOW(), '%Y%m%d%H%i%s')),
#{PART_NO},
#{PART_NAME},
NOW(),
#{CREATE_USER},
NOW(),
#{UPDATE_USER},
NOW()
)
</insert>
<!-- M-BOM 상세 저장 -->
<insert id="insertMbomDetail" parameterType="map">
<selectKey keyProperty="OBJID" resultType="string" order="BEFORE">
SELECT REPLACE(UUID(),'-','') FROM DUAL
</selectKey>
INSERT INTO MBOM_DETAIL (
OBJID,
MBOM_HEADER_OBJID,
PART_NO,
PART_NAME,
QTY,
LEVEL,
REVISION,
SPEC,
PRODUCT_NAME,
STATUS_NAME,
CREATE_USER,
CREATE_DATE,
UPDATE_USER,
UPDATE_DATE
) VALUES (
#{OBJID},
#{MBOM_HEADER_OBJID},
#{PART_NO},
#{PART_NAME},
#{QTY},
#{LEVEL},
#{REVISION},
#{SPEC},
#{PRODUCT_NAME},
#{STATUS_NAME},
#{CREATE_USER},
NOW(),
#{UPDATE_USER},
NOW()
)
</insert>
<!-- E-BOM을 M-BOM으로 복사 -->
<insert id="saveMbomFromEbom" parameterType="map">
/* productionplanning.saveMbomFromEbom - E-BOM을 M-BOM으로 복사 */
/* 1. 새로운 PART_BOM_REPORT 생성 (M-BOM용) */
WITH new_bom_report AS (
INSERT INTO PART_BOM_REPORT (
OBJID,
PART_NO,
PART_NAME,
WRITER,
REGDATE,
STATUS
) VALUES (
(SELECT (FLOOR(RANDOM() * 1000000000) + 1000000000)::INTEGER),
#{PART_NO},
#{PART_NAME},
#{USER_ID},
NOW(),
'Y'
)
RETURNING OBJID
),
/* 2. E-BOM의 BOM_PART_QTY 데이터를 새 M-BOM으로 복사 */
copied_bom_data AS (
INSERT INTO BOM_PART_QTY (
BOM_REPORT_OBJID,
OBJID,
PARENT_OBJID,
CHILD_OBJID,
PARENT_PART_NO,
PART_NO,
QTY,
ITEM_QTY,
QTY_TEMP,
REGDATE,
WRITER,
SEQ,
STATUS,
LAST_PART_OBJID
)
SELECT
(SELECT OBJID FROM new_bom_report),
(SELECT (FLOOR(RANDOM() * 1000000000) + 1000000000 + ROW_NUMBER() OVER (ORDER BY OBJID))::INTEGER),
PARENT_OBJID,
CHILD_OBJID,
PARENT_PART_NO,
PART_NO,
QTY,
ITEM_QTY,
QTY_TEMP,
NOW(),
#{USER_ID},
ROW_NUMBER() OVER (ORDER BY OBJID),
'adding',
LAST_PART_OBJID
FROM BOM_PART_QTY
WHERE BOM_REPORT_OBJID::VARCHAR = #{SOURCE_BOM_OBJID}
AND COALESCE(STATUS, '') NOT IN ('deleting', 'deleted')
RETURNING 1
)
/* 3. PROJECT_MGMT 업데이트 */
UPDATE PROJECT_MGMT
SET
BOM_REPORT_OBJID = (SELECT OBJID FROM new_bom_report),
MBOM_STATUS = 'Y',
PART_NO = #{PART_NO},
PART_NAME = #{PART_NAME}
WHERE OBJID::VARCHAR = #{PROJECT_OBJID}
</insert>
</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;
}
@@ -2393,6 +2410,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

View File

@@ -248,6 +248,7 @@ public class SalesNcollectMgmtController {
@RequestMapping(value = "/salesMgmt/salesRegForm.do", method = RequestMethod.GET)
public String showSalesRegForm(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
try {
// URL 인코딩 처리: 한글 프로젝트 번호 디코딩
if(paramMap.get("orderNo") != null) {
String orderNo = paramMap.get("orderNo").toString();
@@ -280,43 +281,154 @@ public class SalesNcollectMgmtController {
request.setAttribute("saleInfo", saleInfo);
request.setAttribute("orderInfo", saleInfo);
}
// orderNo가 있으면 기존 판매 정보 조회
// 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("saleNo: " + paramMap.get("saleNo"));
System.out.println("remainingQuantity: " + paramMap.get("remainingQuantity"));
// 판매 정보가 비어있거나 금액이 0이면 수주 데이터로 채우기
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)) {
// orderNo로 contractObjId 조회 후 수주 데이터 가져오기
Map<String, Object> orderDataParam = new HashMap<String, Object>();
orderDataParam.put("orderNo", paramMap.get("orderNo"));
Map<String, Object> orderData = salesNcollectMgmtService.getOrderDataByOrderNo(orderDataParam);
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"));
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"));
saleInfo.put("SALES_TOTAL_AMOUNT", orderData.get("SALES_TOTAL_AMOUNT"));
saleInfo.put("SALES_CURRENCY", orderData.get("SALES_CURRENCY"));
saleInfo.put("SALES_CURRENCY_NAME", orderData.get("SALES_CURRENCY_NAME"));
saleInfo.put("SALES_EXCHANGE_RATE", orderData.get("SALES_EXCHANGE_RATE"));
if(saleInfo.get("SHIPPING_DATE") == null || "".equals(saleInfo.get("SHIPPING_DATE"))) {
saleInfo.put("SHIPPING_DATE", orderData.get("SHIPPING_DATE"));
}
salesCurrency = CommonUtils.nullToEmpty((String)orderData.get("SALES_CURRENCY"));
}
// saleNo가 "detail"이면 주문서 관리의 결재 정보 조회 모드
if(paramMap.get("saleNo") != null && "detail".equals(paramMap.get("saleNo"))) {
System.out.println("=== 주문서 관리 결재 정보 조회 모드 ===");
// PROJECT_MGMT에서 CONTRACT_OBJID 조회
Map<String, Object> projectInfo = salesNcollectMgmtService.getProjectInfo(paramMap);
Object contractObjId = projectInfo != null ? projectInfo.get("contract_objid") : null;
if(contractObjId == null && projectInfo != null) {
contractObjId = projectInfo.get("CONTRACT_OBJID");
}
request.setAttribute("saleInfo", saleInfo);
System.out.println("CONTRACT_OBJID: " + contractObjId);
// orderInfo로 견적 정보 전달 (saleInfo가 이미 모든 필요한 정보를 포함)
request.setAttribute("orderInfo", saleInfo);
if(contractObjId != null) {
// CONTRACT_MGMT 상세 정보 조회
Map<String, Object> contractParam = new HashMap<String, Object>();
contractParam.put("objId", contractObjId);
Map<String, Object> contractInfo = commonService.selectOne("contractMgmt.getContractInfo", request, contractParam);
// PROJECT_MGMT에서 품번/품명/S/N/요청납기/고객요청사항/반납사유 조회
if(projectInfo != null) {
// 품번
if(contractInfo.get("PART_NO") == null || "".equals(contractInfo.get("PART_NO"))) {
contractInfo.put("PART_NO", projectInfo.get("part_no") != null ? projectInfo.get("part_no") : projectInfo.get("PART_NO"));
}
// 품명
if(contractInfo.get("PART_NAME") == null || "".equals(contractInfo.get("PART_NAME"))) {
contractInfo.put("PART_NAME", projectInfo.get("part_name") != null ? projectInfo.get("part_name") : projectInfo.get("PART_NAME"));
}
// S/N
Object serialNo = projectInfo.get("serial_no") != null ? projectInfo.get("serial_no") : projectInfo.get("SERIAL_NO");
contractInfo.put("SERIAL_NO", serialNo != null ? serialNo : "-");
// 요청납기
Object reqDelDate = projectInfo.get("req_del_date") != null ? projectInfo.get("req_del_date") : projectInfo.get("REQ_DEL_DATE");
contractInfo.put("REQ_DEL_DATE", reqDelDate != null ? reqDelDate : "-");
// 고객요청사항
Object customerRequest = projectInfo.get("customer_request") != null ? projectInfo.get("customer_request") : projectInfo.get("CUSTOMER_REQUEST");
contractInfo.put("CUSTOMER_REQUEST", customerRequest != null ? customerRequest : "-");
// 반납사유
Object returnReason = projectInfo.get("return_reason") != null ? projectInfo.get("return_reason") : projectInfo.get("RETURN_REASON");
contractInfo.put("RETURN_REASON", returnReason != null ? returnReason : "-");
}
// 결재 라인 정보 조회
Map<String, Object> approvalParam = new HashMap<String, Object>();
approvalParam.put("objId", contractObjId);
List<Map<String, Object>> approvalLine = commonService.selectList("approval.getApprovalLine", request, approvalParam);
request.setAttribute("info", contractInfo);
request.setAttribute("approvalLine", approvalLine);
System.out.println("=== 주문서 관리 데이터 조회 완료 ===");
System.out.println("CATEGORY_NAME: " + contractInfo.get("CATEGORY_NAME"));
System.out.println("PRODUCT_NAME: " + contractInfo.get("PRODUCT_NAME"));
System.out.println("PART_NO: " + contractInfo.get("PART_NO"));
System.out.println("PART_NAME: " + contractInfo.get("PART_NAME"));
System.out.println("결재 라인 개수: " + (approvalLine != null ? approvalLine.size() : 0));
// 별도의 JSP로 이동
return "/salesmgmt/salesMgmt/projectDetailView";
}
}
// saleNo가 있지만 "detail"이 아니면 프로젝트 기본 정보 조회 모드
else if(paramMap.get("saleNo") != null && !paramMap.get("saleNo").equals("")) {
System.out.println("=== 프로젝트 기본 정보 조회 모드 ===");
Map<String, Object> orderDataParam = new HashMap<String, Object>();
orderDataParam.put("orderNo", paramMap.get("orderNo"));
saleInfo = salesNcollectMgmtService.getOrderDataByOrderNo(orderDataParam);
if(saleInfo != null) {
salesCurrency = CommonUtils.nullToEmpty((String)saleInfo.get("SALES_CURRENCY"));
saleInfo.put("ORDER_QUANTITY", saleInfo.get("SALES_QUANTITY"));
Map<String, Object> shipmentParam = new HashMap<String, Object>();
shipmentParam.put("projectNo", paramMap.get("orderNo"));
Integer totalShipped = salesNcollectMgmtService.getTotalShippedQuantity(shipmentParam);
saleInfo.put("SALES_QUANTITY", totalShipped != null ? totalShipped : 0);
System.out.println("=== 프로젝트 기본 정보 조회 완료 ===");
System.out.println("ORDER_QUANTITY (수주수량): " + saleInfo.get("ORDER_QUANTITY"));
System.out.println("SALES_QUANTITY (총 판매수량): " + saleInfo.get("SALES_QUANTITY"));
}
}
// saleNo가 없으면 신규 판매 등록 모드 -> 수주 데이터만 조회
else {
System.out.println("=== 신규 판매 등록 모드 ===");
Map<String, Object> orderDataParam = new HashMap<String, Object>();
orderDataParam.put("orderNo", paramMap.get("orderNo"));
saleInfo = salesNcollectMgmtService.getOrderDataByOrderNo(orderDataParam);
if(saleInfo != null) {
salesCurrency = CommonUtils.nullToEmpty((String)saleInfo.get("SALES_CURRENCY"));
// 수주 데이터의 SALES_QUANTITY를 ORDER_QUANTITY로 변경
saleInfo.put("ORDER_QUANTITY", saleInfo.get("SALES_QUANTITY"));
saleInfo.put("SALES_QUANTITY", ""); // 신규 등록이므로 판매수량은 비워둠
System.out.println("=== 수주 데이터 조회 완료 ===");
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("remainingQuantity") != null && !paramMap.get("remainingQuantity").equals("")) {
if(saleInfo == null) saleInfo = new HashMap<String, Object>();
saleInfo.put("REMAINING_QUANTITY", paramMap.get("remainingQuantity"));
System.out.println("=== 그리드에서 전달받은 잔량 ===");
System.out.println("잔량 (REMAINING_QUANTITY): " + saleInfo.get("REMAINING_QUANTITY"));
}
// 판매수량은 기존 값 유지 (이미 판매 등록된 경우) 또는 비워둠 (신규 등록)
if(saleInfo != null) {
if(saleInfo.get("SALES_QUANTITY") == null || saleInfo.get("SALES_QUANTITY").equals(0)) {
saleInfo.put("SALES_QUANTITY", ""); // 신규 등록: 비워둠
}
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 +530,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 {
System.out.println("=== 거래명세서 저장 Controller ===");
System.out.println("프로젝트 번호: " + paramMap.get("projectNos"));
System.out.println("납품일: " + paramMap.get("deliveryDate"));
System.out.println("비고: " + paramMap.get("noteContent"));
System.out.println("품목 개수: " + ((List)paramMap.get("items")).size());
// Service 호출하여 DB에 저장
resultMap = salesNcollectMgmtService.saveTransactionStatement(paramMap);
System.out.println("저장 결과: " + resultMap.get("success"));
} catch (Exception e) {
e.printStackTrace();
resultMap.put("success", false);
resultMap.put("message", "저장 중 오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
/**
* <pre>
* 계약관리 목록 조회
@@ -798,4 +1000,40 @@ public class SalesNcollectMgmtController {
Map resultMap = salesNcollectMgmtService.salesDeadlineConfirm(request, paramMap);
return resultMap;
}
@RequestMapping(value = "/salesMgmt/getAllSerialNumbers.do", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> getAllSerialNumbers(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<String, Object>();
try {
List<String> serialNumbers = salesNcollectMgmtService.getAllSerialNumbers(paramMap);
resultMap.put("success", true);
resultMap.put("serialNumbers", serialNumbers);
} catch(Exception e) {
e.printStackTrace();
resultMap.put("success", false);
resultMap.put("message", e.getMessage());
}
return resultMap;
}
@RequestMapping(value = "/salesMgmt/getSavedTransactionStatement.do", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> getSavedTransactionStatement(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<String, Object>();
try {
List<Map<String, Object>> savedData = salesNcollectMgmtService.getSavedTransactionStatement(paramMap);
resultMap.put("success", true);
resultMap.put("data", savedData);
} catch(Exception e) {
e.printStackTrace();
resultMap.put("success", false);
resultMap.put("message", e.getMessage());
}
return resultMap;
}
}

View File

@@ -523,13 +523,20 @@
,A.APPROVAL_OBJID
,A.ROUTE_OBJID
,(SELECT objid FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = T.OBJID order by regdate desc limit 1) AS EST_OBJID
-- 최근 차수 견적서 합계 정보
,(SELECT TOTAL_AMOUNT FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = T.OBJID ORDER BY REGDATE DESC LIMIT 1) AS EST_TOTAL_AMOUNT
,(SELECT TOTAL_AMOUNT_KRW FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = T.OBJID ORDER BY REGDATE DESC LIMIT 1) AS EST_TOTAL_AMOUNT_KRW
-- 수주 합계 정보 (CONTRACT_MGMT 테이블에 저장된 값 사용)
,T.ORDER_SUPPLY_PRICE AS ORDER_SUPPLY_PRICE_SUM
,T.ORDER_VAT AS ORDER_VAT_SUM
,T.ORDER_TOTAL_AMOUNT AS ORDER_TOTAL_AMOUNT_SUM
-- 최근 차수 견적서 합계 정보
,(SELECT TOTAL_AMOUNT FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = T.OBJID ORDER BY REGDATE DESC LIMIT 1) AS EST_TOTAL_AMOUNT
,(SELECT TOTAL_AMOUNT_KRW FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = T.OBJID ORDER BY REGDATE DESC LIMIT 1) AS EST_TOTAL_AMOUNT_KRW
-- 견적수량 (ESTIMATE_TEMPLATE_ITEM의 수량 합계)
,(SELECT COALESCE(SUM(CAST(QUANTITY AS NUMERIC)), 0) FROM ESTIMATE_TEMPLATE_ITEM WHERE TEMPLATE_OBJID = (SELECT objid FROM ESTIMATE_TEMPLATE WHERE CONTRACT_OBJID = T.OBJID ORDER BY REGDATE DESC LIMIT 1)) AS ESTIMATE_QUANTITY
-- 수주 합계 정보 (CONTRACT_MGMT 테이블에 저장된 값 사용)
,T.ORDER_SUPPLY_PRICE AS ORDER_SUPPLY_PRICE_SUM
,T.ORDER_VAT AS ORDER_VAT_SUM
,T.ORDER_TOTAL_AMOUNT AS ORDER_TOTAL_AMOUNT_SUM
-- 수주수량 (CONTRACT_MGMT의 QUANTITY 또는 CONTRACT_ITEM 합계)
,COALESCE(
NULLIF(T.QUANTITY, '')::NUMERIC,
(SELECT COALESCE(SUM(CAST(QUANTITY AS NUMERIC)), 0) FROM CONTRACT_ITEM WHERE CONTRACT_OBJID = T.OBJID AND STATUS = 'ACTIVE')
) AS ORDER_QUANTITY
,CASE
WHEN T.ORDER_TOTAL_AMOUNT IS NOT NULL AND T.ORDER_TOTAL_AMOUNT != ''
AND T.EXCHANGE_RATE IS NOT NULL AND T.EXCHANGE_RATE != ''
@@ -790,6 +797,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 (
@@ -4481,6 +4584,103 @@ 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">
@@ -4488,6 +4688,24 @@ WHERE
OBJID,
CONTRACT_NO,
CUSTOMER_OBJID,
(SELECT SUPPLY_NAME FROM SUPPLY_MNG WHERE OBJID = CONTRACT_MGMT.CUSTOMER_OBJID::NUMERIC) AS CUSTOMER_NAME,
-- 주문유형
CATEGORY_CD,
CODE_NAME(CATEGORY_CD) AS CATEGORY_NAME,
-- 제품구분
PRODUCT,
CODE_NAME(PRODUCT) AS PRODUCT_NAME,
-- 국내/해외
AREA_CD,
CODE_NAME(AREA_CD) AS AREA_NAME,
-- 유/무상
PAID_TYPE,
-- 접수일 (이미 날짜 형식이면 그대로, YYYYMMDD 형식이면 변환)
CASE
WHEN RECEIPT_DATE ~ '^\d{8}$' THEN TO_CHAR(TO_DATE(RECEIPT_DATE, 'YYYYMMDD'), 'YYYY-MM-DD')
ELSE RECEIPT_DATE
END AS RECEIPT_DATE,
-- 환종/환율
CONTRACT_CURRENCY,
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CONTRACT_CURRENCY) AS CONTRACT_CURRENCY_NAME,
EXCHANGE_RATE,

View File

@@ -876,16 +876,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,
-- 잔량 계산: 수주수량 - shipment_log의 split_quantity 합계
COALESCE(T.QUANTITY::numeric, 0) - COALESCE(
(SELECT SUM(COALESCE(split_quantity, 0))
FROM shipment_log
WHERE target_objid = T.PROJECT_NO),
0
) AS REMAINING_QUANTITY,
-- 잔량원화총액 계산: (수주수량 - shipment_log 합계) * 판매단가
(COALESCE(T.QUANTITY::numeric, 0) - COALESCE(
(SELECT SUM(COALESCE(split_quantity, 0))
FROM shipment_log
WHERE target_objid = T.PROJECT_NO),
0
)) * COALESCE(SR.sales_unit_price, 0) AS REMAINING_AMOUNT_KRW,
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,
@@ -896,10 +912,10 @@
CASE
WHEN COUNT(DISTINCT shipping_date) = 0 THEN ''
WHEN COUNT(DISTINCT shipping_date) = 1 THEN TO_CHAR(MIN(shipping_date), 'YYYY-MM-DD')
ELSE TO_CHAR(MIN(shipping_date), 'YYYY-MM-DD') || '' || (COUNT(DISTINCT shipping_date) - 1)::TEXT
ELSE TO_CHAR(MIN(shipping_date), 'YYYY-MM-DD') || '' || (COUNT(DISTINCT shipping_date) - 1)::TEXT || '건'
END
FROM shipment_log
WHERE target_objid = T.OBJID::VARCHAR
WHERE target_objid = T.PROJECT_NO
AND shipping_date IS NOT NULL
AND UPPER(COALESCE(shipping_status, '')) != 'CANCELLED'),
COALESCE(TO_CHAR(SR.shipping_date, 'YYYY-MM-DD'), '')
@@ -911,12 +927,17 @@
) AS MANAGER,
COALESCE(SR.incoterms, '') AS INCOTERMS,
T.SALES_STATUS,
T.OBJID::VARCHAR AS SALE_NO,
'ORIGINAL' AS RECORD_TYPE,
'' AS SPLIT_LOG_ID
FROM PROJECT_MGMT AS T
LEFT JOIN sales_registration SR ON T.PROJECT_NO = SR.project_no
WHERE 1 = 1
T.OBJID::VARCHAR AS SALE_NO,
'ORIGINAL' AS RECORD_TYPE,
'' AS SPLIT_LOG_ID,
-- 거래명세서 존재 여부
CASE WHEN EXISTS(
SELECT 1 FROM NSWOS100_TBL
WHERE OdOrderNo = T.PROJECT_NO
) THEN 'Y' ELSE 'N' END AS HAS_TRANSACTION_STATEMENT
FROM PROJECT_MGMT AS T
LEFT JOIN sales_registration SR ON T.PROJECT_NO = SR.project_no
WHERE 1 = 1
AND T.PROJECT_NO IS NOT NULL
AND T.PROJECT_NO != ''
<if test="orderType != null and orderType != ''">
@@ -1398,29 +1419,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>
<!--
/**
@@ -1489,9 +1493,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,
@@ -1499,13 +1509,16 @@ 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,
COALESCE(SR.incoterms, '') AS INCOTERMS
FROM PROJECT_MGMT AS T
LEFT JOIN sales_registration SR ON T.PROJECT_NO = SR.project_no
<if test="saleNo != null and saleNo != ''">
AND SR.sale_no = #{saleNo}::integer
</if>
LEFT JOIN CONTRACT_MGMT CM ON CM.OBJID::VARCHAR = T.CONTRACT_OBJID
WHERE T.PROJECT_NO = #{orderNo}
</select>
@@ -1567,40 +1580,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>
<!--
@@ -1620,5 +1636,342 @@ 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(
CASE
WHEN sales_quantity IS NULL THEN 0
WHEN TRIM(CAST(sales_quantity AS TEXT)) = '' THEN 0
ELSE CAST(sales_quantity AS NUMERIC)
END
), 0) as total
FROM sales_registration
WHERE project_no LIKE #{orderNo} || '%'
</select>
<!-- shipment_log에서 총 출하 수량 조회 -->
<select id="getTotalShippedQuantity" parameterType="map" resultType="int">
/* salesNcollectMgmt.getTotalShippedQuantity - shipment_log의 split_quantity 합계 */
SELECT COALESCE(SUM(split_quantity), 0)
FROM shipment_log
WHERE target_objid = #{projectNo}
</select>
<!-- 프로젝트 기본 정보 조회 (CONTRACT_OBJID 포함) -->
<select id="getProjectInfo" parameterType="map" resultType="map">
/* salesNcollectMgmt.getProjectInfo - PROJECT_MGMT의 상세 정보 조회 (최신 1개) */
SELECT
PM.CONTRACT_OBJID,
PM.PART_NO,
PM.PART_NAME,
-- 요청납기 (CONTRACT_ITEM의 DUE_DATE 우선, 없으면 PROJECT_MGMT.DUE_DATE)
COALESCE(
(SELECT CI.DUE_DATE
FROM CONTRACT_ITEM CI
WHERE CI.CONTRACT_OBJID = PM.CONTRACT_OBJID
AND CI.PART_OBJID = PM.PART_OBJID
AND CI.STATUS = 'ACTIVE'
LIMIT 1),
PM.DUE_DATE
) AS REQ_DEL_DATE,
-- S/N 조회 (CONTRACT_ITEM_SERIAL에서)
(SELECT STRING_AGG(CIS.SERIAL_NO, ', ' ORDER BY CIS.SERIAL_NO)
FROM CONTRACT_ITEM CI
JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID
WHERE CI.CONTRACT_OBJID = PM.CONTRACT_OBJID
AND CI.PART_OBJID = PM.PART_OBJID
AND CI.STATUS = 'ACTIVE'
AND UPPER(CIS.STATUS) = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL
) AS SERIAL_NO,
-- 고객요청사항 (CONTRACT_ITEM에서)
(SELECT CI.CUSTOMER_REQUEST
FROM CONTRACT_ITEM CI
WHERE CI.CONTRACT_OBJID = PM.CONTRACT_OBJID
AND CI.PART_OBJID = PM.PART_OBJID
AND CI.STATUS = 'ACTIVE'
LIMIT 1
) AS CUSTOMER_REQUEST,
-- 반납사유 (CONTRACT_ITEM에서)
(SELECT CI.RETURN_REASON
FROM CONTRACT_ITEM CI
WHERE CI.CONTRACT_OBJID = PM.CONTRACT_OBJID
AND CI.PART_OBJID = PM.PART_OBJID
AND CI.STATUS = 'ACTIVE'
LIMIT 1
) AS RETURN_REASON
FROM PROJECT_MGMT PM
WHERE PM.PROJECT_NO = #{orderNo}
ORDER BY PM.REGDATE DESC
LIMIT 1
</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, #{managerUserId}, #{incoterms}, #{serialNo},
#{parentSaleNo}::integer, #{cretEmpNo}
)
</insert>
<!-- sales_registration의 수량을 shipment_log 합계로 업데이트 -->
<update id="updateSalesQuantityFromShipmentLog" parameterType="map">
/* salesNcollectMgmt.updateSalesQuantityFromShipmentLog - shipment_log 합계로 sales_quantity 업데이트 */
UPDATE sales_registration
SET sales_quantity = (
SELECT COALESCE(SUM(split_quantity), 0)
FROM shipment_log
WHERE target_objid = #{projectNo}
),
sales_supply_price = (
SELECT COALESCE(SUM(sales_supply_price), 0)
FROM shipment_log
WHERE target_objid = #{projectNo}
),
sales_vat = (
SELECT COALESCE(SUM(sales_vat), 0)
FROM shipment_log
WHERE target_objid = #{projectNo}
),
sales_total_amount = (
SELECT COALESCE(SUM(sales_total_amount), 0)
FROM shipment_log
WHERE target_objid = #{projectNo}
)
WHERE sale_no = #{saleNo}
</update>
<!-- 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 - shipment_log에서 모든 분할 출하 조회 */
SELECT
COALESCE(TO_CHAR(SL.shipping_date, 'YYYY-MM-DD'), '미등록') AS shipping_date,
COALESCE(SL.split_quantity, 0) AS shipping_quantity,
COALESCE(SL.shipping_status, '미등록') AS shipping_order_status,
COALESCE(SL.serial_no, '-') AS serial_no,
SL.target_objid AS project_no,
'분할 출하 ' || SL.log_id AS source,
TO_CHAR(SL.reg_date, 'YYYY-MM-DD HH24:MI:SS') AS reg_date
FROM shipment_log SL
WHERE SL.target_objid = #{projectNo}
ORDER BY SL.shipping_date DESC, SL.log_id DESC
</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>
<!-- 거래명세서 저장 - NSWOS100_TBL 테이블 사용 -->
<insert id="saveTransactionStatement" parameterType="map">
/* salesNcollectMgmt.saveTransactionStatement - 거래명세서 저장 */
INSERT INTO NSWOS100_TBL (
SuVndCd /* 업체코드 */
,IssueDt /* 작성일자 */
,IssueNo /* 거래명세서번호 */
,IsNo /* 순번 */
,ProdCd /* 기종코드 */
,OdOrderNo /* 발주번호 */
,ImItemId /* 품번 */
,RmDueDt /* 납기일자 */
,RmOrderQty /* 발주수량 */
,RmRcptQty /* 입고처리수량 */
,RmRemQty /* 잔량 */
,IsDt /* 납기일자 */
,IsQty /* 납품수량 */
,IsPrice /* 납품단가 */
,IsAmount /* 납품금액 */
) VALUES (
#{suVndCd} /* 업체코드 */
,#{issueDt} /* 작성일자 */
,#{issueNo}::integer /* 거래명세서번호 */
,#{isNo}::integer /* 순번 */
,COALESCE(#{prodCd}, '') /* 기종코드 */
,COALESCE(#{odOrderNo}, '') /* 발주번호 */
,COALESCE(#{imItemId}, '') /* 품번 */
,COALESCE(#{rmDueDt}, '') /* 납기일자 */
,COALESCE(#{rmOrderQty}, 0)::integer /* 발주수량 */
,COALESCE(#{rmRcptQty}, 0)::integer /* 입고처리수량 */
,COALESCE(#{rmRemQty}, 0)::integer /* 잔량 */
,COALESCE(#{isDt}, '') /* 납기일자 */
,COALESCE(#{isQty}, 0)::integer /* 납품수량 */
,COALESCE(#{isPrice}, 0)::numeric /* 납품단가 */
,COALESCE(#{isAmount}, 0)::numeric /* 납품금액 */
) ON CONFLICT (SuVndCd, IssueDt, IssueNo, IsNo) DO
UPDATE SET
ProdCd = COALESCE(#{prodCd}, '')
,OdOrderNo = COALESCE(#{odOrderNo}, '')
,ImItemId = COALESCE(#{imItemId}, '')
,RmDueDt = COALESCE(#{rmDueDt}, '')
,RmOrderQty = COALESCE(#{rmOrderQty}, 0)::integer
,RmRcptQty = COALESCE(#{rmRcptQty}, 0)::integer
,RmRemQty = COALESCE(#{rmRemQty}, 0)::integer
,IsDt = COALESCE(#{isDt}, '')
,IsQty = COALESCE(#{isQty}, 0)::integer
,IsPrice = COALESCE(#{isPrice}, 0)::numeric
,IsAmount = COALESCE(#{isAmount}, 0)::numeric
</insert>
<!-- 거래명세서 번호 생성 -->
<select id="getNextTransactionStatementNo" parameterType="map" resultType="int">
/* salesNcollectMgmt.getNextTransactionStatementNo - 거래명세서 번호 생성 */
SELECT COALESCE(MAX(IssueNo), 0) + 1 AS nextNo
FROM NSWOS100_TBL
WHERE SuVndCd = #{suVndCd}
AND IssueDt = #{issueDt}
</select>
<select id="getAllSerialNumbers" parameterType="map" resultType="string">
/* salesNcollectMgmt.getAllSerialNumbers - 프로젝트의 모든 S/N 조회 */
SELECT CIS.SERIAL_NO
FROM PROJECT_MGMT PM
JOIN CONTRACT_ITEM CI ON CI.CONTRACT_OBJID = PM.CONTRACT_OBJID
AND CI.PART_OBJID = PM.PART_OBJID
AND CI.STATUS = 'ACTIVE'
JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID
AND UPPER(CIS.STATUS) = 'ACTIVE'
WHERE PM.PROJECT_NO = #{projectNo}
AND CIS.SERIAL_NO IS NOT NULL
ORDER BY CIS.SERIAL_NO
</select>
<select id="getSavedTransactionStatement" parameterType="map" resultType="map">
/* salesNcollectMgmt.getSavedTransactionStatement - 저장된 거래명세서 조회 */
SELECT
SuVndCd,
IssueDt,
IssueNo,
IsNo,
ProdCd,
OdOrderNo,
ImItemId,
RmDueDt,
RmOrderQty,
RmRcptQty,
RmRemQty,
IsDt,
IsQty,
IsPrice,
IsAmount
FROM NSWOS100_TBL
WHERE OdOrderNo = #{projectNo}
ORDER BY IsNo
</select>
</mapper>

View File

@@ -2452,6 +2452,41 @@ 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;
}
/**
* 견적서 템플릿 품목 조회 (수주등록용)
* 최종 견적서의 품목 정보를 수주등록 형식으로 변환하여 반환

View File

@@ -10,7 +10,9 @@
package com.pms.salesmgmt.service;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -308,10 +310,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 +352,117 @@ public class SalesNcollectMgmtService {
PersonBean person = (PersonBean) request.getSession().getAttribute(Constants.PERSON_BEAN);
paramMap.put("cretEmpNo", person.getUserId());
// sales_registration 테이블에 판매 데이터 저장 (ON CONFLICT로 자동 UPDATE)
sqlSession.insert("salesNcollectMgmt.insertSaleRegistration", paramMap);
sqlSession.commit();
resultMap.put("result", true);
resultMap.put("msg", "저장되었습니다.");
String projectNo = (String) paramMap.get("orderNo");
String saleNo = (String) paramMap.get("saleNo");
System.out.println("=== saveSaleRegistration 시작 ===");
System.out.println("projectNo: " + projectNo);
System.out.println("saleNo (파라미터): " + saleNo);
System.out.println("salesQuantity: " + paramMap.get("salesQuantity"));
// 모든 판매를 shipment_log에 기록 (분할 출하 방식 통일)
// 1. 해당 프로젝트의 sales_registration 레코드 확인
Map<String, Object> checkParam = new HashMap<String, Object>();
checkParam.put("orderNo", projectNo);
Map<String, Object> existingSale = sqlSession.selectOne("salesNcollectMgmt.getSaleInfo", checkParam);
Object saleNoObj = null;
if(existingSale != null) {
saleNoObj = existingSale.get("SALE_NO");
if(saleNoObj == null) saleNoObj = existingSale.get("sale_no");
}
System.out.println("existingSale: " + (existingSale != null ? "있음" : "없음"));
System.out.println("SALE_NO: " + saleNoObj);
// 2. sales_registration 레코드가 없으면 먼저 생성
if(saleNoObj == null) {
System.out.println("sales_registration 레코드 생성 (첫 판매)");
// sales_registration에 기본 레코드 INSERT (수량은 0으로)
Map<String, Object> baseRecord = new HashMap<String, Object>();
baseRecord.put("orderNo", projectNo);
baseRecord.put("salesQuantity", 0); // 기본 레코드는 수량 0
baseRecord.put("salesUnitPrice", paramMap.get("salesUnitPrice"));
baseRecord.put("salesSupplyPrice", 0);
baseRecord.put("salesVat", 0);
baseRecord.put("salesTotalAmount", 0);
baseRecord.put("salesCurrency", paramMap.get("salesCurrency"));
baseRecord.put("salesExchangeRate", paramMap.get("salesExchangeRate"));
baseRecord.put("shippingDate", paramMap.get("shippingDate"));
baseRecord.put("shippingMethod", paramMap.get("shippingMethod"));
baseRecord.put("managerUserId", paramMap.get("managerUserId"));
baseRecord.put("incoterms", paramMap.get("incoterms"));
baseRecord.put("serialNo", paramMap.get("serialNo"));
baseRecord.put("cretEmpNo", paramMap.get("cretEmpNo"));
sqlSession.insert("salesNcollectMgmt.insertSaleRegistration", baseRecord);
// 생성된 sale_no 조회
existingSale = sqlSession.selectOne("salesNcollectMgmt.getSaleInfo", checkParam);
saleNoObj = existingSale.get("SALE_NO");
if(saleNoObj == null) saleNoObj = existingSale.get("sale_no");
System.out.println("생성된 SALE_NO: " + saleNoObj);
}
// 3. shipment_log에 판매 기록 INSERT
System.out.println("shipment_log에 판매 기록 INSERT");
Object orderQtyObj = existingSale.get("ORDER_QUANTITY");
if(orderQtyObj == null) orderQtyObj = existingSale.get("order_quantity");
System.out.println("ORDER_QUANTITY: " + orderQtyObj);
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);
// 4. sales_registration의 sales_quantity를 shipment_log 합계로 업데이트
System.out.println("sales_registration 업데이트 - shipment_log 합계 반영");
Map<String, Object> updateParam = new HashMap<String, Object>();
updateParam.put("projectNo", projectNo);
updateParam.put("saleNo", saleNoObj);
sqlSession.update("salesNcollectMgmt.updateSalesQuantityFromShipmentLog", updateParam);
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 +1048,382 @@ 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;
}
/**
* <pre>
* shipment_log에서 총 출하 수량 조회
* </pre>
* @param paramMap
* @return Integer
*/
public Integer getTotalShippedQuantity(Map<String, Object> paramMap) {
SqlSession sqlSession = null;
Integer result = 0;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
result = sqlSession.selectOne("salesNcollectMgmt.getTotalShippedQuantity", paramMap);
if(result == null) result = 0;
} catch(Exception e) {
e.printStackTrace();
result = 0;
} finally {
if(sqlSession != null) {
sqlSession.close();
}
}
return result;
}
/**
* <pre>
* 프로젝트 기본 정보 조회 (CONTRACT_OBJID 포함)
* </pre>
* @param paramMap
* @return Map
*/
public Map<String, Object> getProjectInfo(Map<String, Object> paramMap) {
SqlSession sqlSession = null;
Map<String, Object> result = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
result = sqlSession.selectOne("salesNcollectMgmt.getProjectInfo", 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;
}
/**
* 거래명세서 저장 (NSWOS100_TBL)
*/
public Map<String, Object> saveTransactionStatement(Map<String, Object> paramMap) {
SqlSession sqlSession = null;
Map<String, Object> resultMap = new HashMap<String, Object>();
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
System.out.println("=== 거래명세서 저장 시작 ===");
System.out.println("paramMap: " + paramMap);
// 업체코드 (고객 정보에서 추출 필요 - 임시로 '0001' 사용)
String suVndCd = "0001";
// 작성일자 (오늘 날짜)
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String issueDt = sdf.format(new Date());
// 납품일자 포맷 변환 (한글 날짜 또는 YYYY-MM-DD → YYYYMMDD)
String deliveryDate = "";
if(paramMap.get("deliveryDate") != null && !paramMap.get("deliveryDate").equals("")) {
String deliveryDateStr = String.valueOf(paramMap.get("deliveryDate"));
// 한글 날짜 형식인 경우 (예: "목요일, 11월 13, 2025")
// 오늘 날짜로 대체
if(deliveryDateStr.contains("") || deliveryDateStr.contains("")) {
deliveryDate = issueDt; // 작성일자와 동일하게 설정
System.out.println("납품일자 변환 (한글): " + deliveryDateStr + "" + deliveryDate + " (오늘 날짜 사용)");
}
// YYYY-MM-DD 형식인 경우
else if(deliveryDateStr.contains("-")) {
deliveryDate = deliveryDateStr.replaceAll("-", ""); // "2025-11-13" → "20251113"
System.out.println("납품일자 변환 (하이픈): " + deliveryDateStr + "" + deliveryDate);
}
// 이미 YYYYMMDD 형식인 경우
else if(deliveryDateStr.length() == 8) {
deliveryDate = deliveryDateStr;
System.out.println("납품일자 변환 (8자리): " + deliveryDateStr + "" + deliveryDate);
}
// 그 외의 경우 오늘 날짜 사용
else {
deliveryDate = issueDt;
System.out.println("납품일자 변환 (기타): " + deliveryDateStr + "" + deliveryDate + " (오늘 날짜 사용)");
}
}
// 거래명세서 번호 생성
Map<String, Object> noParam = new HashMap<String, Object>();
noParam.put("suVndCd", suVndCd);
noParam.put("issueDt", issueDt);
Integer issueNo = sqlSession.selectOne("salesNcollectMgmt.getNextTransactionStatementNo", noParam);
System.out.println("업체코드: " + suVndCd);
System.out.println("작성일자: " + issueDt);
System.out.println("거래명세서번호: " + issueNo);
// 품목 정보 저장
List<Map<String, Object>> items = (List<Map<String, Object>>) paramMap.get("items");
if(items != null && items.size() > 0) {
int seqNo = 1;
for(Map<String, Object> item : items) {
Map<String, Object> insertParam = new HashMap<String, Object>();
insertParam.put("suVndCd", suVndCd);
insertParam.put("issueDt", issueDt);
insertParam.put("issueNo", issueNo);
insertParam.put("isNo", seqNo);
// 품목 정보
insertParam.put("prodCd", ""); // 기종코드
// 발주번호 (프로젝트번호) - VARCHAR(15) 제한
String projectNos = String.valueOf(paramMap.get("projectNos"));
if(projectNos.length() > 15) {
projectNos = projectNos.substring(0, 15); // 15자로 자르기
}
insertParam.put("odOrderNo", projectNos);
// 품번 - VARCHAR(15) 제한
String spec = String.valueOf(item.get("spec"));
if(spec != null && spec.length() > 15) {
spec = spec.substring(0, 15); // 15자로 자르기
}
insertParam.put("imItemId", spec);
insertParam.put("rmDueDt", ""); // 납기일자
// 수량 정보
String quantityStr = String.valueOf(item.get("quantity"));
quantityStr = quantityStr.replaceAll(",", "").replaceAll("[^0-9]", "");
int quantity = 0;
try {
quantity = Integer.parseInt(quantityStr);
} catch(Exception e) {
quantity = 0;
}
insertParam.put("rmOrderQty", quantity); // 발주수량
insertParam.put("rmRcptQty", 0); // 입고처리수량
insertParam.put("rmRemQty", 0); // 잔량
insertParam.put("isDt", deliveryDate); // 납기일자 (포맷 변환된 값)
insertParam.put("isQty", quantity); // 납품수량
// 금액 정보
String unitPriceStr = String.valueOf(item.get("unitPrice"));
unitPriceStr = unitPriceStr.replaceAll(",", "").replaceAll("[^0-9.]", "");
double unitPrice = 0;
try {
unitPrice = Double.parseDouble(unitPriceStr);
} catch(Exception e) {
unitPrice = 0;
}
String supplyPriceStr = String.valueOf(item.get("supplyPrice"));
supplyPriceStr = supplyPriceStr.replaceAll(",", "").replaceAll("[^0-9.]", "");
double supplyPrice = 0;
try {
supplyPrice = Double.parseDouble(supplyPriceStr);
} catch(Exception e) {
supplyPrice = 0;
}
insertParam.put("isPrice", unitPrice); // 납품단가
insertParam.put("isAmount", supplyPrice); // 납품금액
System.out.println("품목 " + seqNo + " 저장: " + item.get("productName"));
System.out.println(" - 품번: " + item.get("spec"));
System.out.println(" - 수량: " + quantity);
System.out.println(" - 단가: " + unitPrice);
System.out.println(" - 금액: " + supplyPrice);
sqlSession.insert("salesNcollectMgmt.saveTransactionStatement", insertParam);
seqNo++;
}
}
sqlSession.commit();
resultMap.put("success", true);
resultMap.put("message", "거래명세서가 저장되었습니다.");
resultMap.put("issueNo", issueNo);
System.out.println("=== 거래명세서 저장 완료 ===");
} catch (Exception e) {
if(sqlSession != null) {
sqlSession.rollback();
}
System.out.println("=== 거래명세서 저장 실패 ===");
e.printStackTrace();
resultMap.put("success", false);
resultMap.put("message", "저장 중 오류가 발생했습니다: " + e.getMessage());
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
return resultMap;
}
public List<String> getAllSerialNumbers(Map<String, Object> paramMap) {
SqlSession sqlSession = null;
List<String> result = new ArrayList<String>();
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
result = sqlSession.selectList("salesNcollectMgmt.getAllSerialNumbers", paramMap);
} catch(Exception e) {
e.printStackTrace();
} finally {
if(sqlSession != null) {
sqlSession.close();
}
}
return result;
}
public List<Map<String, Object>> getSavedTransactionStatement(Map<String, Object> paramMap) {
SqlSession sqlSession = null;
List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
result = sqlSession.selectList("salesNcollectMgmt.getSavedTransactionStatement", paramMap);
} catch(Exception e) {
e.printStackTrace();
} finally {
if(sqlSession != null) {
sqlSession.close();
}
}
return result;
}
}

View File

@@ -3476,6 +3476,35 @@ public class PartMngService extends BaseService {
return "";
}
/**
* 품번으로 BOM OBJID 조회
*/
public Map<String, Object> getBomObjIdByPartNo(Map paramMap) throws Exception{
Map<String, Object> resultMap = null;
SqlSession sqlSession = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
// PART_BOM_REPORT 테이블에서 품번으로 OBJID 조회
resultMap = (Map<String, Object>)sqlSession.selectOne("partMng.getBomObjIdByPartNo", paramMap);
if(resultMap != null) {
resultMap = CommonUtils.toUpperCaseMapKey(resultMap);
}
} catch(Exception e) {
e.printStackTrace();
throw e;
} finally {
if(sqlSession != null) {
sqlSession.close();
}
}
return resultMap;
}
/**
* BOM 복사를 위한 데이터 조회 (엑셀 파싱 형식과 동일하게 반환)
*/

View File

@@ -749,4 +749,57 @@ public class ProductionPlanningService {
return resultMap;
}
/**
* M-BOM 저장
* @param request
* @param mbomData
* @param partNo
* @param partName
* @return
*/
public int saveMbom(HttpServletRequest request, List<Map<String, Object>> mbomData, String partNo, String partName) {
SqlSession sqlSession = null;
int result = 0;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
HttpSession session = request.getSession();
PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
String userId = CommonUtils.checkNull(person.getUserId());
// M-BOM 헤더 정보 생성
Map<String, Object> headerMap = new HashMap<>();
headerMap.put("PART_NO", partNo);
headerMap.put("PART_NAME", partName);
headerMap.put("CREATE_USER", userId);
headerMap.put("UPDATE_USER", userId);
// M-BOM 헤더 저장
sqlSession.insert("productionplanning.insertMbomHeader", headerMap);
String mbomHeaderObjid = (String) headerMap.get("OBJID");
// M-BOM 상세 데이터 저장
for(Map<String, Object> item : mbomData) {
item.put("MBOM_HEADER_OBJID", mbomHeaderObjid);
item.put("CREATE_USER", userId);
item.put("UPDATE_USER", userId);
sqlSession.insert("productionplanning.insertMbomDetail", item);
}
sqlSession.commit();
result = 1;
} catch(Exception e) {
if(sqlSession != null) {
sqlSession.rollback();
}
e.printStackTrace();
} finally {
if(sqlSession != null) {
sqlSession.close();
}
}
return result;
}
}